Merge commit '190440afd5c304438851011526ebe11bcde62cce' into 1.7.x-dev
Change-Id: I74db49506e6b2beb6eb94ec95a5a704d0a29f728
diff --git a/.gitignore b/.gitignore
index b74632a..bc99d6a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
buildSrc/out/
.idea/
.gradle/
+.gradle_user_home
android-data*/
tests/2016-12-19/art.tar.gz
tests/2016-12-19/art
diff --git a/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java b/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java
index a6389da..31e0dbd 100644
--- a/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java
+++ b/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.ZipUtils;
import com.google.common.io.ByteStreams;
import java.io.Closeable;
@@ -60,7 +61,7 @@
assert isArchive(archive);
origin = new PathOrigin(archive);
try {
- zipFile = new ZipFile(archive.toFile(), StandardCharsets.UTF_8);
+ zipFile = FileUtils.createZipFile(archive.toFile(), StandardCharsets.UTF_8);
} catch (IOException e) {
if (!Files.exists(archive)) {
throw new NoSuchFileException(archive.toString());
diff --git a/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java b/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
index c5065f2..d5dd240 100644
--- a/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.ZipUtils;
import com.google.common.io.ByteStreams;
import java.io.IOException;
@@ -62,7 +63,7 @@
Path archive, Predicate<String> include) {
return fromSupplier(
new PathOrigin(archive),
- () -> new ZipFile(archive.toFile(), StandardCharsets.UTF_8),
+ () -> FileUtils.createZipFile(archive.toFile(), StandardCharsets.UTF_8),
include);
}
diff --git a/src/main/java/com/android/tools/r8/BaseCommand.java b/src/main/java/com/android/tools/r8/BaseCommand.java
index 318305d..50e5be1 100644
--- a/src/main/java/com/android/tools/r8/BaseCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCommand.java
@@ -166,15 +166,14 @@
public B addProgramFiles(Collection<Path> files) {
guard(
() -> {
- files.forEach(
- path -> {
- try {
- app.addProgramFile(path);
- programFiles.add(path);
- } catch (CompilationError e) {
- error(new ProgramInputOrigin(path), e);
- }
- });
+ for (Path path : files) {
+ try {
+ app.addProgramFile(path);
+ programFiles.add(path);
+ } catch (CompilationError e) {
+ error(new ProgramInputOrigin(path), e);
+ }
+ }
});
return self();
}
@@ -201,14 +200,13 @@
public B addLibraryFiles(Collection<Path> files) {
guard(
() -> {
- files.forEach(
- path -> {
- try {
- app.addLibraryFile(path);
- } catch (CompilationError e) {
- error(new LibraryInputOrigin(path), e);
- }
- });
+ for (Path path : files) {
+ try {
+ app.addLibraryFile(path);
+ } catch (CompilationError e) {
+ error(new LibraryInputOrigin(path), e);
+ }
+ }
});
return self();
}
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index f17bab9..7834adb 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -113,10 +113,6 @@
options.desugaredLibraryConfiguration.createPrefixRewritingMapper(options.itemFactory);
app = new L8TreePruner(options).prune(app, rewritePrefix);
- if (app.classes().size() == 0) {
- // TODO(b/134732760): report error instead of fatalError.
- throw options.reporter.fatalError("Empty desugared library.");
- }
AppInfo appInfo = new AppInfo(app);
AppView<?> appView = AppView.createForL8(appInfo, options, rewritePrefix);
diff --git a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
index a48bbd1..e77cba3 100644
--- a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
@@ -20,9 +20,9 @@
import com.android.tools.r8.ir.code.StackValue;
import com.android.tools.r8.ir.code.Value;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
@@ -223,7 +223,7 @@
computingVerificationTypes = true;
types = new HashMap<>();
List<ConstNumber> nullsUsedInPhis = new ArrayList<>();
- Set<Value> worklist = new HashSet<>();
+ Set<Value> worklist = Sets.newIdentityHashSet();
{
InstructionIterator it = code.instructionIterator();
Instruction instruction = null;
diff --git a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
index 7321b53..68e9e69 100644
--- a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
+++ b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
@@ -569,7 +569,7 @@
// For each input archive file, add all class files within.
for (Path input : inputs) {
if (FileUtils.isArchive(input)) {
- try (ZipFile zipFile = new ZipFile(input.toFile(), StandardCharsets.UTF_8)) {
+ try (ZipFile zipFile = FileUtils.createZipFile(input.toFile(), StandardCharsets.UTF_8)) {
final Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index 460885c..3425946 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.SetUtils;
@@ -131,7 +132,7 @@
public AppInfoWithSubtyping(DexApplication application) {
super(application);
- typeInfo = Collections.synchronizedMap(new IdentityHashMap<>());
+ typeInfo = new ConcurrentHashMap<>();
// Recompute subtype map if we have modified the graph.
populateSubtypeMap(application.asDirect(), application.dexItemFactory);
}
@@ -140,7 +141,7 @@
super(previous);
missingClasses.addAll(previous.missingClasses);
subtypeMap.putAll(previous.subtypeMap);
- typeInfo = Collections.synchronizedMap(new IdentityHashMap<>(previous.typeInfo));
+ typeInfo = new ConcurrentHashMap<>(previous.typeInfo);
assert app() instanceof DirectMappedDexApplication;
}
@@ -211,6 +212,7 @@
}
private TypeInfo getTypeInfo(DexType type) {
+ assert type != null;
return typeInfo.computeIfAbsent(type, TypeInfo::new);
}
@@ -274,11 +276,11 @@
}
assert !seenTypes.contains(next);
seenTypes.add(next);
- TypeInfo superInfo = getTypeInfo(superType);
TypeInfo nextInfo = getTypeInfo(next);
if (superType == null) {
assert nextInfo.hierarchyLevel == ROOT_LEVEL;
} else {
+ TypeInfo superInfo = getTypeInfo(superType);
assert superInfo.hierarchyLevel == nextInfo.hierarchyLevel - 1
|| (superInfo.hierarchyLevel == ROOT_LEVEL
&& nextInfo.hierarchyLevel == INTERFACE_LEVEL);
@@ -714,8 +716,17 @@
return !isSubtype(type1, type2) && !isSubtype(type2, type1);
}
- public boolean mayHaveFinalizeMethodDirectlyOrIndirectly(DexType type) {
- return computeMayHaveFinalizeMethodDirectlyOrIndirectlyIfAbsent(type, true);
+ public boolean mayHaveFinalizeMethodDirectlyOrIndirectly(ClassTypeLatticeElement type) {
+ Set<DexType> interfaces = type.getInterfaces();
+ if (!interfaces.isEmpty()) {
+ for (DexType interfaceType : interfaces) {
+ if (computeMayHaveFinalizeMethodDirectlyOrIndirectlyIfAbsent(interfaceType, false)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return computeMayHaveFinalizeMethodDirectlyOrIndirectlyIfAbsent(type.getClassType(), true);
}
private boolean computeMayHaveFinalizeMethodDirectlyOrIndirectlyIfAbsent(
@@ -727,8 +738,10 @@
}
DexClass clazz = definitionFor(type);
if (clazz == null) {
- mayHaveFinalizeMethodDirectlyOrIndirectlyCache.put(type, true);
- return true;
+ // This is strictly not conservative but is needed to avoid that we treat Object as having
+ // a subtype that has a non-default finalize() implementation.
+ mayHaveFinalizeMethodDirectlyOrIndirectlyCache.put(type, false);
+ return false;
}
if (clazz.isProgramClass()) {
if (lookUpwards) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 6f5df28..7ce2685 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -924,4 +924,13 @@
assert verifyNoDuplicateMethods();
return true;
}
+
+ public boolean hasStaticSynchronizedMethods() {
+ for (DexEncodedMethod encodedMethod : directMethods()) {
+ if (encodedMethod.accessFlags.isStatic() && encodedMethod.accessFlags.isSynchronized()) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 0cc264a..f60e877 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -62,12 +62,12 @@
public static final String throwableDescriptorString = "Ljava/lang/Throwable;";
- private final ConcurrentHashMap<DexString, DexString> strings = new ConcurrentHashMap<>();
- private final ConcurrentHashMap<DexString, DexType> types = new ConcurrentHashMap<>();
- private final ConcurrentHashMap<DexField, DexField> fields = new ConcurrentHashMap<>();
- private final ConcurrentHashMap<DexProto, DexProto> protos = new ConcurrentHashMap<>();
- private final ConcurrentHashMap<DexMethod, DexMethod> methods = new ConcurrentHashMap<>();
- private final ConcurrentHashMap<DexMethodHandle, DexMethodHandle> methodHandles =
+ private final Map<DexString, DexString> strings = new ConcurrentHashMap<>();
+ private final Map<DexString, DexType> types = new ConcurrentHashMap<>();
+ private final Map<DexField, DexField> fields = new ConcurrentHashMap<>();
+ private final Map<DexProto, DexProto> protos = new ConcurrentHashMap<>();
+ private final Map<DexMethod, DexMethod> methods = new ConcurrentHashMap<>();
+ private final Map<DexMethodHandle, DexMethodHandle> methodHandles =
new ConcurrentHashMap<>();
// DexDebugEvent Canonicalization.
@@ -730,10 +730,13 @@
public class EnumMethods {
- public DexMethod valueOf;
- public DexMethod ordinal;
- public DexMethod name;
- public DexMethod toString;
+ public final DexMethod valueOf;
+ public final DexMethod ordinal;
+ public final DexMethod name;
+ public final DexMethod toString;
+
+ public final DexMethod finalize =
+ createMethod(enumType, createProto(voidType), finalizeMethodName);
private EnumMethods() {
valueOf =
@@ -1152,7 +1155,7 @@
}
}
- private static <T extends DexItem> T canonicalize(ConcurrentHashMap<T, T> map, T item) {
+ private static <T extends DexItem> T canonicalize(Map<T, T> map, T item) {
assert item != null;
assert !DexItemFactory.isInternalSentinel(item);
T previous = map.putIfAbsent(item, item);
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index aa6b651..af70929 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -56,6 +56,22 @@
return null;
}
+ public DexValueType asDexValueType() {
+ return null;
+ }
+
+ public DexValueArray asDexValueArray() {
+ return null;
+ }
+
+ public boolean isDexValueType() {
+ return false;
+ }
+
+ public boolean isDexValueArray() {
+ return false;
+ }
+
public static DexValue fromAsmBootstrapArgument(
Object value, JarApplicationReader application, DexType clazz) {
if (value instanceof Integer) {
@@ -835,6 +851,16 @@
DexMethod method, int instructionOffset) {
value.collectIndexedItems(indexedItems, method, instructionOffset);
}
+
+ @Override
+ public DexValueType asDexValueType() {
+ return this;
+ }
+
+ @Override
+ public boolean isDexValueType() {
+ return true;
+ }
}
static public class DexValueField extends NestedDexValue<DexField> {
@@ -979,6 +1005,16 @@
public String toString() {
return "Array " + Arrays.toString(values);
}
+
+ @Override
+ public boolean isDexValueArray() {
+ return true;
+ }
+
+ @Override
+ public DexValueArray asDexValueArray() {
+ return this;
+ }
}
static public class DexValueAnnotation extends DexValue {
diff --git a/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java b/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java
index 68c7da1..6ff5c75 100644
--- a/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java
+++ b/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java
@@ -5,8 +5,10 @@
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.utils.ArrayUtils;
import java.util.Arrays;
import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.function.Predicate;
/**
@@ -193,4 +195,12 @@
}
return new ParameterAnnotationsList(filtered, missingParameterAnnotations);
}
+
+ public ParameterAnnotationsList rewrite(Function<DexAnnotationSet, DexAnnotationSet> mapper) {
+ DexAnnotationSet[] rewritten = ArrayUtils.map(DexAnnotationSet[].class, values, mapper);
+ if (rewritten != values) {
+ return new ParameterAnnotationsList(rewritten, missingParameterAnnotations);
+ }
+ return this;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
new file mode 100644
index 0000000..d654c87
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
@@ -0,0 +1,261 @@
+// 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.analysis;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.ArrayPut;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeNewArray;
+import com.android.tools.r8.ir.code.NewArrayEmpty;
+import com.android.tools.r8.ir.code.NewArrayFilledData;
+import com.android.tools.r8.ir.code.StaticPut;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.LongInterval;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+public class ValueMayDependOnEnvironmentAnalysis {
+
+ private final AppView<?> appView;
+ private final IRCode code;
+ private final DexType context;
+
+ public ValueMayDependOnEnvironmentAnalysis(AppView<?> appView, IRCode code) {
+ this.appView = appView;
+ this.code = code;
+ this.context = code.method.method.holder;
+ }
+
+ public boolean valueMayDependOnEnvironment(Value value) {
+ Value root = value.getAliasedValue();
+ if (root.isConstant()) {
+ return false;
+ }
+ if (isConstantArrayThroughoutMethod(root)) {
+ return false;
+ }
+ if (isNewInstanceWithoutEnvironmentDependentFields(root)) {
+ return false;
+ }
+ return true;
+ }
+
+ public boolean isConstantArrayThroughoutMethod(Value value) {
+ Value root = value.getAliasedValue();
+ if (root.isPhi()) {
+ // Would need to track the aliases, just give up.
+ return false;
+ }
+
+ Instruction definition = root.definition;
+
+ // Check that it is a constant array with a known size at this point in the IR.
+ long size;
+ if (definition.isInvokeNewArray()) {
+ InvokeNewArray invokeNewArray = definition.asInvokeNewArray();
+ for (Value argument : invokeNewArray.arguments()) {
+ if (!argument.isConstant()) {
+ return false;
+ }
+ }
+ size = invokeNewArray.arguments().size();
+ } else if (definition.isNewArrayEmpty()) {
+ NewArrayEmpty newArrayEmpty = definition.asNewArrayEmpty();
+ Value sizeValue = newArrayEmpty.size().getAliasedValue();
+ if (!sizeValue.hasValueRange()) {
+ return false;
+ }
+ LongInterval sizeRange = sizeValue.getValueRange();
+ if (!sizeRange.isSingleValue()) {
+ return false;
+ }
+ size = sizeRange.getSingleValue();
+ } else {
+ // Some other array creation.
+ return false;
+ }
+
+ if (size < 0) {
+ // Check for NegativeArraySizeException.
+ return false;
+ }
+
+ if (size == 0) {
+ // Empty arrays are always constant.
+ return true;
+ }
+
+ // Allow array-put and new-array-filled-data instructions that immediately follow the array
+ // creation.
+ Set<Instruction> consumedInstructions = Sets.newIdentityHashSet();
+
+ for (Instruction instruction : definition.getBlock().instructionsAfter(definition)) {
+ if (instruction.isArrayPut()) {
+ ArrayPut arrayPut = instruction.asArrayPut();
+ Value array = arrayPut.array().getAliasedValue();
+ if (array != root) {
+ // This ends the chain of array-put instructions that are allowed immediately after the
+ // array creation.
+ break;
+ }
+
+ LongInterval indexRange = arrayPut.index().getValueRange();
+ if (!indexRange.isSingleValue()) {
+ return false;
+ }
+
+ long index = indexRange.getSingleValue();
+ if (index < 0 || index >= size) {
+ return false;
+ }
+
+ if (!arrayPut.value().isConstant()) {
+ return false;
+ }
+
+ consumedInstructions.add(arrayPut);
+ continue;
+ }
+
+ if (instruction.isNewArrayFilledData()) {
+ NewArrayFilledData newArrayFilledData = instruction.asNewArrayFilledData();
+ Value array = newArrayFilledData.src();
+ if (array != root) {
+ break;
+ }
+
+ consumedInstructions.add(newArrayFilledData);
+ continue;
+ }
+
+ if (instruction.instructionMayHaveSideEffects(appView, context)) {
+ // This ends the chain of array-put instructions that are allowed immediately after the
+ // array creation.
+ break;
+ }
+ }
+
+ // Check that the array is not mutated before the end of this method.
+ //
+ // Currently, we only allow the array to flow into static-put instructions that are not
+ // followed by an instruction that may have side effects. Instructions that do not have any
+ // side effects are ignored because they cannot mutate the array.
+ return !valueMayBeMutatedBeforeMethodExit(root, consumedInstructions);
+ }
+
+ private boolean isNewInstanceWithoutEnvironmentDependentFields(Value value) {
+ assert !value.hasAliasedValue();
+
+ if (value.isPhi() || !value.definition.isNewInstance()) {
+ return false;
+ }
+
+ // Find the single constructor invocation.
+ InvokeMethod constructorInvoke = null;
+ for (Instruction instruction : value.uniqueUsers()) {
+ if (!instruction.isInvokeDirect()) {
+ continue;
+ }
+
+ InvokeDirect invoke = instruction.asInvokeDirect();
+ if (!appView.dexItemFactory().isConstructor(invoke.getInvokedMethod())) {
+ continue;
+ }
+
+ if (invoke.getReceiver().getAliasedValue() != value) {
+ continue;
+ }
+
+ if (constructorInvoke == null) {
+ constructorInvoke = invoke;
+ } else {
+ // Not a single constructor invocation, give up.
+ return false;
+ }
+ }
+
+ if (constructorInvoke == null) {
+ // Didn't find a constructor invocation, give up.
+ return false;
+ }
+
+ // Check that it is a trivial initializer (otherwise, the constructor could do anything).
+ DexEncodedMethod constructor = appView.definitionFor(constructorInvoke.getInvokedMethod());
+ if (constructor == null) {
+ return false;
+ }
+
+ TrivialInitializer initializerInfo =
+ constructor.getOptimizationInfo().getTrivialInitializerInfo();
+ if (initializerInfo == null || !initializerInfo.isTrivialInstanceInitializer()) {
+ return false;
+ }
+
+ // Check that none of the arguments to the constructor depend on the environment.
+ for (int i = 1; i < constructorInvoke.arguments().size(); i++) {
+ Value argument = constructorInvoke.arguments().get(i);
+ if (valueMayDependOnEnvironment(argument)) {
+ return false;
+ }
+ }
+
+ // Finally, check that the object does not escape.
+ return !valueMayBeMutatedBeforeMethodExit(value, ImmutableSet.of(constructorInvoke));
+ }
+
+ private boolean valueMayBeMutatedBeforeMethodExit(Value value, Set<Instruction> whitelist) {
+ assert !value.hasAliasedValue();
+
+ if (value.numberOfPhiUsers() > 0) {
+ // Could be mutated indirectly.
+ return true;
+ }
+
+ Set<Instruction> visited = Sets.newIdentityHashSet();
+ for (Instruction user : value.uniqueUsers()) {
+ if (whitelist.contains(user)) {
+ continue;
+ }
+
+ if (user.isStaticPut()) {
+ StaticPut staticPut = user.asStaticPut();
+ if (visited.contains(staticPut)) {
+ // Already visited previously.
+ continue;
+ }
+ for (Instruction instruction : code.getInstructionsReachableFrom(staticPut)) {
+ if (!visited.add(instruction)) {
+ // Already visited previously.
+ continue;
+ }
+ if (instruction.isStaticPut()) {
+ StaticPut otherStaticPut = instruction.asStaticPut();
+ if (otherStaticPut.getField().holder == staticPut.getField().holder
+ && instruction.instructionInstanceCanThrow(appView, context).cannotThrow()) {
+ continue;
+ }
+ return true;
+ }
+ if (instruction.instructionMayTriggerMethodInvocation(appView, context)) {
+ return true;
+ }
+ }
+ continue;
+ }
+
+ // Other user than static-put, just give up.
+ return false;
+ }
+
+ return false;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
index 2d67908..1ef58fb 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.BasicBlock;
@@ -245,9 +246,14 @@
private void updateFieldOptimizationInfo(DexEncodedField field, Value value) {
TypeLatticeElement fieldType =
TypeLatticeElement.fromDexType(field.field.type, Nullability.maybeNull(), appView);
- TypeLatticeElement valueType = value.getTypeLattice();
- if (valueType.strictlyLessThan(fieldType, appView)) {
- feedback.markFieldHasDynamicType(field, valueType);
+ TypeLatticeElement dynamicUpperBoundType = value.getDynamicUpperBoundType(appView);
+ if (dynamicUpperBoundType.strictlyLessThan(fieldType, appView)) {
+ feedback.markFieldHasDynamicUpperBoundType(field, dynamicUpperBoundType);
+ }
+ ClassTypeLatticeElement dynamicLowerBoundType = value.getDynamicLowerBoundType(appView);
+ if (dynamicLowerBoundType != null) {
+ assert dynamicLowerBoundType.lessThanOrEqual(dynamicUpperBoundType, appView);
+ feedback.markFieldHasDynamicLowerBoundType(field, dynamicLowerBoundType);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
index 4071c99..35c483b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.ValueMayDependOnEnvironmentAnalysis;
import com.android.tools.r8.ir.code.ArrayPut;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
@@ -42,6 +43,8 @@
OptionalBool controlFlowMayDependOnEnvironment = OptionalBool.unknown();
boolean mayHaveSideEffects = false;
+ ValueMayDependOnEnvironmentAnalysis environmentAnalysis =
+ new ValueMayDependOnEnvironmentAnalysis(appView, code);
for (Instruction instruction : code.instructions()) {
// Array stores to a newly created array are only observable if they may throw, or if the
// array content may depend on the environment.
@@ -50,14 +53,14 @@
Value array = arrayPut.array().getAliasedValue();
if (array.isPhi()
|| !array.definition.isCreatingArray()
- || arrayPut.index().mayDependOnEnvironment(appView, code)
- || arrayPut.value().mayDependOnEnvironment(appView, code)
+ || environmentAnalysis.valueMayDependOnEnvironment(arrayPut.index())
+ || environmentAnalysis.valueMayDependOnEnvironment(arrayPut.value())
|| arrayPut.instructionInstanceCanThrow(appView, context).isThrowing()) {
return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
}
if (controlFlowMayDependOnEnvironment.isUnknown()) {
controlFlowMayDependOnEnvironment =
- OptionalBool.of(code.controlFlowMayDependOnEnvironment(appView));
+ OptionalBool.of(code.controlFlowMayDependOnEnvironment(environmentAnalysis));
}
if (controlFlowMayDependOnEnvironment.isTrue()) {
return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
@@ -76,7 +79,7 @@
}
if (controlFlowMayDependOnEnvironment.isUnknown()) {
controlFlowMayDependOnEnvironment =
- OptionalBool.of(code.controlFlowMayDependOnEnvironment(appView));
+ OptionalBool.of(code.controlFlowMayDependOnEnvironment(environmentAnalysis));
}
if (controlFlowMayDependOnEnvironment.isTrue()) {
return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
@@ -92,7 +95,7 @@
return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
}
for (Value argument : invokeNewArray.arguments()) {
- if (argument.mayDependOnEnvironment(appView, code)) {
+ if (environmentAnalysis.valueMayDependOnEnvironment(argument)) {
return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
}
}
@@ -111,7 +114,7 @@
DexEncodedField field = appView.appInfo().resolveField(staticPut.getField());
if (field == null
|| field.field.holder != context
- || staticPut.value().mayDependOnEnvironment(appView, code)
+ || environmentAnalysis.valueMayDependOnEnvironment(staticPut.value())
|| instruction.instructionInstanceCanThrow(appView, context).isThrowing()) {
return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
}
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 9ba29fd..37de422 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
@@ -16,6 +16,7 @@
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
+import java.util.Objects;
import java.util.Set;
public class Assume<An extends Assumption> extends Instruction {
@@ -46,14 +47,18 @@
}
public static Assume<DynamicTypeAssumption> createAssumeDynamicTypeInstruction(
- TypeLatticeElement type,
- ClassTypeLatticeElement lowerBoundType,
+ TypeLatticeElement dynamicUpperBoundType,
+ ClassTypeLatticeElement dynamicLowerBoundType,
Value dest,
Value src,
Instruction origin,
AppView<?> appView) {
return new Assume<>(
- new DynamicTypeAssumption(type, lowerBoundType), dest, src, origin, appView);
+ new DynamicTypeAssumption(dynamicUpperBoundType, dynamicLowerBoundType),
+ dest,
+ src,
+ origin,
+ appView);
}
@Override
@@ -170,7 +175,7 @@
return true;
}
if (assumption.isAssumeDynamicType()) {
- outType = asAssumeDynamicType().assumption.getType();
+ outType = asAssumeDynamicType().assumption.getDynamicUpperBoundType();
}
if (appView.appInfo().hasSubtyping()) {
if (outType.isClassType()
@@ -288,19 +293,24 @@
@Override
public String toString() {
- // During branch simplification, the origin `if` could be simplified.
- // It means the assumption became "truth."
- assert origin.hasBlock() || isAssumeNonNull();
+ // `origin` could become obsolete:
+ // 1) during branch simplification, the origin `if` could be simplified, which means the
+ // assumption became "truth."
+ // 2) invoke-interface could be devirtualized, while its dynamic type and/or non-null receiver
+ // are still valid.
String originString =
- origin.hasBlock() ? " (origin: `" + origin.toString() + "`)" : " (origin simplified)";
+ origin.hasBlock() ? " (origin: `" + origin.toString() + "`)" : " (obsolete origin)";
if (isAssumeNone() || isAssumeNonNull()) {
return super.toString() + originString;
}
if (isAssumeDynamicType()) {
DynamicTypeAssumption assumption = asAssumeDynamicType().getAssumption();
return super.toString()
- + "; upper bound: " + assumption.type
- + (assumption.lowerBoundType != null ? "; lower bound: " + assumption.lowerBoundType : "")
+ + "; upper bound: "
+ + assumption.dynamicUpperBoundType
+ + (assumption.dynamicLowerBoundType != null
+ ? "; lower bound: " + assumption.dynamicLowerBoundType
+ : "")
+ originString;
}
return super.toString();
@@ -348,20 +358,21 @@
public static class DynamicTypeAssumption extends Assumption {
- private final TypeLatticeElement type;
- private final ClassTypeLatticeElement lowerBoundType;
+ private final TypeLatticeElement dynamicUpperBoundType;
+ private final ClassTypeLatticeElement dynamicLowerBoundType;
- private DynamicTypeAssumption(TypeLatticeElement type, ClassTypeLatticeElement lowerBoundType) {
- this.type = type;
- this.lowerBoundType = lowerBoundType;
+ private DynamicTypeAssumption(
+ TypeLatticeElement dynamicUpperBoundType, ClassTypeLatticeElement dynamicLowerBoundType) {
+ this.dynamicUpperBoundType = dynamicUpperBoundType;
+ this.dynamicLowerBoundType = dynamicLowerBoundType;
}
- public TypeLatticeElement getType() {
- return type;
+ public TypeLatticeElement getDynamicUpperBoundType() {
+ return dynamicUpperBoundType;
}
- public ClassTypeLatticeElement getLowerBoundType() {
- return lowerBoundType;
+ public ClassTypeLatticeElement getDynamicLowerBoundType() {
+ return dynamicLowerBoundType;
}
@Override
@@ -371,7 +382,7 @@
@Override
public boolean verifyCorrectnessOfValues(Value dest, Value src, AppView<?> appView) {
- assert type.lessThanOrEqualUpToNullability(src.getTypeLattice(), appView);
+ assert dynamicUpperBoundType.lessThanOrEqualUpToNullability(src.getTypeLattice(), appView);
return true;
}
@@ -384,12 +395,13 @@
return false;
}
DynamicTypeAssumption assumption = (DynamicTypeAssumption) other;
- return type == assumption.type;
+ return dynamicUpperBoundType == assumption.dynamicUpperBoundType
+ && dynamicLowerBoundType == assumption.dynamicLowerBoundType;
}
@Override
public int hashCode() {
- return type.hashCode();
+ return Objects.hash(dynamicUpperBoundType, dynamicLowerBoundType);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 78018f9..a4c8d4b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -26,6 +26,7 @@
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
@@ -35,7 +36,6 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@@ -854,7 +854,7 @@
}
public Set<Value> cleanForRemoval() {
- Set<Value> affectedValues = new HashSet<>();
+ Set<Value> affectedValues = Sets.newIdentityHashSet();
for (BasicBlock block : successors) {
affectedValues.addAll(block.getPhis());
block.removePredecessor(this, affectedValues);
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index 63f6636..708e947 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -20,7 +20,6 @@
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.util.ArrayList;
-import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
@@ -617,7 +616,7 @@
if (normalExits.isEmpty()) {
assert inlineeCanThrow;
DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
- Set<Value> affectedValues = new HashSet<>();
+ Set<Value> affectedValues = Sets.newIdentityHashSet();
blocksToRemove.addAll(invokePredecessor.unlink(invokeBlock, dominatorTree, affectedValues));
new TypeAnalysis(appView).narrowing(affectedValues);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index b95376e..72e4c9d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -8,13 +8,16 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.AbstractError;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.ConcreteMutableFieldSet;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.Collections;
import java.util.List;
@@ -163,4 +166,39 @@
assert isFieldPut();
return EmptyFieldSet.getInstance();
}
+
+ /**
+ * Returns {@code true} if this instruction may store an instance of a class that has a non-
+ * default finalize() method in a field. In that case, it is not safe to remove this instruction,
+ * since that could change the lifetime of the value.
+ */
+ boolean isStoringObjectWithFinalizer(AppInfoWithLiveness appInfo) {
+ assert isFieldPut();
+ TypeLatticeElement type = value().getTypeLattice();
+ TypeLatticeElement baseType =
+ type.isArrayType() ? type.asArrayTypeLatticeElement().getArrayBaseTypeLattice() : type;
+ if (baseType.isClassType()) {
+ Value root = value().getAliasedValue();
+ if (!root.isPhi() && root.definition.isNewInstance()) {
+ DexClass clazz = appInfo.definitionFor(root.definition.asNewInstance().clazz);
+ if (clazz == null) {
+ return true;
+ }
+ if (clazz.superType == null) {
+ return false;
+ }
+ DexItemFactory dexItemFactory = appInfo.dexItemFactory();
+ DexEncodedMethod resolutionResult =
+ appInfo
+ .resolveMethod(clazz.type, dexItemFactory.objectMethods.finalize)
+ .asSingleTarget();
+ return resolutionResult != null && resolutionResult.isProgramMethod(appInfo);
+ }
+
+ return appInfo.mayHaveFinalizeMethodDirectlyOrIndirectly(
+ baseType.asClassTypeLatticeElement());
+ }
+
+ return false;
+ }
}
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 99a4956..955182b 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
@@ -10,6 +10,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.TypeChecker;
+import com.android.tools.r8.ir.analysis.ValueMayDependOnEnvironmentAnalysis;
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
@@ -35,6 +36,7 @@
import java.util.Deque;
import java.util.HashSet;
import java.util.IdentityHashMap;
+import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
@@ -152,8 +154,9 @@
worklist.addAll(sorted.reverse());
while (!worklist.isEmpty()) {
BasicBlock block = worklist.poll();
- Set<Value> live = new HashSet<>();
- Set<Value> liveLocals = new HashSet<>();
+ // Note that the iteration order of live values matters when inserting spill/restore moves.
+ Set<Value> live = new LinkedHashSet<>();
+ Set<Value> liveLocals = Sets.newIdentityHashSet();
Deque<Value> liveStack = new ArrayDeque<>();
Set<BasicBlock> exceptionalSuccessors = block.getCatchHandlers().getUniqueTargets();
for (BasicBlock succ : block.getSuccessors()) {
@@ -259,7 +262,8 @@
return liveAtEntrySets;
}
- public boolean controlFlowMayDependOnEnvironment(AppView<?> appView) {
+ public boolean controlFlowMayDependOnEnvironment(
+ ValueMayDependOnEnvironmentAnalysis environmentAnalysis) {
for (BasicBlock block : blocks) {
if (block.hasCatchHandlers()) {
// Whether an instruction throws may generally depend on the environment.
@@ -267,16 +271,16 @@
}
if (block.exit().isIf()) {
If ifInstruction = block.exit().asIf();
- if (ifInstruction.lhs().mayDependOnEnvironment(appView, this)) {
+ if (environmentAnalysis.valueMayDependOnEnvironment(ifInstruction.lhs())) {
return true;
}
if (!ifInstruction.isZeroTest()
- && ifInstruction.rhs().mayDependOnEnvironment(appView, this)) {
+ && environmentAnalysis.valueMayDependOnEnvironment(ifInstruction.rhs())) {
return true;
}
} else if (block.exit().isSwitch()) {
Switch switchInstruction = block.exit().asSwitch();
- if (switchInstruction.value().mayDependOnEnvironment(appView, this)) {
+ if (environmentAnalysis.valueMayDependOnEnvironment(switchInstruction.value())) {
return true;
}
}
@@ -631,7 +635,7 @@
}
private boolean consistentDefUseChains() {
- Set<Value> values = new HashSet<>();
+ Set<Value> values = Sets.newIdentityHashSet();
for (BasicBlock block : blocks) {
int predecessorCount = block.getPredecessors().size();
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index 4dcb749..32484c3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -110,7 +110,8 @@
DexEncodedField encodedField = appInfoWithLiveness.resolveField(getField());
assert encodedField != null : "NoSuchFieldError (resolution failure) should be caught.";
- return appInfoWithLiveness.isFieldRead(encodedField);
+ return appInfoWithLiveness.isFieldRead(encodedField)
+ || isStoringObjectWithFinalizer(appInfoWithLiveness);
}
// In D8, we always have to assume that the field can be read, and thus have side effects.
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 5bddf13..1129616 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -34,8 +34,8 @@
import com.android.tools.r8.utils.StringUtils.BraceType;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
-import java.util.HashSet;
import java.util.Iterator;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
@@ -46,7 +46,7 @@
protected final List<Value> inValues = new ArrayList<>();
private BasicBlock block = null;
private int number = -1;
- private Set<Value> debugValues = null;
+ private LinkedHashSet<Value> debugValues = null;
private Position position = null;
protected Instruction(Value outValue) {
@@ -141,7 +141,7 @@
public void addDebugValue(Value value) {
assert value.hasLocalInfo();
if (debugValues == null) {
- debugValues = new HashSet<>();
+ debugValues = new LinkedHashSet<>();
}
if (debugValues.add(value)) {
value.addDebugUser(this);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
index 48baef4..ab51239 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -25,6 +25,7 @@
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.ArrayList;
import java.util.Collection;
@@ -218,14 +219,21 @@
assert false : "Expected to be able to find the enclosing class of a method definition";
return true;
}
- boolean targetMayHaveSideEffects;
+
if (appViewWithLiveness.appInfo().noSideEffects.containsKey(target.method)) {
- targetMayHaveSideEffects = false;
- } else {
- targetMayHaveSideEffects = target.getOptimizationInfo().mayHaveSideEffects();
+ return false;
}
- return targetMayHaveSideEffects;
+ MethodOptimizationInfo optimizationInfo = target.getOptimizationInfo();
+ if (target.isInstanceInitializer()) {
+ TrivialInitializer trivialInitializerInfo = optimizationInfo.getTrivialInitializerInfo();
+ if (trivialInitializerInfo != null
+ && trivialInitializerInfo.isTrivialInstanceInitializer()) {
+ return false;
+ }
+ }
+
+ return target.getOptimizationInfo().mayHaveSideEffects();
}
return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Move.java b/src/main/java/com/android/tools/r8/ir/code/Move.java
index 84da0d1..9054d07 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Move.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Move.java
@@ -122,4 +122,11 @@
public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, DexType context) {
return false;
}
+
+ @Override
+ public boolean verifyTypes(AppView<?> appView) {
+ super.verifyTypes(appView);
+ assert src().getTypeLattice().equals(outValue().getTypeLattice());
+ return true;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index 7e6fffe..7a38882 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -11,7 +11,10 @@
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
@@ -168,6 +171,18 @@
return true;
}
+ // Verify that the object does not have a finalizer.
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ ResolutionResult finalizeResolutionResult =
+ appView.appInfo().resolveMethod(clazz, dexItemFactory.objectMethods.finalize);
+ if (finalizeResolutionResult.hasSingleTarget()) {
+ DexMethod finalizeMethod = finalizeResolutionResult.asSingleTarget().method;
+ if (finalizeMethod != dexItemFactory.enumMethods.finalize
+ && finalizeMethod != dexItemFactory.objectMethods.finalize) {
+ return true;
+ }
+ }
+
return false;
}
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 95dac01..ae145b3 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
@@ -19,6 +19,7 @@
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -342,7 +343,7 @@
@Override
public boolean isValueOnStack() {
- assert verifyIsStackPhi(new HashSet<>());
+ assert verifyIsStackPhi(Sets.newIdentityHashSet());
return isStackPhi;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index 5d8fb67..63e5c10 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -15,16 +15,12 @@
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -132,40 +128,6 @@
return true;
}
- /**
- * Returns {@code true} if this instruction may store a value that has a non-default finalize()
- * method in a static field. In that case, it is not safe to remove this instruction, since that
- * could change the lifetime of the value.
- */
- private boolean isStoringObjectWithFinalizer(AppInfoWithLiveness appInfo) {
- TypeLatticeElement type = value().getTypeLattice();
- TypeLatticeElement baseType =
- type.isArrayType() ? type.asArrayTypeLatticeElement().getArrayBaseTypeLattice() : type;
- if (baseType.isClassType()) {
- Value root = value().getAliasedValue();
- if (!root.isPhi() && root.definition.isNewInstance()) {
- DexClass clazz = appInfo.definitionFor(root.definition.asNewInstance().clazz);
- if (clazz == null) {
- return true;
- }
- if (clazz.superType == null) {
- return false;
- }
- DexItemFactory dexItemFactory = appInfo.dexItemFactory();
- DexEncodedMethod resolutionResult =
- appInfo
- .resolveMethod(clazz.type, dexItemFactory.objectMethods.finalize)
- .asSingleTarget();
- return resolutionResult != null && resolutionResult.isProgramMethod(appInfo);
- }
-
- return appInfo.mayHaveFinalizeMethodDirectlyOrIndirectly(
- baseType.asClassTypeLatticeElement().getClassType());
- }
-
- return false;
- }
-
@Override
public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
// static-put can be dead as long as it cannot have any of the following:
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 33b06c5..74add42 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
@@ -9,6 +9,8 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
@@ -28,7 +30,6 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@@ -37,7 +38,7 @@
import java.util.Set;
import java.util.function.Predicate;
-public class Value {
+public class Value implements Comparable<Value> {
public void constrainType(
ValueTypeConstraint constraint, DexMethod method, Origin origin, Reporter reporter) {
@@ -431,16 +432,20 @@
public Set<Instruction> aliasedUsers() {
Set<Instruction> users = SetUtils.newIdentityHashSet(uniqueUsers());
- collectAliasedUsersViaAssume(uniqueUsers(), users);
+ Set<Instruction> visited = Sets.newIdentityHashSet();
+ collectAliasedUsersViaAssume(visited, uniqueUsers(), users);
return users;
}
private static void collectAliasedUsersViaAssume(
- Set<Instruction> usersToTest, Set<Instruction> collectedUsers) {
+ Set<Instruction> visited, Set<Instruction> usersToTest, Set<Instruction> collectedUsers) {
for (Instruction user : usersToTest) {
+ if (!visited.add(user)) {
+ continue;
+ }
if (user.isAssume()) {
collectedUsers.addAll(user.outValue().uniqueUsers());
- collectAliasedUsersViaAssume(user.outValue().uniqueUsers(), collectedUsers);
+ collectAliasedUsersViaAssume(visited, user.outValue().uniqueUsers(), collectedUsers);
}
}
}
@@ -510,17 +515,6 @@
return false;
}
- public boolean mayDependOnEnvironment(AppView<?> appView, IRCode code) {
- Value root = getAliasedValue();
- if (root.isConstant()) {
- return false;
- }
- if (root.isConstantArrayThroughoutMethod(appView, code)) {
- return false;
- }
- return true;
- }
-
public boolean usedInMonitorOperation() {
for (Instruction instruction : uniqueUsers()) {
if (instruction.isMonitor()) {
@@ -760,6 +754,11 @@
}
@Override
+ public int compareTo(Value value) {
+ return Integer.compare(this.number, value.number);
+ }
+
+ @Override
public int hashCode() {
return number;
}
@@ -831,149 +830,33 @@
return definition.isOutConstant() && !hasLocalInfo();
}
- public boolean isConstantArrayThroughoutMethod(AppView<?> appView, IRCode code) {
+ public DexEncodedField getEnumField(AppView<?> appView) {
+ if (!appView.enableWholeProgramOptimizations()) {
+ return null;
+ }
+
Value root = getAliasedValue();
- if (root.isPhi()) {
- // Would need to track the aliases, just give up.
- return false;
+ if (root.isPhi() || !root.definition.isStaticGet()) {
+ return null;
}
- DexType context = code.method.method.holder;
- Instruction definition = root.definition;
-
- // Check that it is a constant array with a known size at this point in the IR.
- long size;
- if (definition.isInvokeNewArray()) {
- InvokeNewArray invokeNewArray = definition.asInvokeNewArray();
- for (Value argument : invokeNewArray.arguments()) {
- if (!argument.isConstant()) {
- return false;
- }
- }
- size = invokeNewArray.arguments().size();
- } else if (definition.isNewArrayEmpty()) {
- NewArrayEmpty newArrayEmpty = definition.asNewArrayEmpty();
- Value sizeValue = newArrayEmpty.size().getAliasedValue();
- if (!sizeValue.hasValueRange()) {
- return false;
- }
- LongInterval sizeRange = sizeValue.getValueRange();
- if (!sizeRange.isSingleValue()) {
- return false;
- }
- size = sizeRange.getSingleValue();
- } else {
- // Some other array creation.
- return false;
+ StaticGet staticGet = root.definition.asStaticGet();
+ DexField field = staticGet.getField();
+ if (field.type != field.holder) {
+ return null;
}
- if (size < 0) {
- // Check for NegativeArraySizeException.
- return false;
+ DexEncodedField encodedField = appView.definitionFor(field);
+ if (encodedField == null) {
+ return null;
}
- if (size == 0) {
- // Empty arrays are always constant.
- return true;
+ DexClass clazz = appView.definitionFor(encodedField.field.holder);
+ if (clazz == null || !clazz.isEnum()) {
+ return null;
}
- // Allow array-put and new-array-filled-data instructions that immediately follow the array
- // creation.
- Set<Instruction> consumedInstructions = Sets.newIdentityHashSet();
-
- for (Instruction instruction : definition.getBlock().instructionsAfter(definition)) {
- if (instruction.isArrayPut()) {
- ArrayPut arrayPut = instruction.asArrayPut();
- Value array = arrayPut.array().getAliasedValue();
- if (array != root) {
- // This ends the chain of array-put instructions that are allowed immediately after the
- // array creation.
- break;
- }
-
- LongInterval indexRange = arrayPut.index().getValueRange();
- if (!indexRange.isSingleValue()) {
- return false;
- }
-
- long index = indexRange.getSingleValue();
- if (index < 0 || index >= size) {
- return false;
- }
-
- if (!arrayPut.value().isConstant()) {
- return false;
- }
-
- consumedInstructions.add(arrayPut);
- continue;
- }
-
- if (instruction.isNewArrayFilledData()) {
- NewArrayFilledData newArrayFilledData = instruction.asNewArrayFilledData();
- Value array = newArrayFilledData.src();
- if (array != root) {
- break;
- }
-
- consumedInstructions.add(newArrayFilledData);
- continue;
- }
-
- if (instruction.instructionMayHaveSideEffects(appView, context)) {
- // This ends the chain of array-put instructions that are allowed immediately after the
- // array creation.
- break;
- }
- }
-
- // Check that the array is not mutated before the end of this method.
- //
- // Currently, we only allow the array to flow into static-put instructions that are not
- // followed by an instruction that may have side effects. Instructions that do not have any
- // side effects are ignored because they cannot mutate the array.
- Set<Instruction> visitedFromStaticPut = Sets.newIdentityHashSet();
- for (Instruction user : root.uniqueUsers()) {
- if (consumedInstructions.contains(user)) {
- continue;
- }
-
- if (user.isStaticPut()) {
- StaticPut staticPut = user.asStaticPut();
- if (visitedFromStaticPut.contains(staticPut)) {
- // Already visited previously.
- continue;
- }
- for (Instruction instruction : code.getInstructionsReachableFrom(staticPut)) {
- if (!visitedFromStaticPut.add(instruction)) {
- // Already visited previously.
- continue;
- }
- if (instruction.isStaticPut()) {
- StaticPut otherStaticPut = instruction.asStaticPut();
- if (otherStaticPut.getField().holder == staticPut.getField().holder
- && instruction.instructionInstanceCanThrow(appView, context).cannotThrow()) {
- continue;
- }
- return false;
- }
- if (instruction.instructionMayTriggerMethodInvocation(appView, context)) {
- return false;
- }
- }
- continue;
- }
-
- // Other user than static-put, just give up.
- return false;
- }
-
- if (root.numberOfPhiUsers() > 0) {
- // Could be mutated indirectly.
- return false;
- }
-
- return true;
+ return encodedField;
}
public boolean isPhi() {
@@ -1030,7 +913,7 @@
if (isPhi()) {
Phi self = this.asPhi();
if (seen == null) {
- seen = new HashSet<>();
+ seen = Sets.newIdentityHashSet();
}
if (seen.contains(self)) {
return true;
@@ -1101,7 +984,7 @@
public boolean isDead(AppView<?> appView, IRCode code, Predicate<Instruction> ignoreUser) {
// Totally unused values are trivially dead.
- return !isUsed() || isDead(appView, code, ignoreUser, new HashSet<>());
+ return !isUsed() || isDead(appView, code, ignoreUser, Sets.newIdentityHashSet());
}
/**
@@ -1209,7 +1092,8 @@
if (aliasedValue != null) {
// If there is an alias of the receiver, which is defined by an Assume<DynamicTypeAssumption>
// instruction, then use the dynamic type as the refined receiver type.
- lattice = aliasedValue.definition.asAssumeDynamicType().getAssumption().getType();
+ lattice =
+ aliasedValue.definition.asAssumeDynamicType().getAssumption().getDynamicUpperBoundType();
// For precision, verify that the dynamic type is at least as precise as the static type.
assert lattice.lessThanOrEqualUpToNullability(typeLattice, appView)
@@ -1250,7 +1134,7 @@
Value aliasedValue = getSpecificAliasedValue(value -> value.definition.isAssumeDynamicType());
if (aliasedValue != null) {
ClassTypeLatticeElement lattice =
- aliasedValue.definition.asAssumeDynamicType().getAssumption().getLowerBoundType();
+ aliasedValue.definition.asAssumeDynamicType().getAssumption().getDynamicLowerBoundType();
return lattice != null && typeLattice.isDefinitelyNotNull() && lattice.isNullable()
? lattice.asMeetWithNotNull().asClassTypeLatticeElement()
: lattice;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index 307215a..b9f72f7 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -6,8 +6,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.ir.conversion.CallGraphBuilder.CycleEliminator;
-import com.android.tools.r8.ir.conversion.CallGraphBuilder.CycleEliminator.CycleEliminationResult;
+import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator.CycleEliminationResult;
import com.android.tools.r8.ir.conversion.CallSiteInformation.CallGraphBasedCallSiteInformation;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Timing;
@@ -76,7 +75,14 @@
caller.callees.remove(this);
}
- public void cleanForRemoval() {
+ public void cleanCalleesForRemoval() {
+ assert callers.isEmpty();
+ for (Node callee : callees) {
+ callee.callers.remove(this);
+ }
+ }
+
+ public void cleanCallersForRemoval() {
assert callees.isEmpty();
for (Node caller : callers) {
caller.callees.remove(this);
@@ -103,6 +109,10 @@
return callers.contains(method);
}
+ public boolean isRoot() {
+ return callers.isEmpty();
+ }
+
public boolean isLeaf() {
return callees.isEmpty();
}
@@ -123,21 +133,24 @@
builder.append(callers.size());
builder.append(" callers");
builder.append(", invoke count ").append(numberOfCallSites);
- builder.append(").\n");
+ builder.append(").");
+ builder.append(System.lineSeparator());
if (callees.size() > 0) {
- builder.append("Callees:\n");
+ builder.append("Callees:");
+ builder.append(System.lineSeparator());
for (Node call : callees) {
builder.append(" ");
builder.append(call.method.toSourceString());
- builder.append("\n");
+ builder.append(System.lineSeparator());
}
}
if (callers.size() > 0) {
- builder.append("Callers:\n");
+ builder.append("Callers:");
+ builder.append(System.lineSeparator());
for (Node caller : callers) {
builder.append(" ");
builder.append(caller.method.toSourceString());
- builder.append("\n");
+ builder.append(System.lineSeparator());
}
}
return builder.toString();
@@ -156,14 +169,6 @@
return new CallGraphBuilder(appView);
}
- /**
- * Extract the next set of leaves (nodes with an call (outgoing) degree of 0) if any.
- *
- * <p>All nodes in the graph are extracted if called repeatedly until null is returned. Please
- * note that there are no cycles in this graph (see {@link CycleEliminator#breakCycles}).
- *
- * <p>
- */
static MethodProcessor createMethodProcessor(
AppView<AppInfoWithLiveness> appView, ExecutorService executorService, Timing timing)
throws ExecutionException {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java
index 8078fe0..546c021 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java
@@ -4,59 +4,26 @@
package com.android.tools.r8.ir.conversion;
-import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexCallSite;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.ir.conversion.CallGraph.Node;
-import com.android.tools.r8.ir.conversion.CallGraphBuilder.CycleEliminator.CycleEliminationResult;
-import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.ThreadUtils;
-import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
-import java.util.ArrayDeque;
+import com.google.common.base.Predicates;
import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Deque;
-import java.util.IdentityHashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
-import java.util.function.Consumer;
-public class CallGraphBuilder {
-
- private final AppView<AppInfoWithLiveness> appView;
- private final Map<DexMethod, Node> nodes = new IdentityHashMap<>();
- private final Map<DexMethod, Set<DexEncodedMethod>> possibleTargetsCache =
- new ConcurrentHashMap<>();
+public class CallGraphBuilder extends CallGraphBuilderBase {
CallGraphBuilder(AppView<AppInfoWithLiveness> appView) {
- this.appView = appView;
+ super(appView);
}
- public CallGraph build(ExecutorService executorService, Timing timing) throws ExecutionException {
+ @Override
+ void process(ExecutorService executorService) throws ExecutionException {
List<Future<?>> futures = new ArrayList<>();
for (DexProgramClass clazz : appView.appInfo().classes()) {
if (clazz.hasMethods()) {
@@ -69,19 +36,6 @@
}
}
ThreadUtils.awaitFutures(futures);
-
- assert verifyAllMethodsWithCodeExists();
-
- timing.begin("Cycle elimination");
- // Sort the nodes for deterministic cycle elimination.
- Set<Node> nodesWithDeterministicOrder = Sets.newTreeSet(nodes.values());
- CycleEliminator cycleEliminator =
- new CycleEliminator(nodesWithDeterministicOrder, appView.options());
- CycleEliminationResult cycleEliminationResult = cycleEliminator.breakCycles();
- timing.end();
- assert cycleEliminator.breakCycles().numberOfRemovedEdges() == 0; // The cycles should be gone.
-
- return new CallGraph(nodesWithDeterministicOrder, cycleEliminationResult);
}
private void processClass(DexProgramClass clazz) {
@@ -90,17 +44,13 @@
private void processMethod(DexEncodedMethod method) {
if (method.hasCode()) {
- method.registerCodeReferences(new InvokeExtractor(getOrCreateNode(method)));
+ method.registerCodeReferences(
+ new InvokeExtractor(getOrCreateNode(method), Predicates.alwaysTrue()));
}
}
- private Node getOrCreateNode(DexEncodedMethod method) {
- synchronized (nodes) {
- return nodes.computeIfAbsent(method.method, ignore -> new Node(method));
- }
- }
-
- private boolean verifyAllMethodsWithCodeExists() {
+ @Override
+ boolean verifyAllMethodsWithCodeExists() {
for (DexProgramClass clazz : appView.appInfo().classes()) {
for (DexEncodedMethod method : clazz.methods()) {
assert !method.hasCode() || nodes.get(method.method) != null;
@@ -108,454 +58,4 @@
}
return true;
}
-
- private class InvokeExtractor extends UseRegistry {
-
- private final Node caller;
-
- InvokeExtractor(Node caller) {
- super(appView.dexItemFactory());
- this.caller = caller;
- }
-
- private void addClassInitializerTarget(DexClass clazz) {
- assert clazz != null;
- if (clazz.isProgramClass() && clazz.hasClassInitializer()) {
- addTarget(clazz.getClassInitializer(), false);
- }
- }
-
- private void addClassInitializerTarget(DexType type) {
- assert type.isClassType();
- DexClass clazz = appView.definitionFor(type);
- if (clazz != null) {
- addClassInitializerTarget(clazz);
- }
- }
-
- private void addTarget(DexEncodedMethod callee, boolean likelySpuriousCallEdge) {
- if (callee.accessFlags.isAbstract()) {
- // Not a valid target.
- return;
- }
- if (appView.appInfo().isPinned(callee.method)) {
- // Since the callee is kept, we cannot inline it into the caller, and we also cannot collect
- // any optimization info for the method. Therefore, we drop the call edge to reduce the
- // total number of call graph edges, which should lead to fewer call graph cycles.
- return;
- }
- assert callee.isProgramMethod(appView);
- getOrCreateNode(callee).addCallerConcurrently(caller, likelySpuriousCallEdge);
- }
-
- private void processInvoke(Type originalType, DexMethod originalMethod) {
- DexEncodedMethod source = caller.method;
- DexMethod context = source.method;
- GraphLenseLookupResult result =
- appView.graphLense().lookupMethod(originalMethod, context, originalType);
- DexMethod method = result.getMethod();
- Type type = result.getType();
- if (type == Type.INTERFACE || type == Type.VIRTUAL) {
- // For virtual and interface calls add all potential targets that could be called.
- ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method.holder, method);
- resolutionResult.forEachTarget(target -> processInvokeWithDynamicDispatch(type, target));
- } else {
- DexEncodedMethod singleTarget =
- appView.appInfo().lookupSingleTarget(type, method, context.holder);
- if (singleTarget != null) {
- assert !source.accessFlags.isBridge() || singleTarget != caller.method;
- DexClass clazz = appView.definitionFor(singleTarget.method.holder);
- assert clazz != null;
- if (clazz.isProgramClass()) {
- // For static invokes, the class could be initialized.
- if (type == Type.STATIC) {
- addClassInitializerTarget(clazz);
- }
- addTarget(singleTarget, false);
- }
- }
- }
- }
-
- private void processInvokeWithDynamicDispatch(Type type, DexEncodedMethod encodedTarget) {
- DexMethod target = encodedTarget.method;
- DexClass clazz = appView.definitionFor(target.holder);
- if (clazz == null) {
- assert false : "Unable to lookup holder of `" + target.toSourceString() + "`";
- return;
- }
-
- if (!appView.options().testing.addCallEdgesForLibraryInvokes) {
- if (clazz.isLibraryClass()) {
- // Likely to have many possible targets.
- return;
- }
- }
-
- boolean isInterface = type == Type.INTERFACE;
- Set<DexEncodedMethod> possibleTargets =
- possibleTargetsCache.computeIfAbsent(
- target,
- method -> {
- ResolutionResult resolution =
- appView.appInfo().resolveMethod(method.holder, method, isInterface);
- if (resolution.isValidVirtualTarget(appView.options())) {
- return resolution.lookupVirtualDispatchTargets(isInterface, appView.appInfo());
- }
- return null;
- });
- if (possibleTargets != null) {
- boolean likelySpuriousCallEdge =
- possibleTargets.size() >= appView.options().callGraphLikelySpuriousCallEdgeThreshold;
- for (DexEncodedMethod possibleTarget : possibleTargets) {
- if (possibleTarget.isProgramMethod(appView)) {
- addTarget(possibleTarget, likelySpuriousCallEdge);
- }
- }
- }
- }
-
- private void processFieldAccess(DexField field) {
- // Any field access implicitly calls the class initializer.
- if (field.holder.isClassType()) {
- DexEncodedField encodedField = appView.appInfo().resolveField(field);
- if (encodedField != null && encodedField.isStatic()) {
- addClassInitializerTarget(field.holder);
- }
- }
- }
-
- @Override
- public boolean registerInvokeVirtual(DexMethod method) {
- processInvoke(Type.VIRTUAL, method);
- return false;
- }
-
- @Override
- public boolean registerInvokeDirect(DexMethod method) {
- processInvoke(Type.DIRECT, method);
- return false;
- }
-
- @Override
- public boolean registerInvokeStatic(DexMethod method) {
- processInvoke(Type.STATIC, method);
- return false;
- }
-
- @Override
- public boolean registerInvokeInterface(DexMethod method) {
- processInvoke(Type.INTERFACE, method);
- return false;
- }
-
- @Override
- public boolean registerInvokeSuper(DexMethod method) {
- processInvoke(Type.SUPER, method);
- return false;
- }
-
- @Override
- public boolean registerInstanceFieldWrite(DexField field) {
- processFieldAccess(field);
- return false;
- }
-
- @Override
- public boolean registerInstanceFieldRead(DexField field) {
- processFieldAccess(field);
- return false;
- }
-
- @Override
- public boolean registerNewInstance(DexType type) {
- if (type.isClassType()) {
- addClassInitializerTarget(type);
- }
- return false;
- }
-
- @Override
- public boolean registerStaticFieldRead(DexField field) {
- processFieldAccess(field);
- return false;
- }
-
- @Override
- public boolean registerStaticFieldWrite(DexField field) {
- processFieldAccess(field);
- return false;
- }
-
- @Override
- public boolean registerTypeReference(DexType type) {
- return false;
- }
-
- @Override
- public void registerCallSite(DexCallSite callSite) {
- registerMethodHandle(
- callSite.bootstrapMethod, MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
- }
- }
-
- public static class CycleEliminator {
-
- public static final String CYCLIC_FORCE_INLINING_MESSAGE =
- "Unable to satisfy force inlining constraints due to cyclic force inlining";
-
- private static class CallEdge {
-
- private final Node caller;
- private final Node callee;
-
- public CallEdge(Node caller, Node callee) {
- this.caller = caller;
- this.callee = callee;
- }
-
- public void remove() {
- callee.removeCaller(caller);
- }
- }
-
- public static class CycleEliminationResult {
-
- private Map<Node, Set<Node>> removedEdges;
-
- CycleEliminationResult(Map<Node, Set<Node>> removedEdges) {
- this.removedEdges = removedEdges;
- }
-
- void forEachRemovedCaller(Node callee, Consumer<Node> fn) {
- removedEdges.getOrDefault(callee, ImmutableSet.of()).forEach(fn);
- }
-
- public int numberOfRemovedEdges() {
- int numberOfRemovedEdges = 0;
- for (Set<Node> nodes : removedEdges.values()) {
- numberOfRemovedEdges += nodes.size();
- }
- return numberOfRemovedEdges;
- }
- }
-
- private final Collection<Node> nodes;
- private final InternalOptions options;
-
- // DFS stack.
- private Deque<Node> stack = new ArrayDeque<>();
-
- // Set of nodes on the DFS stack.
- private Set<Node> stackSet = Sets.newIdentityHashSet();
-
- // Set of nodes that have been visited entirely.
- private Set<Node> marked = Sets.newIdentityHashSet();
-
- // Mapping from callee to the set of callers that were removed from the callee.
- private Map<Node, Set<Node>> removedEdges = new IdentityHashMap<>();
-
- private int currentDepth = 0;
- private int maxDepth = 0;
-
- public CycleEliminator(Collection<Node> nodes, InternalOptions options) {
- this.options = options;
-
- // Call to reorderNodes must happen after assigning options.
- this.nodes =
- options.testing.nondeterministicCycleElimination
- ? reorderNodes(new ArrayList<>(nodes))
- : nodes;
- }
-
- public CycleEliminationResult breakCycles() {
- // Break cycles in this call graph by removing edges causing cycles.
- for (Node node : nodes) {
- assert currentDepth == 0;
- traverse(node);
- }
- CycleEliminationResult result = new CycleEliminationResult(removedEdges);
- if (Log.ENABLED) {
- Log.info(getClass(), "# call graph cycles broken: %s", result.numberOfRemovedEdges());
- Log.info(getClass(), "# max call graph depth: %s", maxDepth);
- }
- reset();
- return result;
- }
-
- private void reset() {
- assert currentDepth == 0;
- assert stack.isEmpty();
- assert stackSet.isEmpty();
- marked.clear();
- maxDepth = 0;
- removedEdges = new IdentityHashMap<>();
- }
-
- private void traverse(Node node) {
- if (Log.ENABLED) {
- if (currentDepth > maxDepth) {
- maxDepth = currentDepth;
- }
- }
-
- if (marked.contains(node)) {
- // Already visited all nodes that can be reached from this node.
- return;
- }
-
- push(node);
-
- // The callees must be sorted before calling traverse recursively. This ensures that cycles
- // are broken the same way across multiple compilations.
- Collection<Node> callees = node.getCalleesWithDeterministicOrder();
-
- if (options.testing.nondeterministicCycleElimination) {
- callees = reorderNodes(new ArrayList<>(callees));
- }
-
- Iterator<Node> calleeIterator = callees.iterator();
- while (calleeIterator.hasNext()) {
- Node callee = calleeIterator.next();
-
- // If we've exceeded the depth threshold, then treat it as if we have found a cycle. This
- // ensures that we won't run into stack overflows when the call graph contains large call
- // chains. This should have a negligible impact on code size as long as the threshold is
- // large enough.
- boolean foundCycle = stackSet.contains(callee);
- boolean thresholdExceeded =
- currentDepth >= options.callGraphCycleEliminatorMaxDepthThreshold
- && edgeRemovalIsSafe(node, callee);
- if (foundCycle || thresholdExceeded) {
- // Found a cycle that needs to be eliminated.
- if (edgeRemovalIsSafe(node, callee)) {
- // Break the cycle by removing the edge node->callee.
- if (options.testing.nondeterministicCycleElimination) {
- callee.removeCaller(node);
- } else {
- // Need to remove `callee` from `node.callees` using the iterator to prevent a
- // ConcurrentModificationException. This is not needed when nondeterministic cycle
- // elimination is enabled, because we iterate a copy of `node.callees` in that case.
- calleeIterator.remove();
- callee.getCallersWithDeterministicOrder().remove(node);
- }
- recordEdgeRemoval(node, callee);
-
- if (Log.ENABLED) {
- Log.info(
- CallGraph.class,
- "Removed call edge from method '%s' to '%s'",
- node.method.toSourceString(),
- callee.method.toSourceString());
- }
- } else {
- assert foundCycle;
-
- // The cycle has a method that is marked as force inline.
- LinkedList<Node> cycle = extractCycle(callee);
-
- if (Log.ENABLED) {
- Log.info(
- CallGraph.class, "Extracted cycle to find an edge that can safely be removed");
- }
-
- // Break the cycle by finding an edge that can be removed without breaking force
- // inlining. If that is not possible, this call fails with a compilation error.
- CallEdge edge = findCallEdgeForRemoval(cycle);
-
- // The edge will be null if this cycle has already been eliminated as a result of
- // another cycle elimination.
- if (edge != null) {
- assert edgeRemovalIsSafe(edge.caller, edge.callee);
-
- // Break the cycle by removing the edge caller->callee.
- edge.remove();
- recordEdgeRemoval(edge.caller, edge.callee);
-
- if (Log.ENABLED) {
- Log.info(
- CallGraph.class,
- "Removed call edge from force inlined method '%s' to '%s' to ensure that "
- + "force inlining will succeed",
- node.method.toSourceString(),
- callee.method.toSourceString());
- }
- }
-
- // Recover the stack.
- recoverStack(cycle);
- }
- } else {
- currentDepth++;
- traverse(callee);
- currentDepth--;
- }
- }
- pop(node);
- marked.add(node);
- }
-
- private void push(Node node) {
- stack.push(node);
- boolean changed = stackSet.add(node);
- assert changed;
- }
-
- private void pop(Node node) {
- Node popped = stack.pop();
- assert popped == node;
- boolean changed = stackSet.remove(node);
- assert changed;
- }
-
- private LinkedList<Node> extractCycle(Node entry) {
- LinkedList<Node> cycle = new LinkedList<>();
- do {
- assert !stack.isEmpty();
- cycle.add(stack.pop());
- } while (cycle.getLast() != entry);
- return cycle;
- }
-
- private CallEdge findCallEdgeForRemoval(LinkedList<Node> extractedCycle) {
- Node callee = extractedCycle.getLast();
- for (Node caller : extractedCycle) {
- if (!caller.hasCallee(callee)) {
- // No need to break any edges since this cycle has already been broken previously.
- assert !callee.hasCaller(caller);
- return null;
- }
- if (edgeRemovalIsSafe(caller, callee)) {
- return new CallEdge(caller, callee);
- }
- callee = caller;
- }
- throw new CompilationError(CYCLIC_FORCE_INLINING_MESSAGE);
- }
-
- private static boolean edgeRemovalIsSafe(Node caller, Node callee) {
- // All call edges where the callee is a method that should be force inlined must be kept,
- // to guarantee that the IR converter will process the callee before the caller.
- return !callee.method.getOptimizationInfo().forceInline();
- }
-
- private void recordEdgeRemoval(Node caller, Node callee) {
- removedEdges.computeIfAbsent(callee, ignore -> SetUtils.newIdentityHashSet(2)).add(caller);
- }
-
- private void recoverStack(LinkedList<Node> extractedCycle) {
- Iterator<Node> descendingIt = extractedCycle.descendingIterator();
- while (descendingIt.hasNext()) {
- stack.push(descendingIt.next());
- }
- }
-
- private Collection<Node> reorderNodes(List<Node> nodes) {
- assert options.testing.nondeterministicCycleElimination;
- if (!InternalOptions.DETERMINISTIC_DEBUGGING) {
- Collections.shuffle(nodes);
- }
- return nodes;
- }
- }
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
new file mode 100644
index 0000000..1e0a60d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
@@ -0,0 +1,536 @@
+// 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.conversion;
+
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.conversion.CallGraph.Node;
+import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator.CycleEliminationResult;
+import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+abstract class CallGraphBuilderBase {
+ final AppView<AppInfoWithLiveness> appView;
+ final Map<DexMethod, Node> nodes = new IdentityHashMap<>();
+ private final Map<DexMethod, Set<DexEncodedMethod>> possibleTargetsCache =
+ new ConcurrentHashMap<>();
+
+ CallGraphBuilderBase(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ }
+
+ public CallGraph build(ExecutorService executorService, Timing timing) throws ExecutionException {
+ process(executorService);
+ assert verifyAllMethodsWithCodeExists();
+
+ timing.begin("Cycle elimination");
+ // Sort the nodes for deterministic cycle elimination.
+ Set<Node> nodesWithDeterministicOrder = Sets.newTreeSet(nodes.values());
+ CycleEliminator cycleEliminator =
+ new CycleEliminator(nodesWithDeterministicOrder, appView.options());
+ CycleEliminationResult cycleEliminationResult = cycleEliminator.breakCycles();
+ timing.end();
+ assert cycleEliminator.breakCycles().numberOfRemovedEdges() == 0; // The cycles should be gone.
+
+ return new CallGraph(nodesWithDeterministicOrder, cycleEliminationResult);
+ }
+
+ abstract void process(ExecutorService executorService) throws ExecutionException;
+
+ Node getOrCreateNode(DexEncodedMethod method) {
+ synchronized (nodes) {
+ return nodes.computeIfAbsent(method.method, ignore -> new Node(method));
+ }
+ }
+
+ abstract boolean verifyAllMethodsWithCodeExists();
+
+ class InvokeExtractor extends UseRegistry {
+
+ private final Node caller;
+ private final Predicate<DexEncodedMethod> targetTester;
+
+ InvokeExtractor(Node caller, Predicate<DexEncodedMethod> targetTester) {
+ super(appView.dexItemFactory());
+ this.caller = caller;
+ this.targetTester = targetTester;
+ }
+
+ private void addClassInitializerTarget(DexClass clazz) {
+ assert clazz != null;
+ if (clazz.isProgramClass() && clazz.hasClassInitializer()) {
+ addTarget(clazz.getClassInitializer(), false);
+ }
+ }
+
+ private void addClassInitializerTarget(DexType type) {
+ assert type.isClassType();
+ DexClass clazz = appView.definitionFor(type);
+ if (clazz != null) {
+ addClassInitializerTarget(clazz);
+ }
+ }
+
+ private void addTarget(DexEncodedMethod callee, boolean likelySpuriousCallEdge) {
+ if (!targetTester.test(callee)) {
+ return;
+ }
+ if (callee.accessFlags.isAbstract()) {
+ // Not a valid target.
+ return;
+ }
+ if (appView.appInfo().isPinned(callee.method)) {
+ // Since the callee is kept, we cannot inline it into the caller, and we also cannot collect
+ // any optimization info for the method. Therefore, we drop the call edge to reduce the
+ // total number of call graph edges, which should lead to fewer call graph cycles.
+ return;
+ }
+ assert callee.isProgramMethod(appView);
+ getOrCreateNode(callee).addCallerConcurrently(caller, likelySpuriousCallEdge);
+ }
+
+ private void processInvoke(Invoke.Type originalType, DexMethod originalMethod) {
+ DexEncodedMethod source = caller.method;
+ DexMethod context = source.method;
+ GraphLenseLookupResult result =
+ appView.graphLense().lookupMethod(originalMethod, context, originalType);
+ DexMethod method = result.getMethod();
+ Invoke.Type type = result.getType();
+ if (type == Invoke.Type.INTERFACE || type == Invoke.Type.VIRTUAL) {
+ // For virtual and interface calls add all potential targets that could be called.
+ ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method.holder, method);
+ resolutionResult.forEachTarget(target -> processInvokeWithDynamicDispatch(type, target));
+ } else {
+ DexEncodedMethod singleTarget =
+ appView.appInfo().lookupSingleTarget(type, method, context.holder);
+ if (singleTarget != null) {
+ assert !source.accessFlags.isBridge() || singleTarget != caller.method;
+ DexClass clazz = appView.definitionFor(singleTarget.method.holder);
+ assert clazz != null;
+ if (clazz.isProgramClass()) {
+ // For static invokes, the class could be initialized.
+ if (type == Invoke.Type.STATIC) {
+ addClassInitializerTarget(clazz);
+ }
+ addTarget(singleTarget, false);
+ }
+ }
+ }
+ }
+
+ private void processInvokeWithDynamicDispatch(
+ Invoke.Type type, DexEncodedMethod encodedTarget) {
+ DexMethod target = encodedTarget.method;
+ DexClass clazz = appView.definitionFor(target.holder);
+ if (clazz == null) {
+ assert false : "Unable to lookup holder of `" + target.toSourceString() + "`";
+ return;
+ }
+
+ if (!appView.options().testing.addCallEdgesForLibraryInvokes) {
+ if (clazz.isLibraryClass()) {
+ // Likely to have many possible targets.
+ return;
+ }
+ }
+
+ boolean isInterface = type == Invoke.Type.INTERFACE;
+ Set<DexEncodedMethod> possibleTargets =
+ possibleTargetsCache.computeIfAbsent(
+ target,
+ method -> {
+ ResolutionResult resolution =
+ appView.appInfo().resolveMethod(method.holder, method, isInterface);
+ if (resolution.isValidVirtualTarget(appView.options())) {
+ return resolution.lookupVirtualDispatchTargets(isInterface, appView.appInfo());
+ }
+ return null;
+ });
+ if (possibleTargets != null) {
+ boolean likelySpuriousCallEdge =
+ possibleTargets.size() >= appView.options().callGraphLikelySpuriousCallEdgeThreshold;
+ for (DexEncodedMethod possibleTarget : possibleTargets) {
+ if (possibleTarget.isProgramMethod(appView)) {
+ addTarget(possibleTarget, likelySpuriousCallEdge);
+ }
+ }
+ }
+ }
+
+ private void processFieldAccess(DexField field) {
+ // Any field access implicitly calls the class initializer.
+ if (field.holder.isClassType()) {
+ DexEncodedField encodedField = appView.appInfo().resolveField(field);
+ if (encodedField != null && encodedField.isStatic()) {
+ addClassInitializerTarget(field.holder);
+ }
+ }
+ }
+
+ @Override
+ public boolean registerInvokeVirtual(DexMethod method) {
+ processInvoke(Invoke.Type.VIRTUAL, method);
+ return false;
+ }
+
+ @Override
+ public boolean registerInvokeDirect(DexMethod method) {
+ processInvoke(Invoke.Type.DIRECT, method);
+ return false;
+ }
+
+ @Override
+ public boolean registerInvokeStatic(DexMethod method) {
+ processInvoke(Invoke.Type.STATIC, method);
+ return false;
+ }
+
+ @Override
+ public boolean registerInvokeInterface(DexMethod method) {
+ processInvoke(Invoke.Type.INTERFACE, method);
+ return false;
+ }
+
+ @Override
+ public boolean registerInvokeSuper(DexMethod method) {
+ processInvoke(Invoke.Type.SUPER, method);
+ return false;
+ }
+
+ @Override
+ public boolean registerInstanceFieldWrite(DexField field) {
+ processFieldAccess(field);
+ return false;
+ }
+
+ @Override
+ public boolean registerInstanceFieldRead(DexField field) {
+ processFieldAccess(field);
+ return false;
+ }
+
+ @Override
+ public boolean registerNewInstance(DexType type) {
+ if (type.isClassType()) {
+ addClassInitializerTarget(type);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean registerStaticFieldRead(DexField field) {
+ processFieldAccess(field);
+ return false;
+ }
+
+ @Override
+ public boolean registerStaticFieldWrite(DexField field) {
+ processFieldAccess(field);
+ return false;
+ }
+
+ @Override
+ public boolean registerTypeReference(DexType type) {
+ return false;
+ }
+
+ @Override
+ public void registerCallSite(DexCallSite callSite) {
+ registerMethodHandle(
+ callSite.bootstrapMethod, MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
+ }
+ }
+
+ static class CycleEliminator {
+
+ static final String CYCLIC_FORCE_INLINING_MESSAGE =
+ "Unable to satisfy force inlining constraints due to cyclic force inlining";
+
+ private static class CallEdge {
+
+ private final Node caller;
+ private final Node callee;
+
+ CallEdge(Node caller, Node callee) {
+ this.caller = caller;
+ this.callee = callee;
+ }
+
+ public void remove() {
+ callee.removeCaller(caller);
+ }
+ }
+
+ static class CycleEliminationResult {
+
+ private Map<Node, Set<Node>> removedEdges;
+
+ CycleEliminationResult(Map<Node, Set<Node>> removedEdges) {
+ this.removedEdges = removedEdges;
+ }
+
+ void forEachRemovedCaller(Node callee, Consumer<Node> fn) {
+ removedEdges.getOrDefault(callee, ImmutableSet.of()).forEach(fn);
+ }
+
+ int numberOfRemovedEdges() {
+ int numberOfRemovedEdges = 0;
+ for (Set<Node> nodes : removedEdges.values()) {
+ numberOfRemovedEdges += nodes.size();
+ }
+ return numberOfRemovedEdges;
+ }
+ }
+
+ private final Collection<Node> nodes;
+ private final InternalOptions options;
+
+ // DFS stack.
+ private Deque<Node> stack = new ArrayDeque<>();
+
+ // Set of nodes on the DFS stack.
+ private Set<Node> stackSet = Sets.newIdentityHashSet();
+
+ // Set of nodes that have been visited entirely.
+ private Set<Node> marked = Sets.newIdentityHashSet();
+
+ // Mapping from callee to the set of callers that were removed from the callee.
+ private Map<Node, Set<Node>> removedEdges = new IdentityHashMap<>();
+
+ private int currentDepth = 0;
+ private int maxDepth = 0;
+
+ CycleEliminator(Collection<Node> nodes, InternalOptions options) {
+ this.options = options;
+
+ // Call to reorderNodes must happen after assigning options.
+ this.nodes =
+ options.testing.nondeterministicCycleElimination
+ ? reorderNodes(new ArrayList<>(nodes))
+ : nodes;
+ }
+
+ CycleEliminationResult breakCycles() {
+ // Break cycles in this call graph by removing edges causing cycles.
+ for (Node node : nodes) {
+ assert currentDepth == 0;
+ traverse(node);
+ }
+ CycleEliminationResult result = new CycleEliminationResult(removedEdges);
+ if (Log.ENABLED) {
+ Log.info(getClass(), "# call graph cycles broken: %s", result.numberOfRemovedEdges());
+ Log.info(getClass(), "# max call graph depth: %s", maxDepth);
+ }
+ reset();
+ return result;
+ }
+
+ private void reset() {
+ assert currentDepth == 0;
+ assert stack.isEmpty();
+ assert stackSet.isEmpty();
+ marked.clear();
+ maxDepth = 0;
+ removedEdges = new IdentityHashMap<>();
+ }
+
+ private void traverse(Node node) {
+ if (Log.ENABLED) {
+ if (currentDepth > maxDepth) {
+ maxDepth = currentDepth;
+ }
+ }
+
+ if (marked.contains(node)) {
+ // Already visited all nodes that can be reached from this node.
+ return;
+ }
+
+ push(node);
+
+ // The callees must be sorted before calling traverse recursively. This ensures that cycles
+ // are broken the same way across multiple compilations.
+ Collection<Node> callees = node.getCalleesWithDeterministicOrder();
+
+ if (options.testing.nondeterministicCycleElimination) {
+ callees = reorderNodes(new ArrayList<>(callees));
+ }
+
+ Iterator<Node> calleeIterator = callees.iterator();
+ while (calleeIterator.hasNext()) {
+ Node callee = calleeIterator.next();
+
+ // If we've exceeded the depth threshold, then treat it as if we have found a cycle. This
+ // ensures that we won't run into stack overflows when the call graph contains large call
+ // chains. This should have a negligible impact on code size as long as the threshold is
+ // large enough.
+ boolean foundCycle = stackSet.contains(callee);
+ boolean thresholdExceeded =
+ currentDepth >= options.callGraphCycleEliminatorMaxDepthThreshold
+ && edgeRemovalIsSafe(node, callee);
+ if (foundCycle || thresholdExceeded) {
+ // Found a cycle that needs to be eliminated.
+ if (edgeRemovalIsSafe(node, callee)) {
+ // Break the cycle by removing the edge node->callee.
+ if (options.testing.nondeterministicCycleElimination) {
+ callee.removeCaller(node);
+ } else {
+ // Need to remove `callee` from `node.callees` using the iterator to prevent a
+ // ConcurrentModificationException. This is not needed when nondeterministic cycle
+ // elimination is enabled, because we iterate a copy of `node.callees` in that case.
+ calleeIterator.remove();
+ callee.getCallersWithDeterministicOrder().remove(node);
+ }
+ recordEdgeRemoval(node, callee);
+
+ if (Log.ENABLED) {
+ Log.info(
+ CallGraph.class,
+ "Removed call edge from method '%s' to '%s'",
+ node.method.toSourceString(),
+ callee.method.toSourceString());
+ }
+ } else {
+ assert foundCycle;
+
+ // The cycle has a method that is marked as force inline.
+ LinkedList<Node> cycle = extractCycle(callee);
+
+ if (Log.ENABLED) {
+ Log.info(
+ CallGraph.class, "Extracted cycle to find an edge that can safely be removed");
+ }
+
+ // Break the cycle by finding an edge that can be removed without breaking force
+ // inlining. If that is not possible, this call fails with a compilation error.
+ CallEdge edge = findCallEdgeForRemoval(cycle);
+
+ // The edge will be null if this cycle has already been eliminated as a result of
+ // another cycle elimination.
+ if (edge != null) {
+ assert edgeRemovalIsSafe(edge.caller, edge.callee);
+
+ // Break the cycle by removing the edge caller->callee.
+ edge.remove();
+ recordEdgeRemoval(edge.caller, edge.callee);
+
+ if (Log.ENABLED) {
+ Log.info(
+ CallGraph.class,
+ "Removed call edge from force inlined method '%s' to '%s' to ensure that "
+ + "force inlining will succeed",
+ node.method.toSourceString(),
+ callee.method.toSourceString());
+ }
+ }
+
+ // Recover the stack.
+ recoverStack(cycle);
+ }
+ } else {
+ currentDepth++;
+ traverse(callee);
+ currentDepth--;
+ }
+ }
+ pop(node);
+ marked.add(node);
+ }
+
+ private void push(Node node) {
+ stack.push(node);
+ boolean changed = stackSet.add(node);
+ assert changed;
+ }
+
+ private void pop(Node node) {
+ Node popped = stack.pop();
+ assert popped == node;
+ boolean changed = stackSet.remove(node);
+ assert changed;
+ }
+
+ private LinkedList<Node> extractCycle(Node entry) {
+ LinkedList<Node> cycle = new LinkedList<>();
+ do {
+ assert !stack.isEmpty();
+ cycle.add(stack.pop());
+ } while (cycle.getLast() != entry);
+ return cycle;
+ }
+
+ private CallEdge findCallEdgeForRemoval(LinkedList<Node> extractedCycle) {
+ Node callee = extractedCycle.getLast();
+ for (Node caller : extractedCycle) {
+ if (!caller.hasCallee(callee)) {
+ // No need to break any edges since this cycle has already been broken previously.
+ assert !callee.hasCaller(caller);
+ return null;
+ }
+ if (edgeRemovalIsSafe(caller, callee)) {
+ return new CallEdge(caller, callee);
+ }
+ callee = caller;
+ }
+ throw new CompilationError(CYCLIC_FORCE_INLINING_MESSAGE);
+ }
+
+ private static boolean edgeRemovalIsSafe(Node caller, Node callee) {
+ // All call edges where the callee is a method that should be force inlined must be kept,
+ // to guarantee that the IR converter will process the callee before the caller.
+ return !callee.method.getOptimizationInfo().forceInline();
+ }
+
+ private void recordEdgeRemoval(Node caller, Node callee) {
+ removedEdges.computeIfAbsent(callee, ignore -> SetUtils.newIdentityHashSet(2)).add(caller);
+ }
+
+ private void recoverStack(LinkedList<Node> extractedCycle) {
+ Iterator<Node> descendingIt = extractedCycle.descendingIterator();
+ while (descendingIt.hasNext()) {
+ stack.push(descendingIt.next());
+ }
+ }
+
+ private Collection<Node> reorderNodes(List<Node> nodes) {
+ assert options.testing.nondeterministicCycleElimination;
+ if (!InternalOptions.DETERMINISTIC_DEBUGGING) {
+ Collections.shuffle(nodes);
+ }
+ return nodes;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
index 50fde0e..46a0cc4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.conversion;
import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
public interface FieldOptimizationFeedback {
@@ -13,7 +14,9 @@
void markFieldAsPropagated(DexEncodedField field);
- void markFieldHasDynamicType(DexEncodedField field, TypeLatticeElement type);
+ void markFieldHasDynamicLowerBoundType(DexEncodedField field, ClassTypeLatticeElement type);
+
+ void markFieldHasDynamicUpperBoundType(DexEncodedField field, TypeLatticeElement type);
void markFieldBitsRead(DexEncodedField field, int bitsRead);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 995a583..7ef6cd5 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -620,8 +620,12 @@
}
// Update the IR code if collected call site optimization info has something useful.
+ // While aggregation of parameter information at call sites would be more precise than static
+ // types, those could be still less precise at one single call site, where specific arguments
+ // will be passed during (double) inlining. Instead of adding assumptions and removing invalid
+ // ones, it's better not to insert assumptions for inlinee in the beginning.
CallSiteOptimizationInfo callSiteOptimizationInfo = method.getCallSiteOptimizationInfo();
- if (appView.callSiteOptimizationInfoPropagator() != null) {
+ if (method == context && appView.callSiteOptimizationInfoPropagator() != null) {
appView.callSiteOptimizationInfoPropagator()
.applyCallSiteOptimizationInfo(ir, callSiteOptimizationInfo);
}
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 dd3416f..7e15e72 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
@@ -205,7 +205,6 @@
.map(prefix -> "L" + DescriptorUtils.getPackageBinaryNameFromJavaType(prefix))
.map(options.itemFactory::createString)
.collect(Collectors.toList());
- this.methodOptimizationInfoCollector = new MethodOptimizationInfoCollector(appView);
if (options.isDesugaredLibraryCompilation()) {
// Specific L8 Settings.
// BackportedMethodRewriter is needed for retarget core library members and backports.
@@ -241,6 +240,7 @@
this.stringSwitchRemover = null;
this.desugaredLibraryAPIConverter = null;
this.serviceLoaderRewriter = null;
+ this.methodOptimizationInfoCollector = null;
return;
}
this.lambdaRewriter = options.enableDesugaring ? new LambdaRewriter(appView, this) : null;
@@ -287,13 +287,16 @@
options.enableTreeShakingOfLibraryMethodOverrides
? new LibraryMethodOverrideAnalysis(appViewWithLiveness)
: null;
- this.lensCodeRewriter = new LensCodeRewriter(appViewWithLiveness, lambdaRewriter);
- this.inliner = new Inliner(appViewWithLiveness, mainDexClasses, lensCodeRewriter);
this.lambdaMerger =
options.enableLambdaMerging ? new LambdaMerger(appViewWithLiveness) : null;
+ this.lensCodeRewriter = new LensCodeRewriter(appViewWithLiveness, lambdaRewriter);
+ this.inliner =
+ new Inliner(appViewWithLiveness, mainDexClasses, lambdaMerger, lensCodeRewriter);
this.outliner = new Outliner(appViewWithLiveness, this);
this.memberValuePropagation =
options.enableValuePropagation ? new MemberValuePropagation(appViewWithLiveness) : null;
+ this.methodOptimizationInfoCollector =
+ new MethodOptimizationInfoCollector(appViewWithLiveness);
if (options.isMinifying()) {
this.identifierNameStringMarker = new IdentifierNameStringMarker(appViewWithLiveness);
} else {
@@ -329,6 +332,7 @@
this.d8NestBasedAccessDesugaring =
options.shouldDesugarNests() ? new D8NestBasedAccessDesugaring(appView) : null;
this.serviceLoaderRewriter = null;
+ this.methodOptimizationInfoCollector = null;
}
this.stringSwitchRemover =
options.isStringSwitchConversionEnabled()
@@ -429,7 +433,7 @@
private void synthesizeJava8UtilityClass(
Builder<?> builder, ExecutorService executorService) throws ExecutionException {
if (backportedMethodRewriter != null) {
- backportedMethodRewriter.synthesizeUtilityClass(builder, executorService, options);
+ backportedMethodRewriter.synthesizeUtilityClasses(builder, executorService);
}
}
@@ -685,6 +689,7 @@
CallSiteInformation.empty(),
Outliner::noProcessing),
executorService);
+ feedback.updateVisibleOptimizationInfo();
timing.end();
}
@@ -852,6 +857,7 @@
// unused out-values.
codeRewriter.rewriteMoveResult(code);
deadCodeRemover.run(code);
+ codeRewriter.removeAssumeInstructions(code);
consumer.accept(code, method);
return null;
}));
@@ -879,7 +885,7 @@
private void collectLambdaMergingCandidates(DexApplication application) {
if (lambdaMerger != null) {
- lambdaMerger.collectGroupCandidates(application, appView.withLiveness());
+ lambdaMerger.collectGroupCandidates(application);
}
}
@@ -1116,6 +1122,11 @@
}
}
+ if (lambdaMerger != null) {
+ lambdaMerger.rewriteCode(method, code);
+ assert code.isConsistentSSA();
+ }
+
if (typeChecker != null && !typeChecker.check(code)) {
assert appView.enableWholeProgramOptimizations();
assert options.testing.allowTypeErrors;
@@ -1242,9 +1253,10 @@
if (stringSwitchRemover != null) {
stringSwitchRemover.run(method, code);
}
- codeRewriter.rewriteSwitch(code);
codeRewriter.processMethodsNeverReturningNormally(code);
- codeRewriter.simplifyIf(code);
+ if (codeRewriter.simplifyControlFlow(code)) {
+ codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(code);
+ }
if (options.enableRedundantConstNumberOptimization) {
codeRewriter.redundantConstNumberRemoval(code);
}
@@ -1351,7 +1363,7 @@
previous = printMethod(code, "IR after twr close resource rewriter (SSA)", previous);
if (lambdaMerger != null) {
- lambdaMerger.processMethodCode(method, code);
+ lambdaMerger.analyzeCode(method, code);
assert code.isConsistentSSA();
}
@@ -1389,24 +1401,6 @@
classStaticizer.examineMethodCode(method, code);
}
- 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.
- methodOptimizationInfoCollector.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);
if (appView.enableWholeProgramOptimizations()) {
@@ -1418,31 +1412,30 @@
fieldBitAccessAnalysis.recordFieldAccesses(code, feedback);
}
+ // Arguments can be changed during the debug mode.
+ if (!isDebugMode && appView.callSiteOptimizationInfoPropagator() != null) {
+ appView.callSiteOptimizationInfoPropagator().collectCallSiteOptimizationInfo(code);
+ }
+
// Compute optimization info summary for the current method unless it is pinned
// (in that case we should not be making any assumptions about the behavior of the method).
if (!appView.appInfo().withLiveness().isPinned(method.method)) {
- methodOptimizationInfoCollector.identifyClassInlinerEligibility(method, code, feedback);
- methodOptimizationInfoCollector.identifyParameterUsages(method, code, feedback);
- methodOptimizationInfoCollector.identifyReturnsArgument(method, code, feedback);
- methodOptimizationInfoCollector.identifyTrivialInitializer(method, code, feedback);
-
- if (options.enableInlining && inliner != null) {
- methodOptimizationInfoCollector
- .identifyInvokeSemanticsForInlining(method, code, appView, feedback);
- }
-
methodOptimizationInfoCollector
- .computeDynamicReturnType(dynamicTypeOptimization, feedback, method, code);
+ .collectMethodOptimizationInfo(method, code, feedback, dynamicTypeOptimization);
FieldValueAnalysis.run(appView, code, feedback, method);
- methodOptimizationInfoCollector
- .computeInitializedClassesOnNormalExit(feedback, method, code);
- methodOptimizationInfoCollector.computeMayHaveSideEffects(feedback, method, code);
- methodOptimizationInfoCollector
- .computeReturnValueOnlyDependsOnArguments(feedback, method, code);
- methodOptimizationInfoCollector.computeNonNullParamOrThrow(feedback, method, 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 after computation of optimization info summary (SSA)", previous);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
index 9a55240..a7933d2 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.ir.conversion.CallGraph.Node;
+import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Action;
import com.android.tools.r8.utils.IROrdering;
@@ -70,7 +71,13 @@
return waves;
}
- private static void extractLeaves(Set<Node> nodes, Consumer<Node> fn) {
+ /**
+ * Extract the next set of leaves (nodes with an outgoing call degree of 0) if any.
+ *
+ * <p>All nodes in the graph are extracted if called repeatedly until null is returned. Please
+ * note that there are no cycles in this graph (see {@link CycleEliminator#breakCycles}).
+ */
+ static void extractLeaves(Set<Node> nodes, Consumer<Node> fn) {
Set<Node> removed = Sets.newIdentityHashSet();
Iterator<Node> nodeIterator = nodes.iterator();
while (nodeIterator.hasNext()) {
@@ -81,7 +88,7 @@
removed.add(node);
}
}
- removed.forEach(Node::cleanForRemoval);
+ removed.forEach(Node::cleanCallersForRemoval);
}
/**
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PartialCallGraphBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/PartialCallGraphBuilder.java
new file mode 100644
index 0000000..ae2c5c5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PartialCallGraphBuilder.java
@@ -0,0 +1,54 @@
+// 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.conversion;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ThreadUtils;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+
+public class PartialCallGraphBuilder extends CallGraphBuilderBase {
+ private final Set<DexEncodedMethod> seeds;
+
+ PartialCallGraphBuilder(AppView<AppInfoWithLiveness> appView, Set<DexEncodedMethod> seeds) {
+ super(appView);
+ assert seeds != null && !seeds.isEmpty();
+ this.seeds = seeds;
+ }
+
+ @Override
+ void process(ExecutorService executorService) throws ExecutionException {
+ List<Future<?>> futures = new ArrayList<>();
+ for (DexEncodedMethod method : seeds) {
+ futures.add(
+ executorService.submit(
+ () -> {
+ processMethod(method);
+ return null; // we want a Callable not a Runnable to be able to throw
+ }));
+ }
+ ThreadUtils.awaitFutures(futures);
+ }
+
+ private void processMethod(DexEncodedMethod method) {
+ if (method.hasCode()) {
+ method.registerCodeReferences(
+ new InvokeExtractor(getOrCreateNode(method), seeds::contains));
+ }
+ }
+
+ @Override
+ boolean verifyAllMethodsWithCodeExists() {
+ for (DexEncodedMethod method : seeds) {
+ assert !method.hasCode() || nodes.get(method.method) != null;
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
new file mode 100644
index 0000000..13566359
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -0,0 +1,33 @@
+// 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.conversion;
+
+import com.android.tools.r8.ir.conversion.CallGraph.Node;
+import com.google.common.collect.Sets;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.function.Consumer;
+
+public class PostMethodProcessor {
+
+ /**
+ * Extract the next set of roots (nodes with an incoming call degree of 0) if any.
+ *
+ * <p>All nodes in the graph are extracted if called repeatedly until null is returned.
+ */
+ static void extractRoots(Set<Node> nodes, Consumer<Node> fn) {
+ Set<Node> removed = Sets.newIdentityHashSet();
+ Iterator<Node> nodeIterator = nodes.iterator();
+ while (nodeIterator.hasNext()) {
+ Node node = nodeIterator.next();
+ if (node.isRoot()) {
+ fn.accept(node);
+ nodeIterator.remove();
+ removed.add(node);
+ }
+ }
+ removed.forEach(Node::cleanCalleesForRemoval);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index be6ffb2..bf82f85 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -100,7 +100,34 @@
InvokeMethod invoke = instruction.asInvokeMethod();
MethodProvider provider = getMethodProviderOrNull(invoke.getInvokedMethod());
if (provider == null) {
- continue;
+ if (!rewritableMethods.matchesVirtualRewrite(invoke.getInvokedMethod())) {
+ continue;
+ }
+ // We need to force resolution, even on d8, to know if the invoke has to be rewritten.
+ DexEncodedMethod dexEncodedMethod =
+ quickLookUp(invoke.getInvokedMethod());
+ if (dexEncodedMethod == null) {
+ continue;
+ }
+ provider = getMethodProviderOrNull(dexEncodedMethod.method);
+ if (provider == null) {
+ continue;
+ }
+
+ // Since we are rewriting a virtual method into a static invoke in this case, the look-up
+ // logic gets confused. Final methods rewritten in such a way are always or invokes from a
+ // library class are rewritten into the static invoke, which is correct. However,
+ // overrides of the programmer are currently disabled. We still rewrite everything to make
+ // basic cases work.
+ // TODO(b/142846107): Support overrides of retarget virtual methods by uncommenting the
+ // following and implementing doSomethingSmart().
+
+ // DexClass receiverType = appView.definitionFor(invoke.getInvokedMethod().holder);
+ // if (!(dexEncodedMethod.isFinal()
+ // || (receiverType != null && receiverType.isLibraryClass()))) {
+ // doSomethingSmart();
+ // continue;
+ // }
}
provider.rewriteInvoke(invoke, iterator, code, appView);
@@ -113,6 +140,31 @@
}
}
+ private DexEncodedMethod quickLookUp(DexMethod method) {
+ // Since retargeting cannot be on interface, we do a quick look-up excluding interfaces.
+ // On R8 resolution is immediate, on d8 it may look-up.
+ DexClass current = appView.definitionFor(method.holder);
+ if (current == null) {
+ return null;
+ }
+ DexEncodedMethod dexEncodedMethod = current.lookupVirtualMethod(method);
+ if (dexEncodedMethod != null) {
+ return dexEncodedMethod;
+ }
+ while (current.superType != factory.objectType) {
+ DexType superType = current.superType;
+ current = appView.definitionFor(superType);
+ if (current == null) {
+ return null;
+ }
+ dexEncodedMethod = current.lookupVirtualMethod(method);
+ if (dexEncodedMethod != null) {
+ return dexEncodedMethod;
+ }
+ }
+ return null;
+ }
+
private Collection<DexProgramClass> findSynthesizedFrom(Builder<?> builder, DexType holder) {
for (DexProgramClass synthesizedClass : builder.getSynthesizedClasses()) {
if (holder == synthesizedClass.getType()) {
@@ -126,8 +178,7 @@
return clazz.descriptor.toString().startsWith(UTILITY_CLASS_DESCRIPTOR_PREFIX);
}
- public void synthesizeUtilityClass(
- Builder<?> builder, ExecutorService executorService, InternalOptions options)
+ public void synthesizeUtilityClasses(Builder<?> builder, ExecutorService executorService)
throws ExecutionException {
if (holders.isEmpty()) {
return;
@@ -161,7 +212,7 @@
if (appView.definitionFor(method.holder) != null) {
continue;
}
- Code code = provider.generateTemplateMethod(options, method);
+ Code code = provider.generateTemplateMethod(appView.options(), method);
DexEncodedMethod dexEncodedMethod =
new DexEncodedMethod(
method, flags, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(), code);
@@ -181,7 +232,7 @@
DexAnnotationSet.empty(),
DexEncodedField.EMPTY_ARRAY,
DexEncodedField.EMPTY_ARRAY,
- new DexEncodedMethod[] {dexEncodedMethod},
+ new DexEncodedMethod[]{dexEncodedMethod},
DexEncodedMethod.EMPTY_ARRAY,
factory.getSkipNameValidationForTesting(),
referencingClasses);
@@ -221,6 +272,10 @@
// Map backported method to a provider for creating the actual target method (with code).
private final Map<DexMethod, MethodProvider> rewritable = new IdentityHashMap<>();
+ // Map virtualRewrites hold a methodName->method mapping for virtual methods which are
+ // rewritten while the holder is non final but no superclass implement the method. In this case
+ // d8 needs to force resolution of given methods to see if the invoke needs to be rewritten.
+ private final Map<DexString, List<DexMethod>> virtualRewrites = new IdentityHashMap<>();
RewritableMethods(InternalOptions options, AppView<?> appView) {
DexItemFactory factory = options.itemFactory;
@@ -253,6 +308,19 @@
}
}
+ boolean matchesVirtualRewrite(DexMethod method) {
+ List<DexMethod> dexMethods = virtualRewrites.get(method.name);
+ if (dexMethods == null) {
+ return false;
+ }
+ for (DexMethod dexMethod : dexMethods) {
+ if (method.match(dexMethod)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
boolean isEmpty() {
return rewritable.isEmpty();
}
@@ -1007,7 +1075,8 @@
: new MethodGenerator(
method,
(options, methodArg) ->
- CollectionMethodGenerators.generateListOf(options, methodArg, formalCount)));
+ CollectionMethodGenerators.generateListOf(
+ options, methodArg, formalCount)));
}
proto = factory.createProto(type, factory.objectArrayType);
method = factory.createMethod(type, proto, name);
@@ -1099,25 +1168,25 @@
// Optional{void,Int,Long,Double}.ifPresentOrElse(consumer,runnable)
DexType[] optionalTypes =
- new DexType[] {
- optionalType,
- factory.createType(factory.createString("Ljava/util/OptionalDouble;")),
- factory.createType(factory.createString("Ljava/util/OptionalLong;")),
- factory.createType(factory.createString("Ljava/util/OptionalInt;"))
+ new DexType[]{
+ optionalType,
+ factory.createType(factory.createString("Ljava/util/OptionalDouble;")),
+ factory.createType(factory.createString("Ljava/util/OptionalLong;")),
+ factory.createType(factory.createString("Ljava/util/OptionalInt;"))
};
DexType[] consumerTypes =
- new DexType[] {
- factory.consumerType,
- factory.createType("Ljava/util/function/DoubleConsumer;"),
- factory.createType("Ljava/util/function/LongConsumer;"),
- factory.createType("Ljava/util/function/IntConsumer;")
+ new DexType[]{
+ factory.consumerType,
+ factory.createType("Ljava/util/function/DoubleConsumer;"),
+ factory.createType("Ljava/util/function/LongConsumer;"),
+ factory.createType("Ljava/util/function/IntConsumer;")
};
TemplateMethodFactory[] methodFactories =
- new TemplateMethodFactory[] {
- BackportedMethods::OptionalMethods_ifPresentOrElse,
- BackportedMethods::OptionalMethods_ifPresentOrElseDouble,
- BackportedMethods::OptionalMethods_ifPresentOrElseLong,
- BackportedMethods::OptionalMethods_ifPresentOrElseInt
+ new TemplateMethodFactory[]{
+ BackportedMethods::OptionalMethods_ifPresentOrElse,
+ BackportedMethods::OptionalMethods_ifPresentOrElseDouble,
+ BackportedMethods::OptionalMethods_ifPresentOrElseLong,
+ BackportedMethods::OptionalMethods_ifPresentOrElseInt
};
for (int i = 0; i < optionalTypes.length; i++) {
DexType optional = optionalTypes[i];
@@ -1151,6 +1220,10 @@
DexType newHolder = retargetCoreLibMember.get(methodName).get(inType);
List<DexEncodedMethod> found = findDexEncodedMethodsWithName(methodName, typeClass);
for (DexEncodedMethod encodedMethod : found) {
+ if (!encodedMethod.isStatic()) {
+ virtualRewrites.putIfAbsent(encodedMethod.method.name, new ArrayList<>());
+ virtualRewrites.get(encodedMethod.method.name).add(encodedMethod.method);
+ }
DexProto proto = encodedMethod.method.proto;
DexMethod method = appView.dexItemFactory().createMethod(inType, proto, methodName);
addProvider(
@@ -1185,6 +1258,7 @@
}
public abstract static class MethodProvider {
+
final DexMethod method;
public MethodProvider(DexMethod method) {
@@ -1244,6 +1318,7 @@
}
private static final class InvokeRewriter extends MethodProvider {
+
private final MethodInvokeRewriter rewriter;
InvokeRewriter(DexMethod method, MethodInvokeRewriter rewriter) {
@@ -1251,8 +1326,9 @@
this.rewriter = rewriter;
}
- @Override public void rewriteInvoke(InvokeMethod invoke, InstructionListIterator iterator,
- IRCode code, AppView<?> appView) {
+ @Override
+ public void rewriteInvoke(
+ InvokeMethod invoke, InstructionListIterator iterator, IRCode code, AppView<?> appView) {
rewriter.rewrite(invoke, iterator, appView.dexItemFactory());
assert code.isConsistentSSA();
}
@@ -1365,6 +1441,7 @@
}
private interface MethodInvokeRewriter {
+
void rewrite(InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index d81f3fe..688e751 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.ir.code.Invoke;
@@ -114,7 +115,12 @@
DexClass target = appView.definitionFor(method.holder);
// NOTE: Never add a forwarding method to methods of classes unknown or coming from android.jar
// even if this results in invalid code, these classes are never desugared.
- assert target != null && !rewriter.isNonDesugaredLibraryClass(target);
+ assert target != null;
+ // In desugared library, emulated interface methods can be overridden by retarget lib members.
+ DexMethod forwardMethod =
+ target.isInterface()
+ ? rewriter.defaultAsMethodOfCompanionClass(method)
+ : retargetMethod(method);
// New method will have the same name, proto, and also all the flags of the
// default method, including bridge flag.
DexMethod newMethod = dexItemFactory.createMethod(clazz.type, method.proto, method.name);
@@ -125,7 +131,7 @@
ForwardMethodSourceCode.builder(newMethod);
forwardSourceCodeBuilder
.setReceiver(clazz.type)
- .setTarget(rewriter.defaultAsMethodOfCompanionClass(method))
+ .setTarget(forwardMethod)
.setInvokeType(Invoke.Type.STATIC)
.setIsInterface(false); // Holder is companion class, not an interface.
return new DexEncodedMethod(
@@ -136,6 +142,18 @@
new SynthesizedCode(forwardSourceCodeBuilder::build));
}
+ private DexMethod retargetMethod(DexMethod method) {
+ Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
+ appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
+ Map<DexType, DexType> typeMap = retargetCoreLibMember.get(method.name);
+ assert typeMap != null;
+ assert typeMap.get(method.holder) != null;
+ return dexItemFactory.createMethod(
+ typeMap.get(method.holder),
+ dexItemFactory.prependTypeToProto(method.holder, method.proto),
+ method.name);
+ }
+
// For a given class `clazz` inspects all interfaces it implements directly or
// indirectly and collect a set of all default methods to be implemented
// in this class.
@@ -215,17 +233,29 @@
// Remove from candidates methods defined in class or any of its superclasses.
List<DexEncodedMethod> toBeImplemented = new ArrayList<>(candidates.size());
current = clazz;
+ Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
+ appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
while (true) {
// In desugared library look-up, methods from library classes cannot hide methods from
// emulated interfaces (the method being desugared implied the implementation is not
- // present in the library class).
+ // present in the library class), except through retarget core lib member.
if (desugaredLibraryLookup && current.isLibraryClass()) {
Iterator<DexEncodedMethod> iterator = candidates.iterator();
while (iterator.hasNext()) {
DexEncodedMethod candidate = iterator.next();
- if (rewriter.isEmulatedInterface(candidate.method.holder)) {
- toBeImplemented.add(candidate);
- iterator.remove();
+ if (rewriter.isEmulatedInterface(candidate.method.holder)
+ && current.lookupVirtualMethod(candidate.method) != null) {
+ // A library class overrides an emulated interface method. This override is valid
+ // only if it goes through retarget core lib member, else it needs to be implemented.
+ Map<DexType, DexType> typeMap = retargetCoreLibMember.get(candidate.method.name);
+ if (typeMap != null && typeMap.containsKey(current.type)) {
+ // A rewrite needs to be performed, but instead of rewriting to the companion class,
+ // D8/R8 needs to rewrite to the retarget member.
+ toBeImplemented.add(current.lookupVirtualMethod(candidate.method));
+ } else {
+ toBeImplemented.add(candidate);
+ iterator.remove();
+ }
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
index d60ac1f..cbdef95 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -28,6 +28,7 @@
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -64,11 +65,20 @@
private final DexItemFactory factory;
private final DesugaredLibraryWrapperSynthesizer wrapperSynthesizor;
private final Map<DexClass, Set<DexEncodedMethod>> callBackMethods = new HashMap<>();
+ private final Set<DexMethod> trackedCallBackAPIs;
+ private final Set<DexMethod> trackedAPIs;
public DesugaredLibraryAPIConverter(AppView<?> appView) {
this.appView = appView;
this.factory = appView.dexItemFactory();
this.wrapperSynthesizor = new DesugaredLibraryWrapperSynthesizer(appView, this);
+ if (appView.options().testing.trackDesugaredAPIConversions) {
+ trackedCallBackAPIs = Sets.newConcurrentHashSet();
+ trackedAPIs = Sets.newConcurrentHashSet();
+ } else {
+ trackedCallBackAPIs = null;
+ trackedAPIs = null;
+ }
}
public void desugar(IRCode code) {
@@ -170,6 +180,9 @@
}
private synchronized void generateCallBack(DexClass dexClass, DexEncodedMethod originalMethod) {
+ if (trackedCallBackAPIs != null) {
+ trackedCallBackAPIs.add(originalMethod.method);
+ }
DexMethod methodToInstall =
methodWithVivifiedTypeInSignature(originalMethod.method, dexClass.type, appView);
if (dexClass.isInterface()
@@ -219,6 +232,10 @@
public void generateWrappers(
DexApplication.Builder<?> builder, IRConverter irConverter, ExecutorService executorService)
throws ExecutionException {
+ if (appView.options().testing.trackDesugaredAPIConversions) {
+ generateTrackDesugaredAPIWarnings(trackedAPIs, "");
+ generateTrackDesugaredAPIWarnings(trackedCallBackAPIs, "callback ");
+ }
wrapperSynthesizor.finalizeWrappers(builder, irConverter, executorService);
for (DexClass dexClass : callBackMethods.keySet()) {
Set<DexEncodedMethod> dexEncodedMethods = callBackMethods.get(dexClass);
@@ -227,6 +244,16 @@
}
}
+ private void generateTrackDesugaredAPIWarnings(Set<DexMethod> tracked, String inner) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Tracked ").append(inner).append("desugared API conversions: ");
+ for (DexMethod method : tracked) {
+ sb.append("\n");
+ sb.append(method);
+ }
+ appView.options().reporter.warning(new StringDiagnostic(sb.toString()));
+ }
+
private void warnInvalidInvoke(DexType type, DexMethod invokedMethod, String debugString) {
DexType desugaredType = appView.rewritePrefix.rewrittenType(type);
appView
@@ -260,6 +287,9 @@
InstructionListIterator iterator,
ListIterator<BasicBlock> blockIterator) {
DexMethod invokedMethod = invokeMethod.getInvokedMethod();
+ if (trackedAPIs != null) {
+ trackedAPIs.add(invokedMethod);
+ }
// Create return conversion if required.
Instruction returnConversion = null;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
index 257d002..73214ba 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
@@ -53,7 +53,7 @@
}
public void markUsersForRemoval(Value value) {
- for (Instruction user : value.uniqueUsers()) {
+ for (Instruction user : value.aliasedUsers()) {
if (user.isAssumeDynamicType()) {
assert value.numberOfAllUsers() == 1
: "Expected value flowing into Assume<DynamicTypeAssumption> instruction to have a "
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 31add54..f93d6c5 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
@@ -47,11 +47,8 @@
private enum Mode {
COLLECT, // Set until the end of the 1st round of IR processing. CallSiteOptimizationInfo will
// be updated in this mode only.
- REVISIT, // Set once the all methods are processed. IRBuilder will add other instructions that
+ REVISIT // Set once the all methods are processed. IRBuilder will add other instructions that
// reflect collected CallSiteOptimizationInfo.
- FINISH; // Set once the 2nd round of IR processing is done. Other optimizations that need post
- // IR processing, e.g., outliner, are still using IRBuilder, and this will isolate the
- // impact of IR manipulation due to this optimization.
}
private final AppView<AppInfoWithLiveness> appView;
@@ -256,14 +253,13 @@
}
}
}
+ mode = Mode.REVISIT;
if (targetsToRevisit.isEmpty()) {
- mode = Mode.FINISH;
return;
}
if (revisitedMethods != null) {
revisitedMethods.addAll(targetsToRevisit);
}
- mode = Mode.REVISIT;
List<Future<?>> futures = new ArrayList<>();
for (DexEncodedMethod method : targetsToRevisit) {
futures.add(
@@ -274,6 +270,5 @@
}));
}
ThreadUtils.awaitFutures(futures);
- mode = Mode.FINISH;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
index 6c2110b..fb75832 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
@@ -29,6 +29,7 @@
import com.android.tools.r8.graph.DexValue.DexValueNull;
import com.android.tools.r8.graph.DexValue.DexValueShort;
import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.ir.analysis.ValueMayDependOnEnvironmentAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.ArrayPut;
import com.android.tools.r8.ir.code.BasicBlock;
@@ -327,6 +328,8 @@
private Collection<StaticPut> findFinalFieldPutsWhileCollectingUnnecessaryStaticPuts(
IRCode code, DexClass clazz, Set<StaticPut> unnecessaryStaticPuts) {
+ ValueMayDependOnEnvironmentAnalysis environmentAnalysis =
+ new ValueMayDependOnEnvironmentAnalysis(appView, code);
Map<DexField, StaticPut> finalFieldPuts = Maps.newIdentityHashMap();
Map<DexField, Set<StaticPut>> isWrittenBefore = Maps.newIdentityHashMap();
Set<DexField> isReadBefore = Sets.newIdentityHashSet();
@@ -436,7 +439,7 @@
Value outValue = instruction.outValue();
if (outValue.numberOfAllUsers() > 0) {
if (instruction.isInvokeNewArray()
- && outValue.isConstantArrayThroughoutMethod(appView, code)) {
+ && environmentAnalysis.isConstantArrayThroughoutMethod(outValue)) {
// OK, this value is technically a constant.
continue;
}
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 932c817..a47ba2c 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
@@ -14,6 +14,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
@@ -82,12 +83,14 @@
import com.android.tools.r8.utils.InternalOptions.AssertionProcessing;
import com.android.tools.r8.utils.InternalOutputMode;
import com.android.tools.r8.utils.LongInterval;
+import com.android.tools.r8.utils.SetUtils;
import com.google.common.base.Equivalence;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Sets;
@@ -839,9 +842,9 @@
return outliersAsIfSize;
}
- public void rewriteSwitch(IRCode code) {
+ private boolean rewriteSwitch(IRCode code) {
if (!code.metadata().mayHaveIntSwitch()) {
- return;
+ return false;
}
boolean needToRemoveUnreachableBlocks = false;
@@ -985,15 +988,18 @@
}
}
- if (needToRemoveUnreachableBlocks) {
- code.removeUnreachableBlocks();
- }
-
// Rewriting of switches introduces new branching structure. It relies on critical edges
// being split on the way in but does not maintain this property. We therefore split
// critical edges at exit.
code.splitCriticalEdges();
+
+ Set<Value> affectedValues =
+ needToRemoveUnreachableBlocks ? code.removeUnreachableBlocks() : ImmutableSet.of();
+ if (!affectedValues.isEmpty()) {
+ new TypeAnalysis(appView).narrowing(affectedValues);
+ }
assert code.isConsistentSSA();
+ return !affectedValues.isEmpty();
}
private SwitchCaseEliminator removeUnnecessarySwitchCases(
@@ -1247,10 +1253,10 @@
assumeDynamicTypeRemover.finish();
if (!blocksToBeRemoved.isEmpty()) {
code.removeBlocks(blocksToBeRemoved);
- code.removeAllTrivialPhis();
+ code.removeAllTrivialPhis(affectedValues);
assert code.getUnreachableBlocks().isEmpty();
} else if (mayHaveRemovedTrivialPhi || assumeDynamicTypeRemover.mayHaveIntroducedTrivialPhi()) {
- code.removeAllTrivialPhis();
+ code.removeAllTrivialPhis(affectedValues);
}
if (!affectedValues.isEmpty()) {
new TypeAnalysis(appView).narrowing(affectedValues);
@@ -1407,7 +1413,10 @@
// Removing check-cast may result in a trivial phi:
// v3 <- phi(v1, v1)
if (needToRemoveTrivialPhis) {
- code.removeAllTrivialPhis();
+ code.removeAllTrivialPhis(affectedValues);
+ if (!affectedValues.isEmpty()) {
+ typeAnalysis.narrowing(affectedValues);
+ }
}
assert code.isConsistentSSA();
}
@@ -1524,7 +1533,11 @@
value -> !value.isPhi() && value.definition.isAssumeDynamicType());
if (aliasedValue != null) {
TypeLatticeElement dynamicType =
- aliasedValue.definition.asAssumeDynamicType().getAssumption().getType();
+ aliasedValue
+ .definition
+ .asAssumeDynamicType()
+ .getAssumption()
+ .getDynamicUpperBoundType();
if (dynamicType.isDefinitelyNull()) {
result = InstanceOfResult.FALSE;
} else if (dynamicType.lessThanOrEqual(instanceOfType, appView)
@@ -2378,7 +2391,13 @@
assert code.isConsistentSSA();
}
- public void simplifyIf(IRCode code) {
+ public boolean simplifyControlFlow(IRCode code) {
+ boolean anyAffectedValues = rewriteSwitch(code);
+ anyAffectedValues |= simplifyIf(code);
+ return anyAffectedValues;
+ }
+
+ private boolean simplifyIf(IRCode code) {
for (BasicBlock block : code.blocks) {
// Skip removed (= unreachable) blocks.
if (block.getNumber() != 0 && block.getPredecessors().isEmpty()) {
@@ -2394,27 +2413,26 @@
// Simplify if conditions when possible.
If theIf = block.exit().asIf();
- List<Value> inValues = theIf.inValues();
+ Value lhs = theIf.lhs();
+ Value rhs = theIf.isZeroTest() ? null : theIf.rhs();
- if (inValues.get(0).isConstNumber()
- && (theIf.isZeroTest() || inValues.get(1).isConstNumber())) {
+ if (lhs.isConstNumber() && (theIf.isZeroTest() || rhs.isConstNumber())) {
// Zero test with a constant of comparison between between two constants.
if (theIf.isZeroTest()) {
- ConstNumber cond = inValues.get(0).getConstInstruction().asConstNumber();
+ ConstNumber cond = lhs.getConstInstruction().asConstNumber();
BasicBlock target = theIf.targetFromCondition(cond);
simplifyIfWithKnownCondition(code, block, theIf, target);
} else {
- ConstNumber left = inValues.get(0).getConstInstruction().asConstNumber();
- ConstNumber right = inValues.get(1).getConstInstruction().asConstNumber();
+ ConstNumber left = lhs.getConstInstruction().asConstNumber();
+ ConstNumber right = rhs.getConstInstruction().asConstNumber();
BasicBlock target = theIf.targetFromCondition(left, right);
simplifyIfWithKnownCondition(code, block, theIf, target);
}
- } else if (inValues.get(0).hasValueRange()
- && (theIf.isZeroTest() || inValues.get(1).hasValueRange())) {
+ } else if (lhs.hasValueRange() && (theIf.isZeroTest() || rhs.hasValueRange())) {
// Zero test with a value range, or comparison between between two values,
// each with a value ranges.
if (theIf.isZeroTest()) {
- LongInterval interval = inValues.get(0).getValueRange();
+ LongInterval interval = lhs.getValueRange();
if (!interval.containsValue(0)) {
// Interval doesn't contain zero at all.
int sign = Long.signum(interval.getMin());
@@ -2448,8 +2466,8 @@
}
}
} else {
- LongInterval leftRange = inValues.get(0).getValueRange();
- LongInterval rightRange = inValues.get(1).getValueRange();
+ LongInterval leftRange = lhs.getValueRange();
+ LongInterval rightRange = rhs.getValueRange();
// Two overlapping ranges. Check for single point overlap.
if (!leftRange.overlapsWith(rightRange)) {
// No overlap.
@@ -2483,14 +2501,27 @@
}
}
}
- } else if (theIf.isZeroTest() && !inValues.get(0).isConstNumber()
- && (theIf.getType() == Type.EQ || theIf.getType() == Type.NE)) {
- TypeLatticeElement l = inValues.get(0).getTypeLattice();
- if (l.isReference() && inValues.get(0).isNeverNull()) {
- simplifyIfWithKnownCondition(code, block, theIf, 1);
+ } else if (theIf.getType() == Type.EQ || theIf.getType() == Type.NE) {
+ if (theIf.isZeroTest()) {
+ if (!lhs.isConstNumber()) {
+ TypeLatticeElement l = lhs.getTypeLattice();
+ if (l.isReference() && lhs.isNeverNull()) {
+ simplifyIfWithKnownCondition(code, block, theIf, 1);
+ } else {
+ if (!l.isPrimitive() && !l.isNullable()) {
+ simplifyIfWithKnownCondition(code, block, theIf, 1);
+ }
+ }
+ }
} else {
- if (!l.isPrimitive() && !l.isNullable()) {
- simplifyIfWithKnownCondition(code, block, theIf, 1);
+ DexEncodedField enumField = lhs.getEnumField(appView);
+ if (enumField != null) {
+ DexEncodedField otherEnumField = rhs.getEnumField(appView);
+ if (enumField == otherEnumField) {
+ simplifyIfWithKnownCondition(code, block, theIf, 0);
+ } else if (otherEnumField != null) {
+ simplifyIfWithKnownCondition(code, block, theIf, 1);
+ }
}
}
}
@@ -2501,6 +2532,7 @@
new TypeAnalysis(appView).narrowing(affectedValues);
}
assert code.isConsistentSSA();
+ return !affectedValues.isEmpty();
}
private void simplifyIfWithKnownCondition(
@@ -3500,8 +3532,8 @@
}
private static NewInstance findNewInstance(Phi phi) {
- Set<Phi> seen = new HashSet<>();
- Set<Value> values = new HashSet<>();
+ Set<Phi> seen = Sets.newIdentityHashSet();
+ Set<Value> values = Sets.newIdentityHashSet();
recursiveAddOperands(phi, seen, values);
if (values.size() != 1) {
throw new CompilationError("Failed to identify unique new-instance for <init>");
@@ -3539,7 +3571,7 @@
if (component.size() == 1 && component.iterator().next() == newInstanceValue) {
continue;
}
- Set<Phi> trivialPhis = new HashSet<>();
+ Set<Phi> trivialPhis = Sets.newIdentityHashSet();
for (Value value : component) {
boolean isTrivial = true;
Phi p = value.asPhi();
@@ -3569,7 +3601,7 @@
private int currentTime = 0;
private final Reference2IntMap<Value> discoverTime = new Reference2IntOpenHashMap<>();
- private final Set<Value> unassignedSet = new HashSet<>();
+ private final Set<Value> unassignedSet = Sets.newIdentityHashSet();
private final Deque<Value> unassignedStack = new ArrayDeque<>();
private final Deque<Value> preorderStack = new ArrayDeque<>();
private final List<Set<Value>> components = new ArrayList<>();
@@ -3603,7 +3635,7 @@
// If the current element is the top of the preorder stack, then we are at entry to a
// strongly-connected component consisting of this element and every element above this
// element on the stack.
- Set<Value> component = new HashSet<>(unassignedStack.size());
+ Set<Value> component = SetUtils.newIdentityHashSet(unassignedStack.size());
while (true) {
Value member = unassignedStack.pop();
unassignedSet.remove(member);
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 e0d77fb..2649e62 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
@@ -428,9 +428,28 @@
}
@Override
- public boolean canInlineInstanceInitializer(
- IRCode inlinee,
+ public boolean allowInliningOfInvokeInInlinee(
+ InlineAction action,
+ int inliningDepth,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
+ assert inliningDepth > 0;
+
+ if (action.reason.mustBeInlined()) {
+ return true;
+ }
+
+ int threshold = appView.options().applyInliningToInlineeMaxDepth;
+ if (inliningDepth <= threshold) {
+ return true;
+ }
+
+ whyAreYouNotInliningReporter.reportWillExceedMaxInliningDepth(inliningDepth, threshold);
+ return false;
+ }
+
+ @Override
+ public boolean canInlineInstanceInitializer(
+ IRCode inlinee, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
// In the Java VM Specification section "4.10.2.4. Instance Initialization Methods and
// Newly Created Objects" it says:
//
@@ -685,13 +704,12 @@
@Override
public void updateTypeInformationIfNeeded(
IRCode inlinee, ListIterator<BasicBlock> blockIterator, BasicBlock block) {
- if (appView.options().enableNonNullTracking) {
+ boolean assumersEnabled =
+ appView.options().enableNonNullTracking
+ || appView.options().enableDynamicTypeOptimization
+ || appView.options().testing.forceAssumeNoneInsertion;
+ if (assumersEnabled) {
BasicBlock state = IteratorUtils.peekNext(blockIterator);
- // Move the cursor back to where the first inlinee block was added.
- while (blockIterator.hasPrevious() && blockIterator.previous() != block) {
- // Do nothing.
- }
- assert IteratorUtils.peekNext(blockIterator) == block;
Set<BasicBlock> inlineeBlocks = Sets.newIdentityHashSet();
inlineeBlocks.addAll(inlinee.blocks);
@@ -699,18 +717,28 @@
// Introduce aliases only to the inlinee blocks.
if (appView.options().testing.forceAssumeNoneInsertion) {
insertAssumeInstructionsToInlinee(
- new AliasIntroducer(appView), code, state, blockIterator, inlineeBlocks);
+ new AliasIntroducer(appView), code, block, blockIterator, inlineeBlocks);
}
// Add non-null IRs only to the inlinee blocks.
- Consumer<BasicBlock> splitBlockConsumer = inlineeBlocks::add;
- Assumer nonNullTracker = new NonNullTracker(appView, splitBlockConsumer);
- insertAssumeInstructionsToInlinee(
- nonNullTracker, code, state, blockIterator, inlineeBlocks);
+ if (appView.options().enableNonNullTracking) {
+ Consumer<BasicBlock> splitBlockConsumer = inlineeBlocks::add;
+ Assumer nonNullTracker = new NonNullTracker(appView, splitBlockConsumer);
+ insertAssumeInstructionsToInlinee(
+ nonNullTracker, code, block, blockIterator, inlineeBlocks);
+ }
// Add dynamic type assumptions only to the inlinee blocks.
- insertAssumeInstructionsToInlinee(
- new DynamicTypeOptimization(appView), code, state, blockIterator, inlineeBlocks);
+ if (appView.options().enableDynamicTypeOptimization) {
+ insertAssumeInstructionsToInlinee(
+ new DynamicTypeOptimization(appView), code, block, blockIterator, inlineeBlocks);
+ }
+
+ // Restore the old state of the iterator.
+ while (blockIterator.hasPrevious() && blockIterator.previous() != state) {
+ // Do nothing.
+ }
+ assert IteratorUtils.peekNext(blockIterator) == state;
}
// TODO(b/72693244): need a test where refined env in inlinee affects the caller.
}
@@ -718,17 +746,17 @@
private void insertAssumeInstructionsToInlinee(
Assumer assumer,
IRCode code,
- BasicBlock state,
+ BasicBlock block,
ListIterator<BasicBlock> blockIterator,
Set<BasicBlock> inlineeBlocks) {
- assumer.insertAssumeInstructionsInBlocks(code, blockIterator, inlineeBlocks::contains);
- assert !blockIterator.hasNext();
-
- // Restore the old state of the iterator.
- while (blockIterator.hasPrevious() && blockIterator.previous() != state) {
+ // Move the cursor back to where the first inlinee block was added.
+ while (blockIterator.hasPrevious() && blockIterator.previous() != block) {
// Do nothing.
}
- assert IteratorUtils.peekNext(blockIterator) == state;
+ assert IteratorUtils.peekNext(blockIterator) == block;
+
+ assumer.insertAssumeInstructionsInBlocks(code, blockIterator, inlineeBlocks::contains);
+ assert !blockIterator.hasNext();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
index 26f383b..e5c53b3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
@@ -58,7 +58,7 @@
continue;
}
- TypeLatticeElement dynamicType;
+ TypeLatticeElement dynamicUpperBoundType;
ClassTypeLatticeElement dynamicLowerBoundType;
if (current.isInvokeMethod()) {
InvokeMethod invoke = current.asInvokeMethod();
@@ -80,7 +80,7 @@
continue;
}
- dynamicType = optimizationInfo.getDynamicReturnType();
+ dynamicUpperBoundType = optimizationInfo.getDynamicReturnType();
dynamicLowerBoundType = optimizationInfo.getDynamicLowerBoundType();
} else if (current.isStaticGet()) {
StaticGet staticGet = current.asStaticGet();
@@ -89,23 +89,23 @@
continue;
}
- dynamicType = encodedField.getOptimizationInfo().getDynamicType();
- // TODO(b/140234782): Extend to field values.
- dynamicLowerBoundType = null;
+ dynamicUpperBoundType = encodedField.getOptimizationInfo().getDynamicUpperBoundType();
+ dynamicLowerBoundType = encodedField.getOptimizationInfo().getDynamicLowerBoundType();
} else {
continue;
}
Value outValue = current.outValue();
boolean isTrivial =
- (dynamicType == null || !dynamicType.strictlyLessThan(outValue.getTypeLattice(), appView))
+ (dynamicUpperBoundType == null
+ || !dynamicUpperBoundType.strictlyLessThan(outValue.getTypeLattice(), appView))
&& dynamicLowerBoundType == null;
if (isTrivial) {
continue;
}
- if (dynamicType == null) {
- dynamicType = outValue.getTypeLattice();
+ if (dynamicUpperBoundType == null) {
+ dynamicUpperBoundType = outValue.getTypeLattice();
}
// Split block if needed (only debug instructions are allowed after the throwing
@@ -121,7 +121,12 @@
// Insert AssumeDynamicType instruction.
Assume<DynamicTypeAssumption> assumeInstruction =
Assume.createAssumeDynamicTypeInstruction(
- dynamicType, dynamicLowerBoundType, specializedOutValue, outValue, current, appView);
+ dynamicUpperBoundType,
+ dynamicLowerBoundType,
+ specializedOutValue,
+ outValue,
+ current,
+ appView);
assumeInstruction.setPosition(
appView.options().debug ? current.getPosition() : Position.none());
if (insertionBlock == block) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
index fb1ef58..f52658f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -84,6 +84,15 @@
IRCode inlinee, ListIterator<BasicBlock> blockIterator, BasicBlock block) {}
@Override
+ public boolean allowInliningOfInvokeInInlinee(
+ InlineAction action,
+ int inliningDepth,
+ WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
+ // The purpose of force inlining is generally to inline a given invoke-instruction in the IR.
+ return false;
+ }
+
+ @Override
public boolean canInlineInstanceInitializer(
IRCode code, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
return true;
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 fef9538..40d01c0 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
@@ -46,16 +46,20 @@
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
+import com.android.tools.r8.ir.optimize.lambda.LambdaMerger;
import com.android.tools.r8.kotlin.Kotlin;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.MainDexClasses;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.IteratorUtils;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.ThreadUtils;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
+import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
@@ -70,6 +74,7 @@
protected final AppView<AppInfoWithLiveness> appView;
private final Set<DexMethod> blacklist;
+ private final LambdaMerger lambdaMerger;
private final LensCodeRewriter lensCodeRewriter;
final MainDexClasses mainDexClasses;
@@ -82,10 +87,12 @@
public Inliner(
AppView<AppInfoWithLiveness> appView,
MainDexClasses mainDexClasses,
+ LambdaMerger lambdaMerger,
LensCodeRewriter lensCodeRewriter) {
Kotlin.Intrinsics intrinsics = appView.dexItemFactory().kotlin.intrinsics;
this.appView = appView;
this.blacklist = ImmutableSet.of(intrinsics.throwNpe, intrinsics.throwParameterIsNullException);
+ this.lambdaMerger = lambdaMerger;
this.lensCodeRewriter = lensCodeRewriter;
this.mainDexClasses = mainDexClasses;
}
@@ -585,6 +592,7 @@
ValueNumberGenerator generator,
AppView<? extends AppInfoWithSubtyping> appView,
Position callerPosition,
+ LambdaMerger lambdaMerger,
LensCodeRewriter lensCodeRewriter) {
DexItemFactory dexItemFactory = appView.dexItemFactory();
InternalOptions options = appView.options();
@@ -752,6 +760,9 @@
if (!target.isProcessed()) {
lensCodeRewriter.rewrite(code, target);
}
+ if (lambdaMerger != null) {
+ lambdaMerger.rewriteCodeForInlining(target, code, context);
+ }
assert code.isConsistentSSA();
return new InlineeWithReason(code, reason);
}
@@ -870,8 +881,13 @@
ListIterator<BasicBlock> blockIterator = code.listIterator();
ClassInitializationAnalysis classInitializationAnalysis =
new ClassInitializationAnalysis(appView, code);
+ Deque<BasicBlock> inlineeStack = new ArrayDeque<>();
+ InternalOptions options = appView.options();
while (blockIterator.hasNext()) {
BasicBlock block = blockIterator.next();
+ if (!inlineeStack.isEmpty() && inlineeStack.peekFirst() == block) {
+ inlineeStack.pop();
+ }
if (blocksToRemove.contains(block)) {
continue;
}
@@ -895,12 +911,19 @@
oracle.computeInlining(
invoke, singleTarget, classInitializationAnalysis, whyAreYouNotInliningReporter);
if (action == null) {
- assert whyAreYouNotInliningReporter.verifyReasonHasBeenReported();
+ assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
+ continue;
+ }
+
+ if (!inlineeStack.isEmpty()
+ && !strategy.allowInliningOfInvokeInInlinee(
+ action, inlineeStack.size(), whyAreYouNotInliningReporter)) {
+ assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
continue;
}
if (!strategy.stillHasBudget(action, whyAreYouNotInliningReporter)) {
- assert whyAreYouNotInliningReporter.verifyReasonHasBeenReported();
+ assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
continue;
}
@@ -910,10 +933,11 @@
code.valueNumberGenerator,
appView,
getPositionForInlining(invoke, context),
+ lambdaMerger,
lensCodeRewriter);
if (strategy.willExceedBudget(
code, invoke, inlinee, block, whyAreYouNotInliningReporter)) {
- assert whyAreYouNotInliningReporter.verifyReasonHasBeenReported();
+ assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
continue;
}
@@ -926,7 +950,7 @@
if (singleTarget.isInstanceInitializer()
&& !strategy.canInlineInstanceInitializer(
inlinee.code, whyAreYouNotInliningReporter)) {
- assert whyAreYouNotInliningReporter.verifyReasonHasBeenReported();
+ assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
continue;
}
@@ -936,6 +960,8 @@
assumeDynamicTypeRemover.markUsersForRemoval(outValue);
}
+ boolean inlineeMayHaveInvokeMethod = inlinee.code.metadata().mayHaveInvokeMethod();
+
// Inline the inlinee code in place of the invoke instruction
// Back up before the invoke instruction.
iterator.previous();
@@ -962,11 +988,26 @@
}
context.copyMetadata(singleTarget);
+
+ if (inlineeMayHaveInvokeMethod && options.applyInliningToInlinee) {
+ if (inlineeStack.size() + 1 > options.applyInliningToInlineeMaxDepth
+ && appView.appInfo().alwaysInline.isEmpty()
+ && appView.appInfo().forceInline.isEmpty()) {
+ continue;
+ }
+ // Record that we will be inside the inlinee until the next block.
+ BasicBlock inlineeEnd = IteratorUtils.peekNext(blockIterator);
+ inlineeStack.push(inlineeEnd);
+ // Move the cursor back to where the first inlinee block was added.
+ IteratorUtils.previousUntil(blockIterator, previous -> previous == block);
+ blockIterator.next();
+ }
} else if (current.isAssumeDynamicType()) {
assumeDynamicTypeRemover.removeIfMarked(current.asAssumeDynamicType(), iterator);
}
}
}
+ assert inlineeStack.isEmpty();
assumeDynamicTypeRemover.removeMarkedInstructions(blocksToRemove);
assumeDynamicTypeRemover.finish();
classInitializationAnalysis.finish();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
index 5f9bb2a..e0b0a2c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
@@ -17,6 +17,11 @@
interface InliningStrategy {
+ boolean allowInliningOfInvokeInInlinee(
+ InlineAction action,
+ int inliningDepth,
+ WhyAreYouNotInliningReporter whyAreYouNotInliningReporter);
+
boolean canInlineInstanceInitializer(
IRCode code, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 62d5bd8..cea6258 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -3,6 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize;
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.stringClassType;
+
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexDefinition;
@@ -193,7 +196,8 @@
appView.dexItemFactory().stringType,
typeLattice.asClassTypeLatticeElement().getClassType())
.isTrue();
- Value returnedValue = code.createValue(typeLattice, debugLocalInfo);
+ Value returnedValue =
+ code.createValue(stringClassType(appView, definitelyNotNull()), debugLocalInfo);
ConstString instruction =
new ConstString(
returnedValue, constant, ThrowingInfo.defaultForConstString(appView.options()));
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 cea57a8..9752859 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
@@ -134,8 +134,8 @@
DexEncodedField encodedField = appView.appInfo().resolveField(field);
if (encodedField != null) {
FieldOptimizationInfo optimizationInfo = encodedField.getOptimizationInfo();
- if (optimizationInfo.getDynamicType() != null
- && optimizationInfo.getDynamicType().isDefinitelyNotNull()) {
+ if (optimizationInfo.getDynamicUpperBoundType() != null
+ && optimizationInfo.getDynamicUpperBoundType().isDefinitelyNotNull()) {
knownToBeNonNullValues.add(outValue);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index fdfa3ee..5088d99 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -373,7 +373,7 @@
public void rewrite(IRCode code) {
Set<BasicBlock> blocksToBeRemoved = Sets.newIdentityHashSet();
ListIterator<BasicBlock> blockIterator = code.listIterator();
- Set<Value> valuesToNarrow = new HashSet<>();
+ Set<Value> valuesToNarrow = Sets.newIdentityHashSet();
while (blockIterator.hasNext()) {
BasicBlock block = blockIterator.next();
if (blocksToBeRemoved.contains(block)) {
@@ -422,12 +422,12 @@
}
}
}
+ code.removeBlocks(blocksToBeRemoved);
+ code.removeAllTrivialPhis(valuesToNarrow);
+ code.removeUnreachableBlocks();
if (!valuesToNarrow.isEmpty()) {
new TypeAnalysis(appView).narrowing(valuesToNarrow);
}
- code.removeBlocks(blocksToBeRemoved);
- code.removeAllTrivialPhis();
- code.removeUnreachableBlocks();
assert code.isConsistentSSA();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index 95f29b9..a938ff2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -226,6 +226,13 @@
List<DexEncodedMethod> directMethods = clazz.directMethods();
for (int i = 0; i < directMethods.size(); i++) {
DexEncodedMethod method = directMethods.get(i);
+
+ // If this is a private or static method that is targeted by an invoke-super instruction, then
+ // don't remove any unused arguments.
+ if (appView.appInfo().brokenSuperInvokes.contains(method.method)) {
+ continue;
+ }
+
RemovedArgumentsInfo unused = collectUnusedArguments(method);
if (unused != null && unused.hasRemovedArguments()) {
DexProto newProto = createProtoWithRemovedArguments(method, unused);
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 f550309..d576a22 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
@@ -246,7 +246,7 @@
// particularly important for bridge methods.
codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(code);
// If a method was inlined we may be able to prune additional branches.
- codeRewriter.simplifyIf(code);
+ codeRewriter.simplifyControlFlow(code);
// If a method was inlined we may see more trivial computation/conversion of String.
boolean isDebugMode =
appView.options().debug || method.getOptimizationInfo().isReachabilitySensitive();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
index 30721f5..e82afb7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.optimize.info;
+import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
public class DefaultFieldOptimizationInfo extends FieldOptimizationInfo {
@@ -32,7 +33,12 @@
}
@Override
- public TypeLatticeElement getDynamicType() {
+ public ClassTypeLatticeElement getDynamicLowerBoundType() {
+ return null;
+ }
+
+ @Override
+ public TypeLatticeElement getDynamicUpperBoundType() {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
index 58432ba..cc89f84 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.optimize.info;
+import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
public abstract class FieldOptimizationInfo {
@@ -18,7 +19,9 @@
*/
public abstract int getReadBits();
- public abstract TypeLatticeElement getDynamicType();
+ public abstract ClassTypeLatticeElement getDynamicLowerBoundType();
+
+ public abstract TypeLatticeElement getDynamicUpperBoundType();
public abstract boolean valueHasBeenPropagated();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 78369e3..8cbc644 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -46,6 +46,7 @@
import com.android.tools.r8.kotlin.Kotlin;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.Sets;
@@ -60,17 +61,16 @@
import java.util.function.Function;
public class MethodOptimizationInfoCollector {
- private final AppView<?> appView;
+ private final AppView<AppInfoWithLiveness> appView;
private final InternalOptions options;
private final DexItemFactory dexItemFactory;
- public MethodOptimizationInfoCollector(AppView<?> appView) {
+ public MethodOptimizationInfoCollector(AppView<AppInfoWithLiveness> appView) {
this.appView = appView;
this.options = appView.options();
this.dexItemFactory = appView.dexItemFactory();
}
- // TODO(b/141656615): Use this and then make all utils in this collector `private`.
public void collectMethodOptimizationInfo(
DexEncodedMethod method,
IRCode code,
@@ -91,7 +91,7 @@
computeNonNullParamOnNormalExits(feedback, code);
}
- public void identifyClassInlinerEligibility(
+ private void identifyClassInlinerEligibility(
DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
// Method eligibility is calculated in similar way for regular method
// and for the constructor. To be eligible method should only be using its
@@ -126,7 +126,11 @@
boolean receiverUsedAsReturnValue = false;
boolean seenSuperInitCall = false;
- for (Instruction insn : receiver.uniqueUsers()) {
+ for (Instruction insn : receiver.aliasedUsers()) {
+ if (insn.isAssume()) {
+ continue;
+ }
+
if (insn.isMonitor()) {
continue;
}
@@ -140,11 +144,11 @@
if (insn.isInstancePut()) {
InstancePut instancePutInstruction = insn.asInstancePut();
// Only allow field writes to the receiver.
- if (instancePutInstruction.object() != receiver) {
+ if (instancePutInstruction.object().getAliasedValue() != receiver) {
return;
}
// Do not allow the receiver to escape via a field write.
- if (instancePutInstruction.value() == receiver) {
+ if (instancePutInstruction.value().getAliasedValue() == receiver) {
return;
}
}
@@ -162,7 +166,8 @@
DexMethod invokedMethod = invokedDirect.getInvokedMethod();
if (dexItemFactory.isConstructor(invokedMethod)
&& invokedMethod.holder == clazz.superType
- && invokedDirect.inValues().lastIndexOf(receiver) == 0
+ && ListUtils.lastIndexMatching(
+ invokedDirect.inValues(), v -> v.getAliasedValue() == receiver) == 0
&& !seenSuperInitCall
&& instanceInitializer) {
seenSuperInitCall = true;
@@ -184,7 +189,7 @@
method, new ClassInlinerEligibility(receiverUsedAsReturnValue));
}
- public void identifyParameterUsages(
+ private void identifyParameterUsages(
DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
List<ParameterUsage> usages = new ArrayList<>();
List<Value> values = code.collectArguments();
@@ -203,7 +208,7 @@
private ParameterUsage collectParameterUsages(int i, Value value) {
ParameterUsageBuilder builder = new ParameterUsageBuilder(value, i);
- for (Instruction user : value.uniqueUsers()) {
+ for (Instruction user : value.aliasedUsers()) {
if (!builder.note(user)) {
return null;
}
@@ -211,7 +216,7 @@
return builder.build();
}
- public void identifyReturnsArgument(
+ private void identifyReturnsArgument(
DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
List<BasicBlock> normalExits = code.computeNormalExitBlocks();
if (normalExits.isEmpty()) {
@@ -233,18 +238,19 @@
isNeverNull &= value.getTypeLattice().isReference() && value.isNeverNull();
}
if (returnValue != null) {
- if (returnValue.isArgument()) {
+ Value aliasedValue = returnValue.getAliasedValue();
+ if (aliasedValue.isArgument()) {
// Find the argument number.
- int index = code.collectArguments().indexOf(returnValue);
- assert index != -1;
+ int index = aliasedValue.computeArgumentPosition(code);
+ assert index >= 0;
feedback.methodReturnsArgument(method, index);
}
- if (returnValue.isConstant()) {
- if (returnValue.definition.isConstNumber()) {
- long value = returnValue.definition.asConstNumber().getRawValue();
+ if (aliasedValue.isConstant()) {
+ if (aliasedValue.definition.isConstNumber()) {
+ long value = aliasedValue.definition.asConstNumber().getRawValue();
feedback.methodReturnsConstantNumber(method, value);
- } else if (returnValue.definition.isConstString()) {
- ConstString constStringInstruction = returnValue.definition.asConstString();
+ } else if (aliasedValue.definition.isConstString()) {
+ ConstString constStringInstruction = aliasedValue.definition.asConstString();
if (!constStringInstruction.instructionInstanceCanThrow()) {
feedback.methodReturnsConstantString(method, constStringInstruction.getValue());
}
@@ -256,9 +262,11 @@
}
}
- public void identifyTrivialInitializer(
+ private void identifyTrivialInitializer(
DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
- if (!method.isInstanceInitializer() && !method.isClassInitializer()) {
+ assert !appView.appInfo().isPinned(method.method);
+
+ if (!method.isInitializer()) {
return;
}
@@ -266,8 +274,13 @@
return;
}
+ if (appView.appInfo().mayHaveSideEffects.containsKey(method.method)) {
+ return;
+ }
+
DexClass clazz = appView.appInfo().definitionFor(method.method.holder);
if (clazz == null) {
+ assert false;
return;
}
@@ -303,6 +316,10 @@
continue;
}
+ if (insn.isAssume()) {
+ continue;
+ }
+
if (insn.isNewInstance()) {
NewInstance newInstance = insn.asNewInstance();
if (createdSingletonInstance != null
@@ -374,6 +391,10 @@
continue;
}
+ if (insn.isAssume()) {
+ continue;
+ }
+
if (insn.isArgument()) {
continue;
}
@@ -440,7 +461,7 @@
return TrivialInstanceInitializer.INSTANCE;
}
- public void identifyInvokeSemanticsForInlining(
+ private void identifyInvokeSemanticsForInlining(
DexEncodedMethod method, IRCode code, AppView<?> appView, OptimizationFeedback feedback) {
if (method.isStatic()) {
// Identifies if the method preserves class initialization after inlining.
@@ -692,7 +713,7 @@
return true;
}
- public void computeDynamicReturnType(
+ private void computeDynamicReturnType(
DynamicTypeOptimization dynamicTypeOptimization,
OptimizationFeedback feedback,
DexEncodedMethod method,
@@ -721,7 +742,7 @@
}
}
- public void computeInitializedClassesOnNormalExit(
+ private void computeInitializedClassesOnNormalExit(
OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
if (options.enableInitializedClassesAnalysis && appView.appInfo().hasLiveness()) {
AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
@@ -734,7 +755,7 @@
}
}
- public void computeMayHaveSideEffects(
+ private void computeMayHaveSideEffects(
OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
// If the method is native, we don't know what could happen.
assert !method.accessFlags.isNative();
@@ -802,7 +823,7 @@
return false;
}
- public void computeReturnValueOnlyDependsOnArguments(
+ private void computeReturnValueOnlyDependsOnArguments(
OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
if (!options.enableDeterminismAnalysis) {
return;
@@ -815,7 +836,7 @@
}
// Track usage of parameters and compute their nullability and possibility of NPE.
- public void computeNonNullParamOrThrow(
+ private void computeNonNullParamOrThrow(
OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
if (method.getOptimizationInfo().getNonNullParamOrThrow() != null) {
return;
@@ -843,7 +864,7 @@
}
}
- public void computeNonNullParamOnNormalExits(OptimizationFeedback feedback, IRCode code) {
+ private void computeNonNullParamOnNormalExits(OptimizationFeedback feedback, IRCode code) {
Set<BasicBlock> normalExits = Sets.newIdentityHashSet();
normalExits.addAll(code.computeNormalExitBlocks());
DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
index 55ce1a4..9d3bb4a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import java.util.function.Function;
@@ -22,12 +23,16 @@
private int readBits = 0;
private boolean cannotBeKept = false;
private boolean valueHasBeenPropagated = false;
- private TypeLatticeElement dynamicType = null;
+ private ClassTypeLatticeElement dynamicLowerBoundType = null;
+ private TypeLatticeElement dynamicUpperBoundType = null;
public void fixupClassTypeReferences(
Function<DexType, DexType> mapping, AppView<? extends AppInfoWithSubtyping> appView) {
- if (dynamicType != null) {
- dynamicType = dynamicType.fixupClassTypeReferences(mapping, appView);
+ if (dynamicLowerBoundType != null) {
+ dynamicLowerBoundType = dynamicLowerBoundType.fixupClassTypeReferences(mapping, appView);
+ }
+ if (dynamicUpperBoundType != null) {
+ dynamicUpperBoundType = dynamicUpperBoundType.fixupClassTypeReferences(mapping, appView);
}
}
@@ -58,12 +63,21 @@
}
@Override
- public TypeLatticeElement getDynamicType() {
- return dynamicType;
+ public ClassTypeLatticeElement getDynamicLowerBoundType() {
+ return dynamicLowerBoundType;
}
- void setDynamicType(TypeLatticeElement type) {
- dynamicType = type;
+ void setDynamicLowerBoundType(ClassTypeLatticeElement type) {
+ dynamicLowerBoundType = type;
+ }
+
+ @Override
+ public TypeLatticeElement getDynamicUpperBoundType() {
+ return dynamicUpperBoundType;
+ }
+
+ void setDynamicUpperBoundType(TypeLatticeElement type) {
+ dynamicUpperBoundType = type;
}
@Override
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 d185a37..a994f5d 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
@@ -95,8 +95,14 @@
}
@Override
- public void markFieldHasDynamicType(DexEncodedField field, TypeLatticeElement type) {
- getFieldOptimizationInfoForUpdating(field).setDynamicType(type);
+ public void markFieldHasDynamicLowerBoundType(
+ DexEncodedField field, ClassTypeLatticeElement type) {
+ getFieldOptimizationInfoForUpdating(field).setDynamicLowerBoundType(type);
+ }
+
+ @Override
+ public void markFieldHasDynamicUpperBoundType(DexEncodedField field, TypeLatticeElement type) {
+ getFieldOptimizationInfoForUpdating(field).setDynamicUpperBoundType(type);
}
@Override
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 79b2e7f..9b5c988 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
@@ -36,7 +36,11 @@
public void markFieldAsPropagated(DexEncodedField field) {}
@Override
- public void markFieldHasDynamicType(DexEncodedField field, TypeLatticeElement type) {}
+ public void markFieldHasDynamicLowerBoundType(
+ DexEncodedField field, ClassTypeLatticeElement type) {}
+
+ @Override
+ public void markFieldHasDynamicUpperBoundType(DexEncodedField field, TypeLatticeElement type) {}
@Override
public void markFieldBitsRead(DexEncodedField field, int bitsRead) {}
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 9119b4b..474a963 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
@@ -40,7 +40,13 @@
}
@Override
- public void markFieldHasDynamicType(DexEncodedField field, TypeLatticeElement type) {
+ public void markFieldHasDynamicLowerBoundType(
+ DexEncodedField field, ClassTypeLatticeElement type) {
+ // Ignored.
+ }
+
+ @Override
+ public void markFieldHasDynamicUpperBoundType(DexEncodedField field, TypeLatticeElement type) {
// Ignored.
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java
index 0495bba..7e61600 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.Return;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.Pair;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -110,6 +111,10 @@
// Returns false if the instruction is not supported.
public boolean note(Instruction instruction) {
+ if (instruction.isAssume()) {
+ // Keep examining other users, but the param usage builder should consider aliased users.
+ return true;
+ }
if (instruction.isIf()) {
return note(instruction.asIf());
}
@@ -141,7 +146,8 @@
private boolean note(If ifInstruction) {
if (ifInstruction.asIf().isZeroTest()) {
- assert ifInstruction.inValues().size() == 1 && ifInstruction.inValues().get(0) == arg;
+ assert ifInstruction.inValues().size() == 1
+ && ifInstruction.inValues().get(0).getAliasedValue() == arg;
ifZeroTestTypes.add(ifInstruction.asIf().getType());
return true;
}
@@ -150,7 +156,7 @@
private boolean note(InstanceGet instanceGetInstruction) {
assert arg != instanceGetInstruction.outValue();
- if (instanceGetInstruction.object() == arg) {
+ if (instanceGetInstruction.object().getAliasedValue() == arg) {
hasFieldRead = true;
return true;
}
@@ -159,12 +165,12 @@
private boolean note(InstancePut instancePutInstruction) {
assert arg != instancePutInstruction.outValue();
- if (instancePutInstruction.object() == arg) {
+ if (instancePutInstruction.object().getAliasedValue() == arg) {
hasFieldAssignment = true;
- isAssignedToField |= instancePutInstruction.value() == arg;
+ isAssignedToField |= instancePutInstruction.value().getAliasedValue() == arg;
return true;
}
- if (instancePutInstruction.value() == arg) {
+ if (instancePutInstruction.value().getAliasedValue() == arg) {
isAssignedToField = true;
return true;
}
@@ -172,7 +178,8 @@
}
private boolean note(InvokeMethodWithReceiver invokeInstruction) {
- if (invokeInstruction.inValues().lastIndexOf(arg) == 0) {
+ if (ListUtils.lastIndexMatching(
+ invokeInstruction.inValues(), v -> v.getAliasedValue() == arg) == 0) {
callsOnReceiver.add(
new Pair<>(
invokeInstruction.asInvokeMethodWithReceiver().getType(),
@@ -183,7 +190,8 @@
}
private boolean note(Return returnInstruction) {
- assert returnInstruction.inValues().size() == 1 && returnInstruction.inValues().get(0) == arg;
+ assert returnInstruction.inValues().size() == 1
+ && returnInstruction.inValues().get(0).getAliasedValue() == arg;
isReturned = true;
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
index 06e7332..09a8d13 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
@@ -371,7 +371,12 @@
// We may get more precise type information if the method is reprocessed (e.g., due to
// optimization info collected from all call sites), and hence the `returnsObjectOfType` is
// allowed to become more precise.
- assert returnsObjectOfType == UNKNOWN_TYPE || type.lessThanOrEqual(returnsObjectOfType, appView)
+ // TODO(b/142559221): non-materializable assume instructions?
+ // Nullability could be less precise, though. For example, suppose a value is known to be
+ // non-null after a safe invocation, hence recorded with the non-null variant. If that call is
+ // inlined and the method is reprocessed, such non-null assumption cannot be made again.
+ assert returnsObjectOfType == UNKNOWN_TYPE
+ || type.lessThanOrEqualUpToNullability(returnsObjectOfType, appView)
: "return type changed from " + returnsObjectOfType + " to " + type;
returnsObjectOfType = type;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
index 467a5b6..8cbb1f4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
@@ -119,11 +119,14 @@
public void reportWillExceedInstructionBudget(int numberOfInstructions, int threshold) {}
@Override
+ public void reportWillExceedMaxInliningDepth(int actualInliningDepth, int threshold) {}
+
+ @Override
public void reportWillExceedMonitorEnterValuesBudget(
int numberOfMonitorEnterValuesAfterInlining, int threshold) {}
@Override
- public boolean verifyReasonHasBeenReported() {
+ public boolean unsetReasonHasBeenReportedFlag() {
return true;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
index ab80544..1ff91c0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
@@ -113,8 +113,10 @@
public abstract void reportWillExceedInstructionBudget(int numberOfInstructions, int threshold);
+ public abstract void reportWillExceedMaxInliningDepth(int actualInliningDepth, int threshold);
+
public abstract void reportWillExceedMonitorEnterValuesBudget(
int numberOfMonitorEnterValuesAfterInlining, int threshold);
- public abstract boolean verifyReasonHasBeenReported();
+ public abstract boolean unsetReasonHasBeenReportedFlag();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
index 7d25661..64026ca 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
@@ -247,6 +247,15 @@
}
@Override
+ public void reportWillExceedMaxInliningDepth(int actualInliningDepth, int threshold) {
+ printWithExceededThreshold(
+ "would exceed the maximum inlining depth",
+ "current inlining depth",
+ actualInliningDepth,
+ threshold);
+ }
+
+ @Override
public void reportWillExceedMonitorEnterValuesBudget(
int numberOfMonitorEnterValuesAfterInlining, int threshold) {
printWithExceededThreshold(
@@ -257,8 +266,9 @@
}
@Override
- public boolean verifyReasonHasBeenReported() {
+ public boolean unsetReasonHasBeenReportedFlag() {
assert reasonHasBeenReported;
+ reasonHasBeenReported = false;
return true;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
index 5c6cb8f..f10c3fe 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.CheckCast;
@@ -147,12 +148,25 @@
public final ListIterator<BasicBlock> blocks;
private InstructionListIterator instructions;
+ // The inlining context (caller), if any.
+ private final DexEncodedMethod context;
+
CodeProcessor(
AppView<AppInfoWithLiveness> appView,
Function<DexType, Strategy> strategyProvider,
LambdaTypeVisitor lambdaChecker,
DexEncodedMethod method,
IRCode code) {
+ this(appView, strategyProvider, lambdaChecker, method, code, null);
+ }
+
+ CodeProcessor(
+ AppView<AppInfoWithLiveness> appView,
+ Function<DexType, Strategy> strategyProvider,
+ LambdaTypeVisitor lambdaChecker,
+ DexEncodedMethod method,
+ IRCode code,
+ DexEncodedMethod context) {
this.appView = appView;
this.strategyProvider = strategyProvider;
this.factory = appView.dexItemFactory();
@@ -161,6 +175,7 @@
this.method = method;
this.code = code;
this.blocks = code.listIterator();
+ this.context = context;
}
public final InstructionListIterator instructions() {
@@ -178,6 +193,19 @@
}
}
+ private boolean shouldRewrite(DexField field) {
+ return shouldRewrite(field.holder);
+ }
+
+ private boolean shouldRewrite(DexMethod method) {
+ return shouldRewrite(method.holder);
+ }
+
+ private boolean shouldRewrite(DexType type) {
+ // Rewrite references to lambda classes if we are outside the class.
+ return type != (context != null ? context : method).method.holder;
+ }
+
@Override
public Void handleInvoke(Invoke invoke) {
if (invoke.isInvokeNewArray()) {
@@ -199,7 +227,7 @@
// Invalidate signature, there still should not be lambda references.
lambdaChecker.accept(invokeMethod.getInvokedMethod().proto);
// Only rewrite references to lambda classes if we are outside the class.
- if (invokeMethod.getInvokedMethod().holder != this.method.method.holder) {
+ if (shouldRewrite(invokeMethod.getInvokedMethod())) {
process(strategy, invokeMethod);
}
return null;
@@ -218,7 +246,7 @@
Strategy strategy = strategyProvider.apply(newInstance.clazz);
if (strategy.isValidNewInstance(this, newInstance)) {
// Only rewrite references to lambda classes if we are outside the class.
- if (newInstance.clazz != this.method.method.holder) {
+ if (shouldRewrite(newInstance.clazz)) {
process(strategy, newInstance);
}
}
@@ -260,7 +288,7 @@
DexField field = instanceGet.getField();
Strategy strategy = strategyProvider.apply(field.holder);
if (strategy.isValidInstanceFieldRead(this, field)) {
- if (field.holder != this.method.method.holder) {
+ if (shouldRewrite(field)) {
// Only rewrite references to lambda classes if we are outside the class.
process(strategy, instanceGet);
}
@@ -279,7 +307,7 @@
DexField field = instancePut.getField();
Strategy strategy = strategyProvider.apply(field.holder);
if (strategy.isValidInstanceFieldWrite(this, field)) {
- if (field.holder != this.method.method.holder) {
+ if (shouldRewrite(field)) {
// Only rewrite references to lambda classes if we are outside the class.
process(strategy, instancePut);
}
@@ -298,7 +326,7 @@
DexField field = staticGet.getField();
Strategy strategy = strategyProvider.apply(field.holder);
if (strategy.isValidStaticFieldRead(this, field)) {
- if (field.holder != this.method.method.holder) {
+ if (shouldRewrite(field)) {
// Only rewrite references to lambda classes if we are outside the class.
process(strategy, staticGet);
}
@@ -314,7 +342,7 @@
DexField field = staticPut.getField();
Strategy strategy = strategyProvider.apply(field.holder);
if (strategy.isValidStaticFieldWrite(this, field)) {
- if (field.holder != this.method.method.holder) {
+ if (shouldRewrite(field)) {
// Only rewrite references to lambda classes if we are outside the class.
process(strategy, staticPut);
}
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 a90704a..b226521 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
@@ -56,7 +56,6 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
-import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -84,6 +83,43 @@
// 5. synthesize group lambda classes.
//
public final class LambdaMerger {
+
+ private abstract static class Mode {
+
+ void rewriteCode(DexEncodedMethod method, IRCode code, DexEncodedMethod context) {}
+
+ void analyzeCode(DexEncodedMethod method, IRCode code) {}
+ }
+
+ private class AnalyzeMode extends Mode {
+
+ @Override
+ void analyzeCode(DexEncodedMethod method, IRCode code) {
+ new AnalysisStrategy(method, code).processCode();
+ }
+ }
+
+ private class ApplyMode extends Mode {
+
+ private final Set<DexType> lambdaGroupsClasses;
+ private final LambdaMergerOptimizationInfoFixer optimizationInfoFixer;
+
+ ApplyMode(
+ Set<DexType> lambdaGroupTypes, LambdaMergerOptimizationInfoFixer optimizationInfoFixer) {
+ this.lambdaGroupsClasses = lambdaGroupTypes;
+ this.optimizationInfoFixer = optimizationInfoFixer;
+ }
+
+ @Override
+ void rewriteCode(DexEncodedMethod method, IRCode code, DexEncodedMethod context) {
+ if (lambdaGroupsClasses.contains(method.method.holder)) {
+ // Don't rewrite the methods that we have synthesized for the lambda group classes.
+ return;
+ }
+ new ApplyStrategy(method, code, context, optimizationInfoFixer).processCode();
+ }
+ }
+
// Maps lambda into a group, only contains lambdas we decided to merge.
// NOTE: needs synchronization.
private final Map<DexType, LambdaGroup> lambdas = new IdentityHashMap<>();
@@ -113,7 +149,7 @@
private final Kotlin kotlin;
private final DiagnosticsHandler reporter;
- private BiFunction<DexEncodedMethod, IRCode, CodeProcessor> strategyFactory = null;
+ private Mode mode;
// Lambda visitor invalidating lambdas it sees.
private final LambdaTypeVisitor lambdaInvalidator;
@@ -153,8 +189,7 @@
// Collect all group candidates and assign unique lambda ids inside each group.
// We do this before methods are being processed to guarantee stable order of
// lambdas inside each group.
- public final void collectGroupCandidates(
- DexApplication app, AppView<AppInfoWithLiveness> appView) {
+ public final void collectGroupCandidates(DexApplication app) {
// Collect lambda groups.
app.classes().stream()
.filter(cls -> !appView.appInfo().isPinned(cls.type))
@@ -188,20 +223,50 @@
// Remove trivial groups.
removeTrivialLambdaGroups();
- assert strategyFactory == null;
- strategyFactory = AnalysisStrategy::new;
+ assert mode == null;
+ mode = new AnalyzeMode();
}
- // Is called by IRConverter::rewriteCode, performs different actions
- // depending on phase:
- // - in ANALYZE phase just analyzes invalid usages of lambda classes
- // inside the method code, invalidated such lambda classes,
- // collects methods that need to be patched.
- // - in APPLY phase patches the code to use lambda group classes, also
- // asserts that there are no more invalid lambda class references.
- public final void processMethodCode(DexEncodedMethod method, IRCode code) {
- if (strategyFactory != null) {
- strategyFactory.apply(method, code).processCode();
+ /**
+ * Is called by IRConverter::rewriteCode. Performs different actions depending on the current
+ * mode.
+ *
+ * <ol>
+ * <li>in ANALYZE mode analyzes invalid usages of lambda classes inside the method code,
+ * invalidated such lambda classes, collects methods that need to be patched.
+ * <li>in APPLY mode does nothing.
+ * </ol>
+ */
+ public final void analyzeCode(DexEncodedMethod method, IRCode code) {
+ if (mode != null) {
+ mode.analyzeCode(method, code);
+ }
+ }
+
+ /**
+ * Is called by IRConverter::rewriteCode. Performs different actions depending on the current
+ * mode.
+ *
+ * <ol>
+ * <li>in ANALYZE mode does nothing.
+ * <li>in APPLY mode patches the code to use lambda group classes, also asserts that there are
+ * no more invalid lambda class references.
+ * </ol>
+ */
+ public final void rewriteCode(DexEncodedMethod method, IRCode code) {
+ if (mode != null) {
+ mode.rewriteCode(method, code, null);
+ }
+ }
+
+ /**
+ * Similar to {@link #rewriteCode(DexEncodedMethod, IRCode)}, but for rewriting code for inlining.
+ * The {@param context} is the caller that {@param method} is being inlined into.
+ */
+ public final void rewriteCodeForInlining(
+ DexEncodedMethod method, IRCode code, DexEncodedMethod context) {
+ if (mode != null) {
+ mode.rewriteCode(method, code, context);
}
}
@@ -239,7 +304,9 @@
feedback.fixupOptimizationInfos(appView, executorService, optimizationInfoFixer);
// Switch to APPLY strategy.
- this.strategyFactory = (method, code) -> new ApplyStrategy(method, code, optimizationInfoFixer);
+ Set<DexType> lambdaGroupTypes =
+ lambdaGroupsClasses.values().stream().map(clazz -> clazz.type).collect(Collectors.toSet());
+ this.mode = new ApplyMode(lambdaGroupTypes, optimizationInfoFixer);
// Add synthesized lambda group classes to the builder.
for (Entry<LambdaGroup, DexProgramClass> entry : lambdaGroupsClasses.entrySet()) {
@@ -274,7 +341,7 @@
// Rewrite lambda class references into lambda group class
// references inside methods from the processing queue.
rewriteLambdaReferences(converter, executorService, feedback);
- this.strategyFactory = null;
+ this.mode = null;
}
private void analyzeReferencesInProgramClasses(
@@ -459,13 +526,15 @@
private ApplyStrategy(
DexEncodedMethod method,
IRCode code,
+ DexEncodedMethod context,
LambdaMergerOptimizationInfoFixer optimizationInfoFixer) {
super(
LambdaMerger.this.appView,
LambdaMerger.this::strategyProvider,
lambdaChecker,
method,
- code);
+ code,
+ context);
this.optimizationInfoFixer = optimizationInfoFixer;
}
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 8456912..26fbcee 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -38,11 +38,13 @@
import com.android.tools.r8.ir.regalloc.RegisterPositions.Type;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multiset;
import com.google.common.collect.Multisets;
+import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
@@ -280,7 +282,8 @@
boolean isEntryBlock = true;
for (BasicBlock block : blocks) {
InstructionListIterator instructionIterator = block.listIterator(code);
- Set<Value> liveLocalValues = new HashSet<>(liveAtEntrySets.get(block).liveLocalValues);
+ Set<Value> liveLocalValues =
+ SetUtils.newIdentityHashSet(liveAtEntrySets.get(block).liveLocalValues);
// Skip past arguments and open argument and phi locals.
if (isEntryBlock) {
isEntryBlock = false;
@@ -2516,9 +2519,9 @@
Map<BasicBlock, LiveAtEntrySets> liveAtEntrySets,
List<LiveIntervals> liveIntervals) {
for (BasicBlock block : code.topologicallySortedBlocks()) {
- Set<Value> live = new HashSet<>();
- Set<Value> phiOperands = new HashSet<>();
- Set<Value> liveAtThrowingInstruction = new HashSet<>();
+ Set<Value> live = Sets.newIdentityHashSet();
+ Set<Value> phiOperands = Sets.newIdentityHashSet();
+ Set<Value> liveAtThrowingInstruction = Sets.newIdentityHashSet();
Set<BasicBlock> exceptionalSuccessors = block.getCatchHandlers().getUniqueTargets();
for (BasicBlock successor : block.getSuccessors()) {
// Values live at entry to a block that is an exceptional successor are only live
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationFixer.java b/src/main/java/com/android/tools/r8/shaking/AnnotationFixer.java
new file mode 100644
index 0000000..356f609
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationFixer.java
@@ -0,0 +1,83 @@
+// 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.shaking;
+
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationElement;
+import com.android.tools.r8.graph.DexEncodedAnnotation;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.utils.ArrayUtils;
+
+public class AnnotationFixer {
+
+ private final GraphLense lense;
+
+ public AnnotationFixer(GraphLense lense) {
+ this.lense = lense;
+ }
+
+ public void run(Iterable<DexProgramClass> classes) {
+ for (DexProgramClass clazz : classes) {
+ clazz.annotations = clazz.annotations.rewrite(this::rewriteAnnotation);
+ clazz.forEachMethod(this::processMethod);
+ clazz.forEachField(this::processField);
+ }
+ }
+
+ private void processMethod(DexEncodedMethod method) {
+ method.annotations = method.annotations.rewrite(this::rewriteAnnotation);
+ method.parameterAnnotationsList =
+ method.parameterAnnotationsList.rewrite(
+ dexAnnotationSet -> dexAnnotationSet.rewrite(this::rewriteAnnotation));
+ }
+
+ private void processField(DexEncodedField field) {
+ field.annotations = field.annotations.rewrite(this::rewriteAnnotation);
+ }
+
+ private DexAnnotation rewriteAnnotation(DexAnnotation original) {
+ return original.rewrite(this::rewriteEncodedAnnotation);
+ }
+
+ private DexEncodedAnnotation rewriteEncodedAnnotation(DexEncodedAnnotation original) {
+ DexEncodedAnnotation rewritten =
+ original.rewrite(lense::lookupType, this::rewriteAnnotationElement);
+ assert rewritten != null;
+ return rewritten;
+ }
+
+ private DexAnnotationElement rewriteAnnotationElement(DexAnnotationElement original) {
+ DexValue rewrittenValue = rewriteValue(original.value);
+ if (rewrittenValue != original.value) {
+ return new DexAnnotationElement(original.name, rewrittenValue);
+ }
+ return original;
+ }
+
+ private DexValue rewriteValue(DexValue value) {
+ if (value.isDexValueType()) {
+ DexType originalType = value.asDexValueType().value;
+ DexType rewrittenType = lense.lookupType(originalType);
+ if (rewrittenType != originalType) {
+ return new DexValueType(rewrittenType);
+ }
+ } else if (value.isDexValueArray()) {
+ DexValue[] originalValues = value.asDexValueArray().getValues();
+ DexValue[] rewrittenValues =
+ ArrayUtils.map(DexValue[].class, originalValues, this::rewriteValue);
+ if (rewrittenValues != originalValues) {
+ return new DexValueArray(rewrittenValues);
+ }
+ }
+ return value;
+ }
+}
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 693058b..701904c 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -108,11 +108,6 @@
private boolean isAnnotationTypeLive(DexAnnotation annotation) {
DexType annotationType = annotation.annotation.type.toBaseType(appView.dexItemFactory());
- DexClass definition = appView.definitionFor(annotationType);
- // TODO(b/73102187): How to handle annotations without definition.
- if (appView.options().isShrinking() && definition == null) {
- return false;
- }
return appView.appInfo().isNonProgramTypeOrLiveProgramType(annotationType);
}
@@ -276,7 +271,8 @@
private DexAnnotationElement rewriteAnnotationElement(
DexType annotationType, DexAnnotationElement original) {
DexClass definition = appView.definitionFor(annotationType);
- // TODO(b/73102187): How to handle annotations without definition.
+ // We cannot strip annotations where we cannot look up the definition, because this will break
+ // apps that rely on the annotation to exist. See b/134766810 for more information.
if (definition == null) {
return original;
}
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 0689533..5eebf8b 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -182,6 +182,10 @@
/** Set of live types defined in the library and classpath. Used to avoid duplicate tracing. */
private final Set<DexClass> liveNonProgramTypes = Sets.newIdentityHashSet();
+ /** Mapping from each unused interface to the set of live types that implements the interface. */
+ private final Map<DexProgramClass, Set<DexProgramClass>> unusedInterfaceTypes =
+ new IdentityHashMap<>();
+
/**
* Set of proto extension types that are technically live, but which we have not traced because
* they are dead according to the generated extension registry shrinker.
@@ -226,7 +230,7 @@
* Set of methods that belong to live classes and can be reached by invokes. These need to be
* kept.
*/
- private final SetWithReason<DexEncodedMethod> liveMethods;
+ private final LiveMethodsSet liveMethods;
/**
* Set of fields that belong to live classes and can be reached by invokes. These need to be kept.
@@ -309,7 +313,7 @@
liveAnnotations = new SetWithReason<>(graphReporter::registerAnnotation);
instantiatedTypes = new SetWithReason<>(graphReporter::registerClass);
targetedMethods = new SetWithReason<>(graphReporter::registerMethod);
- liveMethods = new SetWithReason<>(graphReporter::registerMethod);
+ liveMethods = new LiveMethodsSet(graphReporter::registerMethod);
liveFields = new SetWithReason<>(graphReporter::registerField);
instantiatedInterfaceTypes = new SetWithReason<>(graphReporter::registerInterface);
}
@@ -483,7 +487,7 @@
private boolean enqueueMarkMethodLiveAction(
DexProgramClass clazz, DexEncodedMethod method, KeepReason reason) {
assert method.method.holder == clazz.type;
- if (liveMethods.add(method, reason)) {
+ if (liveMethods.add(clazz, method, reason)) {
workList.enqueueMarkMethodLiveAction(clazz, method, reason);
return true;
}
@@ -1128,7 +1132,7 @@
KeepReason reason = KeepReason.reachableFromLiveType(holder.type);
for (DexType iface : holder.interfaces.values) {
- markInterfaceTypeAsLiveViaInheritanceClause(iface, reason);
+ markInterfaceTypeAsLiveViaInheritanceClause(iface, holder);
}
if (holder.superType != null) {
@@ -1139,6 +1143,10 @@
markTypeAsLive(holder.superType, reason);
}
+ // If this is an interface that has just become live, then report previously seen but unreported
+ // implemented-by edges.
+ transitionUnusedInterfaceToLive(holder);
+
// We cannot remove virtual methods defined earlier in the type hierarchy if it is widening
// access and is defined in an interface:
//
@@ -1204,32 +1212,29 @@
}
}
- private void markInterfaceTypeAsLiveViaInheritanceClause(DexType type, KeepReason reason) {
- if (appView.options().enableUnusedInterfaceRemoval && !mode.isTracingMainDex()) {
- DexProgramClass clazz = getProgramClassOrNull(type);
- if (clazz == null) {
- return;
- }
+ private void markInterfaceTypeAsLiveViaInheritanceClause(
+ DexType type, DexProgramClass implementer) {
+ DexProgramClass clazz = getProgramClassOrNull(type);
+ if (clazz == null) {
+ return;
+ }
- assert clazz.isInterface();
-
- if (!clazz.interfaces.isEmpty()) {
- markTypeAsLive(type, reason);
- return;
- }
-
- for (DexEncodedMethod method : clazz.virtualMethods()) {
- if (!method.accessFlags.isAbstract()) {
- markTypeAsLive(type, reason);
- return;
- }
- }
-
- // No need to mark the type as live. If an interface type is only reachable via the
- // inheritance clause of another type, and the interface only has abstract methods, it can
- // simply be removed from the inheritance clause.
+ if (!appView.options().enableUnusedInterfaceRemoval || mode.isTracingMainDex()) {
+ markTypeAsLive(clazz, graphReporter.reportClassReferencedFrom(clazz, implementer));
} else {
- markTypeAsLive(type, reason);
+ if (liveTypes.contains(clazz)) {
+ // The interface is already live, so make sure to report this implements-edge.
+ graphReporter.reportClassReferencedFrom(clazz, implementer);
+ } else {
+ // No need to mark the type as live. If an interface type is only reachable via the
+ // inheritance clause of another type it can simply be removed from the inheritance clause.
+ // The interface is needed if it has a live default interface method or field, though.
+ // Therefore, we record that this implemented-by edge has not been reported, such that we
+ // can report it in the future if one its members becomes live.
+ unusedInterfaceTypes
+ .computeIfAbsent(clazz, ignore -> Sets.newIdentityHashSet())
+ .add(implementer);
+ }
}
}
@@ -1394,9 +1399,7 @@
// Already targeted.
return;
}
- markTypeAsLive(method.method.holder,
- holder -> graphReporter.reportClassReferencedFrom(holder, method));
- markParameterAndReturnTypesAsLive(method);
+ markReferencedTypesAsLive(method);
processAnnotations(method, method.annotations.annotations);
method.parameterAnnotationsList.forEachAnnotation(
annotation -> processAnnotation(method, annotation));
@@ -1652,6 +1655,19 @@
&& !instantiatedTypes.contains(current.asProgramClass()));
}
+ private void transitionUnusedInterfaceToLive(DexProgramClass clazz) {
+ if (clazz.isInterface()) {
+ Set<DexProgramClass> implementedBy = unusedInterfaceTypes.remove(clazz);
+ if (implementedBy != null) {
+ for (DexProgramClass implementer : implementedBy) {
+ markTypeAsLive(clazz, graphReporter.reportClassReferencedFrom(clazz, implementer));
+ }
+ }
+ } else {
+ assert !unusedInterfaceTypes.containsKey(clazz);
+ }
+ }
+
private void markFieldAsTargeted(DexField field, DexEncodedMethod context) {
markTypeAsLive(field.type, clazz -> graphReporter.reportClassReferencedFrom(clazz, context));
markTypeAsLive(field.holder, clazz -> graphReporter.reportClassReferencedFrom(clazz, context));
@@ -2083,7 +2099,7 @@
}
if (resolutionTarget.accessFlags.isPrivate() || resolutionTarget.accessFlags.isStatic()) {
- brokenSuperInvokes.add(method);
+ brokenSuperInvokes.add(resolutionTarget.method);
}
DexProgramClass resolutionTargetClass = getProgramClassOrNull(resolutionTarget.method.holder);
if (resolutionTargetClass != null) {
@@ -2103,7 +2119,7 @@
return;
}
if (target.accessFlags.isPrivate()) {
- brokenSuperInvokes.add(method);
+ brokenSuperInvokes.add(resolutionTarget.method);
}
if (Log.ENABLED) {
Log.verbose(getClass(), "Adding super constraint from `%s` to `%s`", from.method,
@@ -2474,11 +2490,9 @@
}
}
markParameterAndReturnTypesAsLive(method);
- if (appView.definitionFor(method.method.holder).isProgramClass()) {
- processAnnotations(method, method.annotations.annotations);
- method.parameterAnnotationsList.forEachAnnotation(
- annotation -> processAnnotation(method, annotation));
- }
+ processAnnotations(method, method.annotations.annotations);
+ method.parameterAnnotationsList.forEachAnnotation(
+ annotation -> processAnnotation(method, annotation));
method.registerCodeReferences(new UseRegistry(options.itemFactory, clazz, method));
// Add all dependent members to the workqueue.
@@ -2488,6 +2502,12 @@
analyses.forEach(analysis -> analysis.processNewlyLiveMethod(method));
}
+ private void markReferencedTypesAsLive(DexEncodedMethod method) {
+ markTypeAsLive(
+ method.method.holder, clazz -> graphReporter.reportClassReferencedFrom(clazz, method));
+ markParameterAndReturnTypesAsLive(method);
+ }
+
private void markParameterAndReturnTypesAsLive(DexEncodedMethod method) {
for (DexType parameterType : method.method.proto.parameters.values) {
markTypeAsLive(
@@ -2880,6 +2900,31 @@
}
}
+ private class LiveMethodsSet {
+
+ private final Set<DexEncodedMethod> items = Sets.newIdentityHashSet();
+
+ private final BiConsumer<DexEncodedMethod, KeepReason> register;
+
+ LiveMethodsSet(BiConsumer<DexEncodedMethod, KeepReason> register) {
+ this.register = register;
+ }
+
+ boolean add(DexProgramClass clazz, DexEncodedMethod method, KeepReason reason) {
+ register.accept(method, reason);
+ transitionUnusedInterfaceToLive(clazz);
+ return items.add(method);
+ }
+
+ boolean contains(DexEncodedMethod method) {
+ return items.contains(method);
+ }
+
+ Set<DexEncodedMethod> getItems() {
+ return Collections.unmodifiableSet(items);
+ }
+ }
+
private static class SetWithReason<T> {
private final Set<T> items = Sets.newIdentityHashSet();
diff --git a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
index 5dc405e..521b90c 100644
--- a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
+++ b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
@@ -199,6 +199,16 @@
}
public KeepReasonWitness reportClassReferencedFrom(
+ DexProgramClass clazz, DexProgramClass implementer) {
+ if (keptGraphConsumer != null) {
+ ClassGraphNode source = getClassGraphNode(implementer.type);
+ ClassGraphNode target = getClassGraphNode(clazz.type);
+ return reportEdge(source, target, EdgeKind.ReferencedFrom);
+ }
+ return KeepReasonWitness.INSTANCE;
+ }
+
+ public KeepReasonWitness reportClassReferencedFrom(
DexProgramClass clazz, DexEncodedMethod method) {
if (keptGraphConsumer != null) {
MethodGraphNode source = getMethodGraphNode(method.method);
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceStrings.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceStrings.java
index 5ab6474..cc9fafa 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceStrings.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceStrings.java
@@ -32,7 +32,7 @@
}
@VisibleForTesting
- static ProguardConfigurationSourceStrings createConfigurationForTesting(
+ public static ProguardConfigurationSourceStrings createConfigurationForTesting(
List<String> config) {
return new ProguardConfigurationSourceStrings(config);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
index 487afa2..9326e31 100644
--- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
@@ -157,6 +157,8 @@
private final HashMultiset<Wrapper<DexField>> fieldBuckets = HashMultiset.create();
private final HashMultiset<Wrapper<DexMethod>> methodBuckets = HashMultiset.create();
+ private boolean hasSynchronizedMethods = false;
+
public Representative(DexProgramClass clazz) {
this.clazz = clazz;
include(clazz);
@@ -168,10 +170,14 @@
Wrapper<DexField> wrapper = fieldEquivalence.wrap(field.field);
fieldBuckets.add(wrapper);
}
+ boolean classHasSynchronizedMethods = false;
for (DexEncodedMethod method : clazz.methods()) {
+ assert !hasSynchronizedMethods || !method.accessFlags.isSynchronized();
+ classHasSynchronizedMethods |= method.accessFlags.isSynchronized();
Wrapper<DexMethod> wrapper = methodEquivalence.wrap(method.method);
methodBuckets.add(wrapper);
}
+ hasSynchronizedMethods |= classHasSynchronizedMethods;
}
// Returns true if this representative should no longer be used. The current heuristic is to
@@ -322,83 +328,42 @@
assert satisfiesMergeCriteria(clazz) == group;
assert group != MergeGroup.DONT_MERGE;
- String pkg = clazz.type.getPackageDescriptor();
- return mayMergeAcrossPackageBoundaries(clazz)
- ? mergeGlobally(clazz, pkg, group)
- : mergeInsidePackage(clazz, pkg, group);
+ return merge(
+ clazz,
+ mayMergeAcrossPackageBoundaries(clazz)
+ ? group.globalKey()
+ : group.key(clazz.type.getPackageDescriptor()));
}
- private boolean mergeGlobally(DexProgramClass clazz, String pkg, MergeGroup group) {
- Representative globalRepresentative = representatives.get(group.globalKey());
- if (globalRepresentative == null) {
- if (isValidRepresentative(clazz)) {
- // Make the current class the global representative.
- setRepresentative(group.globalKey(), getOrCreateRepresentative(group.key(pkg), clazz));
- } else {
- clearRepresentative(group.globalKey());
- }
-
- // Do not attempt to merge this class inside its own package, because that could lead to
- // an increase in the global representative, which is not desirable.
- return false;
- } else {
- // Check if we can merge the current class into the current global representative.
- globalRepresentative.include(clazz);
-
- if (globalRepresentative.isFull()) {
- if (isValidRepresentative(clazz)) {
- // Make the current class the global representative instead.
- setRepresentative(group.globalKey(), getOrCreateRepresentative(group.key(pkg), clazz));
- } else {
- clearRepresentative(group.globalKey());
- }
-
- // Do not attempt to merge this class inside its own package, because that could lead to
- // an increase in the global representative, which is not desirable.
+ private boolean merge(DexProgramClass clazz, MergeGroup.Key key) {
+ Representative representative = representatives.get(key);
+ if (representative != null) {
+ if (representative.hasSynchronizedMethods && clazz.hasStaticSynchronizedMethods()) {
+ // We are not allowed to merge synchronized classes with synchronized methods.
return false;
- } else {
- // Merge this class into the global representative.
- moveMembersFromSourceToTarget(clazz, globalRepresentative.clazz);
- return true;
}
- }
- }
-
- private boolean mergeInsidePackage(DexProgramClass clazz, String pkg, MergeGroup group) {
- MergeGroup.Key key = group.key(pkg);
- Representative packageRepresentative = representatives.get(key);
- if (packageRepresentative != null) {
+ // Check if current candidate is a better choice depending on visibility. For package private
+ // or protected, the key is parameterized by the package name already, so we just have to
+ // check accessibility-flags. For global this is no-op.
if (isValidRepresentative(clazz)
- && clazz.accessFlags.isMoreVisibleThan(
- packageRepresentative.clazz.accessFlags,
- clazz.type.getPackageName(),
- packageRepresentative.clazz.type.getPackageName())) {
- // Use `clazz` as a representative for this package instead.
+ && !representative.clazz.accessFlags.isAtLeastAsVisibleAs(clazz.accessFlags)) {
+ assert clazz.type.getPackageDescriptor().equals(key.packageOrGlobal);
+ assert representative.clazz.type.getPackageDescriptor().equals(key.packageOrGlobal);
Representative newRepresentative = getOrCreateRepresentative(key, clazz);
- newRepresentative.include(packageRepresentative.clazz);
-
+ newRepresentative.include(representative.clazz);
if (!newRepresentative.isFull()) {
- setRepresentative(group.key(pkg), newRepresentative);
- moveMembersFromSourceToTarget(packageRepresentative.clazz, clazz);
+ setRepresentative(key, newRepresentative);
+ moveMembersFromSourceToTarget(representative.clazz, clazz);
return true;
}
-
- // We are not allowed to merge members into a class that is less visible.
- return false;
- }
-
- // Merge current class into the representative of this package if it has room.
- packageRepresentative.include(clazz);
-
- // If there is room, then merge, otherwise fall-through to update the representative of this
- // package.
- if (!packageRepresentative.isFull()) {
- moveMembersFromSourceToTarget(clazz, packageRepresentative.clazz);
- return true;
+ } else {
+ representative.include(clazz);
+ if (!representative.isFull()) {
+ moveMembersFromSourceToTarget(clazz, representative.clazz);
+ return true;
+ }
}
}
-
- // We were unable to use the current representative for this package (if any).
if (isValidRepresentative(clazz)) {
setRepresentative(key, getOrCreateRepresentative(key, clazz));
}
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index cd23ff7..cccf0f4 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -25,6 +25,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
@@ -108,32 +109,41 @@
}
private void pruneUnusedInterfaces(DexProgramClass clazz) {
- int numberOfReachableInterfaces = 0;
+ boolean implementsUnusedInterfaces = false;
for (DexType type : clazz.interfaces.values) {
// TODO(christofferqa): Extend unused interface removal to library classes.
- if (isTypeLive(type)) {
- numberOfReachableInterfaces++;
+ if (!isTypeLive(type)) {
+ implementsUnusedInterfaces = true;
+ break;
}
}
- if (numberOfReachableInterfaces == clazz.interfaces.size()) {
+ if (!implementsUnusedInterfaces) {
return;
}
- if (numberOfReachableInterfaces == 0) {
- clazz.interfaces = DexTypeList.empty();
- return;
- }
-
- DexType[] reachableInterfaces = new DexType[numberOfReachableInterfaces];
- int i = 0;
+ Set<DexType> reachableInterfaces = new LinkedHashSet<>();
for (DexType type : clazz.interfaces.values) {
- if (isTypeLive(type)) {
- reachableInterfaces[i] = type;
- i++;
+ retainReachableInterfacesFrom(type, reachableInterfaces);
+ }
+ if (reachableInterfaces.isEmpty()) {
+ clazz.interfaces = DexTypeList.empty();
+ } else {
+ clazz.interfaces = new DexTypeList(reachableInterfaces.toArray(DexType.EMPTY_ARRAY));
+ }
+ }
+
+ private void retainReachableInterfacesFrom(DexType type, Set<DexType> reachableInterfaces) {
+ if (isTypeLive(type)) {
+ reachableInterfaces.add(type);
+ } else {
+ DexProgramClass unusedInterface = appView.definitionForProgramType(type);
+ assert unusedInterface != null;
+ assert unusedInterface.isInterface();
+ for (DexType interfaceType : unusedInterface.interfaces.values) {
+ retainReachableInterfacesFrom(interfaceType, reachableInterfaces);
}
}
- clazz.interfaces = new DexTypeList(reachableInterfaces);
}
private void pruneMembersAndAttributes(DexProgramClass clazz) {
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index b8b8482..4dc15ab 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -138,6 +138,7 @@
RESOLUTION_FOR_METHODS_MAY_CHANGE,
SERVICE_LOADER,
STATIC_INITIALIZERS,
+ STATIC_SYNCHRONIZED_METHODS,
UNHANDLED_INVOKE_DIRECT,
UNHANDLED_INVOKE_SUPER,
UNSAFE_INLINING,
@@ -198,6 +199,9 @@
case UNSUPPORTED_ATTRIBUTES:
message = "since inner-class attributes are not supported";
break;
+ case STATIC_SYNCHRONIZED_METHODS:
+ message = "since it has static synchronized methods which can lead to unwanted behavior";
+ break;
default:
assert false;
}
@@ -806,6 +810,14 @@
assert isStillMergeCandidate(clazz);
}
+ // Check for static synchronized methods on source
+ if (clazz.hasStaticSynchronizedMethods()) {
+ if (Log.ENABLED) {
+ AbortReason.STATIC_SYNCHRONIZED_METHODS.printLogMessageForClass(clazz);
+ }
+ return;
+ }
+
// Guard against the case where we have two methods that may get the same signature
// if we replace types. This is rare, so we approximate and err on the safe side here.
if (new CollisionDetector(clazz.type, targetClass.type).mayCollide()) {
@@ -1460,7 +1472,9 @@
for (SynthesizedBridgeCode synthesizedBridge : synthesizedBridges) {
synthesizedBridge.updateMethodSignatures(this::fixupMethod);
}
- return lensBuilder.build(appView, mergedClasses);
+ GraphLense graphLense = lensBuilder.build(appView, mergedClasses);
+ new AnnotationFixer(graphLense).run(appView.appInfo().classes());
+ return graphLense;
}
private void fixupMethods(List<DexEncodedMethod> methods, MethodSetter setter) {
diff --git a/src/main/java/com/android/tools/r8/utils/AarArchiveResourceProvider.java b/src/main/java/com/android/tools/r8/utils/AarArchiveResourceProvider.java
index 7de79dc..6b36180 100644
--- a/src/main/java/com/android/tools/r8/utils/AarArchiveResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/AarArchiveResourceProvider.java
@@ -66,7 +66,7 @@
private List<ProgramResource> readArchive() throws IOException {
List<ProgramResource> classResources = null;
- try (ZipFile zipFile = new ZipFile(archive.toFile(), StandardCharsets.UTF_8)) {
+ try (ZipFile zipFile = FileUtils.createZipFile(archive.toFile(), StandardCharsets.UTF_8)) {
final Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java b/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
index e612e60..5a50f1b 100644
--- a/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
@@ -53,7 +53,8 @@
private List<ProgramResource> readArchive() throws IOException {
List<ProgramResource> dexResources = new ArrayList<>();
List<ProgramResource> classResources = new ArrayList<>();
- try (ZipFile zipFile = new ZipFile(archive.getPath().toFile(), StandardCharsets.UTF_8)) {
+ try (ZipFile zipFile =
+ FileUtils.createZipFile(archive.getPath().toFile(), StandardCharsets.UTF_8)) {
final Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
@@ -109,7 +110,8 @@
@Override
public void accept(Visitor resourceBrowser) throws ResourceException {
- try (ZipFile zipFile = new ZipFile(archive.getPath().toFile(), StandardCharsets.UTF_8)) {
+ try (ZipFile zipFile =
+ FileUtils.createZipFile(archive.getPath().toFile(), StandardCharsets.UTF_8)) {
final Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
diff --git a/src/main/java/com/android/tools/r8/utils/ClassMap.java b/src/main/java/com/android/tools/r8/utils/ClassMap.java
index 416b389..474f5d0 100644
--- a/src/main/java/com/android/tools/r8/utils/ClassMap.java
+++ b/src/main/java/com/android/tools/r8/utils/ClassMap.java
@@ -42,7 +42,7 @@
* We also allow the transition from Supplier of a null value to the actual value null and vice
* versa.
*/
- private final ConcurrentHashMap<DexType, Supplier<T>> classes;
+ private final Map<DexType, Supplier<T>> classes;
/**
* Class provider if available.
@@ -55,7 +55,7 @@
*/
private final AtomicReference<ClassProvider<T>> classProvider = new AtomicReference<>();
- ClassMap(ConcurrentHashMap<DexType, Supplier<T>> classes, ClassProvider<T> classProvider) {
+ ClassMap(Map<DexType, Supplier<T>> classes, ClassProvider<T> classProvider) {
assert classProvider == null || classProvider.getClassKind() == getClassKind();
this.classes = classes == null ? new ConcurrentHashMap<>() : classes;
this.classProvider.set(classProvider);
diff --git a/src/main/java/com/android/tools/r8/utils/FileUtils.java b/src/main/java/com/android/tools/r8/utils/FileUtils.java
index 87b8506..7185445 100644
--- a/src/main/java/com/android/tools/r8/utils/FileUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/FileUtils.java
@@ -9,12 +9,14 @@
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.List;
+import java.util.zip.ZipFile;
public class FileUtils {
@@ -28,6 +30,9 @@
public static final String JAVA_EXTENSION = ".java";
public static final String MODULE_INFO_CLASS = "module-info.class";
+ public static final boolean isAndroid =
+ System.getProperty("java.vm.name").equalsIgnoreCase("Dalvik");
+
public static boolean isDexFile(Path path) {
String name = path.getFileName().toString().toLowerCase();
return name.endsWith(DEX_EXTENSION);
@@ -94,7 +99,7 @@
public static Path validateOutputFile(Path path, Reporter reporter) {
if (path != null) {
boolean isJarOrZip = isZipFile(path) || isJarFile(path);
- if (!isJarOrZip && !(Files.exists(path) && Files.isDirectory(path))) {
+ if (!isJarOrZip && !(Files.exists(path) && Files.isDirectory(path))) {
reporter.error(new StringDiagnostic(
"Invalid output: "
+ path
@@ -184,4 +189,21 @@
return path.replace('/', '\\');
}
}
+
+ public static ZipFile createZipFile(File file, Charset charset) throws IOException {
+ if (!isAndroid) {
+ return new ZipFile(file, charset);
+ }
+ // On Android pre-26 we cannot use the constructor ZipFile(file, charset).
+ // By default Android use UTF_8 as the charset, so we can use the default constructor.
+ if (Charset.defaultCharset() == StandardCharsets.UTF_8) {
+ return new ZipFile(file);
+ }
+ // If the Android runtime is started with a different default charset, the default constructor
+ // won't work. It is possible to support this case if we read/write the ZipFile not with it's
+ // own Input/OutputStream, but an external one which one can define on a different Charset than
+ // default. We do not support this at the moment since R8 on dex is used only in tests, and
+ // UTF_8 is the default charset used in tests.
+ throw new RuntimeException("R8 can run on dex only with UTF_8 as the default charset.");
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalArchiveClassFileProvider.java b/src/main/java/com/android/tools/r8/utils/InternalArchiveClassFileProvider.java
index cf30d7a..e5557d9 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalArchiveClassFileProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalArchiveClassFileProvider.java
@@ -100,7 +100,7 @@
private ZipFile getOpenZipFile() throws IOException {
if (openedZipFile == null) {
try {
- openedZipFile = new ZipFile(path.toFile(), StandardCharsets.UTF_8);
+ openedZipFile = FileUtils.createZipFile(path.toFile(), StandardCharsets.UTF_8);
} catch (IOException e) {
if (!Files.exists(path)) {
throw new NoSuchFileException(path.toString());
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 89aeb2d..687e7da 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -198,6 +198,9 @@
public boolean enableNonNullTracking = true;
public boolean enableInlining =
!Version.isDev() || System.getProperty("com.android.tools.r8.disableinlining") == null;
+ // TODO(b/141451716): Evaluate the effect of allowing inlining in the inlinee.
+ public boolean applyInliningToInlinee = false;
+ public int applyInliningToInlineeMaxDepth = 0;
public boolean enableInliningOfInvokesWithNullableReceivers = true;
public boolean disableInliningOfLibraryMethodOverrides = true;
public boolean enableClassInlining = true;
@@ -970,6 +973,8 @@
public boolean dontReportFailingCheckDiscarded = false;
public boolean deterministicSortingBasedOnDexType = true;
public PrintStream whyAreYouNotInliningConsumer = System.out;
+ public boolean trackDesugaredAPIConversions =
+ System.getProperty("com.android.tools.r8.trackDesugaredAPIConversions") != null;
// Flag to turn on/off JDK11+ nest-access control even when not required (Cf backend)
public boolean enableForceNestBasedAccessDesugaringForTest = false;
diff --git a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
index 8e3fe1a..7ffce35 100644
--- a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.utils;
import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -65,6 +66,16 @@
return null;
}
+ public static <T> T previousUntil(ListIterator<T> iterator, Predicate<T> predicate) {
+ while (iterator.hasPrevious()) {
+ T previous = iterator.previous();
+ if (predicate.test(previous)) {
+ return previous;
+ }
+ }
+ throw new Unreachable();
+ }
+
public static <T> void removeIf(Iterator<T> iterator, Predicate<T> predicate) {
while (iterator.hasNext()) {
T item = iterator.next();
diff --git a/src/test/examples/classmerging/ConflictingInterfaceSignaturesTest.java b/src/test/examples/classmerging/ConflictingInterfaceSignaturesTest.java
index 223ff37..7de7a1f 100644
--- a/src/test/examples/classmerging/ConflictingInterfaceSignaturesTest.java
+++ b/src/test/examples/classmerging/ConflictingInterfaceSignaturesTest.java
@@ -12,6 +12,17 @@
B b = new InterfaceImpl();
b.foo();
+
+ // Ensure that the instantiations are not dead code eliminated.
+ escape(a);
+ escape(b);
+ }
+
+ @NeverInline
+ static void escape(Object o) {
+ if (System.currentTimeMillis() < 0) {
+ System.out.println(o);
+ }
}
public interface A {
diff --git a/src/test/examples/classmerging/MethodCollisionTest.java b/src/test/examples/classmerging/MethodCollisionTest.java
index a9010a4..4456498 100644
--- a/src/test/examples/classmerging/MethodCollisionTest.java
+++ b/src/test/examples/classmerging/MethodCollisionTest.java
@@ -7,8 +7,22 @@
public class MethodCollisionTest {
public static void main(String[] args) {
- new B().m();
- new D().m();
+ B b = new B();
+ b.m();
+
+ D d = new D();
+ d.m();
+
+ // Ensure that the instantiations are not dead code eliminated.
+ escape(b);
+ escape(d);
+ }
+
+ @NeverInline
+ static void escape(Object o) {
+ if (System.currentTimeMillis() < 0) {
+ System.out.println(o);
+ }
}
public static class A {
diff --git a/src/test/examples/classmerging/NeverInline.java b/src/test/examples/classmerging/NeverInline.java
new file mode 100644
index 0000000..9438902
--- /dev/null
+++ b/src/test/examples/classmerging/NeverInline.java
@@ -0,0 +1,10 @@
+// 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 classmerging;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD})
+public @interface NeverInline {}
diff --git a/src/test/examples/classmerging/SimpleInterfaceAccessTest.java b/src/test/examples/classmerging/SimpleInterfaceAccessTest.java
index ce7bf14..6fde90e 100644
--- a/src/test/examples/classmerging/SimpleInterfaceAccessTest.java
+++ b/src/test/examples/classmerging/SimpleInterfaceAccessTest.java
@@ -18,6 +18,17 @@
// package references OtherSimpleInterface.
OtherSimpleInterface y = new OtherSimpleInterfaceImpl();
y.bar();
+
+ // Ensure that the instantiations are not dead code eliminated.
+ escape(x);
+ escape(y);
+ }
+
+ @NeverInline
+ static void escape(Object o) {
+ if (System.currentTimeMillis() < 0) {
+ System.out.println(o);
+ }
}
// Should only be merged into OtherSimpleInterfaceImpl if access modifications are allowed.
diff --git a/src/test/examples/classmerging/SuperCallRewritingTest.java b/src/test/examples/classmerging/SuperCallRewritingTest.java
index 7a3d45c..6f59419 100644
--- a/src/test/examples/classmerging/SuperCallRewritingTest.java
+++ b/src/test/examples/classmerging/SuperCallRewritingTest.java
@@ -7,6 +7,17 @@
public class SuperCallRewritingTest {
public static void main(String[] args) {
System.out.println("Calling referencedMethod on SubClassThatReferencesSuperMethod");
- System.out.println(new SubClassThatReferencesSuperMethod().referencedMethod());
+ SubClassThatReferencesSuperMethod obj = new SubClassThatReferencesSuperMethod();
+ System.out.println(obj.referencedMethod());
+
+ // Ensure that the instantiations are not dead code eliminated.
+ escape(obj);
+ }
+
+ @NeverInline
+ static void escape(Object o) {
+ if (System.currentTimeMillis() < 0) {
+ System.out.println(o);
+ }
}
}
diff --git a/src/test/examples/classmerging/SyntheticBridgeSignaturesTest.java b/src/test/examples/classmerging/SyntheticBridgeSignaturesTest.java
index a17d30a..c263e98 100644
--- a/src/test/examples/classmerging/SyntheticBridgeSignaturesTest.java
+++ b/src/test/examples/classmerging/SyntheticBridgeSignaturesTest.java
@@ -15,6 +15,17 @@
BSub b = new BSub();
a.m(b);
b.m(a);
+
+ // Ensure that the instantiations are not dead code eliminated.
+ escape(a);
+ escape(b);
+ }
+
+ @NeverInline
+ static void escape(Object o) {
+ if (System.currentTimeMillis() < 0) {
+ System.out.println(o);
+ }
}
private static class A {
diff --git a/src/test/examples/classmerging/TemplateMethodTest.java b/src/test/examples/classmerging/TemplateMethodTest.java
index ac9de15..fe7de76 100644
--- a/src/test/examples/classmerging/TemplateMethodTest.java
+++ b/src/test/examples/classmerging/TemplateMethodTest.java
@@ -9,6 +9,16 @@
public static void main(String[] args) {
AbstractClass obj = new AbstractClassImpl();
obj.foo();
+
+ // Ensure that the instantiations are not dead code eliminated.
+ escape(obj);
+ }
+
+ @NeverInline
+ static void escape(Object o) {
+ if (System.currentTimeMillis() < 0) {
+ System.out.println(o);
+ }
}
private abstract static class AbstractClass {
diff --git a/src/test/examples/classmerging/Test.java b/src/test/examples/classmerging/Test.java
index 6d5c51f..d3b2762 100644
--- a/src/test/examples/classmerging/Test.java
+++ b/src/test/examples/classmerging/Test.java
@@ -13,8 +13,17 @@
ConflictingInterfaceImpl impl = new ConflictingInterfaceImpl();
callMethodOnIface(impl);
System.out.println(new SubClassThatReferencesSuperMethod().referencedMethod());
- System.out.println(new Outer().getInstance().method());
+ Outer outer = new Outer();
+ Outer.SubClass inner = outer.getInstance();
+ System.out.println(outer.getInstance().method());
System.out.println(new SubClass(42));
+
+ // Ensure that the instantiations are not dead code eliminated.
+ escape(clazz);
+ escape(iface);
+ escape(impl);
+ escape(inner);
+ escape(outer);
}
private static void callMethodOnIface(GenericInterface iface) {
@@ -31,4 +40,11 @@
System.out.println(ClassWithConflictingMethod.conflict(null));
System.out.println(OtherClassWithConflictingMethod.conflict(null));
}
+
+ @NeverInline
+ static void escape(Object o) {
+ if (System.currentTimeMillis() < 0) {
+ System.out.println(o);
+ }
+ }
}
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index c75058d..11a66c6 100644
--- a/src/test/examples/classmerging/keep-rules.txt
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -68,4 +68,8 @@
public static void main(...);
}
+-neverinline class * {
+ @classmerging.NeverInline <methods>;
+}
+
-printmapping
diff --git a/src/test/examples/hello/keep-rules.txt b/src/test/examples/hello/keep-rules.txt
new file mode 100644
index 0000000..5a49e41
--- /dev/null
+++ b/src/test/examples/hello/keep-rules.txt
@@ -0,0 +1 @@
+-keep class hello.Hello { public static void main(...);}
diff --git a/src/test/examplesAndroidO/classmerging/MergeDefaultMethodIntoClassTest.java b/src/test/examplesAndroidO/classmerging/MergeDefaultMethodIntoClassTest.java
index 10f995d..5158228 100644
--- a/src/test/examplesAndroidO/classmerging/MergeDefaultMethodIntoClassTest.java
+++ b/src/test/examplesAndroidO/classmerging/MergeDefaultMethodIntoClassTest.java
@@ -11,6 +11,16 @@
// invoke-interface instruction and not invoke-virtual instruction.
A obj = new B();
obj.f();
+
+ // Ensure that the instantiations are not dead code eliminated.
+ escape(obj);
+ }
+
+ @NeverInline
+ static void escape(Object o) {
+ if (System.currentTimeMillis() < 0) {
+ System.out.println(o);
+ }
}
public interface A {
diff --git a/src/test/examplesAndroidO/classmerging/NestedDefaultInterfaceMethodsTest.java b/src/test/examplesAndroidO/classmerging/NestedDefaultInterfaceMethodsTest.java
index 7a0e325..f237c2e 100644
--- a/src/test/examplesAndroidO/classmerging/NestedDefaultInterfaceMethodsTest.java
+++ b/src/test/examplesAndroidO/classmerging/NestedDefaultInterfaceMethodsTest.java
@@ -7,7 +7,18 @@
public class NestedDefaultInterfaceMethodsTest {
public static void main(String[] args) {
- new C().m();
+ C obj = new C();
+ obj.m();
+
+ // Ensure that the instantiations are not dead code eliminated.
+ escape(obj);
+ }
+
+ @NeverInline
+ static void escape(Object o) {
+ if (System.currentTimeMillis() < 0) {
+ System.out.println(o);
+ }
}
public interface A {
diff --git a/src/test/examplesAndroidO/classmerging/NeverInline.java b/src/test/examplesAndroidO/classmerging/NeverInline.java
new file mode 100644
index 0000000..9438902
--- /dev/null
+++ b/src/test/examplesAndroidO/classmerging/NeverInline.java
@@ -0,0 +1,10 @@
+// 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 classmerging;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD})
+public @interface NeverInline {}
diff --git a/src/test/examplesAndroidO/classmerging/keep-rules.txt b/src/test/examplesAndroidO/classmerging/keep-rules.txt
index 4df182e..18a133d 100644
--- a/src/test/examplesAndroidO/classmerging/keep-rules.txt
+++ b/src/test/examplesAndroidO/classmerging/keep-rules.txt
@@ -14,5 +14,9 @@
public static void main(...);
}
+-neverinline class * {
+ @classmerging.NeverInline <methods>;
+}
+
# TODO(herhut): Consider supporting merging of inner-class attributes.
# -keepattributes *
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/AlwaysInline.java b/src/test/java/com/android/tools/r8/AlwaysInline.java
new file mode 100644
index 0000000..97a799a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/AlwaysInline.java
@@ -0,0 +1,10 @@
+// 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;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD})
+public @interface AlwaysInline {}
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index 6695f46..e53fdce 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -17,6 +17,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
@@ -193,6 +194,28 @@
}
@Override
+ public ProguardTestBuilder addLibraryClasses(Class<?>... classes) {
+ addLibraryClasses(Arrays.asList(classes));
+ return self();
+ }
+
+ @Override
+ public ProguardTestBuilder addLibraryClasses(Collection<Class<?>> classes) {
+ List<Path> pathsForClasses = new ArrayList<>(classes.size());
+ for (Class<?> clazz : classes) {
+ pathsForClasses.add(ToolHelper.getClassFileForTestClass(clazz));
+ }
+ try {
+ Path out = getState().getNewTempFolder().resolve("out.jar");
+ TestBase.writeClassFilesToJar(out, pathsForClasses);
+ libraryjars.add(out);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return self();
+ }
+
+ @Override
public ProguardTestBuilder addClasspathClasses(Collection<Class<?>> classes) {
throw new Unimplemented("No support for adding classpath data directly");
}
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index b834aac..8a7d989 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -196,6 +196,19 @@
options -> options.testing.allowUnusedProguardConfigurationRules = true);
}
+ public T enableAlwaysInliningAnnotations() {
+ return enableAlwaysInliningAnnotations(AlwaysInline.class.getPackage().getName());
+ }
+
+ public T enableAlwaysInliningAnnotations(String annotationPackageName) {
+ if (!enableInliningAnnotations) {
+ enableInliningAnnotations = true;
+ addInternalKeepRules(
+ "-alwaysinline class * { @" + annotationPackageName + ".AlwaysInline *; }");
+ }
+ return self();
+ }
+
public T enableInliningAnnotations() {
return enableInliningAnnotations(NeverInline.class.getPackage().getName());
}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 9b8abe6..797d185 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -147,6 +147,17 @@
return testForJvm(temp);
}
+ public TestBuilder<?, ?> testForRuntime(TestRuntime runtime, AndroidApiLevel apiLevel) {
+ if (runtime.isCf()) {
+ return testForJvm();
+ } else {
+ assert runtime.isDex();
+ D8TestBuilder d8TestBuilder = testForD8();
+ d8TestBuilder.setMinApi(apiLevel);
+ return d8TestBuilder;
+ }
+ }
+
public ProguardTestBuilder testForProguard() {
return testForProguard(temp);
}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 0464ccb..84e8cc9 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -137,7 +137,7 @@
private static final String PROGUARD6_0_1 = "third_party/proguard/proguard6.0.1/bin/proguard";
private static final String PROGUARD = PROGUARD5_2_1;
public static final String JACOCO_AGENT = "third_party/jacoco/org.jacoco.agent-0.8.2-runtime.jar";
- public static final String PROGUARD_SETTINGS_FOR_INTERNAL_APPS = "third_party/proguardsettings";
+ public static final String PROGUARD_SETTINGS_FOR_INTERNAL_APPS = "third_party/proguardsettings/";
private static final String RETRACE6_0_1 = "third_party/proguard/proguard6.0.1/bin/retrace";
private static final String RETRACE = RETRACE6_0_1;
@@ -1147,6 +1147,10 @@
return compatSink.build();
}
+ public static void runL8(L8Command command) throws CompilationFailedException {
+ runL8(command, options -> {});
+ }
+
public static void runL8(L8Command command, Consumer<InternalOptions> optionsModifier)
throws CompilationFailedException {
InternalOptions internalOptions = command.getInternalOptions();
diff --git a/src/test/java/com/android/tools/r8/classmerging/HorizontalClassMergerShouldMergeSynchronizedMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/HorizontalClassMergerShouldMergeSynchronizedMethodTest.java
new file mode 100644
index 0000000..7007821
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/HorizontalClassMergerShouldMergeSynchronizedMethodTest.java
@@ -0,0 +1,82 @@
+// 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.classmerging;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+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;
+
+@RunWith(Parameterized.class)
+public class HorizontalClassMergerShouldMergeSynchronizedMethodTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public HorizontalClassMergerShouldMergeSynchronizedMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testNoMergingOfClassUsedInMonitor()
+ throws IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(HorizontalClassMergerShouldMergeSynchronizedMethodTest.class)
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("1", "2", "3")
+ .inspect(
+ inspector -> {
+ assertFalse(
+ inspector.clazz(LockOne.class).isPresent()
+ && inspector.clazz(LockTwo.class).isPresent());
+ assertTrue(
+ inspector.clazz(LockOne.class).isPresent()
+ || inspector.clazz(LockTwo.class).isPresent());
+ });
+ }
+
+ // Will be merged with LockTwo.
+ static class LockOne {
+
+ static synchronized void print1() {
+ System.out.println("1");
+ }
+
+ static synchronized void print2() {
+ System.out.println("2");
+ }
+ }
+
+ public static class LockTwo {
+
+ static void print3() {
+ System.out.println("3");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ LockOne.print1();
+ LockOne.print2();
+ LockTwo.print3();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/HorizontalClassMergerSynchronizedMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/HorizontalClassMergerSynchronizedMethodTest.java
new file mode 100644
index 0000000..a811257
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/HorizontalClassMergerSynchronizedMethodTest.java
@@ -0,0 +1,150 @@
+// 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.classmerging;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import java.io.IOException;
+import java.lang.Thread.State;
+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;
+
+@RunWith(Parameterized.class)
+public class HorizontalClassMergerSynchronizedMethodTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ // The 4.0.4 runtime will flakily mark threads as blocking and report DEADLOCKED.
+ return getTestParameters()
+ .withDexRuntimesStartingFromExcluding(Version.V4_0_4)
+ .withAllApiLevels()
+ .build();
+ }
+
+ public HorizontalClassMergerSynchronizedMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testOnRuntime() throws IOException, CompilationFailedException, ExecutionException {
+ testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+ .addInnerClasses(HorizontalClassMergerSynchronizedMethodTest.class)
+ .run(parameters.getRuntime(), HorizontalClassMergerSynchronizedMethodTest.Main.class)
+ .assertSuccessWithOutput("Hello World!");
+ }
+
+ @Test
+ public void testNoMergingOfClassUsedInMonitor()
+ throws IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(HorizontalClassMergerSynchronizedMethodTest.class)
+ .addKeepMainRule(Main.class)
+ .enableMergeAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput("Hello World!")
+ .inspect(inspector -> assertThat(inspector.clazz(LockOne.class), isPresent()));
+ }
+
+ private interface I {
+
+ void action();
+ }
+
+ // Will be merged with LockTwo.
+ static class LockOne {
+
+ static synchronized void acquire(I c) {
+ c.action();
+ }
+ }
+
+ public static class LockTwo {
+
+ static synchronized void acquire(I c) {
+ Main.inTwoCritical = true;
+ while (!Main.inThreeCritical) {}
+ c.action();
+ }
+ }
+
+ @NeverMerge
+ public static class LockThree {
+
+ static synchronized void acquire(I c) {
+ Main.inThreeCritical = true;
+ while (!Main.inTwoCritical) {}
+ c.action();
+ }
+ }
+
+ public static class AcquireOne implements I {
+
+ @Override
+ public void action() {
+ LockOne.acquire(() -> System.out.print("Hello "));
+ }
+ }
+
+ public static class AcquireThree implements I {
+
+ @Override
+ public void action() {
+ LockThree.acquire(() -> System.out.print("World!"));
+ }
+ }
+
+ public static class Main {
+
+ static volatile boolean inTwoCritical = false;
+ static volatile boolean inThreeCritical = false;
+ static volatile boolean arnoldWillNotBeBack = false;
+
+ private static volatile Thread t1 = new Thread(Main::lockThreeThenOne);
+ private static volatile Thread t2 = new Thread(Main::lockTwoThenThree);
+ private static volatile Thread terminator = new Thread(Main::arnold);
+
+ public static void main(String[] args) {
+ t1.start();
+ t2.start();
+ // This thread is started to ensure termination in case we are rewriting incorrectly.
+ terminator.start();
+
+ while (!arnoldWillNotBeBack) {}
+ }
+
+ static void lockThreeThenOne() {
+ LockThree.acquire(new AcquireOne());
+ }
+
+ static void lockTwoThenThree() {
+ LockTwo.acquire(new AcquireThree());
+ }
+
+ static void arnold() {
+ while (t1.getState() != State.TERMINATED || t2.getState() != State.TERMINATED) {
+ if (t1.getState() == State.BLOCKED && t2.getState() == State.BLOCKED) {
+ System.err.println("DEADLOCKED!");
+ System.exit(1);
+ break;
+ }
+ }
+ arnoldWillNotBeBack = true;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/StaticClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/StaticClassMergerTest.java
index cd0f8b6..3fa47cb 100644
--- a/src/test/java/com/android/tools/r8/classmerging/StaticClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/StaticClassMergerTest.java
@@ -7,15 +7,17 @@
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.AssumeMayHaveSideEffects;
+import com.android.tools.r8.CompilationFailedException;
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.ToolHelper;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import java.io.IOException;
import java.util.List;
+import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -51,6 +53,9 @@
}
}
+ private final String EXPECTED =
+ StringUtils.joinLines("StaticMergeCandidateA.m()", "StaticMergeCandidateB.m()");
+
private final TestParameters parameters;
public StaticClassMergerTest(TestParameters parameters) {
@@ -59,16 +64,19 @@
@Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+ testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+ .addInnerClasses(StaticClassMergerTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
}
@Test
public void testStaticClassIsRemoved() throws Exception {
- String expected =
- StringUtils.joinLines("StaticMergeCandidateA.m()", "StaticMergeCandidateB.m()");
-
- testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expected);
-
CodeInspector inspector =
testForR8(parameters.getBackend())
.addInnerClasses(StaticClassMergerTest.class)
@@ -76,8 +84,9 @@
.noMinification()
.enableInliningAnnotations()
.enableSideEffectAnnotations()
+ .setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(expected)
+ .assertSuccessWithOutput(EXPECTED)
.inspector();
// Check that one of the two static merge candidates has been removed
diff --git a/src/test/java/com/android/tools/r8/classmerging/StaticClassMergerVisibilityTest.java b/src/test/java/com/android/tools/r8/classmerging/StaticClassMergerVisibilityTest.java
new file mode 100644
index 0000000..46ccfd2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/StaticClassMergerVisibilityTest.java
@@ -0,0 +1,112 @@
+// 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.classmerging;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+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.CodeInspector;
+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 StaticClassMergerVisibilityTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ public StaticClassMergerVisibilityTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testStaticClassIsRemoved() throws Exception {
+ CodeInspector inspector =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(StaticClassMergerVisibilityTest.class)
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A.print()", "B.print()", "C.print()", "D.print()")
+ .inspector();
+
+ // The global group will have one representative, which is C.
+ ClassSubject clazzC = inspector.clazz(C.class);
+ assertThat(clazzC, isPresent());
+ assertEquals(1, clazzC.allMethods().size());
+
+ // The package group will be merged into one of A, B or C.
+ assertExactlyOneIsPresent(
+ inspector.clazz(A.class), inspector.clazz(B.class), inspector.clazz(D.class));
+ }
+
+ private void assertExactlyOneIsPresent(ClassSubject... subjects) {
+ boolean seenPresent = false;
+ for (ClassSubject subject : subjects) {
+ if (subject.isPresent()) {
+ assertFalse(seenPresent);
+ seenPresent = true;
+ }
+ }
+ assertTrue(seenPresent);
+ }
+
+ // Will be merged into the package group.
+ private static class A {
+ @NeverInline
+ private static void print() {
+ System.out.println("A.print()");
+ }
+ }
+
+ // Will be merged into the package group.
+ static class B {
+ @NeverInline
+ static void print() {
+ System.out.println("B.print()");
+ }
+ }
+
+ // Will be merged into global group
+ public static class C {
+ @NeverInline
+ public static void print() {
+ System.out.println("C.print()");
+ }
+ }
+
+ // Will be merged into the package group.
+ protected static class D {
+ @NeverInline
+ protected static void print() {
+ System.out.println("D.print()");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ A.print();
+ B.print();
+ C.print();
+ D.print();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerShouldMergeSynchronizedMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerShouldMergeSynchronizedMethodTest.java
new file mode 100644
index 0000000..5f6e255
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerShouldMergeSynchronizedMethodTest.java
@@ -0,0 +1,85 @@
+// 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.classmerging;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+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;
+
+@RunWith(Parameterized.class)
+public class VerticalClassMergerShouldMergeSynchronizedMethodTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public VerticalClassMergerShouldMergeSynchronizedMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testNoMergingOfClassUsedInMonitor()
+ throws IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(VerticalClassMergerShouldMergeSynchronizedMethodTest.class)
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("1", "2", "3", "4")
+ .inspect(
+ inspector -> {
+ assertThat(inspector.clazz(LockOne.class), not(isPresent()));
+ assertThat(inspector.clazz(LockTwo.class), isPresent());
+ });
+ }
+
+ // Will be merged with LockTwo.
+ static class LockOne {
+
+ synchronized void print1() {
+ System.out.println("1");
+ print2();
+ }
+
+ private synchronized void print2() {
+ System.out.println("2");
+ }
+ }
+
+ public static class LockTwo extends LockOne {
+
+ synchronized void print3() {
+ System.out.println("3");
+ }
+
+ static void print4() {
+ System.out.println("4");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ LockTwo lockTwo = new LockTwo();
+ lockTwo.print1();
+ lockTwo.print3();
+ LockTwo.print4();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSynchronizedBlockTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSynchronizedBlockTest.java
new file mode 100644
index 0000000..9b3c384
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSynchronizedBlockTest.java
@@ -0,0 +1,128 @@
+// 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.classmerging;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.StringContains.containsString;
+
+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.ToolHelper.DexVm.Version;
+import java.io.IOException;
+import java.lang.Thread.State;
+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;
+
+@RunWith(Parameterized.class)
+public class VerticalClassMergerSynchronizedBlockTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ // The 4.0.4 runtime will flakily mark threads as blocking and report DEADLOCKED.
+ return getTestParameters()
+ .withDexRuntimesStartingFromExcluding(Version.V4_0_4)
+ .withAllApiLevels()
+ .build();
+ }
+
+ public VerticalClassMergerSynchronizedBlockTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testOnRuntime() throws IOException, CompilationFailedException, ExecutionException {
+ testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+ .addInnerClasses(VerticalClassMergerSynchronizedBlockTest.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput("Hello World!");
+ }
+
+ @Test
+ public void testNoMergingOfClassUsedInMonitor()
+ throws IOException, CompilationFailedException, ExecutionException {
+ // TODO(b/142438687): Fix expectation when fixed.
+ R8TestRunResult result =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(VerticalClassMergerSynchronizedBlockTest.class)
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatMatches(containsString("DEADLOCKED!"));
+ if (result.getExitCode() == 0) {
+ result.inspect(inspector -> assertThat(inspector.clazz(LockOne.class), isPresent()));
+ }
+ }
+
+ abstract static class LockOne {}
+
+ static class LockTwo extends LockOne {}
+
+ static class LockThree {}
+
+ public static class Main {
+
+ private static volatile boolean inLockThreeCritical = false;
+ private static volatile boolean inLockTwoCritical = false;
+ private static volatile boolean arnoldWillNotBeBack = false;
+
+ private static volatile Thread t1 = new Thread(Main::lockThreeThenOne);
+ private static volatile Thread t2 = new Thread(Main::lockTwoThenThree);
+ private static volatile Thread t3 = new Thread(Main::arnold);
+
+ static void synchronizedAccessThroughLocks(String arg) {
+ System.out.print(arg);
+ }
+
+ public static void main(String[] args) {
+ t1.start();
+ t2.start();
+ // This thread is started to ensure termination in case we are rewriting incorrectly.
+ t3.start();
+
+ while (!arnoldWillNotBeBack) {}
+ }
+
+ static void lockThreeThenOne() {
+ synchronized (LockThree.class) {
+ inLockThreeCritical = true;
+ while (!inLockTwoCritical) {}
+ synchronized (LockOne.class) {
+ synchronizedAccessThroughLocks("Hello ");
+ }
+ }
+ }
+
+ static void lockTwoThenThree() {
+ synchronized (LockTwo.class) {
+ inLockTwoCritical = true;
+ while (!inLockThreeCritical) {}
+ synchronized (LockThree.class) {
+ synchronizedAccessThroughLocks("World!");
+ }
+ }
+ }
+
+ static void arnold() {
+ while (t1.getState() != State.TERMINATED || t2.getState() != State.TERMINATED) {
+ if (t1.getState() == State.BLOCKED && t2.getState() == State.BLOCKED) {
+ System.err.println("DEADLOCKED!");
+ System.exit(1);
+ break;
+ }
+ }
+ arnoldWillNotBeBack = true;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSynchronizedMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSynchronizedMethodTest.java
new file mode 100644
index 0000000..e77ee05
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSynchronizedMethodTest.java
@@ -0,0 +1,150 @@
+// 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.classmerging;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import java.io.IOException;
+import java.lang.Thread.State;
+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;
+
+@RunWith(Parameterized.class)
+public class VerticalClassMergerSynchronizedMethodTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ // The 4.0.4 runtime will flakily mark threads as blocking and report DEADLOCKED.
+ return getTestParameters()
+ .withDexRuntimesStartingFromExcluding(Version.V4_0_4)
+ .withAllApiLevels()
+ .build();
+ }
+
+ public VerticalClassMergerSynchronizedMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testOnRuntime() throws IOException, CompilationFailedException, ExecutionException {
+ testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+ .addInnerClasses(VerticalClassMergerSynchronizedMethodTest.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput("Hello World!");
+ }
+
+ @Test
+ public void testNoMergingOfClassUsedInMonitor()
+ throws IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(VerticalClassMergerSynchronizedMethodTest.class)
+ .addKeepMainRule(Main.class)
+ .enableMergeAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput("Hello World!")
+ .inspect(inspector -> assertThat(inspector.clazz(LockOne.class), isPresent()));
+ }
+
+ private interface I {
+
+ void action();
+ }
+
+ // Will be merged into LockTwo.
+ abstract static class LockOne {
+
+ static synchronized void acquire(I c) {
+ c.action();
+ }
+ }
+
+ @NeverMerge
+ public static class LockTwo extends LockOne {
+
+ static synchronized void acquire(I c) {
+ Main.inTwoCritical = true;
+ while (!Main.inThreeCritical) {}
+ c.action();
+ }
+ }
+
+ @NeverMerge
+ public static class LockThree {
+
+ static synchronized void acquire(I c) {
+ Main.inThreeCritical = true;
+ while (!Main.inTwoCritical) {}
+ c.action();
+ }
+ }
+
+ public static class AcquireOne implements I {
+
+ @Override
+ public void action() {
+ LockOne.acquire(() -> System.out.print("Hello "));
+ }
+ }
+
+ public static class AcquireThree implements I {
+
+ @Override
+ public void action() {
+ LockThree.acquire(() -> System.out.print("World!"));
+ }
+ }
+
+ public static class Main {
+
+ static volatile boolean inTwoCritical = false;
+ static volatile boolean inThreeCritical = false;
+ static volatile boolean arnoldWillNotBeBack = false;
+
+ private static volatile Thread t1 = new Thread(Main::lockThreeThenOne);
+ private static volatile Thread t2 = new Thread(Main::lockTwoThenThree);
+ private static volatile Thread terminator = new Thread(Main::arnold);
+
+ public static void main(String[] args) {
+ t1.start();
+ t2.start();
+ // This thread is started to ensure termination in case we are rewriting incorrectly.
+ terminator.start();
+
+ while (!arnoldWillNotBeBack) {}
+ }
+
+ static void lockThreeThenOne() {
+ LockThree.acquire(new AcquireOne());
+ }
+
+ static void lockTwoThenThree() {
+ LockTwo.acquire(new AcquireThree());
+ }
+
+ static void arnold() {
+ while (t1.getState() != State.TERMINATED || t2.getState() != State.TERMINATED) {
+ if (t1.getState() == State.BLOCKED && t2.getState() == State.BLOCKED) {
+ System.err.println("DEADLOCKED!");
+ System.exit(1);
+ break;
+ }
+ }
+ arnoldWillNotBeBack = true;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
index 6b4f17d..82385a2 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
@@ -10,8 +10,6 @@
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.OutputMode;
@@ -78,7 +76,6 @@
.resolve("classmerging").resolve("keep-rules-dontoptimize.txt");
private void configure(InternalOptions options) {
- options.enableClassInlining = false;
options.enableSideEffectAnalysis = false;
options.enableUnusedInterfaceRemoval = false;
options.testing.nondeterministicCycleElimination = true;
@@ -90,6 +87,7 @@
testForR8(Backend.DEX)
.addProgramFiles(EXAMPLE_JAR)
.addKeepRuleFiles(proguardConfig)
+ .enableProguardTestOptions()
.noMinification()
.addOptionsModification(optionsConsumer)
.compile()
@@ -110,18 +108,18 @@
runR8(EXAMPLE_KEEP, this::configure);
// GenericInterface should be merged into GenericInterfaceImpl.
for (String candidate : CAN_BE_MERGED) {
- assertFalse(inspector.clazz(candidate).isPresent());
+ assertThat(inspector.clazz(candidate), not(isPresent()));
}
- assertTrue(inspector.clazz("classmerging.GenericInterfaceImpl").isPresent());
- assertTrue(inspector.clazz("classmerging.Outer$SubClass").isPresent());
- assertTrue(inspector.clazz("classmerging.SubClass").isPresent());
+ assertThat(inspector.clazz("classmerging.GenericInterfaceImpl"), isPresent());
+ assertThat(inspector.clazz("classmerging.Outer$SubClass"), isPresent());
+ assertThat(inspector.clazz("classmerging.SubClass"), isPresent());
}
@Test
public void testClassesHaveNotBeenMerged() throws Throwable {
runR8(DONT_OPTIMIZE, null);
for (String candidate : CAN_BE_MERGED) {
- assertTrue(inspector.clazz(candidate).isPresent());
+ assertThat(inspector.clazz(candidate), isPresent());
}
}
@@ -304,8 +302,8 @@
@Test
public void testConflictWasDetected() throws Throwable {
runR8(EXAMPLE_KEEP, this::configure);
- assertTrue(inspector.clazz("classmerging.ConflictingInterface").isPresent());
- assertTrue(inspector.clazz("classmerging.ConflictingInterfaceImpl").isPresent());
+ assertThat(inspector.clazz("classmerging.ConflictingInterface"), isPresent());
+ assertThat(inspector.clazz("classmerging.ConflictingInterfaceImpl"), isPresent());
}
@Test
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/EmptyDesugaredLibrary.java b/src/test/java/com/android/tools/r8/desugar/corelib/EmptyDesugaredLibrary.java
new file mode 100644
index 0000000..998682c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/EmptyDesugaredLibrary.java
@@ -0,0 +1,101 @@
+// 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.desugar.corelib;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.L8Command;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.ToolHelper;
+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.Set;
+import java.util.zip.ZipFile;
+import org.junit.Test;
+
+public class EmptyDesugaredLibrary extends CoreLibDesugarTestBase {
+
+ private L8Command.Builder prepareL8Builder(AndroidApiLevel minApiLevel) {
+ return L8Command.builder()
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+ .addProgramFiles(ToolHelper.getDesugarJDKLibs())
+ .addDesugaredLibraryConfiguration(
+ StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING))
+ .setMinApiLevel(minApiLevel.getLevel());
+ }
+
+ static class CountingProgramConsumer extends DexIndexedConsumer.ForwardingConsumer {
+
+ public int count = 0;
+
+ public CountingProgramConsumer() {
+ super(null);
+ }
+
+ @Override
+ public void accept(
+ int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
+ count++;
+ }
+ }
+
+ @Test
+ public void testEmptyDesugaredLibrary() throws Exception {
+ for (AndroidApiLevel apiLevel : AndroidApiLevel.values()) {
+ if (apiLevel.getLevel() < AndroidApiLevel.K.getLevel()) {
+ // No need to test all API levels.
+ continue;
+ }
+ CountingProgramConsumer programConsumer = new CountingProgramConsumer();
+ ToolHelper.runL8(prepareL8Builder(apiLevel).setProgramConsumer(programConsumer).build());
+ assertEquals(
+ apiLevel.getLevel() >= AndroidApiLevel.O.getLevel() ? 0 : 1, programConsumer.count);
+ }
+ }
+
+ @Test
+ public void testEmptyDesugaredLibraryDexZip() throws Exception {
+ for (AndroidApiLevel apiLevel : AndroidApiLevel.values()) {
+ if (apiLevel.getLevel() < AndroidApiLevel.K.getLevel()) {
+ // No need to test all API levels.
+ continue;
+ }
+ Path desugaredLibraryZip = temp.newFolder().toPath().resolve("desugar_jdk_libs_dex.zip");
+ ToolHelper.runL8(
+ prepareL8Builder(apiLevel).setOutput(desugaredLibraryZip, OutputMode.DexIndexed).build());
+ assertTrue(Files.exists(desugaredLibraryZip));
+ assertEquals(
+ apiLevel.getLevel() >= AndroidApiLevel.O.getLevel() ? 0 : 1,
+ new ZipFile(desugaredLibraryZip.toFile(), StandardCharsets.UTF_8).size());
+ }
+ }
+
+ @Test
+ public void testEmptyDesugaredLibraryDexDirectory() throws Exception {
+ for (AndroidApiLevel apiLevel : AndroidApiLevel.values()) {
+ if (apiLevel.getLevel() < AndroidApiLevel.K.getLevel()) {
+ // No need to test all API levels.
+ continue;
+ }
+ Path desugaredLibraryDirectory = temp.newFolder().toPath();
+ ToolHelper.runL8(
+ prepareL8Builder(apiLevel)
+ .setOutput(desugaredLibraryDirectory, OutputMode.DexIndexed)
+ .build());
+ assertEquals(
+ apiLevel.getLevel() >= AndroidApiLevel.O.getLevel() ? 0 : 1,
+ Files.walk(desugaredLibraryDirectory)
+ .filter(path -> path.toString().endsWith(".dex"))
+ .count());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/HelloWorldCompiledOnArtTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/HelloWorldCompiledOnArtTest.java
new file mode 100644
index 0000000..044f075
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/HelloWorldCompiledOnArtTest.java
@@ -0,0 +1,135 @@
+// 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.desugar.corelib;
+
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+import static junit.framework.TestCase.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+import org.junit.BeforeClass;
+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 HelloWorldCompiledOnArtTest extends CoreLibDesugarTestBase {
+
+ // TODO(b/142621961): Create an abstraction to easily run tests on External DexR8.
+ // Manage pathMock in the abstraction.
+ private static Path pathMock;
+
+ @BeforeClass
+ public static void compilePathBackport() throws Exception {
+ assumeTrue("JDK8 is not checked-in on Windows", !ToolHelper.isWindows());
+ pathMock = getStaticTemp().newFolder("PathMock").toPath();
+ ProcessResult processResult =
+ ToolHelper.runJavac(
+ CfVm.JDK8,
+ Collections.emptyList(),
+ pathMock,
+ getAllFilesWithSuffixInDirectory(Paths.get("src/test/r8OnArtBackport"), "java"));
+ assertEquals(0, processResult.exitCode);
+ }
+
+ public static Path[] getPathBackport() throws Exception {
+ return getAllFilesWithSuffixInDirectory(pathMock, "class");
+ }
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withDexRuntimesStartingFromIncluding(Version.V7_0_0)
+ .withApiLevelsStartingAtIncluding(AndroidApiLevel.L)
+ .build();
+ }
+
+ public HelloWorldCompiledOnArtTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private static String commandLinePathFor(String string) {
+ return Paths.get(string).toAbsolutePath().toString();
+ }
+
+ private static final String HELLO_KEEP =
+ commandLinePathFor("src/test/examples/hello/keep-rules.txt");
+ private static final String HELLO_PATH =
+ commandLinePathFor(ToolHelper.EXAMPLES_BUILD_DIR + "hello" + JAR_EXTENSION);
+
+ @Test
+ public void testHelloCompiledWithR8Dex() throws Exception {
+ Path helloOutput = temp.newFolder("helloOutput").toPath().resolve("out.zip").toAbsolutePath();
+ compileR8ToDexWithD8()
+ .run(
+ parameters.getRuntime(),
+ R8.class,
+ "--release",
+ "--output",
+ helloOutput.toString(),
+ "--lib",
+ commandLinePathFor(ToolHelper.JAVA_8_RUNTIME),
+ "--pg-conf",
+ HELLO_KEEP,
+ HELLO_PATH)
+ .assertSuccess();
+ verifyResult(helloOutput);
+ }
+
+ @Test
+ public void testHelloCompiledWithD8Dex() throws Exception {
+ Path helloOutput = temp.newFolder("helloOutput").toPath().resolve("out.zip").toAbsolutePath();
+ compileR8ToDexWithD8()
+ .run(
+ parameters.getRuntime(),
+ D8.class,
+ "--release",
+ "--output",
+ helloOutput.toString(),
+ "--lib",
+ commandLinePathFor(ToolHelper.JAVA_8_RUNTIME),
+ HELLO_PATH)
+ .assertSuccess();
+ verifyResult(helloOutput);
+ }
+
+ private void verifyResult(Path helloOutput) throws IOException {
+ ProcessResult processResult = ToolHelper.runArtRaw(helloOutput.toString(), "hello.Hello");
+ assertEquals(StringUtils.lines("Hello, world"), processResult.stdout);
+ }
+
+ private D8TestCompileResult compileR8ToDexWithD8() throws Exception {
+ D8TestBuilder d8TestBuilder =
+ testForD8().addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR);
+ if (parameters.getApiLevel().getLevel() < AndroidApiLevel.O.getLevel()) {
+ d8TestBuilder.addProgramFiles(getPathBackport());
+ }
+ return d8TestBuilder
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
+ .withArt6Plus64BitsLib()
+ .withArtFrameworks();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/LinkedHashSetTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/LinkedHashSetTest.java
new file mode 100644
index 0000000..178af61
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/LinkedHashSetTest.java
@@ -0,0 +1,120 @@
+// 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.desugar.corelib;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.util.LinkedHashSet;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LinkedHashSetTest extends CoreLibDesugarTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public LinkedHashSetTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testLinkedHashSetOverrides() throws Exception {
+ String stdOut =
+ testForD8()
+ .addInnerClasses(LinkedHashSetTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary, parameters.getApiLevel())
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccess()
+ .getStdOut();
+ assertLines2By2Correct(stdOut);
+ }
+
+ static class Executor {
+ @SuppressWarnings("RedundantOperationOnEmptyContainer")
+ public static void main(String[] args) {
+ Spliterator<String> spliterator;
+
+ // Spliterator of Set is only distinct.
+ // Spliterator of LinkedHashSet is distinct and ordered.
+ // Spliterator of CustomLinkedHashSetOverride is distinct, ordered and immutable.
+ // If an incorrect method is found, characteristics are incorrect.
+
+ spliterator = new LinkedHashSet<String>().spliterator();
+ System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+ System.out.println("false");
+
+ spliterator = new CustomLinkedHashSetOverride<String>().spliterator();
+ System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+ System.out.println("true");
+
+ spliterator = new CustomLinkedHashSetNoOverride<String>().spliterator();
+ System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+ System.out.println("false");
+
+ spliterator = new CustomLinkedHashSetOverride<String>().superSpliterator();
+ System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+ System.out.println("false");
+
+ spliterator = new SubclassOverride<String>().superSpliterator();
+ System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+ System.out.println("true");
+ }
+ }
+
+ static class CustomLinkedHashSetOverride<E> extends LinkedHashSet<E> {
+
+ @Override
+ public Spliterator<E> spliterator() {
+ return Spliterators.spliterator(
+ this, Spliterator.DISTINCT | Spliterator.ORDERED | Spliterator.IMMUTABLE);
+ }
+
+ public Spliterator<E> superSpliterator() {
+ return super.spliterator();
+ }
+ }
+
+ static class SubclassOverride<E> extends CustomLinkedHashSetOverride<E> {
+ @Override
+ public Spliterator<E> superSpliterator() {
+ return super.spliterator();
+ }
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ static class CustomLinkedHashSetNoOverride<E> extends LinkedHashSet<E> {}
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/RetargetOverrideTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/RetargetOverrideTest.java
new file mode 100644
index 0000000..df4e4d8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/RetargetOverrideTest.java
@@ -0,0 +1,156 @@
+// 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.desugar.corelib;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.time.Instant;
+import java.time.ZonedDateTime;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.concurrent.atomic.AtomicInteger;
+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 RetargetOverrideTest extends CoreLibDesugarTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public RetargetOverrideTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRetargetOverrideD8() throws Exception {
+ String stdout =
+ testForD8()
+ .addInnerClasses(RetargetOverrideTest.class)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary, parameters.getApiLevel())
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccess()
+ .getStdOut();
+ assertLines2By2Correct(stdout);
+ }
+
+ @Test
+ public void testRetargetOverrideR8() throws Exception {
+ String stdout =
+ testForR8(Backend.DEX)
+ .addKeepMainRule(Executor.class)
+ .addInnerClasses(RetargetOverrideTest.class)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary, parameters.getApiLevel())
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccess()
+ .getStdOut();
+ assertLines2By2Correct(stdout);
+ }
+
+ static class Executor {
+
+ public static void main(String[] args) {
+ java.sql.Date date = new java.sql.Date(123456789);
+ System.out.println(date.toInstant());
+ System.out.println("1970-01-02T10:17:36.789Z");
+
+ GregorianCalendar gregCal = new GregorianCalendar(1990, 2, 22);
+ System.out.println(gregCal.toInstant());
+ System.out.println("1990-03-22T00:00:00Z");
+
+ // TODO(b/142846107): Enable overrides of retarget core members.
+ // MyCalendarOverride myCal = new MyCalendarOverride(1990, 2, 22);
+ // System.out.println(myCal.toZonedDateTime());
+ // System.out.println("1990-11-22T00:00Z[GMT]");
+ // System.out.println(myCal.toInstant());
+ // System.out.println("1990-03-22T00:00:00Z");
+
+ MyCalendarNoOverride myCalN = new MyCalendarNoOverride(1990, 2, 22);
+ System.out.println(myCalN.toZonedDateTime());
+ System.out.println("1990-03-22T00:00Z[GMT]");
+ System.out.println(myCalN.toInstant());
+ System.out.println("1990-03-22T00:00:00Z");
+
+ // TODO(b/142846107): Enable overrides of retarget core members.
+ // MyDateOverride myDate = new MyDateOverride(123456789);
+ // System.out.println(myDate.toInstant());
+ // System.out.println("1970-01-02T10:17:45.789Z");
+
+ MyDateNoOverride myDateN = new MyDateNoOverride(123456789);
+ System.out.println(myDateN.toInstant());
+ System.out.println("1970-01-02T10:17:36.789Z");
+
+ MyAtomicInteger myAtomicInteger = new MyAtomicInteger(42);
+ System.out.println(myAtomicInteger.getAndUpdate(x -> x + 1));
+ System.out.println("42");
+ System.out.println(myAtomicInteger.getAndUpdate(x -> x + 5));
+ System.out.println("43");
+ System.out.println(myAtomicInteger.updateAndGet(x -> x + 100));
+ System.out.println("148");
+ System.out.println(myAtomicInteger.updateAndGet(x -> x + 500));
+ System.out.println("648");
+ }
+ }
+
+ // static class MyCalendarOverride extends GregorianCalendar {
+ //
+ // public MyCalendarOverride(int year, int month, int dayOfMonth) {
+ // super(year, month, dayOfMonth);
+ // }
+ //
+ // // Cannot override toInstant (final).
+ //
+ // @Override
+ // public ZonedDateTime toZonedDateTime() {
+ // return super.toZonedDateTime().withMonth(11);
+ // }
+ // }
+
+ static class MyCalendarNoOverride extends GregorianCalendar {
+ public MyCalendarNoOverride(int year, int month, int dayOfMonth) {
+ super(year, month, dayOfMonth);
+ }
+ }
+
+ // static class MyDateOverride extends Date {
+ //
+ // public MyDateOverride(long date) {
+ // super(date);
+ // }
+ //
+ // @Override
+ // public Instant toInstant() {
+ // return super.toInstant().plusSeconds(9);
+ // }
+ // }
+
+ static class MyDateNoOverride extends Date {
+
+ public MyDateNoOverride(long date) {
+ super(date);
+ }
+ }
+
+ static class MyAtomicInteger extends AtomicInteger {
+ // No overrides, all final methods.
+ public MyAtomicInteger(int initialValue) {
+ super(initialValue);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/AllTimeConversionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/AllTimeConversionTest.java
index ed5ca26..3775eba 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/AllTimeConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/AllTimeConversionTest.java
@@ -4,6 +4,11 @@
package com.android.tools.r8.desugar.corelib.conversionTests;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertTrue;
+
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.TestDiagnosticMessages;
import com.android.tools.r8.TestRuntime.DexRuntime;
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -22,12 +27,15 @@
@Test
public void testRewrittenAPICalls() throws Exception {
Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
- testForD8()
- .setMinApi(AndroidApiLevel.B)
- .addProgramClasses(Executor.class)
- .addLibraryClasses(CustomLibClass.class)
- .enableCoreLibraryDesugaring(AndroidApiLevel.B)
- .compile()
+ D8TestCompileResult compileResult =
+ testForD8()
+ .setMinApi(AndroidApiLevel.B)
+ .addProgramClasses(Executor.class)
+ .addLibraryClasses(CustomLibClass.class)
+ .addOptionsModification(options -> options.testing.trackDesugaredAPIConversions = true)
+ .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .compile();
+ compileResult
.addDesugaredCoreLibraryRunClassPath(
this::buildDesugaredLibraryWithConversionExtension, AndroidApiLevel.B)
.addRunClasspathFiles(customLib)
@@ -41,8 +49,29 @@
"-1000000000-01-01T00:00:00.999999999Z",
"GMT",
"GMT"));
+ assertTrackedAPIS(compileResult.getDiagnosticMessages());
}
+ private void assertTrackedAPIS(TestDiagnosticMessages diagnosticMessages) {
+ assertTrue(
+ diagnosticMessages
+ .getWarnings()
+ .get(0)
+ .getDiagnosticMessage()
+ .startsWith("Tracked desugared API conversions:"));
+ assertEquals(
+ 9, diagnosticMessages.getWarnings().get(0).getDiagnosticMessage().split("\n").length);
+ assertTrue(
+ diagnosticMessages
+ .getWarnings()
+ .get(1)
+ .getDiagnosticMessage()
+ .startsWith("Tracked callback desugared API conversions:"));
+ assertEquals(
+ 1, diagnosticMessages.getWarnings().get(1).getDiagnosticMessage().split("\n").length);
+ }
+
+
static class Executor {
private static final String ZONE_ID = "GMT";
diff --git a/src/test/java/com/android/tools/r8/internal/Gmail17060416TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/Gmail17060416TreeShakeJarVerificationTest.java
new file mode 100644
index 0000000..82e7e93
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/Gmail17060416TreeShakeJarVerificationTest.java
@@ -0,0 +1,49 @@
+// 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.internal;
+
+import static com.android.tools.r8.ToolHelper.isLocalDevelopment;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.nio.file.Paths;
+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 Gmail17060416TreeShakeJarVerificationTest extends GmailCompilationBase {
+ private static final int MAX_SIZE = 20000000;
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().build();
+ }
+
+ public Gmail17060416TreeShakeJarVerificationTest(TestParameters parameters) {
+ super(170604, 16);
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void buildAndTreeShakeFromDeployJar() throws Exception {
+ assumeTrue(isLocalDevelopment());
+
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addKeepRuleFiles(Paths.get(base).resolve(BASE_PG_CONF))
+ .allowUnusedProguardConfigurationRules()
+ .compile();
+
+ int appSize = applicationSize(compileResult.app);
+ assertTrue("Expected max size of " + MAX_SIZE+ ", got " + appSize, appSize < MAX_SIZE);
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java
new file mode 100644
index 0000000..0f6a563
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java
@@ -0,0 +1,51 @@
+// 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.internal;
+
+import static com.android.tools.r8.ToolHelper.isLocalDevelopment;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import java.nio.file.Paths;
+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 Gmail18082615TreeShakeJarVerificationTest extends GmailCompilationBase {
+ private static final int MAX_SIZE = 20000000;
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().build();
+ }
+
+ public Gmail18082615TreeShakeJarVerificationTest(TestParameters parameters) {
+ super(180826, 15);
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void buildAndTreeShakeFromDeployJar() throws Exception {
+ assumeTrue(isLocalDevelopment());
+
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addKeepRuleFiles(
+ Paths.get(base).resolve(BASE_PG_CONF),
+ Paths.get(ToolHelper.PROGUARD_SETTINGS_FOR_INTERNAL_APPS, PG_CONF))
+ .allowUnusedProguardConfigurationRules()
+ .compile();
+
+ int appSize = applicationSize(compileResult.app);
+ assertTrue("Expected max size of " + MAX_SIZE+ ", got " + appSize, appSize < MAX_SIZE);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/GmailCompilationBase.java b/src/test/java/com/android/tools/r8/internal/GmailCompilationBase.java
new file mode 100644
index 0000000..7332dd8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/GmailCompilationBase.java
@@ -0,0 +1,19 @@
+// 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.internal;
+
+abstract class GmailCompilationBase extends CompilationTestBase {
+ static final String APK = "Gmail_release_unsigned.apk";
+ static final String DEPLOY_JAR = "Gmail_release_unstripped_deploy.jar";
+ static final String PG_JAR = "Gmail_release_unstripped_proguard.jar";
+ static final String PG_MAP = "Gmail_release_unstripped_proguard.map";
+ static final String BASE_PG_CONF = "Gmail_release_unstripped_proguard.config";
+ static final String PG_CONF = "Gmail_proguard.config";
+
+ final String base;
+
+ GmailCompilationBase(int majorVersion, int minorVersion) {
+ this.base = "third_party/gmail/gmail_android_" + majorVersion + "." + minorVersion + "/";
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLatestTreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLatestTreeShakeJarVerificationTest.java
index 0168988..e8417f1 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLatestTreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLatestTreeShakeJarVerificationTest.java
@@ -22,7 +22,7 @@
public void buildAndTreeShakeFromDeployJar() throws Exception {
List<String> additionalProguardConfiguration =
ImmutableList.of(
- ToolHelper.PROGUARD_SETTINGS_FOR_INTERNAL_APPS + "/GmsCore_proguard.config");
+ ToolHelper.PROGUARD_SETTINGS_FOR_INTERNAL_APPS + "GmsCore_proguard.config");
AndroidApp app1 =
buildAndTreeShakeFromDeployJar(
CompilationMode.RELEASE,
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeCompilationBase.java b/src/test/java/com/android/tools/r8/internal/YouTubeCompilationBase.java
index 4ffbe2b..92b7c68 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeCompilationBase.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeCompilationBase.java
@@ -28,13 +28,13 @@
this.base = "third_party/youtube/youtube.android_" + majorVersion + "." + minorVersion + "/";
}
- public List<Path> getKeepRuleFiles() {
+ protected List<Path> getKeepRuleFiles() {
return ImmutableList.of(
Paths.get(base).resolve(PG_CONF),
Paths.get(ToolHelper.PROGUARD_SETTINGS_FOR_INTERNAL_APPS).resolve(PG_CONF));
}
- public List<Path> getProgramFiles() throws IOException {
+ protected List<Path> getProgramFiles() throws IOException {
List<Path> result = new ArrayList<>();
for (Path keepRuleFile : getKeepRuleFiles()) {
for (String line : FileUtils.readAllLines(keepRuleFile)) {
@@ -47,12 +47,12 @@
return result;
}
- public Path getReleaseApk() {
+ Path getReleaseApk() {
return Paths.get(base).resolve("YouTubeRelease.apk");
}
- public Path getReleaseProguardMap() {
- return Paths.get(base).resolve("YouTubeRelease_proguard.map");
+ Path getReleaseProguardMap() {
+ return Paths.get(base).resolve(PG_MAP);
}
void runR8AndCheckVerification(CompilationMode mode, String input) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCanBePostponedTest.java b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCanBePostponedTest.java
new file mode 100644
index 0000000..7813a2d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCanBePostponedTest.java
@@ -0,0 +1,86 @@
+// 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.analysis.sideeffect;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+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.CodeInspector;
+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 SingletonClassInitializerPatternCanBePostponedTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public SingletonClassInitializerPatternCanBePostponedTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(SingletonClassInitializerPatternCanBePostponedTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject classSubject = inspector.clazz(A.class);
+ assertThat(classSubject, isPresent());
+
+ // A.inlineable() should be inlined because we should be able to determine that A.<clinit>() can
+ // safely be postponed.
+ assertThat(classSubject.uniqueMethodWithName("inlineable"), not(isPresent()));
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ A.inlineable();
+ System.out.println(A.getInstance().getMessage());
+ }
+ }
+
+ static class A {
+
+ static A INSTANCE = new A(" world!");
+
+ final String message;
+
+ A(String message) {
+ this.message = message;
+ }
+
+ static void inlineable() {
+ System.out.print("Hello");
+ }
+
+ static A getInstance() {
+ return INSTANCE;
+ }
+
+ String getMessage() {
+ return message;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCannotBePostponedTest.java b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCannotBePostponedTest.java
new file mode 100644
index 0000000..d29753b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCannotBePostponedTest.java
@@ -0,0 +1,92 @@
+// 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.analysis.sideeffect;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+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.CodeInspector;
+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 SingletonClassInitializerPatternCannotBePostponedTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public SingletonClassInitializerPatternCannotBePostponedTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(SingletonClassInitializerPatternCannotBePostponedTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject classSubject = inspector.clazz(A.class);
+ assertThat(classSubject, isPresent());
+
+ // A.inlineable() cannot be inlined because it should trigger the class initialization of A,
+ // which should trigger the class initialization of B, which will print "Hello".
+ assertThat(classSubject.uniqueMethodWithName("inlineable"), isPresent());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ A.inlineable();
+ System.out.println(A.getInstance().getMessage());
+ }
+ }
+
+ static class A {
+
+ static B INSTANCE = new B("world!");
+
+ static void inlineable() {
+ System.out.print(" ");
+ }
+
+ static B getInstance() {
+ return INSTANCE;
+ }
+ }
+
+ static class B {
+
+ static {
+ System.out.print("Hello");
+ }
+
+ final String message;
+
+ B(String message) {
+ this.message = message;
+ }
+
+ String getMessage() {
+ return message;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java b/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java
new file mode 100644
index 0000000..b567014
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java
@@ -0,0 +1,31 @@
+// 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.conversion;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.ir.conversion.CallGraph.Node;
+
+class CallGraphTestBase extends TestBase {
+ private DexItemFactory dexItemFactory = new DexItemFactory();
+
+ Node createNode(String methodName) {
+ DexMethod signature =
+ dexItemFactory.createMethod(
+ dexItemFactory.objectType,
+ dexItemFactory.createProto(dexItemFactory.voidType),
+ methodName);
+ return new Node(
+ new DexEncodedMethod(signature, null, null, ParameterAnnotationsList.empty(), null));
+ }
+
+ Node createForceInlinedNode(String methodName) {
+ Node node = createNode(methodName);
+ node.method.getMutableOptimizationInfo().markForceInline();
+ return node;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/callgraph/CycleEliminationTest.java b/src/test/java/com/android/tools/r8/ir/conversion/CycleEliminationTest.java
similarity index 87%
rename from src/test/java/com/android/tools/r8/ir/callgraph/CycleEliminationTest.java
rename to src/test/java/com/android/tools/r8/ir/conversion/CycleEliminationTest.java
index f767bf2..f38629f 100644
--- a/src/test/java/com/android/tools/r8/ir/callgraph/CycleEliminationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/CycleEliminationTest.java
@@ -2,7 +2,7 @@
// 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.callgraph;
+package com.android.tools.r8.ir.conversion;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -11,14 +11,9 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import com.android.tools.r8.TestBase;
import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.ir.conversion.CallGraph.Node;
-import com.android.tools.r8.ir.conversion.CallGraphBuilder.CycleEliminator;
+import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -29,7 +24,7 @@
import java.util.function.BooleanSupplier;
import org.junit.Test;
-public class CycleEliminationTest extends TestBase {
+public class CycleEliminationTest extends CallGraphTestBase {
private static class Configuration {
@@ -44,8 +39,6 @@
}
}
- private DexItemFactory dexItemFactory = new DexItemFactory();
-
@Test
public void testSimpleCycle() {
Node method = createNode("n1");
@@ -197,20 +190,4 @@
}
}
}
-
- private Node createNode(String methodName) {
- DexMethod signature =
- dexItemFactory.createMethod(
- dexItemFactory.objectType,
- dexItemFactory.createProto(dexItemFactory.voidType),
- methodName);
- return new Node(
- new DexEncodedMethod(signature, null, null, ParameterAnnotationsList.empty(), null));
- }
-
- private Node createForceInlinedNode(String methodName) {
- Node node = createNode(methodName);
- node.method.getMutableOptimizationInfo().markForceInline();
- return node;
- }
}
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java b/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java
new file mode 100644
index 0000000..2cde812
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java
@@ -0,0 +1,223 @@
+// 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.conversion;
+
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ir.conversion.CallGraph.Node;
+import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.Sets;
+import java.util.Set;
+import java.util.TreeSet;
+import org.junit.Test;
+
+public class NodeExtractionTest extends CallGraphTestBase {
+
+ private InternalOptions options = new InternalOptions();
+
+ // Note that building a test graph is intentionally repeated to avoid race conditions and/or
+ // non-deterministic test results due to cycle elimination.
+
+ @Test
+ public void testExtractLeaves_withoutCycle() {
+ Node n1, n2, n3, n4, n5, n6;
+ Set<Node> nodes;
+
+ n1 = createNode("n1");
+ n2 = createNode("n2");
+ n3 = createNode("n3");
+ n4 = createNode("n4");
+ n5 = createNode("n5");
+ n6 = createNode("n6");
+
+ n2.addCallerConcurrently(n1);
+ n3.addCallerConcurrently(n2);
+ n4.addCallerConcurrently(n2);
+ n4.addCallerConcurrently(n5);
+ n6.addCallerConcurrently(n5);
+
+ nodes = new TreeSet<>();
+ nodes.add(n1);
+ nodes.add(n2);
+ nodes.add(n3);
+ nodes.add(n4);
+ nodes.add(n5);
+ nodes.add(n6);
+
+ Set<Node> wave = Sets.newIdentityHashSet();
+
+ MethodProcessor.extractLeaves(nodes, wave::add);
+ assertEquals(3, wave.size());
+ assertThat(wave, hasItem(n3));
+ assertThat(wave, hasItem(n4));
+ assertThat(wave, hasItem(n6));
+ wave.clear();
+
+ MethodProcessor.extractLeaves(nodes, wave::add);
+ assertEquals(2, wave.size());
+ assertThat(wave, hasItem(n2));
+ assertThat(wave, hasItem(n5));
+ wave.clear();
+
+ MethodProcessor.extractLeaves(nodes, wave::add);
+ assertEquals(1, wave.size());
+ assertThat(wave, hasItem(n1));
+ assertTrue(nodes.isEmpty());
+ }
+
+ @Test
+ public void testExtractLeaves_withCycle() {
+ Node n1, n2, n3, n4, n5, n6;
+ Set<Node> nodes;
+
+ n1 = createNode("n1");
+ n2 = createNode("n2");
+ n3 = createNode("n3");
+ n4 = createNode("n4");
+ n5 = createNode("n5");
+ n6 = createNode("n6");
+
+ n2.addCallerConcurrently(n1);
+ n3.addCallerConcurrently(n2);
+ n4.addCallerConcurrently(n2);
+ n4.addCallerConcurrently(n5);
+ n6.addCallerConcurrently(n5);
+
+ nodes = new TreeSet<>();
+ nodes.add(n1);
+ nodes.add(n2);
+ nodes.add(n3);
+ nodes.add(n4);
+ nodes.add(n5);
+ nodes.add(n6);
+
+ n1.addCallerConcurrently(n3);
+ n3.method.getMutableOptimizationInfo().markForceInline();
+ CycleEliminator cycleEliminator = new CycleEliminator(nodes, options);
+ assertEquals(1, cycleEliminator.breakCycles().numberOfRemovedEdges());
+
+ Set<Node> wave = Sets.newIdentityHashSet();
+
+ MethodProcessor.extractLeaves(nodes, wave::add);
+ assertEquals(3, wave.size());
+ assertThat(wave, hasItem(n3));
+ assertThat(wave, hasItem(n4));
+ assertThat(wave, hasItem(n6));
+ wave.clear();
+
+ MethodProcessor.extractLeaves(nodes, wave::add);
+ assertEquals(2, wave.size());
+ assertThat(wave, hasItem(n2));
+ assertThat(wave, hasItem(n5));
+ wave.clear();
+
+ MethodProcessor.extractLeaves(nodes, wave::add);
+ assertEquals(1, wave.size());
+ assertThat(wave, hasItem(n1));
+ assertTrue(nodes.isEmpty());
+ }
+
+ @Test
+ public void testExtractRoots_withoutCycle() {
+ Node n1, n2, n3, n4, n5, n6;
+ Set<Node> nodes;
+
+ n1 = createNode("n1");
+ n2 = createNode("n2");
+ n3 = createNode("n3");
+ n4 = createNode("n4");
+ n5 = createNode("n5");
+ n6 = createNode("n6");
+
+ n2.addCallerConcurrently(n1);
+ n3.addCallerConcurrently(n2);
+ n4.addCallerConcurrently(n2);
+ n4.addCallerConcurrently(n5);
+ n6.addCallerConcurrently(n5);
+
+ nodes = new TreeSet<>();
+ nodes.add(n1);
+ nodes.add(n2);
+ nodes.add(n3);
+ nodes.add(n4);
+ nodes.add(n5);
+ nodes.add(n6);
+
+ Set<Node> wave = Sets.newIdentityHashSet();
+
+ PostMethodProcessor.extractRoots(nodes, wave::add);
+ assertEquals(2, wave.size());
+ assertThat(wave, hasItem(n1));
+ assertThat(wave, hasItem(n5));
+ wave.clear();
+
+ PostMethodProcessor.extractRoots(nodes, wave::add);
+ assertEquals(2, wave.size());
+ assertThat(wave, hasItem(n2));
+ assertThat(wave, hasItem(n6));
+ wave.clear();
+
+ PostMethodProcessor.extractRoots(nodes, wave::add);
+ assertEquals(2, wave.size());
+ assertThat(wave, hasItem(n3));
+ assertThat(wave, hasItem(n4));
+ assertTrue(nodes.isEmpty());
+ }
+
+ @Test
+ public void testExtractRoots_withCycle() {
+ Node n1, n2, n3, n4, n5, n6;
+ Set<Node> nodes;
+
+ n1 = createNode("n1");
+ n2 = createNode("n2");
+ n3 = createNode("n3");
+ n4 = createNode("n4");
+ n5 = createNode("n5");
+ n6 = createNode("n6");
+
+ n2.addCallerConcurrently(n1);
+ n3.addCallerConcurrently(n2);
+ n4.addCallerConcurrently(n2);
+ n4.addCallerConcurrently(n5);
+ n6.addCallerConcurrently(n5);
+
+ nodes = new TreeSet<>();
+ nodes.add(n1);
+ nodes.add(n2);
+ nodes.add(n3);
+ nodes.add(n4);
+ nodes.add(n5);
+ nodes.add(n6);
+
+ n1.addCallerConcurrently(n3);
+ n3.method.getMutableOptimizationInfo().markForceInline();
+ CycleEliminator cycleEliminator = new CycleEliminator(nodes, options);
+ assertEquals(1, cycleEliminator.breakCycles().numberOfRemovedEdges());
+
+ Set<Node> wave = Sets.newIdentityHashSet();
+
+ PostMethodProcessor.extractRoots(nodes, wave::add);
+ assertEquals(2, wave.size());
+ assertThat(wave, hasItem(n1));
+ assertThat(wave, hasItem(n5));
+ wave.clear();
+
+ PostMethodProcessor.extractRoots(nodes, wave::add);
+ assertEquals(2, wave.size());
+ assertThat(wave, hasItem(n2));
+ assertThat(wave, hasItem(n6));
+ wave.clear();
+
+ PostMethodProcessor.extractRoots(nodes, wave::add);
+ assertEquals(2, wave.size());
+ assertThat(wave, hasItem(n3));
+ assertThat(wave, hasItem(n4));
+ assertTrue(nodes.isEmpty());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java b/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
new file mode 100644
index 0000000..769515c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
@@ -0,0 +1,198 @@
+// 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.conversion;
+
+import static com.android.tools.r8.shaking.ProguardConfigurationSourceStrings.createConfigurationForTesting;
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppServices;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.conversion.CallGraph.Node;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.shaking.EnqueuerFactory;
+import com.android.tools.r8.shaking.ProguardConfigurationParser;
+import com.android.tools.r8.shaking.RootSetBuilder;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import org.junit.Test;
+
+public class PartialCallGraphTest extends CallGraphTestBase {
+ private final AppView<AppInfoWithLiveness> appView;
+ private final InternalOptions options = new InternalOptions();
+ private final ExecutorService executorService = ThreadUtils.getExecutorService(options);
+
+ public PartialCallGraphTest() throws Exception {
+ Timing timing = new Timing("PartialCallGraphTest.setup");
+ AndroidApp app = testForD8().addProgramClasses(TestClass.class).compile().app;
+ DexApplication application = new ApplicationReader(app, options, timing).read().toDirect();
+ AppView<AppInfoWithSubtyping> appView =
+ AppView.createForR8(new AppInfoWithSubtyping(application), options);
+ appView.setAppServices(AppServices.builder(appView).build());
+ ProguardConfigurationParser parser =
+ new ProguardConfigurationParser(appView.dexItemFactory(), options.reporter);
+ parser.parse(
+ createConfigurationForTesting(
+ ImmutableList.of("-keep class ** { void m1(); void m5(); }")));
+ appView.setRootSet(
+ new RootSetBuilder(
+ appView, application, parser.getConfig().getRules()).run(executorService));
+ Enqueuer enqueuer = EnqueuerFactory.createForInitialTreeShaking(appView);
+ this.appView =
+ appView.setAppInfo(
+ enqueuer.traceApplication(
+ appView.rootSet(),
+ parser.getConfig().getDontWarnPatterns(),
+ executorService,
+ timing));
+ }
+
+ @Test
+ public void testFullGraph() throws Exception {
+ CallGraph cg =
+ new CallGraphBuilder(appView).build(executorService, new Timing("testFullGraph"));
+ Node m1 = findNode(cg.nodes, "m1");
+ Node m2 = findNode(cg.nodes, "m2");
+ Node m3 = findNode(cg.nodes, "m3");
+ Node m4 = findNode(cg.nodes, "m4");
+ Node m5 = findNode(cg.nodes, "m5");
+ Node m6 = findNode(cg.nodes, "m6");
+ assertNotNull(m1);
+ assertNotNull(m2);
+ assertNotNull(m3);
+ assertNotNull(m4);
+ assertNotNull(m5);
+ assertNotNull(m6);
+
+ Set<Node> wave = Sets.newIdentityHashSet();
+
+ MethodProcessor.extractLeaves(cg.nodes, wave::add);
+ assertEquals(4, wave.size()); // including <init>
+ assertThat(wave, hasItem(m3));
+ assertThat(wave, hasItem(m4));
+ assertThat(wave, hasItem(m6));
+ wave.clear();
+
+ MethodProcessor.extractLeaves(cg.nodes, wave::add);
+ assertEquals(2, wave.size());
+ assertThat(wave, hasItem(m2));
+ assertThat(wave, hasItem(m5));
+ wave.clear();
+
+ MethodProcessor.extractLeaves(cg.nodes, wave::add);
+ assertEquals(1, wave.size());
+ assertThat(wave, hasItem(m1));
+ assertTrue(cg.nodes.isEmpty());
+ }
+
+ @Test
+ public void testPartialGraph() throws Exception {
+ DexEncodedMethod em1 = findMethod("m1");
+ DexEncodedMethod em2 = findMethod("m2");
+ DexEncodedMethod em4 = findMethod("m4");
+ DexEncodedMethod em5 = findMethod("m5");
+ assertNotNull(em1);
+ assertNotNull(em2);
+ assertNotNull(em4);
+ assertNotNull(em5);
+
+ CallGraph pg =
+ new PartialCallGraphBuilder(appView, ImmutableSet.of(em1, em2, em4, em5))
+ .build(executorService, new Timing("tetPartialGraph"));
+
+ Node m1 = findNode(pg.nodes, "m1");
+ Node m2 = findNode(pg.nodes, "m2");
+ Node m4 = findNode(pg.nodes, "m4");
+ Node m5 = findNode(pg.nodes, "m5");
+ assertNotNull(m1);
+ assertNotNull(m2);
+ assertNotNull(m4);
+ assertNotNull(m5);
+
+ Set<Node> wave = Sets.newIdentityHashSet();
+
+ PostMethodProcessor.extractRoots(pg.nodes, wave::add);
+ assertEquals(2, wave.size());
+ assertThat(wave, hasItem(m1));
+ assertThat(wave, hasItem(m5));
+ wave.clear();
+
+ PostMethodProcessor.extractRoots(pg.nodes, wave::add);
+ assertEquals(1, wave.size());
+ assertThat(wave, hasItem(m2));
+ wave.clear();
+
+ PostMethodProcessor.extractRoots(pg.nodes, wave::add);
+ assertEquals(1, wave.size());
+ assertThat(wave, hasItem(m4));
+ assertTrue(pg.nodes.isEmpty());
+ }
+
+ private Node findNode(Iterable<Node> nodes, String name) {
+ for (Node n : nodes) {
+ if (n.method.method.name.toString().equals(name)) {
+ return n;
+ }
+ }
+ return null;
+ }
+
+ private DexEncodedMethod findMethod(String name) {
+ for (DexClass clazz : appView.appInfo().classes()) {
+ for (DexEncodedMethod method : clazz.methods()) {
+ if (method.method.name.toString().equals(name)) {
+ return method;
+ }
+ }
+ }
+ return null;
+ }
+
+ static class TestClass {
+ void m1() {
+ System.out.println("m1");
+ m2();
+ }
+
+ void m2() {
+ System.out.println("m2");
+ m3();
+ m4();
+ }
+
+ void m3() {
+ System.out.println("m3");
+ }
+
+ void m4() {
+ System.out.println("m4");
+ }
+
+ void m5() {
+ System.out.println("m5");
+ m6();
+ m4();
+ }
+
+ void m6() {
+ System.out.println("m6");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index 86245a3..5e0e77f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -121,6 +121,9 @@
o.enableInlining = inlining;
o.enableInliningOfInvokesWithNullableReceivers = false;
o.inliningInstructionLimit = 6;
+ // Tests depend on nullability of receiver and argument in general. Learning very accurate
+ // nullability from actual usage in tests bothers what we want to test.
+ o.enableCallSiteOptimizationInfoPropagation = false;
});
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/TrivialTypeTestsAfterBranchPruningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/TrivialTypeTestsAfterBranchPruningTest.java
new file mode 100644
index 0000000..a1577a3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/TrivialTypeTestsAfterBranchPruningTest.java
@@ -0,0 +1,103 @@
+// 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.checkcast;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+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.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+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 TrivialTypeTestsAfterBranchPruningTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public TrivialTypeTestsAfterBranchPruningTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(TrivialTypeTestsAfterBranchPruningTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject classSubject = inspector.clazz(TestClass.class);
+ assertThat(classSubject, isPresent());
+ assertThat(classSubject.uniqueMethodWithName("dead"), not(isPresent()));
+
+ MethodSubject trivialCastMethodSubject =
+ classSubject.uniqueMethodWithName("trivialCastAfterBranchPruningTest");
+ assertThat(trivialCastMethodSubject, isPresent());
+ assertTrue(
+ trivialCastMethodSubject.streamInstructions().noneMatch(InstructionSubject::isCheckCast));
+
+ MethodSubject branchPruningMethodSubject =
+ classSubject.uniqueMethodWithName("branchPruningAfterInstanceOfOptimization");
+ assertThat(branchPruningMethodSubject, isPresent());
+ assertTrue(
+ branchPruningMethodSubject
+ .streamInstructions()
+ .noneMatch(InstructionSubject::isInstanceOf));
+ }
+
+ static class TestClass {
+
+ static boolean alwaysTrue = true;
+
+ public static void main(String[] args) {
+ branchPruningAfterInstanceOfOptimization(trivialCastAfterBranchPruningTest());
+ }
+
+ @NeverInline
+ static A trivialCastAfterBranchPruningTest() {
+ return (A) (alwaysTrue ? new A() : new B());
+ }
+
+ @NeverInline
+ static void branchPruningAfterInstanceOfOptimization(A obj) {
+ if (obj instanceof A) {
+ System.out.println("Hello world!");
+ } else {
+ dead();
+ }
+ }
+
+ @NeverInline
+ static void dead() {
+ throw new RuntimeException();
+ }
+ }
+
+ static class A {}
+
+ static class B {}
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index fbcc6a1..afdfabb 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -64,7 +64,8 @@
@RunWith(Parameterized.class)
public class ClassInlinerTest extends TestBase {
- private Backend backend;
+
+ private final Backend backend;
@Parameterized.Parameters(name = "Backend: {0}")
public static Backend[] data() {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/EscapingBuilderTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/EscapingBuilderTest.java
index 4dd3551..bf5e92a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/EscapingBuilderTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/EscapingBuilderTest.java
@@ -74,7 +74,11 @@
}
@NeverInline
- public static void escape(Object o) {}
+ static void escape(Object o) {
+ if (System.currentTimeMillis() < 0) {
+ System.out.println(o);
+ }
+ }
}
// Simple builder that should be class inlined.
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ifs/EnumComparisonTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ifs/EnumComparisonTest.java
new file mode 100644
index 0000000..94d775b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ifs/EnumComparisonTest.java
@@ -0,0 +1,78 @@
+// 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.ifs;
+
+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.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.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+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 EnumComparisonTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public EnumComparisonTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(EnumComparisonTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("MyEnum.A == MyEnum.A", "MyEnum.A != MyEnum.B");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject classSubject = inspector.clazz(TestClass.class);
+ assertThat(classSubject, isPresent());
+
+ MethodSubject mainMethodSubject = classSubject.mainMethod();
+ assertThat(mainMethodSubject, isPresent());
+ assertTrue(mainMethodSubject.streamInstructions().noneMatch(InstructionSubject::isThrow));
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ if (MyEnum.A == MyEnum.A) {
+ System.out.println("MyEnum.A == MyEnum.A");
+ } else {
+ throw new RuntimeException();
+ }
+ if (MyEnum.A == MyEnum.B) {
+ throw new RuntimeException();
+ } else {
+ System.out.println("MyEnum.A != MyEnum.B");
+ }
+ }
+ }
+
+ enum MyEnum {
+ A,
+ B
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
index e8308f7..5f1b7a5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
@@ -4,19 +4,19 @@
package com.android.tools.r8.ir.optimize.inliner;
-import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
+import com.android.tools.r8.AlwaysInline;
import com.android.tools.r8.AssumeMayHaveSideEffects;
import com.android.tools.r8.NeverClassInline;
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.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -25,14 +25,17 @@
@RunWith(Parameterized.class)
public class SingleTargetAfterInliningTest extends TestBase {
+ private final int maxInliningDepth;
private final TestParameters parameters;
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ @Parameters(name = "{1}, max inlining depth: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ ImmutableList.of(0, 1), getTestParameters().withAllRuntimesAndApiLevels().build());
}
- public SingleTargetAfterInliningTest(TestParameters parameters) {
+ public SingleTargetAfterInliningTest(int maxInliningDepth, TestParameters parameters) {
+ this.maxInliningDepth = maxInliningDepth;
this.parameters = parameters;
}
@@ -41,31 +44,41 @@
testForR8(parameters.getBackend())
.addInnerClasses(SingleTargetAfterInliningTest.class)
.addKeepMainRule(TestClass.class)
+ .addOptionsModification(
+ options -> {
+ options.applyInliningToInlinee = true;
+ options.applyInliningToInlineeMaxDepth = maxInliningDepth;
+ })
+ .enableAlwaysInliningAnnotations()
.enableClassInliningAnnotations()
.enableSideEffectAnnotations()
- .setMinApi(parameters.getRuntime())
- .noMinification()
+ .setMinApi(parameters.getApiLevel())
.compile()
.inspect(this::inspect)
.run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutputLines("B");
+ .assertSuccessWithOutputLines("B.foo()", "B.bar()");
}
private void inspect(CodeInspector inspector) {
- ClassSubject aClassSubject = inspector.clazz(A.class);
- assertThat(aClassSubject, isPresent());
-
ClassSubject testClassSubject = inspector.clazz(TestClass.class);
assertThat(testClassSubject, isPresent());
// The indirection() method should be inlined.
assertThat(testClassSubject.uniqueMethodWithName("indirection"), not(isPresent()));
- // The main() method invokes A.method().
- // TODO(b/141451716): A.method() should be inlined into main().
- MethodSubject mainMethodSubject = testClassSubject.mainMethod();
- assertThat(mainMethodSubject, isPresent());
- assertThat(mainMethodSubject, invokesMethod(aClassSubject.uniqueMethodWithName("method")));
+ // A.foo() should be absent if the max inlining depth is 1, because indirection() has been
+ // inlined into main(), which makes A.foo() eligible for inlining into main().
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ if (maxInliningDepth == 0) {
+ assertThat(aClassSubject.uniqueMethodWithName("foo"), isPresent());
+ } else {
+ assert maxInliningDepth == 1;
+ assertThat(aClassSubject.uniqueMethodWithName("foo"), not(isPresent()));
+ }
+
+ // A.bar() should always be inlined because it is marked as @AlwaysInline.
+ assertThat(aClassSubject.uniqueMethodWithName("bar"), not(isPresent()));
}
static class TestClass {
@@ -78,13 +91,16 @@
}
private static void indirection(A obj) {
- obj.method();
+ obj.foo();
+ obj.bar();
}
}
abstract static class A {
- abstract void method();
+ abstract void foo();
+
+ abstract void bar();
}
@NeverClassInline
@@ -94,8 +110,14 @@
B() {}
@Override
- void method() {
- System.out.println("B");
+ void foo() {
+ System.out.println("B.foo()");
+ }
+
+ @AlwaysInline
+ @Override
+ void bar() {
+ System.out.println("B.bar()");
}
}
@@ -106,8 +128,13 @@
C() {}
@Override
- void method() {
- System.out.println("C");
+ void foo() {
+ System.out.println("C.foo()");
+ }
+
+ @Override
+ void bar() {
+ System.out.println("C.bar()");
}
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
index b0828fe..f0c6a78 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -167,6 +167,7 @@
testForR8(parameters.getBackend())
.addProgramClasses(classes)
.enableInliningAnnotations()
+ .enableSideEffectAnnotations()
.addKeepMainRule(main)
.allowAccessModification()
.noMinification()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/MoveToHostFieldOnlyTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/MoveToHostFieldOnlyTestClass.java
index f725d21..eb8e6a4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/MoveToHostFieldOnlyTestClass.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/MoveToHostFieldOnlyTestClass.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize.staticizer.movetohost;
+import com.android.tools.r8.AssumeMayHaveSideEffects;
import com.android.tools.r8.NeverInline;
public class MoveToHostFieldOnlyTestClass {
@@ -12,6 +13,7 @@
test.testOk_fieldOnly();
}
+ @AssumeMayHaveSideEffects
@NeverInline
private void testOk_fieldOnly() {
// Any instance method call whose target holder is not the candidate will invalidate candidacy,
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalTest.java
new file mode 100644
index 0000000..d2fa3f8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalTest.java
@@ -0,0 +1,119 @@
+// 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.unusedinterfaces;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverMerge;
+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.CodeInspector;
+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 UnusedInterfaceRemovalTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public UnusedInterfaceRemovalTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(UnusedInterfaceRemovalTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableMergeAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("A.foo()", "A.bar()");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject iClassSubject = inspector.clazz(I.class);
+ assertThat(iClassSubject, isPresent());
+
+ ClassSubject jClassSubject = inspector.clazz(J.class);
+ assertThat(jClassSubject, isPresent());
+
+ ClassSubject kClassSubject = inspector.clazz(K.class);
+ assertThat(kClassSubject, not(isPresent()));
+
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertEquals(2, aClassSubject.getDexClass().interfaces.size());
+ assertEquals(
+ aClassSubject.getDexClass().interfaces.values[0], iClassSubject.getDexClass().type);
+ assertEquals(
+ aClassSubject.getDexClass().interfaces.values[1], jClassSubject.getDexClass().type);
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ I obj = System.currentTimeMillis() >= 0 ? new A() : new B();
+ obj.foo();
+ ((J) obj).bar();
+ }
+ }
+
+ @NeverMerge
+ interface I {
+
+ void foo();
+ }
+
+ @NeverMerge
+ interface J {
+
+ void bar();
+ }
+
+ interface K extends I, J {}
+
+ static class A implements K {
+
+ @Override
+ public void foo() {
+ System.out.println("A.foo()");
+ }
+
+ @Override
+ public void bar() {
+ System.out.println("A.bar()");
+ }
+ }
+
+ // To prevent that we detect a single target and start inlining or rewriting the signature in the
+ // invoke.
+ static class B implements K {
+
+ @Override
+ public void foo() {
+ throw new RuntimeException();
+ }
+
+ @Override
+ public void bar() {
+ throw new RuntimeException();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java
new file mode 100644
index 0000000..90761db
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java
@@ -0,0 +1,97 @@
+// 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.unusedinterfaces;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+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.CodeInspector;
+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 UnusedInterfaceWithDefaultMethodTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public UnusedInterfaceWithDefaultMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(UnusedInterfaceWithDefaultMethodTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .enableMergeAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("J.m()");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject iClassSubject = inspector.clazz(I.class);
+ assertThat(iClassSubject, isPresent());
+
+ ClassSubject jClassSubject = inspector.clazz(J.class);
+ assertThat(jClassSubject, isPresent());
+
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+
+ // Verify that J is not considered an unused interface, since it provides an implementation of
+ // m() that happens to be used.
+ assertEquals(1, aClassSubject.getDexClass().interfaces.size());
+ assertEquals(
+ jClassSubject.getDexClass().type, aClassSubject.getDexClass().interfaces.values[0]);
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ indirection(new A());
+ }
+
+ @NeverInline
+ private static void indirection(I obj) {
+ obj.m();
+ }
+ }
+
+ @NeverMerge
+ interface I {
+
+ void m();
+ }
+
+ @NeverMerge
+ interface J extends I {
+
+ @NeverInline
+ @Override
+ default void m() {
+ System.out.println("J.m()");
+ }
+ }
+
+ static class A implements J {}
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
index f0a8a84..cfeb611 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
@@ -291,30 +291,18 @@
@Test
public void testAccessor() throws Exception {
TestKotlinCompanionClass testedClass = ACCESSOR_COMPANION_PROPERTY_CLASS;
- String mainClass = addMainToClasspath("accessors.AccessorKt",
- "accessor_accessPropertyFromCompanionClass");
+ String mainClass =
+ addMainToClasspath("accessors.AccessorKt", "accessor_accessPropertyFromCompanionClass");
runTest(
"accessors",
mainClass,
disableClassStaticizer,
- (app) -> {
+ app -> {
+ // The classes are removed entirely as a result of member value propagation, inlining, and
+ // the fact that the classes do not have observable side effects.
CodeInspector codeInspector = new CodeInspector(app);
- ClassSubject outerClass =
- checkClassIsKept(codeInspector, testedClass.getOuterClassName());
- ClassSubject companionClass = checkClassIsKept(codeInspector, testedClass.getClassName());
-
- // Property field has been removed due to member value propagation.
- String propertyName = "property";
- checkFieldIsRemoved(outerClass, JAVA_LANG_STRING, propertyName);
-
- // The getter is always inlined since it just calls into the accessor.
- MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
- checkMethodIsAbsent(companionClass, getter);
-
- // The accessor is also inlined.
- MemberNaming.MethodSignature getterAccessor =
- testedClass.getGetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION);
- checkMethodIsRemoved(outerClass, getterAccessor);
+ checkClassIsRemoved(codeInspector, testedClass.getOuterClassName());
+ checkClassIsRemoved(codeInspector, testedClass.getClassName());
});
}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceClInitTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceClInitTest.java
new file mode 100644
index 0000000..cb4bed0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceClInitTest.java
@@ -0,0 +1,104 @@
+// 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.naming.applymapping;
+
+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.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+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.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Ignore;
+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 ApplyMappingInterfaceClInitTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ApplyMappingInterfaceClInitTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ @Ignore("b/142909857")
+ public void testNotRenamingClInitIfNotInMap()
+ throws ExecutionException, CompilationFailedException, IOException {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ApplyMappingInterfaceClInitTest.class)
+ .addKeepMainRule(Main.class)
+ .addKeepClassAndMembersRules(TestInterface.class)
+ .addApplyMapping("")
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .inspect(this::verifyNoRenamingOfClInit);
+ }
+
+ @Test
+ @Ignore("b/142909857")
+ public void testNotRenamingClInitIfInMap()
+ throws ExecutionException, CompilationFailedException, IOException {
+ String interfaceName = TestInterface.class.getTypeName();
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ApplyMappingInterfaceClInitTest.class)
+ .addKeepMainRule(Main.class)
+ .addKeepClassAndMembersRules(TestInterface.class)
+ .addApplyMapping(
+ StringUtils.lines(
+ interfaceName + " -> " + interfaceName + ":", " void <clinit>() -> a"))
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .inspect(this::verifyNoRenamingOfClInit);
+ }
+
+ private void verifyNoRenamingOfClInit(CodeInspector inspector) {
+ ClassSubject interfaceSubject = inspector.clazz(TestInterface.class);
+ assertThat(interfaceSubject, isPresent());
+ interfaceSubject.allMethods().stream()
+ .allMatch(
+ method -> {
+ boolean classInitNotRenamed = !method.isClassInitializer() || !method.isRenamed();
+ assertTrue(classInitNotRenamed);
+ return classInitNotRenamed;
+ });
+ }
+
+ public interface TestInterface {
+ Throwable t = new Throwable();
+
+ void foo();
+ }
+
+ @NeverClassInline
+ public static class Main implements TestInterface {
+
+ public static void main(String[] args) {
+ new Main().foo();
+ }
+
+ @Override
+ @NeverInline
+ public void foo() {
+ System.out.println("Hello World!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationShakingBehaviorTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationShakingBehaviorTest.java
new file mode 100644
index 0000000..484fb10
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationShakingBehaviorTest.java
@@ -0,0 +1,159 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking.annotations;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+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 java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+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;
+
+@RunWith(Parameterized.class)
+public class AnnotationShakingBehaviorTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public AnnotationShakingBehaviorTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testKeepUnusedTypeInAnnotation()
+ throws IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Factory.class, Main.class, C.class)
+ .addKeepMainRule(Main.class)
+ .addKeepClassAndMembersRules(Factory.class)
+ .addKeepAttributes("*Annotation*")
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello World!")
+ .inspect(inspector -> assertThat(inspector.clazz(C.class), isPresent()));
+ }
+
+ @Test
+ public void testWillKeepAnnotatedProgramClass()
+ throws IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Factory.class, MainWithMethodAnnotation.class, C.class)
+ .addKeepMainRule(MainWithMethodAnnotation.class)
+ .addKeepClassAndMembersRules(Factory.class)
+ .addKeepAttributes("*Annotation*")
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), MainWithMethodAnnotation.class)
+ .assertSuccessWithOutputLines("Hello World!")
+ .inspect(inspector -> assertThat(inspector.clazz(C.class), isPresent()));
+ }
+
+ @Test
+ public void testGenericSignatureDoNotKeepType()
+ throws IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(MainWithGenericC.class, C.class)
+ .addKeepMainRule(MainWithGenericC.class)
+ .addKeepAttributes("Signature", "InnerClasses", "EnclosingMethod")
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), MainWithGenericC.class)
+ .assertSuccessWithOutputLines("Hello World!")
+ .inspect(
+ inspector -> {
+ assertThat(inspector.clazz(C.class), not(isPresent()));
+ });
+ }
+
+ @Test
+ public void testDisappearsWhenMerging()
+ throws IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Factory.class, MainWithNewB.class, A.class, B.class, C.class)
+ .addKeepMainRule(MainWithNewB.class)
+ .addKeepClassAndMembersRules(Factory.class)
+ .addKeepAttributes("*Annotation*")
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), MainWithNewB.class)
+ .assertSuccessWithOutputLines("Hello World!")
+ .inspect(
+ inspector -> {
+ assertThat(inspector.clazz(C.class), not(isPresent()));
+ });
+ }
+
+ @Retention(value = RetentionPolicy.RUNTIME)
+ public @interface Factory {
+ Class<?> ref() default Object.class;
+ }
+
+ public static class C {}
+
+ @Factory(ref = C.class) // <-- we are explicitly keeping Main.
+ public static class Main {
+
+ public static void main(String[] args) {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public static class MainWithMethodAnnotation {
+
+ public static void main(String[] args) {
+ test();
+ }
+
+ @Factory(ref = C.class) // <-- We are not explicitly saying that test() should be kept.
+ @NeverInline
+ public static void test() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public static class MainWithGenericC {
+
+ static List<C> cs = new ArrayList<>();
+
+ public static void main(String[] args) {
+ if (cs.size() == args.length) {
+ System.out.println("Hello World!");
+ }
+ }
+ }
+
+ @Factory(ref = C.class)
+ public static class A {}
+
+ @NeverClassInline
+ public static class B extends A {
+ @NeverInline
+ public void world() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public static class MainWithNewB {
+
+ public static void main(String[] args) {
+ new B().world();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/LibraryAndMissingAnnotationsTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/LibraryAndMissingAnnotationsTest.java
new file mode 100644
index 0000000..4e6adad
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/LibraryAndMissingAnnotationsTest.java
@@ -0,0 +1,181 @@
+// 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.shaking.annotations;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.BaseCompilerCommand;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.function.Function;
+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 LibraryAndMissingAnnotationsTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final boolean includeOnLibraryPath;
+
+ @Parameters(name = "{0}, includeOnLibraryPath: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+ }
+
+ private static Function<TestParameters, Path> compilationResults =
+ memoizeFunction(LibraryAndMissingAnnotationsTest::compileLibraryAnnotationToRuntime);
+
+ private static Path compileLibraryAnnotationToRuntime(TestParameters parameters)
+ throws CompilationFailedException, IOException {
+ return testForR8(getStaticTemp(), parameters.getBackend())
+ .addProgramClasses(LibraryAnnotation.class)
+ .addKeepClassAndMembersRulesWithAllowObfuscation(LibraryAnnotation.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .writeToZip();
+ }
+
+ public LibraryAndMissingAnnotationsTest(TestParameters parameters, boolean includeOnLibraryPath) {
+ this.parameters = parameters;
+ this.includeOnLibraryPath = includeOnLibraryPath;
+ }
+
+ @Test
+ public void testMainWithUseR8() throws Exception {
+ runTest(testForR8(parameters.getBackend()), MainWithUse.class);
+ }
+
+ @Test
+ public void testMainWithUseR8Compat() throws Exception {
+ runTest(testForR8Compat(parameters.getBackend()), MainWithUse.class);
+ }
+
+ @Test
+ public void testMainWithUseProguard() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ runTest(testForProguard(), MainWithUse.class);
+ }
+
+ @Test
+ public void testMainWithoutUseR8() throws Exception {
+ runTest(testForR8(parameters.getBackend()), MainWithoutUse.class);
+ }
+
+ @Test
+ public void testMainWithoutUseR8Compat() throws Exception {
+ runTest(testForR8Compat(parameters.getBackend()), MainWithoutUse.class);
+ }
+
+ @Test
+ public void testMainWithoutUseProguard() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ runTest(testForProguard(), MainWithoutUse.class);
+ }
+
+ private <
+ C extends BaseCompilerCommand,
+ B extends BaseCompilerCommand.Builder<C, B>,
+ CR extends TestCompileResult<CR, RR>,
+ RR extends TestRunResult<RR>,
+ T extends TestShrinkerBuilder<C, B, CR, RR, T>>
+ void runTest(TestShrinkerBuilder<C, B, CR, RR, T> builder, Class<?> mainClass)
+ throws Exception {
+ T t =
+ builder
+ .addProgramClasses(Foo.class, mainClass)
+ .addKeepAttributes("*Annotation*")
+ .addLibraryFiles(runtimeJar(parameters))
+ .addKeepClassAndMembersRules(Foo.class)
+ .addKeepRules("-dontwarn " + LibraryAndMissingAnnotationsTest.class.getTypeName())
+ .addKeepMainRule(mainClass)
+ .setMinApi(parameters.getApiLevel());
+ if (includeOnLibraryPath) {
+ t.addLibraryClasses(LibraryAnnotation.class);
+ } else {
+ t.addKeepRules("-dontwarn " + LibraryAnnotation.class.getTypeName());
+ }
+ t.compile()
+ .addRunClasspathFiles(compilationResults.apply(parameters))
+ .run(parameters.getRuntime(), mainClass)
+ .inspect(
+ inspector -> {
+ ClassSubject clazz = inspector.clazz(Foo.class);
+ assertThat(clazz, isPresent());
+ assertThat(clazz.annotation(LibraryAnnotation.class.getTypeName()), isPresent());
+ MethodSubject foo = clazz.uniqueMethodWithName("foo");
+ assertThat(foo, isPresent());
+ assertThat(foo.annotation(LibraryAnnotation.class.getTypeName()), isPresent());
+ assertFalse(foo.getMethod().parameterAnnotationsList.isEmpty());
+ assertEquals(
+ LibraryAnnotation.class.getTypeName(),
+ foo.getMethod()
+ .parameterAnnotationsList
+ .get(0)
+ .annotations[0]
+ .getAnnotationType()
+ .toSourceString());
+ assertThat(
+ clazz
+ .uniqueFieldWithName("bar")
+ .annotation(LibraryAnnotation.class.getTypeName()),
+ isPresent());
+ })
+ .assertSuccessWithOutputLines("Hello World!");
+ }
+
+ @Test
+ public void testMainWithoutUse() {}
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface LibraryAnnotation {}
+
+ @LibraryAnnotation
+ public static class Foo {
+
+ @LibraryAnnotation public String bar = "Hello World!";
+
+ @LibraryAnnotation
+ public void foo(@LibraryAnnotation String arg) {
+ System.out.println(arg);
+ }
+ }
+
+ public static class MainWithoutUse {
+
+ public static void main(String[] args) {
+ Foo foo = new Foo();
+ foo.foo(foo.bar);
+ }
+ }
+
+ public static class MainWithUse {
+ public static void main(String[] args) {
+ if (args.length > 0 && args[0].equals(LibraryAnnotation.class.getTypeName())) {
+ System.out.print("This will never be printed");
+ }
+ Foo foo = new Foo();
+ foo.foo(foo.bar);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/PrunedOrMergedAnnotationTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/PrunedOrMergedAnnotationTest.java
new file mode 100644
index 0000000..45b29f7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/PrunedOrMergedAnnotationTest.java
@@ -0,0 +1,119 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking.annotations;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+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.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexEncodedAnnotation;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+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;
+
+@RunWith(Parameterized.class)
+public class PrunedOrMergedAnnotationTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public PrunedOrMergedAnnotationTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRewritingInFactory()
+ throws IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(PrunedOrMergedAnnotationTest.class)
+ .addKeepMainRule(Main.class)
+ .addKeepAttributes("*Annotation*")
+ .addKeepClassAndMembersRules(Factory.class)
+ .enableInliningAnnotations()
+ .enableClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello", "World!")
+ .inspect(
+ inspector -> {
+ assertThat(inspector.clazz(A.class), not(isPresent()));
+ DexType mergedType = inspector.clazz(B.class).getDexClass().type;
+ ClassSubject classC = inspector.clazz(C.class);
+ assertThat(classC, isPresent());
+ DexEncodedAnnotation annotation =
+ classC.annotation(Factory.class.getTypeName()).getAnnotation();
+ assertTrue(valueIsDexType(mergedType, annotation.elements[0].value));
+ assertTrue(
+ Arrays.stream(annotation.elements[1].value.asDexValueArray().getValues())
+ .allMatch(value -> valueIsDexType(mergedType, value)));
+ // Check that method parameter annotations are rewritten as well.
+ DexEncodedMethod method = inspector.clazz(Main.class).mainMethod().getMethod();
+ DexAnnotationSet annotationSet = method.parameterAnnotationsList.get(0);
+ DexEncodedAnnotation parameterAnnotation = annotationSet.annotations[0].annotation;
+ assertTrue(valueIsDexType(mergedType, parameterAnnotation.elements[0].value));
+ });
+ }
+
+ private boolean valueIsDexType(DexType type, DexValue value) {
+ assertTrue(value.isDexValueType());
+ assertEquals(type, value.asDexValueType().value);
+ return true;
+ }
+
+ public @interface Factory {
+
+ Class<?> extending() default Object.class;
+
+ Class<?>[] other() default Object[].class;
+ }
+
+ public static class A {}
+
+ @NeverClassInline
+ public static class B extends A {
+ @NeverInline
+ public void world() {
+ System.out.println("World!");
+ }
+ }
+
+ @Factory(
+ extending = A.class,
+ other = {A.class, B.class})
+ public static class C {
+ @NeverInline
+ public static void hello() {
+ System.out.println("Hello");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(@Factory(extending = A.class) String[] args) {
+ C.hello();
+ new B().world();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideTest.java b/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideTest.java
index ca354f9..a5fbc59 100644
--- a/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideTest.java
@@ -8,7 +8,6 @@
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
-import com.android.tools.r8.AssumeMayHaveSideEffects;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverMerge;
import com.android.tools.r8.TestBase;
@@ -29,7 +28,7 @@
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public LibraryMethodOverrideTest(TestParameters parameters) {
@@ -44,8 +43,7 @@
.addOptionsModification(options -> options.enableTreeShakingOfLibraryMethodOverrides = true)
.enableClassInliningAnnotations()
.enableMergeAnnotations()
- .enableSideEffectAnnotations()
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
.inspect(this::verifyOutput)
.run(parameters.getRuntime(), TestClass.class)
@@ -68,7 +66,16 @@
for (Class<?> nonEscapingClass : nonEscapingClasses) {
ClassSubject classSubject = inspector.clazz(nonEscapingClass);
assertThat(classSubject, isPresent());
- assertThat(classSubject.uniqueMethodWithName("toString"), not(isPresent()));
+
+ // TODO(b/142772856): None of the non-escaping classes should have a toString() method. It is
+ // a requirement that the instance initializers are considered trivial for this to work,
+ // though, even when they have a side effect (as long as the receiver does not escape via the
+ // side effecting instruction).
+ if (nonEscapingClass == DoesNotEscapeWithSubThatDoesNotOverrideSub.class) {
+ assertThat(classSubject.uniqueMethodWithName("toString"), not(isPresent()));
+ } else {
+ assertThat(classSubject.uniqueMethodWithName("toString"), isPresent());
+ }
}
}
@@ -128,8 +135,12 @@
@NeverClassInline
static class DoesNotEscape {
- @AssumeMayHaveSideEffects
- DoesNotEscape() {}
+ // TODO(b/142772856): Should be classified as a trivial instance initializer although it has a
+ // side effect.
+ DoesNotEscape() {
+ // Side effect to ensure that the constructor is not removed from main().
+ System.out.print("");
+ }
@Override
public String toString() {
@@ -140,8 +151,12 @@
@NeverClassInline
static class DoesNotEscapeWithSubThatDoesNotOverride {
- @AssumeMayHaveSideEffects
- DoesNotEscapeWithSubThatDoesNotOverride() {}
+ // TODO(b/142772856): Should be classified as a trivial instance initializer although it has a
+ // side effect.
+ DoesNotEscapeWithSubThatDoesNotOverride() {
+ // Side effect to ensure that the constructor is not removed from main().
+ System.out.print("");
+ }
@Override
public String toString() {
@@ -153,15 +168,23 @@
static class DoesNotEscapeWithSubThatDoesNotOverrideSub
extends DoesNotEscapeWithSubThatDoesNotOverride {
- @AssumeMayHaveSideEffects
- DoesNotEscapeWithSubThatDoesNotOverrideSub() {}
+ // TODO(b/142772856): Should be classified as a trivial instance initializer although it has a
+ // side effect.
+ DoesNotEscapeWithSubThatDoesNotOverrideSub() {
+ // Side effect to ensure that the constructor is not removed from main().
+ System.out.print("");
+ }
}
@NeverClassInline
static class DoesNotEscapeWithSubThatOverrides {
- @AssumeMayHaveSideEffects
- DoesNotEscapeWithSubThatOverrides() {}
+ // TODO(b/142772856): Should be classified as a trivial instance initializer although it has a
+ // side effect.
+ DoesNotEscapeWithSubThatOverrides() {
+ // Side effect to ensure that the constructor is not removed from main().
+ System.out.print("");
+ }
@Override
public String toString() {
@@ -172,8 +195,12 @@
@NeverClassInline
static class DoesNotEscapeWithSubThatOverridesSub extends DoesNotEscapeWithSubThatOverrides {
- @AssumeMayHaveSideEffects
- DoesNotEscapeWithSubThatOverridesSub() {}
+ // TODO(b/142772856): Should be classified as a trivial instance initializer although it has a
+ // side effect.
+ DoesNotEscapeWithSubThatOverridesSub() {
+ // Side effect to ensure that the constructor is not removed from main().
+ System.out.print("");
+ }
@Override
public String toString() {
@@ -188,8 +215,12 @@
@NeverClassInline
static class DoesNotEscapeWithSubThatOverridesAndEscapes {
- @AssumeMayHaveSideEffects
- DoesNotEscapeWithSubThatOverridesAndEscapes() {}
+ // TODO(b/142772856): Should be classified as a trivial instance initializer although it has a
+ // side effect.
+ DoesNotEscapeWithSubThatOverridesAndEscapes() {
+ // Side effect to ensure that the constructor is not removed from main().
+ System.out.print("");
+ }
@Override
public String toString() {
@@ -201,8 +232,12 @@
static class DoesNotEscapeWithSubThatOverridesAndEscapesSub
extends DoesNotEscapeWithSubThatOverridesAndEscapes {
- @AssumeMayHaveSideEffects
- DoesNotEscapeWithSubThatOverridesAndEscapesSub() {}
+ // TODO(b/142772856): Should be classified as a trivial instance initializer although it has a
+ // side effect.
+ DoesNotEscapeWithSubThatOverridesAndEscapesSub() {
+ // Side effect to ensure that the constructor is not removed from main().
+ System.out.print("");
+ }
@Override
public String toString() {
diff --git a/src/test/r8OnArtBackport/FileAlreadyExistsException.java b/src/test/r8OnArtBackport/FileAlreadyExistsException.java
new file mode 100644
index 0000000..a725932
--- /dev/null
+++ b/src/test/r8OnArtBackport/FileAlreadyExistsException.java
@@ -0,0 +1,13 @@
+// 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 java.nio.file;
+
+import java.io.IOException;
+
+public class FileAlreadyExistsException extends IOException {
+ public FileAlreadyExistsException(String file) {
+ super(file);
+ }
+}
diff --git a/src/test/r8OnArtBackport/Files.java b/src/test/r8OnArtBackport/Files.java
new file mode 100644
index 0000000..99d1956
--- /dev/null
+++ b/src/test/r8OnArtBackport/Files.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 java.nio.file;
+
+import com.android.tools.r8.MockedPath;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+
+public final class Files {
+ private Files() {}
+
+ public static boolean exists(Path path, LinkOption... options) {
+ if (options.length != 0) {
+ throw new RuntimeException("Unsupported in the Files mock.");
+ }
+ return new File(path.toString()).exists();
+ }
+
+ public static boolean isDirectory(Path path, LinkOption... options) {
+ if (options.length != 0) {
+ throw new RuntimeException("Unsupported in the Files mock.");
+ }
+ return new File(path.toString()).isDirectory();
+ }
+
+ public static byte[] readAllBytes(Path path) throws IOException {
+ if (!(path instanceof MockedPath)) {
+ throw new RuntimeException("Unsupported in the Files mock.");
+ }
+ MockedPath mockedPath = (MockedPath) path;
+ return mockedPath.getAllBytes();
+ }
+
+ public static OutputStream newOutputStream(Path path, OpenOption... options) throws IOException {
+ boolean append = false;
+ for (OpenOption option : options) {
+ if (option == StandardOpenOption.APPEND) {
+ append = true;
+ }
+ }
+ if (!(path instanceof MockedPath)) {
+ throw new RuntimeException("Unsupported in the Files mock.");
+ }
+ MockedPath mockedPath = (MockedPath) path;
+ return mockedPath.newOutputStream(append);
+ }
+}
diff --git a/src/test/r8OnArtBackport/LinkOption.java b/src/test/r8OnArtBackport/LinkOption.java
new file mode 100644
index 0000000..3b4b510
--- /dev/null
+++ b/src/test/r8OnArtBackport/LinkOption.java
@@ -0,0 +1,7 @@
+// 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 java.nio.file;
+
+public interface LinkOption {}
diff --git a/src/test/r8OnArtBackport/MockedPath.java b/src/test/r8OnArtBackport/MockedPath.java
new file mode 100644
index 0000000..c5147df
--- /dev/null
+++ b/src/test/r8OnArtBackport/MockedPath.java
@@ -0,0 +1,151 @@
+// 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;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+
+// Mock Path since Path is not present before Dex-8.
+// File is present and it contains most of the utilities.
+public class MockedPath implements Path {
+
+ private MockedPath(File file) {
+ this.wrappedFile = file;
+ }
+
+ public static Path of(File file, String... more) {
+ // Delegate to File separator management.
+ File current = file;
+ for (String s : more) {
+ current = new File(current.getPath(), s);
+ }
+ return new MockedPath(current);
+ }
+
+ public static Path of(String first, String... more) {
+ return of(new File(first), more);
+ }
+
+ // MockedPath wraps the path in a file.
+ private File wrappedFile;
+
+ @Override
+ public FileSystem getFileSystem() {
+ throw new RuntimeException("Mocked Path does not implement getFileSystem");
+ }
+
+ @Override
+ public boolean isAbsolute() {
+ throw new RuntimeException("Mocked Path does not implement isAbsolute");
+ }
+
+ @Override
+ public Path getRoot() {
+ throw new RuntimeException("Mocked Path does not implement getRoot");
+ }
+
+ @Override
+ public Path getFileName() {
+ return of(wrappedFile.getName());
+ }
+
+ @Override
+ public Path getParent() {
+ return of(wrappedFile.getParent());
+ }
+
+ @Override
+ public int getNameCount() {
+ throw new RuntimeException("Mocked Path does not implement getNameCount");
+ }
+
+ @Override
+ public Path getName(int index) {
+ throw new RuntimeException("Mocked Path does not implement getName");
+ }
+
+ @Override
+ public Path subpath(int beginIndex, int endIndex) {
+ throw new RuntimeException("Mocked Path does not implement subpath");
+ }
+
+ @Override
+ public boolean startsWith(Path other) {
+ throw new RuntimeException("Mocked Path does not implement startsWith");
+ }
+
+ @Override
+ public boolean endsWith(Path other) {
+ throw new RuntimeException("Mocked Path does not implement endswith");
+ }
+
+ @Override
+ public Path normalize() {
+ throw new RuntimeException("Mocked Path does not implement normalize");
+ }
+
+ @Override
+ public Path resolve(Path other) {
+ return new MockedPath(new File(wrappedFile.getPath(), other.toString()));
+ }
+
+ @Override
+ public Path relativize(Path other) {
+ throw new RuntimeException("Mocked Path does not implement relativize");
+ }
+
+ @Override
+ public URI toUri() {
+ throw new RuntimeException("Mocked Path does not implement toUri");
+ }
+
+ @Override
+ public Path toAbsolutePath() {
+ throw new RuntimeException("Mocked Path does not implement toAbsolutePath");
+ }
+
+ @Override
+ public Path toRealPath(LinkOption... options) throws IOException {
+ throw new RuntimeException("Mocked Path does not implement toRealPath");
+ }
+
+ @Override
+ public int compareTo(Path other) {
+ throw new RuntimeException("Mocked Path does not implement compareTo");
+ }
+
+ @Override
+ public String toString() {
+ return wrappedFile.toString();
+ }
+
+ @Override
+ public File toFile() {
+ return wrappedFile;
+ }
+
+ // Compatibility with Files.
+
+ public byte[] getAllBytes() throws IOException {
+ FileInputStream fileInputStream = new FileInputStream(wrappedFile);
+ // In android the result of file.length() is long
+ // byte count of the file-content
+ long byteLength = wrappedFile.length();
+ byte[] filecontent = new byte[(int) byteLength];
+ fileInputStream.read(filecontent, 0, (int) byteLength);
+ return filecontent;
+ }
+
+ public OutputStream newOutputStream(boolean append) throws IOException {
+ return new FileOutputStream(wrappedFile, append);
+ }
+}
diff --git a/src/test/r8OnArtBackport/NoSuchFileException.java b/src/test/r8OnArtBackport/NoSuchFileException.java
new file mode 100644
index 0000000..9c8e9cf
--- /dev/null
+++ b/src/test/r8OnArtBackport/NoSuchFileException.java
@@ -0,0 +1,13 @@
+// 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 java.nio.file;
+
+import java.io.IOException;
+
+public class NoSuchFileException extends IOException {
+ public NoSuchFileException(String file) {
+ super(file);
+ }
+}
diff --git a/src/test/r8OnArtBackport/OpenOption.java b/src/test/r8OnArtBackport/OpenOption.java
new file mode 100644
index 0000000..21b5fb6
--- /dev/null
+++ b/src/test/r8OnArtBackport/OpenOption.java
@@ -0,0 +1,7 @@
+// 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 java.nio.file;
+
+public interface OpenOption {}
diff --git a/src/test/r8OnArtBackport/Path.java b/src/test/r8OnArtBackport/Path.java
new file mode 100644
index 0000000..474725f
--- /dev/null
+++ b/src/test/r8OnArtBackport/Path.java
@@ -0,0 +1,91 @@
+// 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 java.nio.file;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.util.Iterator;
+
+// Removed Watchable.
+public interface Path extends Comparable<Path>, Iterable<Path> {
+ public static Path of(String first, String... more) {
+ throw new RuntimeException("Path does not implement of, use MockedPath.of");
+ }
+
+ public static Path of(URI uri) {
+ throw new RuntimeException("Path does not implement of, use MockedPath.of");
+ }
+
+ FileSystem getFileSystem();
+
+ boolean isAbsolute();
+
+ Path getRoot();
+
+ Path getFileName();
+
+ Path getParent();
+
+ int getNameCount();
+
+ Path getName(int index);
+
+ Path subpath(int beginIndex, int endIndex);
+
+ boolean startsWith(Path other);
+
+ default boolean startsWith(String other) {
+ throw new RuntimeException("Path does not implement startsWith");
+ }
+
+ boolean endsWith(Path other);
+
+ default boolean endsWith(String other) {
+ throw new RuntimeException("Path does not implement endsWith");
+ }
+
+ Path normalize();
+
+ Path resolve(Path other);
+
+ default Path resolve(String other) {
+ throw new RuntimeException("Path does not implement resolve");
+ }
+
+ default Path resolveSibling(Path other) {
+ throw new RuntimeException("Path does not implement resolveSibling");
+ }
+
+ default Path resolveSibling(String other) {
+ throw new RuntimeException("Path does not implement resolveSibling");
+ }
+
+ Path relativize(Path other);
+
+ URI toUri();
+
+ Path toAbsolutePath();
+
+ Path toRealPath(LinkOption... options) throws IOException;
+
+ default File toFile() {
+ throw new RuntimeException("Path does not implement toFile");
+ }
+
+ @Override
+ default Iterator<Path> iterator() {
+ throw new RuntimeException("Path does not implement iterator");
+ }
+
+ @Override
+ int compareTo(Path other);
+
+ boolean equals(Object other);
+
+ int hashCode();
+
+ String toString();
+}
diff --git a/src/test/r8OnArtBackport/Paths.java b/src/test/r8OnArtBackport/Paths.java
new file mode 100644
index 0000000..06d044f
--- /dev/null
+++ b/src/test/r8OnArtBackport/Paths.java
@@ -0,0 +1,16 @@
+// 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 java.nio.file;
+
+import com.android.tools.r8.MockedPath;
+
+public final class Paths {
+
+ private Paths() {}
+
+ public static Path get(String first, String... more) {
+ return MockedPath.of(first, more);
+ }
+}
diff --git a/src/test/r8OnArtBackport/StandardOpenOption.java b/src/test/r8OnArtBackport/StandardOpenOption.java
new file mode 100644
index 0000000..0e4d208
--- /dev/null
+++ b/src/test/r8OnArtBackport/StandardOpenOption.java
@@ -0,0 +1,18 @@
+// 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 java.nio.file;
+
+public enum StandardOpenOption implements OpenOption {
+ READ,
+ WRITE,
+ APPEND,
+ TRUNCATE_EXISTING,
+ CREATE,
+ CREATE_NEW,
+ DELETE_ON_CLOSE,
+ SPARSE,
+ SYNC,
+ DSYNC;
+}
diff --git a/tools/gradle.py b/tools/gradle.py
index 842816e..0e926e6 100755
--- a/tools/gradle.py
+++ b/tools/gradle.py
@@ -31,6 +31,11 @@
default=False, action='store_true')
parser.add_argument('--java-home', '--java_home',
help='Use a custom java version to run gradle.')
+ parser.add_argument('--worktree',
+ help='Gradle is running in a worktree and may lock up '
+ 'the gradle caches.',
+ action='store_true',
+ default=False)
return parser.parse_known_args()
def GetJavaEnv(env):
@@ -101,6 +106,8 @@
args.append('-Dorg.gradle.java.home=' + options.java_home)
if options.no_internal:
args.append('-Pno_internal')
+ if options.worktree:
+ args.append('-g=' + os.path.join(utils.REPO_ROOT, ".gradle_user_home"))
return RunGradle(args)
if __name__ == '__main__':
diff --git a/tools/internal_test.py b/tools/internal_test.py
index 9654b4b..c85942b 100755
--- a/tools/internal_test.py
+++ b/tools/internal_test.py
@@ -77,7 +77,7 @@
'find-xmx-min': 800,
'find-xmx-max': 1200,
'find-xmx-range': 32,
- 'oom-threshold': 1000,
+ 'oom-threshold': 1037,
},
{
'app': 'iosched',
@@ -90,6 +90,8 @@
]
def find_min_xmx_command(record):
+ assert record['find-xmx-min'] < record['find-xmx-max']
+ assert record['find-xmx-range'] < record['find-xmx-max'] - record['find-xmx-min']
return [
'tools/run_on_app.py',
'--compiler=r8',
diff --git a/tools/test.py b/tools/test.py
index 2d45d1c..5091f61 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -128,6 +128,9 @@
result.add_option('--fail-fast', '--fail_fast',
default=False, action='store_true',
help='Stop on first failure. Passes --fail-fast to gradle test runner.')
+ result.add_option('--worktree',
+ default=False, action='store_true',
+ help='Tests are run in worktree and should not use gradle user home.')
return result.parse_args()
def archive_failures():
@@ -215,6 +218,8 @@
gradle_args.append('R8LibNoDeps')
if options.r8lib_no_deps:
gradle_args.append('-Pr8lib_no_deps')
+ if options.worktree:
+ gradle_args.append('-g=' + os.path.join(utils.REPO_ROOT, ".gradle_user_home"))
# Build an R8 with dependencies for bootstrapping tests before adding test sources.
gradle_args.append('r8WithRelocatedDeps')