Merge commit '3a4ce52559b6599d93172be4ae2011e6ddd99da7' into dev-release
diff --git a/build.gradle b/build.gradle
index d3b511c..1a9edec 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2347,3 +2347,11 @@
}
}
}
+
+allprojects {
+ tasks.withType(Exec) {
+ doFirst {
+ println commandLine
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 15053ae..74b5d06 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -149,7 +149,7 @@
private static void run(AndroidApp inputApp, InternalOptions options, ExecutorService executor)
throws IOException {
- Timing timing = new Timing("D8");
+ Timing timing = Timing.create("D8", options);
try {
// Disable global optimizations.
options.disableGlobalOptimizations();
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
index 8ce6eed..830f6b6 100644
--- a/src/main/java/com/android/tools/r8/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -201,7 +201,7 @@
AndroidApp library =
AndroidApp.builder().addLibraryFiles(getAndroidJarPath(compilationApiLevel)).build();
DirectMappedDexApplication dexApplication =
- new ApplicationReader(library, options, new Timing()).read().toDirect();
+ new ApplicationReader(library, options, Timing.empty()).read().toDirect();
// Collect all the methods that the library desugar configuration adds support for.
Set<DexClass> classesWithAllMethodsSupported = Sets.newIdentityHashSet();
@@ -285,7 +285,7 @@
// Build a plain text file with the desugared APIs.
List<String> desugaredApisSignatures = new ArrayList<>();
- DexApplication.Builder builder = DexApplication.builder(options, new Timing());
+ DexApplication.Builder builder = DexApplication.builder(options, Timing.empty());
supportedMethods.supportedMethods.forEach(
(clazz, methods) -> {
String classBinaryName =
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index 87c15ab..50a4a70 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -104,7 +104,7 @@
private static void desugar(
AndroidApp inputApp, InternalOptions options, ExecutorService executor) throws IOException {
- Timing timing = new Timing("L8 desugaring");
+ Timing timing = Timing.create("L8 desugaring", options);
try {
// Disable global optimizations.
options.disableGlobalOptimizations();
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 77c4895..58e68dc 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -17,7 +17,6 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.AppliedGraphLens;
import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -102,7 +101,6 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
-import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
@@ -148,7 +146,7 @@
if (options.printMemory) {
System.gc();
}
- this.timing = new Timing("R8", options.printMemory);
+ timing = Timing.create("R8", options);
options.itemFactory.resetSortedIndices();
}
@@ -316,6 +314,7 @@
.run(executorService));
AppView<AppInfoWithLiveness> appViewWithLiveness = runEnqueuer(executorService, appView);
+ application = appViewWithLiveness.appInfo().app();
assert appView.rootSet().verifyKeptFieldsAreAccessedAndLive(appViewWithLiveness.appInfo());
assert appView.rootSet().verifyKeptMethodsAreTargetedAndLive(appViewWithLiveness.appInfo());
assert appView.rootSet().verifyKeptTypesAreLive(appViewWithLiveness.appInfo());
@@ -503,13 +502,10 @@
appView.setAppServices(appView.appServices().rewrittenWithLens(appView.graphLense()));
timing.begin("Create IR");
- Map<String, String> additionalRewritePrefix;
- Set<DexCallSite> desugaredCallSites;
CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
try {
IRConverter converter = new IRConverter(appView, timing, printer, mainDexClasses);
application = converter.optimize(executorService);
- desugaredCallSites = converter.getDesugaredCallSites();
} finally {
timing.end();
}
@@ -705,13 +701,12 @@
options.reporter, options.getProguardConfiguration().getApplyMappingFile());
timing.begin("apply-mapping");
namingLens =
- new ProguardMapMinifier(appView.withLiveness(), seedMapper, desugaredCallSites)
+ new ProguardMapMinifier(appView.withLiveness(), seedMapper)
.run(executorService, timing);
timing.end();
} else if (options.isMinifying()) {
timing.begin("Minification");
- namingLens =
- new Minifier(appView.withLiveness(), desugaredCallSites).run(executorService, timing);
+ namingLens = new Minifier(appView.withLiveness()).run(executorService, timing);
timing.end();
} else {
// Rewrite signature annotations for applications that are not minified.
diff --git a/src/main/java/com/android/tools/r8/ReadKeepFile.java b/src/main/java/com/android/tools/r8/ReadKeepFile.java
index c953c8d..37ca30b 100644
--- a/src/main/java/com/android/tools/r8/ReadKeepFile.java
+++ b/src/main/java/com/android/tools/r8/ReadKeepFile.java
@@ -16,7 +16,7 @@
private static final String DEFAULT_KEEP_FILE_NAME = "build/proguard.cfg";
- final Timing timing = new Timing("ReadKeepFile");
+ final Timing timing = Timing.empty();
private void readProguardKeepFile(String fileName) {
System.out.println(" - reading " + fileName);
diff --git a/src/main/java/com/android/tools/r8/ReadProguardMap.java b/src/main/java/com/android/tools/r8/ReadProguardMap.java
index a05537c..31bc2d8 100644
--- a/src/main/java/com/android/tools/r8/ReadProguardMap.java
+++ b/src/main/java/com/android/tools/r8/ReadProguardMap.java
@@ -16,7 +16,7 @@
private static final String DEFAULT_MAP_FILE_NAME = "third_party/gmscore/v5/proguard.map";
- final Timing timing = new Timing("ReadProguardMap");
+ final Timing timing = Timing.empty();
private void readProguardMapFile(String fileName) {
try {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNop.java b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
index 0ae7685..e124a0b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
@@ -28,7 +28,7 @@
@Override
public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
- // Intentionally left empty.
+ builder.addNop();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/code/Const.java b/src/main/java/com/android/tools/r8/code/Const.java
index 1f5c1f1..9026684 100644
--- a/src/main/java/com/android/tools/r8/code/Const.java
+++ b/src/main/java/com/android/tools/r8/code/Const.java
@@ -59,7 +59,7 @@
public void buildIR(IRBuilder builder) {
int value = decodedValue();
TypeLatticeElement typeLattice =
- value == 0 ? TypeLatticeElement.TOP : TypeLatticeElement.SINGLE;
+ value == 0 ? TypeLatticeElement.getTop() : TypeLatticeElement.getSingle();
builder.addConst(typeLattice, AA, value);
}
}
diff --git a/src/main/java/com/android/tools/r8/code/Const16.java b/src/main/java/com/android/tools/r8/code/Const16.java
index 5bee254..f859228 100644
--- a/src/main/java/com/android/tools/r8/code/Const16.java
+++ b/src/main/java/com/android/tools/r8/code/Const16.java
@@ -53,7 +53,7 @@
public void buildIR(IRBuilder builder) {
int value = decodedValue();
TypeLatticeElement typeLattice =
- value == 0 ? TypeLatticeElement.TOP : TypeLatticeElement.SINGLE;
+ value == 0 ? TypeLatticeElement.getTop() : TypeLatticeElement.getSingle();
builder.addConst(typeLattice, AA, value);
}
}
diff --git a/src/main/java/com/android/tools/r8/code/Const4.java b/src/main/java/com/android/tools/r8/code/Const4.java
index 4f73b29..a036d57 100644
--- a/src/main/java/com/android/tools/r8/code/Const4.java
+++ b/src/main/java/com/android/tools/r8/code/Const4.java
@@ -59,7 +59,7 @@
public void buildIR(IRBuilder builder) {
int value = decodedValue();
TypeLatticeElement typeLattice =
- value == 0 ? TypeLatticeElement.TOP : TypeLatticeElement.SINGLE;
+ value == 0 ? TypeLatticeElement.getTop() : TypeLatticeElement.getSingle();
builder.addConst(typeLattice, A, value);
}
}
diff --git a/src/main/java/com/android/tools/r8/code/ConstHigh16.java b/src/main/java/com/android/tools/r8/code/ConstHigh16.java
index 28d1c73..f3821a6 100644
--- a/src/main/java/com/android/tools/r8/code/ConstHigh16.java
+++ b/src/main/java/com/android/tools/r8/code/ConstHigh16.java
@@ -59,7 +59,7 @@
public void buildIR(IRBuilder builder) {
int value = decodedValue();
TypeLatticeElement typeLattice =
- value == 0 ? TypeLatticeElement.TOP : TypeLatticeElement.SINGLE;
+ value == 0 ? TypeLatticeElement.getTop() : TypeLatticeElement.getSingle();
builder.addConst(typeLattice, AA, value);
}
}
diff --git a/src/main/java/com/android/tools/r8/code/ConstWide.java b/src/main/java/com/android/tools/r8/code/ConstWide.java
index 1820172..31c1089 100644
--- a/src/main/java/com/android/tools/r8/code/ConstWide.java
+++ b/src/main/java/com/android/tools/r8/code/ConstWide.java
@@ -57,6 +57,6 @@
@Override
public void buildIR(IRBuilder builder) {
- builder.addConst(TypeLatticeElement.WIDE, AA, decodedValue());
+ builder.addConst(TypeLatticeElement.getWide(), AA, decodedValue());
}
}
diff --git a/src/main/java/com/android/tools/r8/code/ConstWide16.java b/src/main/java/com/android/tools/r8/code/ConstWide16.java
index ad7e84f..2f7634d 100644
--- a/src/main/java/com/android/tools/r8/code/ConstWide16.java
+++ b/src/main/java/com/android/tools/r8/code/ConstWide16.java
@@ -57,6 +57,6 @@
@Override
public void buildIR(IRBuilder builder) {
- builder.addConst(TypeLatticeElement.WIDE, AA, decodedValue());
+ builder.addConst(TypeLatticeElement.getWide(), AA, decodedValue());
}
}
diff --git a/src/main/java/com/android/tools/r8/code/ConstWide32.java b/src/main/java/com/android/tools/r8/code/ConstWide32.java
index 0b75213..51ef097 100644
--- a/src/main/java/com/android/tools/r8/code/ConstWide32.java
+++ b/src/main/java/com/android/tools/r8/code/ConstWide32.java
@@ -57,6 +57,6 @@
@Override
public void buildIR(IRBuilder builder) {
- builder.addConst(TypeLatticeElement.WIDE, AA, decodedValue());
+ builder.addConst(TypeLatticeElement.getWide(), AA, decodedValue());
}
}
diff --git a/src/main/java/com/android/tools/r8/code/ConstWideHigh16.java b/src/main/java/com/android/tools/r8/code/ConstWideHigh16.java
index e139b35..8437a35 100644
--- a/src/main/java/com/android/tools/r8/code/ConstWideHigh16.java
+++ b/src/main/java/com/android/tools/r8/code/ConstWideHigh16.java
@@ -57,6 +57,6 @@
@Override
public void buildIR(IRBuilder builder) {
- builder.addConst(TypeLatticeElement.WIDE, AA, decodedValue());
+ builder.addConst(TypeLatticeElement.getWide(), AA, decodedValue());
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
index 61def68..c709a31 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -225,6 +225,10 @@
unset(Constants.ACC_SYNTHETIC);
}
+ public void demoteFromSynthetic() {
+ demote(Constants.ACC_SYNTHETIC);
+ }
+
public void promoteToFinal() {
promote(Constants.ACC_FINAL);
}
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 5c0e98c..17e63e6 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -459,10 +459,6 @@
return getTypeInfo(type).isUnknown();
}
- public boolean isMarkedAsInterface(DexType type) {
- return getTypeInfo(type).isInterface();
- }
-
public boolean hasSubtypes(DexType type) {
return !getTypeInfo(type).directSubtypes.isEmpty();
}
@@ -566,64 +562,6 @@
}
}
- // TODO(b/130636783): inconsistent location
- public DexType computeLeastUpperBoundOfClasses(DexType subtype, DexType other) {
- if (subtype == other) {
- return subtype;
- }
- DexType objectType = dexItemFactory().objectType;
- TypeInfo subInfo = getTypeInfo(subtype);
- TypeInfo superInfo = getTypeInfo(other);
- // If we have no definition for either class, stop proceeding.
- if (subInfo.hierarchyLevel == UNKNOWN_LEVEL || superInfo.hierarchyLevel == UNKNOWN_LEVEL) {
- return objectType;
- }
- if (subtype == objectType || other == objectType) {
- return objectType;
- }
- TypeInfo t1;
- TypeInfo t2;
- if (superInfo.hierarchyLevel < subInfo.hierarchyLevel) {
- t1 = superInfo;
- t2 = subInfo;
- } else {
- t1 = subInfo;
- t2 = superInfo;
- }
- // From now on, t2.hierarchyLevel >= t1.hierarchyLevel
- DexClass dexClass;
- // Make both of other and this in the same level.
- while (t2.hierarchyLevel > t1.hierarchyLevel) {
- dexClass = definitionFor(t2.type);
- if (dexClass == null || dexClass.superType == null) {
- return objectType;
- }
- t2 = getTypeInfo(dexClass.superType);
- }
- // At this point, they are at the same level.
- // lubType starts from t1, and will move up; t2 starts from itself, and will move up, too.
- // They move up in their own hierarchy tree, and will repeat the process until they meet.
- // (It will stop at anytime when either one's definition is not found.)
- DexType lubType = t1.type;
- DexType t2Type = t2.type;
- while (t2Type != lubType) {
- assert getTypeInfo(t2Type).hierarchyLevel == getTypeInfo(lubType).hierarchyLevel;
- dexClass = definitionFor(t2Type);
- if (dexClass == null) {
- lubType = objectType;
- break;
- }
- t2Type = dexClass.superType;
- dexClass = definitionFor(lubType);
- if (dexClass == null) {
- lubType = objectType;
- break;
- }
- lubType = dexClass.superType;
- }
- return lubType;
- }
-
public boolean inDifferentHierarchy(DexType type1, DexType type2) {
return !isSubtype(type1, type2) && !isSubtype(type2, type1);
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 1388051..9c23fd1 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -196,16 +196,15 @@
}
public OptionalBool isInterface(DexType type) {
+ assert type.isClassType();
// Without whole program information we should not assume anything about any other class than
// the current holder in a given context.
if (enableWholeProgramOptimizations()) {
- assert appInfo().hasSubtyping();
- if (appInfo().hasSubtyping()) {
- AppInfoWithSubtyping appInfo = appInfo().withSubtyping();
- return appInfo.isUnknown(type)
- ? OptionalBool.unknown()
- : OptionalBool.of(appInfo.isMarkedAsInterface(type));
+ DexClass clazz = definitionFor(type);
+ if (clazz == null) {
+ return OptionalBool.unknown();
}
+ return OptionalBool.of(clazz.isInterface());
}
return OptionalBool.unknown();
}
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 5402e3a..cbc8303 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -95,7 +95,7 @@
private final DexType originalHolder;
private final int maxStack;
- private final int maxLocals;
+ private int maxLocals;
public List<CfInstruction> instructions;
private final List<CfTryCatch> tryCatchRanges;
private final List<LocalVariableInfo> localVariables;
@@ -127,6 +127,10 @@
return maxLocals;
}
+ public void setMaxLocals(int newMaxLocals) {
+ maxLocals = newMaxLocals;
+ }
+
public List<CfTryCatch> getTryCatchRanges() {
return tryCatchRanges;
}
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 358af04..d86a90c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -186,6 +186,19 @@
directMethods = newMethods;
}
+ public void removeDirectMethod(DexMethod method) {
+ int index = -1;
+ for (int i = 0; i < directMethods.length; i++) {
+ if (method.match(directMethods[i])) {
+ index = i;
+ break;
+ }
+ }
+ if (index >= 0) {
+ removeDirectMethod(index);
+ }
+ }
+
public void setDirectMethod(int index, DexEncodedMethod method) {
cachedClassInitializer = null;
directMethods[index] = method;
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index ff31e35..7b529da 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -47,6 +47,7 @@
import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.info.UpdatableMethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -1237,15 +1238,18 @@
public void copyMetadata(DexEncodedMethod from) {
checkIfObsolete();
setKotlinMemberInfo(from.kotlinMemberInfo);
- // Record that the current method uses identifier name string if the inlinee did so.
- if (from.getOptimizationInfo().useIdentifierNameString()) {
- getMutableOptimizationInfo().markUseIdentifierNameString();
- }
if (from.classFileVersion > classFileVersion) {
upgradeClassFileVersion(from.getClassFileVersion());
}
}
+ public void copyMetadata(DexEncodedMethod from, OptimizationFeedback feedback) {
+ if (from.getOptimizationInfo().useIdentifierNameString()) {
+ feedback.markUseIdentifierNameString(this);
+ }
+ copyMetadata(from);
+ }
+
private static Builder syntheticBuilder(DexEncodedMethod from) {
return new Builder(from, true);
}
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 ab96c0d..193fd91 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -158,6 +158,7 @@
public final DexString trimName = createString("trim");
public final DexString valueOfMethodName = createString("valueOf");
+ public final DexString valuesMethodName = createString("values");
public final DexString toStringMethodName = createString("toString");
public final DexString internMethodName = createString("intern");
@@ -481,6 +482,9 @@
createProto(callSiteType, lookupType, stringType, methodTypeType, objectArrayType),
createString(METAFACTORY_ALT_METHOD_NAME));
+ public final DexMethod deserializeLambdaMethod =
+ createMethod(objectType, deserializeLambdaMethodProto, deserializeLambdaMethodName);
+
public final DexType stringConcatFactoryType =
createType("Ljava/lang/invoke/StringConcatFactory;");
@@ -561,7 +565,7 @@
.addAll(primitiveToBoxed.values())
.build();
- public final Set<DexType> libraryClassesWithoutStaticInitialization =
+ public Set<DexType> libraryClassesWithoutStaticInitialization =
ImmutableSet.of(boxedBooleanType, enumType, objectType, stringBufferType, stringBuilderType);
private boolean skipNameValidationForTesting = false;
@@ -775,6 +779,7 @@
public final DexMethod ordinal;
public final DexMethod name;
public final DexMethod toString;
+ public final DexMethod compareTo;
public final DexMethod constructor =
createMethod(enumType, createProto(voidType, stringType, intType), constructorMethodName);
@@ -806,6 +811,29 @@
toStringMethodName,
stringDescriptor,
DexString.EMPTY_ARRAY);
+ compareTo =
+ createMethod(
+ enumDescriptor,
+ compareToMethodName,
+ stringDescriptor,
+ new DexString[] {enumDescriptor});
+ }
+
+ public boolean isValuesMethod(DexMethod method, DexClass enumClass) {
+ assert enumClass.isEnum();
+ return method.holder == enumClass.type
+ && method.proto.returnType == enumClass.type.toArrayType(1, DexItemFactory.this)
+ && method.proto.parameters.size() == 0
+ && method.name == valuesMethodName;
+ }
+
+ public boolean isValueOfMethod(DexMethod method, DexClass enumClass) {
+ assert enumClass.isEnum();
+ return method.holder == enumClass.type
+ && method.proto.returnType == enumClass.type
+ && method.proto.parameters.size() == 1
+ && method.proto.parameters.values[0] == stringType
+ && method.name == valueOfMethodName;
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
index ca16abe..652228c 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.errors.Unreachable;
import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.function.BooleanSupplier;
@@ -80,7 +79,7 @@
@Override
public int getAsKotlinFlags() {
- throw new Unreachable("Kotlin property is not directly mapped to JVM field.");
+ return super.getAsKotlinFlags();
}
public boolean isVolatile() {
diff --git a/src/main/java/com/android/tools/r8/graph/SmaliWriter.java b/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
index 024c0c9..c632f75 100644
--- a/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
@@ -26,9 +26,8 @@
public static String smali(AndroidApp application, InternalOptions options) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
try (PrintStream ps = new PrintStream(os)) {
- DexApplication dexApplication = new ApplicationReader(application, options,
- new Timing("SmaliWriter"))
- .read();
+ DexApplication dexApplication =
+ new ApplicationReader(application, options, Timing.empty()).read();
SmaliWriter writer = new SmaliWriter(dexApplication, options);
writer.write(ps);
} catch (IOException | ExecutionException e) {
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 1d0b4fb..fb41239 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -99,8 +99,10 @@
boolean isLambdaMetaFactory =
factory.isLambdaMetafactoryMethod(callSite.bootstrapMethod.asMethod());
- registerMethodHandle(
- callSite.bootstrapMethod, MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
+ if (!isLambdaMetaFactory) {
+ registerMethodHandle(
+ callSite.bootstrapMethod, MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
+ }
// Lambda metafactory will use this type as the main SAM
// interface for the dynamically created lambda class.
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
index e7c6418..7df903b 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
@@ -10,6 +10,7 @@
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.ir.analysis.type.ClassTypeLatticeElement;
import java.util.IdentityHashMap;
import java.util.Map;
@@ -78,8 +79,8 @@
mapping.getOrDefault(key, guaranteedToBeInitialized);
mapping.put(
key,
- appInfo.computeLeastUpperBoundOfClasses(
- guaranteedToBeInitialized, existingGuaranteedToBeInitialized));
+ ClassTypeLatticeElement.computeLeastUpperBoundOfClasses(
+ appInfo, guaranteedToBeInitialized, existingGuaranteedToBeInitialized));
}
@Override
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 e9060a4..35bbf2f 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
@@ -32,6 +32,7 @@
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.DequeUtils;
@@ -68,7 +69,11 @@
}
public static void run(
- AppView<?> appView, IRCode code, OptimizationFeedback feedback, DexEncodedMethod method) {
+ AppView<?> appView,
+ IRCode code,
+ ClassInitializerDefaultsResult classInitializerDefaultsResult,
+ OptimizationFeedback feedback,
+ DexEncodedMethod method) {
if (!appView.enableWholeProgramOptimizations()) {
return;
}
@@ -91,7 +96,7 @@
}
new FieldValueAnalysis(appView.withLiveness(), code, feedback, clazz, method)
- .computeFieldOptimizationInfo();
+ .computeFieldOptimizationInfo(classInitializerDefaultsResult);
}
private Map<BasicBlock, AbstractFieldSet> getOrCreateFieldsMaybeReadBeforeBlockInclusive() {
@@ -102,7 +107,8 @@
}
/** This method analyzes initializers with the purpose of computing field optimization info. */
- private void computeFieldOptimizationInfo() {
+ private void computeFieldOptimizationInfo(
+ ClassInitializerDefaultsResult classInitializerDefaultsResult) {
AppInfoWithLiveness appInfo = appView.appInfo();
DominatorTree dominatorTree = null;
@@ -112,20 +118,21 @@
// guaranteed to be assigned only in the current initializer.
boolean isStraightLineCode = true;
Map<DexEncodedField, LinkedList<FieldInstruction>> putsPerField = new IdentityHashMap<>();
- for (Instruction instruction : code.instructions()) {
- if (instruction.isFieldPut()) {
- FieldInstruction fieldPut = instruction.asFieldInstruction();
- DexField field = fieldPut.getField();
- DexEncodedField encodedField = appInfo.resolveField(field);
- if (encodedField != null
- && encodedField.field.holder == context
- && appInfo.isFieldOnlyWrittenInMethod(encodedField, method)) {
- putsPerField.computeIfAbsent(encodedField, ignore -> new LinkedList<>()).add(fieldPut);
- }
+ for (BasicBlock block : code.blocks) {
+ if (block.getSuccessors().size() >= 2) {
+ isStraightLineCode = false;
}
- if (instruction.isJumpInstruction()) {
- if (!instruction.isGoto() && !instruction.isReturn()) {
- isStraightLineCode = false;
+ for (Instruction instruction : block.getInstructions()) {
+ if (instruction.isFieldPut()) {
+ FieldInstruction fieldPut = instruction.asFieldInstruction();
+ DexField field = fieldPut.getField();
+ DexEncodedField encodedField = appInfo.resolveField(field);
+ if (encodedField != null
+ && encodedField.field.holder == context
+ && encodedField.isStatic() == method.isStatic()
+ && appInfo.isFieldOnlyWrittenInMethod(encodedField, method)) {
+ putsPerField.computeIfAbsent(encodedField, ignore -> new LinkedList<>()).add(fieldPut);
+ }
}
}
}
@@ -146,7 +153,9 @@
continue;
}
}
- if (fieldMaybeReadBeforeInstruction(encodedField, fieldPut)) {
+ boolean priorReadsWillReadSameValue =
+ !classInitializerDefaultsResult.hasStaticValue(encodedField) && fieldPut.value().isZero();
+ if (!priorReadsWillReadSameValue && fieldMaybeReadBeforeInstruction(encodedField, fieldPut)) {
continue;
}
updateFieldOptimizationInfo(encodedField, fieldPut.value());
@@ -239,20 +248,24 @@
if (!blockOrPredecessorMaybeReadAnyField) {
// Finally, we update the read set with the fields that are read by the instructions in the
- // current block.
- for (Instruction instruction : block.getInstructions()) {
- AbstractFieldSet instructionReadSet = instruction.readSet(appView, context);
- if (instructionReadSet.isBottom()) {
- continue;
+ // current block. This can be skipped if the block has already been processed.
+ if (seenBefore) {
+ assert verifyFieldSetContainsAllFieldReadsInBlock(knownReadSet, block, context);
+ } else {
+ for (Instruction instruction : block.getInstructions()) {
+ AbstractFieldSet instructionReadSet = instruction.readSet(appView, context);
+ if (instructionReadSet.isBottom()) {
+ continue;
+ }
+ if (instructionReadSet.isTop()) {
+ blockOrPredecessorMaybeReadAnyField = true;
+ break;
+ }
+ if (!knownReadSet.isConcreteFieldSet()) {
+ knownReadSet = new ConcreteMutableFieldSet();
+ }
+ knownReadSet.asConcreteFieldSet().addAll(instructionReadSet.asConcreteFieldSet());
}
- if (instructionReadSet.isTop()) {
- blockOrPredecessorMaybeReadAnyField = true;
- break;
- }
- if (!knownReadSet.isConcreteFieldSet()) {
- knownReadSet = new ConcreteMutableFieldSet();
- }
- knownReadSet.asConcreteFieldSet().addAll(instructionReadSet.asConcreteFieldSet());
}
}
@@ -279,6 +292,21 @@
return result;
}
+ private boolean verifyFieldSetContainsAllFieldReadsInBlock(
+ KnownFieldSet readSet, BasicBlock block, DexType context) {
+ for (Instruction instruction : block.getInstructions()) {
+ AbstractFieldSet instructionReadSet = instruction.readSet(appView, context);
+ assert !instructionReadSet.isTop();
+ if (instructionReadSet.isBottom()) {
+ continue;
+ }
+ for (DexEncodedField field : instructionReadSet.asConcreteFieldSet().getFields()) {
+ assert readSet.contains(field);
+ }
+ }
+ return true;
+ }
+
private void updateFieldOptimizationInfo(DexEncodedField field, Value value) {
// Abstract value.
Value root = value.getAliasedValue();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/KnownFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/KnownFieldSet.java
index b96918e..263101b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/KnownFieldSet.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/KnownFieldSet.java
@@ -4,8 +4,12 @@
package com.android.tools.r8.ir.analysis.fieldvalueanalysis;
+import com.android.tools.r8.graph.DexEncodedField;
+
public interface KnownFieldSet {
+ boolean contains(DexEncodedField field);
+
default boolean isConcreteFieldSet() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
index 31b6588..cc7307f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
@@ -163,7 +163,6 @@
new NewArrayEmpty(newObjectsValue, sizeValue, appView.dexItemFactory().objectArrayType));
// Populate the `objects` array.
- boolean hasIntroducedIdentifierNameString = false;
for (int i = 0; i < objects.size(); i++) {
Value indexValue = instructionIterator.insertConstIntInstruction(code, appView.options(), i);
Instruction materializingInstruction = objects.get(i).buildIR(appView, code);
@@ -171,20 +170,12 @@
instructionIterator.add(
new ArrayPut(
MemberType.OBJECT, newObjectsValue, indexValue, materializingInstruction.outValue()));
-
- if (materializingInstruction.isDexItemBasedConstString()) {
- hasIntroducedIdentifierNameString = true;
- }
}
// Pass the newly created `objects` array to RawMessageInfo.<init>(...) or
// GeneratedMessageLite.newMessageInfo().
setObjectsValueForMessageInfoConstructionInvoke(
newMessageInfoInvoke, newObjectsValue, references);
-
- if (hasIntroducedIdentifierNameString) {
- method.getMutableOptimizationInfo().markUseIdentifierNameString();
- }
}
public static InvokeMethod getNewMessageInfoInvoke(IRCode code, ProtoReferences references) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
index aacb2a0..014ab85 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
@@ -16,7 +16,9 @@
public class ProtoReferences {
+ public final DexType enumLiteMapType;
public final DexType extendableMessageType;
+ public final DexType extensionDescriptorType;
public final DexType extensionRegistryLiteType;
public final DexType generatedExtensionType;
public final DexType generatedMessageLiteType;
@@ -25,7 +27,9 @@
public final DexType rawMessageInfoType;
public final DexType messageLiteType;
public final DexType methodToInvokeType;
+ public final DexType wireFormatFieldType;
+ public final GeneratedExtensionMethods generatedExtensionMethods;
public final GeneratedMessageLiteMethods generatedMessageLiteMethods;
public final GeneratedMessageLiteBuilderMethods generatedMessageLiteBuilderMethods;
public final GeneratedMessageLiteExtendableBuilderMethods
@@ -44,28 +48,24 @@
public ProtoReferences(DexItemFactory factory) {
// Types.
+ enumLiteMapType = factory.createType("Lcom/google/protobuf/Internal$EnumLiteMap;");
extendableMessageType =
- factory.createType(
- factory.createString("Lcom/google/protobuf/GeneratedMessageLite$ExtendableMessage;"));
- extensionRegistryLiteType =
- factory.createType(factory.createString("Lcom/google/protobuf/ExtensionRegistryLite;"));
+ factory.createType("Lcom/google/protobuf/GeneratedMessageLite$ExtendableMessage;");
+ extensionDescriptorType =
+ factory.createType("Lcom/google/protobuf/GeneratedMessageLite$ExtensionDescriptor;");
+ extensionRegistryLiteType = factory.createType("Lcom/google/protobuf/ExtensionRegistryLite;");
generatedExtensionType =
- factory.createType(
- factory.createString("Lcom/google/protobuf/GeneratedMessageLite$GeneratedExtension;"));
- generatedMessageLiteType =
- factory.createType(factory.createString("Lcom/google/protobuf/GeneratedMessageLite;"));
+ factory.createType("Lcom/google/protobuf/GeneratedMessageLite$GeneratedExtension;");
+ generatedMessageLiteType = factory.createType("Lcom/google/protobuf/GeneratedMessageLite;");
generatedMessageLiteBuilderType =
- factory.createType(
- factory.createString("Lcom/google/protobuf/GeneratedMessageLite$Builder;"));
+ factory.createType("Lcom/google/protobuf/GeneratedMessageLite$Builder;");
generatedMessageLiteExtendableBuilderType =
- factory.createType(
- factory.createString("Lcom/google/protobuf/GeneratedMessageLite$ExtendableBuilder;"));
- rawMessageInfoType =
- factory.createType(factory.createString("Lcom/google/protobuf/RawMessageInfo;"));
- messageLiteType = factory.createType(factory.createString("Lcom/google/protobuf/MessageLite;"));
+ factory.createType("Lcom/google/protobuf/GeneratedMessageLite$ExtendableBuilder;");
+ rawMessageInfoType = factory.createType("Lcom/google/protobuf/RawMessageInfo;");
+ messageLiteType = factory.createType("Lcom/google/protobuf/MessageLite;");
methodToInvokeType =
- factory.createType(
- factory.createString("Lcom/google/protobuf/GeneratedMessageLite$MethodToInvoke;"));
+ factory.createType("Lcom/google/protobuf/GeneratedMessageLite$MethodToInvoke;");
+ wireFormatFieldType = factory.createType("Lcom/google/protobuf/WireFormat$FieldType;");
// Names.
dynamicMethodName = factory.createString("dynamicMethod");
@@ -93,6 +93,7 @@
factory.voidType, messageLiteType, factory.stringType, factory.objectArrayType),
factory.constructorMethodName);
+ generatedExtensionMethods = new GeneratedExtensionMethods(factory);
generatedMessageLiteMethods = new GeneratedMessageLiteMethods(factory);
generatedMessageLiteBuilderMethods = new GeneratedMessageLiteBuilderMethods(factory);
generatedMessageLiteExtendableBuilderMethods =
@@ -123,7 +124,8 @@
public boolean isFindLiteExtensionByNumberMethod(DexMethod method) {
return method.proto == findLiteExtensionByNumberProto
- && method.name.startsWith(findLiteExtensionByNumberName);
+ && method.name.startsWith(findLiteExtensionByNumberName)
+ && method.holder != extensionRegistryLiteType;
}
public boolean isGeneratedMessageLiteBuilder(DexProgramClass clazz) {
@@ -136,11 +138,47 @@
return method.match(newMessageInfoMethod) || method == rawMessageInfoConstructor;
}
- class GeneratedMessageLiteMethods {
+ public class GeneratedExtensionMethods {
+
+ public final DexMethod constructor;
+ public final DexMethod constructorWithClass;
+
+ private GeneratedExtensionMethods(DexItemFactory dexItemFactory) {
+ constructor =
+ dexItemFactory.createMethod(
+ generatedExtensionType,
+ dexItemFactory.createProto(
+ dexItemFactory.voidType,
+ messageLiteType,
+ dexItemFactory.objectType,
+ messageLiteType,
+ extensionDescriptorType),
+ dexItemFactory.constructorMethodName);
+ constructorWithClass =
+ dexItemFactory.createMethod(
+ generatedExtensionType,
+ dexItemFactory.createProto(
+ dexItemFactory.voidType,
+ messageLiteType,
+ dexItemFactory.objectType,
+ messageLiteType,
+ extensionDescriptorType,
+ dexItemFactory.classType),
+ dexItemFactory.constructorMethodName);
+ }
+
+ public boolean isConstructor(DexMethod method) {
+ return method == constructor || method == constructorWithClass;
+ }
+ }
+
+ public class GeneratedMessageLiteMethods {
public final DexMethod createBuilderMethod;
public final DexMethod dynamicMethodBridgeMethod;
public final DexMethod isInitializedMethod;
+ public final DexMethod newRepeatedGeneratedExtension;
+ public final DexMethod newSingularGeneratedExtension;
private GeneratedMessageLiteMethods(DexItemFactory dexItemFactory) {
createBuilderMethod =
@@ -158,6 +196,32 @@
generatedMessageLiteType,
dexItemFactory.createProto(dexItemFactory.booleanType),
"isInitialized");
+ newRepeatedGeneratedExtension =
+ dexItemFactory.createMethod(
+ generatedMessageLiteType,
+ dexItemFactory.createProto(
+ generatedExtensionType,
+ messageLiteType,
+ messageLiteType,
+ enumLiteMapType,
+ dexItemFactory.intType,
+ wireFormatFieldType,
+ dexItemFactory.booleanType,
+ dexItemFactory.classType),
+ "newRepeatedGeneratedExtension");
+ newSingularGeneratedExtension =
+ dexItemFactory.createMethod(
+ generatedMessageLiteType,
+ dexItemFactory.createProto(
+ generatedExtensionType,
+ messageLiteType,
+ dexItemFactory.objectType,
+ messageLiteType,
+ enumLiteMapType,
+ dexItemFactory.intType,
+ wireFormatFieldType,
+ dexItemFactory.classType),
+ "newSingularGeneratedExtension");
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
index 92954e4..820789a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
@@ -4,11 +4,14 @@
package com.android.tools.r8.ir.analysis.proto.schema;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
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.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
@@ -17,14 +20,22 @@
import com.android.tools.r8.ir.analysis.proto.ProtoReferences;
import com.android.tools.r8.ir.analysis.proto.ProtoShrinker;
import com.android.tools.r8.ir.analysis.proto.RawMessageInfoDecoder;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.IRCodeUtils;
+import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.code.StaticPut;
+import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.EnqueuerWorklist;
import com.android.tools.r8.shaking.KeepReason;
import com.android.tools.r8.utils.BitUtils;
import com.android.tools.r8.utils.OptionalBool;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.util.IdentityHashMap;
import java.util.List;
@@ -36,10 +47,6 @@
// implementation. The current caching mechanism is unsafe, because we may mark a message as not
// containing a map/required field in presence of cycles, although it does.
-// TODO(b/112437944): Handle extensions in the map/required field detection + add a test that fails
-// with the current implementation. If there is a field whose type is an extension, then we should
-// look if any of the applicable extensions could contain a map/required field.
-
// TODO(b/112437944): Handle incomplete information about extensions + add a test that fails with
// the current implementation. If there are some extensions that cannot be resolved, then we should
// keep fields that could reach extensions to be conservative.
@@ -68,6 +75,12 @@
private final Set<DexEncodedMethod> dynamicMethodsWithTracedProtoObjects =
Sets.newIdentityHashSet();
+ // The findLiteExtensionByNumber() methods that have become live since the last fixpoint.
+ private final Set<DexEncodedMethod> findLiteExtensionByNumberMethods = Sets.newIdentityHashSet();
+
+ // Mapping from extension container types to the extensions for that type.
+ private final Map<DexType, Set<DexType>> extensionGraph = new IdentityHashMap<>();
+
public ProtoEnqueuerExtension(AppView<?> appView) {
ProtoShrinker protoShrinker = appView.protoShrinker();
this.appView = appView;
@@ -82,6 +95,11 @@
*/
@Override
public void processNewlyLiveMethod(DexEncodedMethod encodedMethod) {
+ if (references.isFindLiteExtensionByNumberMethod(encodedMethod.method)) {
+ findLiteExtensionByNumberMethods.add(encodedMethod);
+ return;
+ }
+
if (!references.isDynamicMethod(encodedMethod)) {
return;
}
@@ -124,6 +142,8 @@
@Override
public void notifyFixpoint(Enqueuer enqueuer, EnqueuerWorklist worklist) {
+ populateExtensionGraph(enqueuer);
+
markMapOrRequiredFieldsAsReachable(enqueuer, worklist);
// The ProtoEnqueuerUseRegistry does not trace the const-class instructions in dynamicMethod().
@@ -139,6 +159,166 @@
}
}
+ /**
+ * For each extension field referenced from any of the methods in {@link
+ * #findLiteExtensionByNumberMethods}, this method finds the definition of the field, and then
+ * adds an edge to the proto extension graph {@link #extensionGraph} based on the definition of
+ * the field.
+ *
+ * <p>Example: If the field is defined as below, then an edge is added from the container type
+ * MyProtoMessage (argument #0) to the extension type MyProtoMessage$Ext (argument #2).
+ *
+ * <pre>
+ * GeneratedMessageLite.newSingularGeneratedExtension(
+ * MyProtoMessage.getDefaultInstance(),
+ * MyProtoMessage.Ext.getDefaultInstance(),
+ * MyProtoMessage.Ext.getDefaultInstance(),
+ * null,
+ * 10,
+ * WireFormat.FieldType.MESSAGE,
+ * MyProtoMessage.Ext.class);
+ * </pre>
+ *
+ * In addition to {@code newSingularGeneratedExtension()} we also model {@code
+ * newRepeatedGeneratedExtension()}. Both of these methods forward to {@code
+ * GeneratedExtension.<init>()}, hence we also model this constructor for robustness against
+ * inlining.
+ */
+ private void populateExtensionGraph(Enqueuer enqueuer) {
+ collectExtensionFields()
+ .forEach(
+ (clazz, extensionFields) -> {
+ DexEncodedMethod clinit = clazz.getClassInitializer();
+ if (clinit == null) {
+ assert false; // Should generally not happen.
+ return;
+ }
+
+ IRCode code = clinit.buildIR(appView, appView.appInfo().originFor(clazz.type));
+ Map<DexEncodedField, StaticPut> uniqueStaticPuts =
+ IRCodeUtils.findUniqueStaticPuts(appView, code, extensionFields);
+ for (DexEncodedField extensionField : extensionFields) {
+ StaticPut staticPut = uniqueStaticPuts.get(extensionField);
+ if (staticPut == null) {
+ // Could happen after we have optimized the code.
+ assert enqueuer.getMode().isFinalTreeShaking();
+ continue;
+ }
+ populateExtensionGraphWithExtensionFieldDefinition(staticPut);
+ }
+ });
+
+ // Clear the set of methods such that we don't re-analyze these methods upon the next fixpoint.
+ findLiteExtensionByNumberMethods.clear();
+ }
+
+ /**
+ * Finds the extension fields referenced in the methods in {@link
+ * #findLiteExtensionByNumberMethods}.
+ */
+ private Map<DexProgramClass, Set<DexEncodedField>> collectExtensionFields() {
+ Map<DexProgramClass, Set<DexEncodedField>> extensionFieldsByClass = new IdentityHashMap<>();
+ for (DexEncodedMethod findLiteExtensionByNumberMethod : findLiteExtensionByNumberMethods) {
+ IRCode code =
+ findLiteExtensionByNumberMethod.buildIR(
+ appView, appView.appInfo().originFor(findLiteExtensionByNumberMethod.method.holder));
+ for (BasicBlock block : code.blocks(BasicBlock::isReturnBlock)) {
+ Value returnValue = block.exit().asReturn().returnValue().getAliasedValue();
+ if (returnValue.isPhi()) {
+ assert false;
+ continue;
+ }
+
+ if (returnValue.isZero()) {
+ continue; // OK.
+ }
+
+ Instruction definition = returnValue.definition;
+ if (definition.isStaticGet()) {
+ StaticGet staticGet = definition.asStaticGet();
+ DexEncodedField field = appView.appInfo().resolveField(staticGet.getField());
+ if (field == null) {
+ assert false;
+ continue;
+ }
+
+ DexProgramClass holder = asProgramClassOrNull(appView.definitionFor(field.field.holder));
+ if (holder == null) {
+ assert false;
+ continue;
+ }
+
+ extensionFieldsByClass
+ .computeIfAbsent(holder, ignore -> Sets.newIdentityHashSet())
+ .add(field);
+ continue;
+ }
+
+ assert definition.isInvokeMethodWithReceiver()
+ && references.isFindLiteExtensionByNumberMethod(
+ definition.asInvokeMethodWithReceiver().getInvokedMethod());
+ }
+ }
+ return extensionFieldsByClass;
+ }
+
+ /**
+ * Updates {@link #extensionGraph} based on the definition of {@param staticPut}.
+ *
+ * <p>See also {@link #populateExtensionGraph}.
+ */
+ private void populateExtensionGraphWithExtensionFieldDefinition(StaticPut staticPut) {
+ Value value = staticPut.value().getAliasedValue();
+ if (value.isPhi()) {
+ return;
+ }
+
+ Instruction extensionFactory = value.definition;
+ if (extensionFactory.isNewInstance()) {
+ extensionFactory =
+ extensionFactory.asNewInstance().getUniqueConstructorInvoke(appView.dexItemFactory());
+ if (extensionFactory == null) {
+ assert false;
+ return;
+ }
+ }
+
+ if (extensionFactory.isInvokeDirect() || extensionFactory.isInvokeStatic()) {
+ InvokeMethod invoke = extensionFactory.asInvokeMethod();
+ DexMethod invokedMethod = invoke.getInvokedMethod();
+
+ TypeLatticeElement containerType, extensionType;
+ if (invokedMethod == references.generatedMessageLiteMethods.newRepeatedGeneratedExtension) {
+ containerType = invoke.arguments().get(0).getTypeLattice();
+ extensionType = invoke.arguments().get(1).getTypeLattice();
+ } else if (invokedMethod
+ == references.generatedMessageLiteMethods.newSingularGeneratedExtension) {
+ containerType = invoke.arguments().get(0).getTypeLattice();
+ extensionType = invoke.arguments().get(2).getTypeLattice();
+ } else if (references.generatedExtensionMethods.isConstructor(invokedMethod)) {
+ containerType = invoke.arguments().get(1).getTypeLattice();
+ extensionType = invoke.arguments().get(3).getTypeLattice();
+ } else {
+ return;
+ }
+
+ if (extensionType.isNullType()) {
+ return; // Extension is a primitive type.
+ }
+
+ if (!containerType.isClassType() || !extensionType.isClassType()) {
+ assert false; // Should generally not happen.
+ return;
+ }
+
+ extensionGraph
+ .computeIfAbsent(
+ containerType.asClassTypeLatticeElement().getClassType(),
+ ignore -> Sets.newIdentityHashSet())
+ .add(extensionType.asClassTypeLatticeElement().getClassType());
+ }
+ }
+
private void markMapOrRequiredFieldsAsReachable(Enqueuer enqueuer, EnqueuerWorklist worklist) {
// TODO(b/112437944): We only need to check if a given field can reach a map/required field
// once. Maybe maintain a map `newlyLiveProtos` that store the set of proto messages that have
@@ -388,9 +568,10 @@
* @return true if this proto message contains a map/required field directly or indirectly.
*/
private boolean reachesMapOrRequiredField(ProtoMessageInfo protoMessageInfo) {
- if (!protoMessageInfo.hasFields()) {
+ if (!protoMessageInfo.hasFields() && !extensionGraph.containsKey(protoMessageInfo.getType())) {
return false;
}
+
OptionalBool cache =
reachesMapOrRequiredFieldFromMessageCache.getOrDefault(
protoMessageInfo, OptionalBool.unknown());
@@ -403,8 +584,21 @@
reachesMapOrRequiredFieldFromMessageCache.put(protoMessageInfo, OptionalBool.of(false));
// Check if any of the fields contains a map/required field.
- for (ProtoFieldInfo protoFieldInfo : protoMessageInfo.getFields()) {
- if (reachesMapOrRequiredField(protoFieldInfo)) {
+ if (protoMessageInfo.hasFields()) {
+ for (ProtoFieldInfo protoFieldInfo : protoMessageInfo.getFields()) {
+ if (reachesMapOrRequiredField(protoFieldInfo)) {
+ reachesMapOrRequiredFieldFromMessageCache.put(protoMessageInfo, OptionalBool.of(true));
+ return true;
+ }
+ }
+ }
+
+ Iterable<DexType> extensionTypes =
+ extensionGraph.getOrDefault(protoMessageInfo.getType(), ImmutableSet.of());
+ for (DexType extensionType : extensionTypes) {
+ ProtoMessageInfo protoExtensionMessageInfo = getOrCreateProtoMessageInfo(extensionType);
+ assert protoExtensionMessageInfo != null;
+ if (reachesMapOrRequiredField(protoExtensionMessageInfo)) {
reachesMapOrRequiredFieldFromMessageCache.put(protoMessageInfo, OptionalBool.of(true));
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoMessageInfo.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoMessageInfo.java
index 663d9f6..ac88305 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoMessageInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoMessageInfo.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.analysis.proto.schema;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.proto.ProtoUtils;
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
@@ -218,6 +219,10 @@
return oneOfObjects;
}
+ public DexType getType() {
+ return dynamicMethod.method.holder;
+ }
+
public boolean hasFields() {
return fields != null && !fields.isEmpty();
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java
index e2af564..39ebff8 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java
@@ -63,7 +63,7 @@
}
public TypeLatticeElement getArrayMemberTypeAsValueType() {
- return memberTypeLattice.isFineGrainedType() ? INT : memberTypeLattice;
+ return memberTypeLattice.isFineGrainedType() ? getInt() : memberTypeLattice;
}
public TypeLatticeElement getArrayBaseTypeLattice() {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
index 3ad41d4..eff9681 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
@@ -8,8 +8,11 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.SetUtils;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
@@ -203,10 +206,8 @@
Collections.emptySet());
}
DexType lubType =
- appView
- .appInfo()
- .withSubtyping()
- .computeLeastUpperBoundOfClasses(getClassType(), other.getClassType());
+ computeLeastUpperBoundOfClasses(
+ appView.appInfo().withSubtyping(), getClassType(), other.getClassType());
Set<DexType> c1lubItfs = getInterfaces();
Set<DexType> c2lubItfs = other.getInterfaces();
Set<DexType> lubItfs = null;
@@ -234,7 +235,50 @@
}
}
- // TODO(b/130636783): inconsistent location
+ public static DexType computeLeastUpperBoundOfClasses(
+ AppInfoWithSubtyping appInfo, DexType type1, DexType type2) {
+ // Compiling R8 with R8, this hits more than 1/3 of cases.
+ if (type1 == type2) {
+ return type1;
+ }
+ // Compiling R8 with R8, this hits more than 1/3 of cases.
+ DexType objectType = appInfo.dexItemFactory().objectType;
+ if (type1 == objectType || type2 == objectType) {
+ return objectType;
+ }
+ // Compiling R8 with R8, there are no hierarchies above height 10.
+ // The overhead of a hash map likely outweighs the speed of scanning an array.
+ Collection<DexType> types = new ArrayList<>(10);
+ DexType type = type1;
+ while (true) {
+ if (type == type2) {
+ return type;
+ }
+ types.add(type);
+ DexClass clazz = appInfo.definitionFor(type);
+ if (clazz == null || clazz.superType == null || clazz.superType == objectType) {
+ break;
+ }
+ type = clazz.superType;
+ }
+ // In pathological cases, realloc to a set if large.
+ if (types.size() > 20) {
+ types = SetUtils.newIdentityHashSet(types);
+ }
+ type = type2;
+ while (true) {
+ if (types.contains(type)) {
+ return type;
+ }
+ DexClass clazz = appInfo.definitionFor(type);
+ if (clazz == null || clazz.superType == null || clazz.superType == objectType) {
+ break;
+ }
+ type = clazz.superType;
+ }
+ return objectType;
+ }
+
public static Set<DexType> computeLeastUpperBoundOfInterfaces(
AppView<? extends AppInfoWithSubtyping> appView, Set<DexType> s1, Set<DexType> s2) {
if (s1.isEmpty() || s2.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java b/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java
index 10f2d3e..3af208a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java
@@ -40,7 +40,7 @@
Deque<Phi> worklist = new ArrayDeque<>(affectedPhis);
while (!worklist.isEmpty()) {
Phi phi = worklist.poll();
- phi.setTypeLattice(TypeLatticeElement.BOTTOM);
+ phi.setTypeLattice(TypeLatticeElement.getBottom());
for (Phi affectedPhi : phi.uniquePhiUsers()) {
if (affectedPhis.add(affectedPhi)) {
worklist.add(affectedPhi);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeLatticeElement.java
index f43ebab..6a16fdc 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeLatticeElement.java
@@ -78,32 +78,32 @@
switch (descriptor) {
case 'Z':
if (asArrayElementType) {
- return TypeLatticeElement.BOOLEAN;
+ return TypeLatticeElement.getBoolean();
}
// fall through
case 'B':
if (asArrayElementType) {
- return TypeLatticeElement.BYTE;
+ return TypeLatticeElement.getByte();
}
// fall through
case 'S':
if (asArrayElementType) {
- return TypeLatticeElement.SHORT;
+ return TypeLatticeElement.getShort();
}
// fall through
case 'C':
if (asArrayElementType) {
- return TypeLatticeElement.CHAR;
+ return TypeLatticeElement.getChar();
}
// fall through
case 'I':
- return TypeLatticeElement.INT;
+ return TypeLatticeElement.getInt();
case 'F':
- return TypeLatticeElement.FLOAT;
+ return TypeLatticeElement.getFloat();
case 'J':
- return TypeLatticeElement.LONG;
+ return TypeLatticeElement.getLong();
case 'D':
- return TypeLatticeElement.DOUBLE;
+ return TypeLatticeElement.getDouble();
case 'V':
throw new InternalCompilerError("No value type for void type.");
default:
@@ -117,13 +117,13 @@
case CHAR:
case SHORT:
case INT:
- return TypeLatticeElement.INT;
+ return TypeLatticeElement.getInt();
case FLOAT:
- return TypeLatticeElement.FLOAT;
+ return TypeLatticeElement.getFloat();
case LONG:
- return TypeLatticeElement.LONG;
+ return TypeLatticeElement.getLong();
case DOUBLE:
- return TypeLatticeElement.DOUBLE;
+ return TypeLatticeElement.getDouble();
default:
throw new Unreachable("Invalid numeric type '" + numericType + "'");
}
@@ -135,16 +135,16 @@
}
if (isSinglePrimitive()) {
if (other.isSinglePrimitive()) {
- return TypeLatticeElement.SINGLE;
+ return TypeLatticeElement.getSingle();
}
assert other.isWidePrimitive();
- return TypeLatticeElement.TOP;
+ return TypeLatticeElement.getTop();
}
assert isWidePrimitive();
if (other.isWidePrimitive()) {
- return TypeLatticeElement.WIDE;
+ return TypeLatticeElement.getWide();
}
assert other.isSinglePrimitive();
- return TypeLatticeElement.TOP;
+ return TypeLatticeElement.getTop();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
index 09a9fad..de7b894 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
@@ -16,22 +16,58 @@
* The base abstraction of lattice elements for local type analysis.
*/
public abstract class TypeLatticeElement {
- public static final BottomTypeLatticeElement BOTTOM = BottomTypeLatticeElement.getInstance();
- public static final TopTypeLatticeElement TOP = TopTypeLatticeElement.getInstance();
- public static final BooleanTypeLatticeElement BOOLEAN = BooleanTypeLatticeElement.getInstance();
- public static final ByteTypeLatticeElement BYTE = ByteTypeLatticeElement.getInstance();
- static final ShortTypeLatticeElement SHORT = ShortTypeLatticeElement.getInstance();
- static final CharTypeLatticeElement CHAR = CharTypeLatticeElement.getInstance();
- public static final IntTypeLatticeElement INT = IntTypeLatticeElement.getInstance();
- public static final FloatTypeLatticeElement FLOAT = FloatTypeLatticeElement.getInstance();
- public static final SinglePrimitiveTypeLatticeElement SINGLE =
- SinglePrimitiveTypeLatticeElement.getInstance();
- public static final LongTypeLatticeElement LONG = LongTypeLatticeElement.getInstance();
- public static final DoubleTypeLatticeElement DOUBLE = DoubleTypeLatticeElement.getInstance();
- public static final WidePrimitiveTypeLatticeElement WIDE =
- WidePrimitiveTypeLatticeElement.getInstance();
- public static final ReferenceTypeLatticeElement NULL =
- ReferenceTypeLatticeElement.getNullTypeLatticeElement();
+
+ public static BottomTypeLatticeElement getBottom() {
+ return BottomTypeLatticeElement.getInstance();
+ }
+
+ public static TopTypeLatticeElement getTop() {
+ return TopTypeLatticeElement.getInstance();
+ }
+
+ public static BooleanTypeLatticeElement getBoolean() {
+ return BooleanTypeLatticeElement.getInstance();
+ }
+
+ public static ByteTypeLatticeElement getByte() {
+ return ByteTypeLatticeElement.getInstance();
+ }
+
+ public static ShortTypeLatticeElement getShort() {
+ return ShortTypeLatticeElement.getInstance();
+ }
+
+ public static CharTypeLatticeElement getChar() {
+ return CharTypeLatticeElement.getInstance();
+ }
+
+ public static IntTypeLatticeElement getInt() {
+ return IntTypeLatticeElement.getInstance();
+ }
+
+ public static FloatTypeLatticeElement getFloat() {
+ return FloatTypeLatticeElement.getInstance();
+ }
+
+ public static SinglePrimitiveTypeLatticeElement getSingle() {
+ return SinglePrimitiveTypeLatticeElement.getInstance();
+ }
+
+ public static LongTypeLatticeElement getLong() {
+ return LongTypeLatticeElement.getInstance();
+ }
+
+ public static DoubleTypeLatticeElement getDouble() {
+ return DoubleTypeLatticeElement.getInstance();
+ }
+
+ public static WidePrimitiveTypeLatticeElement getWide() {
+ return WidePrimitiveTypeLatticeElement.getInstance();
+ }
+
+ public static ReferenceTypeLatticeElement getNull() {
+ return ReferenceTypeLatticeElement.getNullTypeLatticeElement();
+ }
public TypeLatticeElement fixupClassTypeReferences(
Function<DexType, DexType> mapping, AppView<? extends AppInfoWithSubtyping> appView) {
@@ -62,16 +98,16 @@
return this;
}
if (isTop() || other.isTop()) {
- return TOP;
+ return getTop();
}
if (isPrimitive()) {
return other.isPrimitive()
? asPrimitiveTypeLatticeElement().join(other.asPrimitiveTypeLatticeElement())
- : TOP;
+ : getTop();
}
if (other.isPrimitive()) {
// By the above case, !(isPrimitive())
- return TOP;
+ return getTop();
}
// From now on, this and other are precise reference types, i.e., either ArrayType or ClassType.
assert isReference() && other.isReference();
@@ -100,7 +136,7 @@
public static TypeLatticeElement join(
Iterable<TypeLatticeElement> typeLattices, AppView<?> appView) {
- TypeLatticeElement result = BOTTOM;
+ TypeLatticeElement result = getBottom();
for (TypeLatticeElement other : typeLattices) {
result = result.join(other, appView);
}
@@ -373,7 +409,7 @@
DexType type, Nullability nullability, AppView<?> appView, boolean asArrayElementType) {
if (type == DexItemFactory.nullValueType) {
assert !nullability.isDefinitelyNotNull();
- return NULL;
+ return getNull();
}
if (type.isPrimitiveType()) {
return PrimitiveTypeLatticeElement.fromDexType(type, asArrayElementType);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
index 64401fd..9a43d23 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
@@ -66,7 +66,7 @@
assert !typeLattice.isReference() || value == 0;
Value returnedValue =
code.createValue(
- typeLattice.isReference() ? TypeLatticeElement.NULL : typeLattice, debugLocalInfo);
+ typeLattice.isReference() ? TypeLatticeElement.getNull() : typeLattice, debugLocalInfo);
return new ConstNumber(returnedValue, value);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java b/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
index cc154e9..5d97e99 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
@@ -140,20 +140,20 @@
ConstNumber newConst;
if (type == NumericType.INT) {
int result = foldIntegers(leftConst.getIntValue(), rightConst.getIntValue());
- Value value = code.createValue(TypeLatticeElement.INT, getLocalInfo());
+ Value value = code.createValue(TypeLatticeElement.getInt(), getLocalInfo());
newConst = new ConstNumber(value, result);
} else if (type == NumericType.LONG) {
long result = foldLongs(leftConst.getLongValue(), rightConst.getLongValue());
- Value value = code.createValue(TypeLatticeElement.LONG, getLocalInfo());
+ Value value = code.createValue(TypeLatticeElement.getLong(), getLocalInfo());
newConst = new ConstNumber(value, result);
} else if (type == NumericType.FLOAT) {
float result = foldFloat(leftConst.getFloatValue(), rightConst.getFloatValue());
- Value value = code.createValue(TypeLatticeElement.FLOAT, getLocalInfo());
+ Value value = code.createValue(TypeLatticeElement.getFloat(), getLocalInfo());
newConst = new ConstNumber(value, Float.floatToIntBits(result));
} else {
assert type == NumericType.DOUBLE;
double result = foldDouble(leftConst.getDoubleValue(), rightConst.getDoubleValue());
- Value value = code.createValue(TypeLatticeElement.DOUBLE, getLocalInfo());
+ Value value = code.createValue(TypeLatticeElement.getDouble(), getLocalInfo());
newConst = new ConstNumber(value, Double.doubleToLongBits(result));
}
return new ConstLatticeElement(newConst);
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
index d90bf21..0c118fb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
@@ -96,11 +96,11 @@
case BOOLEAN_OR_BYTE:
ArrayTypeLatticeElement arrayType = array().getTypeLattice().asArrayTypeLatticeElement();
if (arrayType != null
- && arrayType.getArrayMemberTypeAsMemberType() == TypeLatticeElement.BOOLEAN) {
+ && arrayType.getArrayMemberTypeAsMemberType() == TypeLatticeElement.getBoolean()) {
instruction = new AgetBoolean(dest, array, index);
} else {
assert array().getTypeLattice().isDefinitelyNull()
- || arrayType.getArrayMemberTypeAsMemberType() == TypeLatticeElement.BYTE;
+ || arrayType.getArrayMemberTypeAsMemberType() == TypeLatticeElement.getByte();
instruction = new AgetByte(dest, array, index);
}
break;
@@ -203,9 +203,10 @@
// the instruction cannot return. For now we return NULL as the type to ensure we have a
// type consistent witness for the out-value type. We could consider returning bottom in
// this case as the value is indeed empty, i.e., the instruction will always fail.
- TypeLatticeElement valueType = arrayTypeLattice == null
- ? TypeLatticeElement.NULL
- : arrayTypeLattice.getArrayMemberTypeAsValueType();
+ TypeLatticeElement valueType =
+ arrayTypeLattice == null
+ ? TypeLatticeElement.getNull()
+ : arrayTypeLattice.getArrayMemberTypeAsValueType();
assert valueType.isReference();
return valueType;
case BOOLEAN_OR_BYTE:
@@ -214,19 +215,19 @@
case INT:
assert arrayTypeLattice == null
|| arrayTypeLattice.getArrayMemberTypeAsValueType().isInt();
- return TypeLatticeElement.INT;
+ return TypeLatticeElement.getInt();
case FLOAT:
assert arrayTypeLattice == null
|| arrayTypeLattice.getArrayMemberTypeAsValueType().isFloat();
- return TypeLatticeElement.FLOAT;
+ return TypeLatticeElement.getFloat();
case LONG:
assert arrayTypeLattice == null
|| arrayTypeLattice.getArrayMemberTypeAsValueType().isLong();
- return TypeLatticeElement.LONG;
+ return TypeLatticeElement.getLong();
case DOUBLE:
assert arrayTypeLattice == null
|| arrayTypeLattice.getArrayMemberTypeAsValueType().isDouble();
- return TypeLatticeElement.DOUBLE;
+ return TypeLatticeElement.getDouble();
case INT_OR_FLOAT:
assert arrayTypeLattice == null
|| arrayTypeLattice.getArrayMemberTypeAsValueType().isSinglePrimitive();
@@ -268,7 +269,7 @@
public boolean outTypeKnownToBeBoolean(Set<Phi> seen) {
return array().getTypeLattice().isArrayType()
&& array().getTypeLattice().asArrayTypeLatticeElement().getArrayMemberTypeAsMemberType()
- == TypeLatticeElement.BOOLEAN;
+ == TypeLatticeElement.getBoolean();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
index f767de8..b2f908c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
@@ -131,7 +131,7 @@
@Override
public TypeLatticeElement evaluate(AppView<?> appView) {
- return TypeLatticeElement.INT;
+ return TypeLatticeElement.getInt();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index 8917cc0..b8d8526 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -92,11 +92,11 @@
case BOOLEAN_OR_BYTE:
ArrayTypeLatticeElement arrayType = array().getTypeLattice().asArrayTypeLatticeElement();
if (arrayType != null
- && arrayType.getArrayMemberTypeAsMemberType() == TypeLatticeElement.BOOLEAN) {
+ && arrayType.getArrayMemberTypeAsMemberType() == TypeLatticeElement.getBoolean()) {
instruction = new AputBoolean(value, array, index);
} else {
assert array().getTypeLattice().isDefinitelyNull()
- || arrayType.getArrayMemberTypeAsMemberType() == TypeLatticeElement.BYTE;
+ || arrayType.getArrayMemberTypeAsMemberType() == TypeLatticeElement.getByte();
instruction = new AputByte(value, array, index);
}
break;
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 5203cb9..4c3822a 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
@@ -612,6 +612,10 @@
return instructions.isEmpty();
}
+ public boolean isReturnBlock() {
+ return exit().isReturn();
+ }
+
public Instruction entry() {
return instructions.get(0);
}
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 301be93..3cae82a 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
@@ -719,7 +719,7 @@
new Phi(
code.valueNumberGenerator.next(),
newExitBlock,
- TypeLatticeElement.BOTTOM,
+ TypeLatticeElement.getBottom(),
null,
RegisterReadType.NORMAL);
phi.addOperands(operands);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Cmp.java b/src/main/java/com/android/tools/r8/ir/code/Cmp.java
index 97c76ce..acccf31 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Cmp.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Cmp.java
@@ -186,7 +186,7 @@
result = (int) Math.signum(left - right);
}
}
- Value value = code.createValue(TypeLatticeElement.INT, getLocalInfo());
+ Value value = code.createValue(TypeLatticeElement.getInt(), getLocalInfo());
ConstNumber newConst = new ConstNumber(value, result);
return new ConstLatticeElement(newConst);
} else if (leftLattice.isValueRange() && rightLattice.isConst()) {
@@ -214,7 +214,7 @@
return Bottom.getInstance();
}
int result = Integer.signum(Long.compare(leftRange.getMin(), rightRange.getMin()));
- Value value = code.createValue(TypeLatticeElement.INT, getLocalInfo());
+ Value value = code.createValue(TypeLatticeElement.getInt(), getLocalInfo());
ConstNumber newConst = new ConstNumber(value, result);
return new ConstLatticeElement(newConst);
}
@@ -236,7 +236,7 @@
@Override
public TypeLatticeElement evaluate(AppView<?> appView) {
- return TypeLatticeElement.INT;
+ return TypeLatticeElement.getInt();
}
}
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 3cae1e4..39b6fb8 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
@@ -99,7 +99,7 @@
}
// * IllegalAccessError (not visible from the access context).
if (!isMemberVisibleFromOriginalContext(
- appView, context, resolvedField.field.holder, resolvedField.accessFlags)) {
+ appView, context, field.holder, resolvedField.accessFlags)) {
return AbstractError.specific(appView.dexItemFactory().illegalAccessErrorType);
}
// TODO(b/137168535): Without non-null tracking, only locally created receiver is allowed in D8.
@@ -209,6 +209,10 @@
assert isFieldGet();
DexEncodedField field = appView.appInfo().resolveField(getField());
if (field != null) {
+ DexClass holder = appView.definitionFor(field.field.holder);
+ if (holder != null && holder.isLibraryClass() && field.isStatic() && field.isFinal()) {
+ return appView.abstractValueFactory().createSingleFieldValue(field.field);
+ }
return field.getOptimizationInfo().getAbstractValue();
}
return UnknownValue.getInstance();
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 9594d10..39c3d39 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
@@ -949,6 +949,10 @@
return true;
}
+ public Iterable<BasicBlock> blocks(Predicate<BasicBlock> predicate) {
+ return () -> IteratorUtils.filter(listIterator(), predicate);
+ }
+
public Iterable<Instruction> instructions() {
return this::instructionIterator;
}
@@ -1046,12 +1050,12 @@
}
public ConstNumber createDoubleConstant(double value, DebugLocalInfo local) {
- Value out = createValue(TypeLatticeElement.DOUBLE, local);
+ Value out = createValue(TypeLatticeElement.getDouble(), local);
return new ConstNumber(out, Double.doubleToLongBits(value));
}
public ConstNumber createFloatConstant(float value, DebugLocalInfo local) {
- Value out = createValue(TypeLatticeElement.FLOAT, local);
+ Value out = createValue(TypeLatticeElement.getFloat(), local);
return new ConstNumber(out, Float.floatToIntBits(value));
}
@@ -1060,12 +1064,12 @@
}
public ConstNumber createIntConstant(int value, DebugLocalInfo local) {
- Value out = createValue(TypeLatticeElement.INT, local);
+ Value out = createValue(TypeLatticeElement.getInt(), local);
return new ConstNumber(out, value);
}
public ConstNumber createLongConstant(long value, DebugLocalInfo local) {
- Value out = createValue(TypeLatticeElement.LONG, local);
+ Value out = createValue(TypeLatticeElement.getLong(), local);
return new ConstNumber(out, value);
}
@@ -1084,12 +1088,12 @@
}
public ConstNumber createConstNull() {
- Value out = createValue(TypeLatticeElement.NULL);
+ Value out = createValue(TypeLatticeElement.getNull());
return new ConstNumber(out, 0);
}
public ConstNumber createConstNull(DebugLocalInfo local) {
- Value out = createValue(TypeLatticeElement.NULL, local);
+ Value out = createValue(TypeLatticeElement.getNull(), local);
return new ConstNumber(out, 0);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java
index a394ce5..f6348ac 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java
@@ -4,15 +4,40 @@
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.utils.DequeUtils;
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
import java.util.Deque;
+import java.util.IdentityHashMap;
+import java.util.Map;
import java.util.Set;
public class IRCodeUtils {
/**
+ * Finds the single assignment to the fields in {@param fields} in {@param code}. Note that this
+ * does not guarantee that the assignments found dominate all the normal exits.
+ */
+ public static Map<DexEncodedField, StaticPut> findUniqueStaticPuts(
+ AppView<?> appView, IRCode code, Set<DexEncodedField> fields) {
+ Set<DexEncodedField> writtenMoreThanOnce = Sets.newIdentityHashSet();
+ Map<DexEncodedField, StaticPut> uniqueStaticPuts = new IdentityHashMap<>();
+ for (StaticPut staticPut : code.<StaticPut>instructions(Instruction::isStaticPut)) {
+ DexEncodedField field = appView.appInfo().resolveField(staticPut.getField());
+ if (field == null || !fields.contains(field) || writtenMoreThanOnce.contains(field)) {
+ continue;
+ }
+ if (uniqueStaticPuts.put(field, staticPut) != null) {
+ writtenMoreThanOnce.add(field);
+ uniqueStaticPuts.remove(field);
+ }
+ }
+ return uniqueStaticPuts;
+ }
+
+ /**
* Removes {@param instruction} if it is a {@link NewArrayEmpty} instruction, which only has
* array-put users. Also removes all instructions that contribute to the computation of the
* indices and the elements, if they end up being unused, even if the instructions may have side
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
index d59316f..de4fa66 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
@@ -92,7 +92,7 @@
@Override
public TypeLatticeElement evaluate(AppView<?> appView) {
- return TypeLatticeElement.INT;
+ return TypeLatticeElement.getInt();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java b/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
index ad3d344..7f87253 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
@@ -117,7 +117,7 @@
ConstNumber newConst;
if (type == NumericType.INT) {
int result = foldIntegers(leftConst.getIntValue(), rightConst.getIntValue());
- Value value = code.createValue(TypeLatticeElement.INT, getLocalInfo());
+ Value value = code.createValue(TypeLatticeElement.getInt(), getLocalInfo());
newConst = new ConstNumber(value, result);
} else {
assert type == NumericType.LONG;
@@ -129,7 +129,7 @@
right = rightConst.getLongValue();
}
long result = foldLongs(leftConst.getLongValue(), right);
- Value value = code.createValue(TypeLatticeElement.LONG, getLocalInfo());
+ Value value = code.createValue(TypeLatticeElement.getLong(), getLocalInfo());
newConst = new ConstNumber(value, result);
}
return new ConstLatticeElement(newConst);
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 407742e..aed4b9e 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
@@ -36,6 +36,27 @@
this.clazz = clazz;
}
+ public InvokeDirect getUniqueConstructorInvoke(DexItemFactory dexItemFactory) {
+ InvokeDirect result = null;
+ for (Instruction user : outValue().uniqueUsers()) {
+ if (user.isInvokeDirect()) {
+ InvokeDirect invoke = user.asInvokeDirect();
+ if (!dexItemFactory.isConstructor(invoke.getInvokedMethod())) {
+ continue;
+ }
+ if (invoke.getReceiver() != outValue()) {
+ continue;
+ }
+ if (result != null) {
+ // Does not have a unique constructor invoke.
+ return null;
+ }
+ result = invoke;
+ }
+ }
+ return result;
+ }
+
@Override
public int opcode() {
return Opcodes.NEW_INSTANCE;
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 16fc789..eecf8c3 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
@@ -393,7 +393,7 @@
// Type of phi(v1, v2, ..., vn) is the least upper bound of all those n operands.
public TypeLatticeElement computePhiType(AppView<?> appView) {
- TypeLatticeElement result = TypeLatticeElement.BOTTOM;
+ TypeLatticeElement result = TypeLatticeElement.getBottom();
for (Value operand : getOperands()) {
result = result.join(operand.getTypeLattice(), appView);
}
@@ -416,7 +416,7 @@
}
}
Set<Value> visitedOperands = Sets.newIdentityHashSet();
- TypeLatticeElement result = TypeLatticeElement.BOTTOM;
+ TypeLatticeElement result = TypeLatticeElement.getBottom();
for (Phi phi : reachablePhis) {
for (Value operand : phi.getOperands()) {
if (!operand.getAliasedValue().isPhi() && visitedOperands.add(operand)) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/StackValues.java b/src/main/java/com/android/tools/r8/ir/code/StackValues.java
index 1ce52f9..528cddd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StackValues.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StackValues.java
@@ -16,7 +16,7 @@
private final StackValue[] stackValues;
public StackValues(StackValue... stackValues) {
- super(Value.UNDEFINED_NUMBER, TypeLatticeElement.BOTTOM, null);
+ super(Value.UNDEFINED_NUMBER, TypeLatticeElement.getBottom(), null);
this.stackValues = stackValues;
assert stackValues.length >= 2;
}
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 bba9148..8e75d62 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
@@ -79,9 +79,9 @@
if (typeLattice.isTop()) {
if (definition != null && definition.isConstNumber()) {
assert definition.asConstNumber().isZero();
- return TypeLatticeElement.NULL;
+ return TypeLatticeElement.getNull();
} else {
- return TypeLatticeElement.BOTTOM;
+ return TypeLatticeElement.getBottom();
}
}
if (typeLattice.isReference()) {
@@ -98,17 +98,17 @@
break;
case INT:
if (typeLattice.isTop() || (typeLattice.isSinglePrimitive() && !typeLattice.isFloat())) {
- return TypeLatticeElement.INT;
+ return TypeLatticeElement.getInt();
}
break;
case FLOAT:
if (typeLattice.isTop() || (typeLattice.isSinglePrimitive() && !typeLattice.isInt())) {
- return TypeLatticeElement.FLOAT;
+ return TypeLatticeElement.getFloat();
}
break;
case INT_OR_FLOAT:
if (typeLattice.isTop()) {
- return TypeLatticeElement.SINGLE;
+ return TypeLatticeElement.getSingle();
}
if (typeLattice.isSinglePrimitive()) {
return typeLattice;
@@ -116,12 +116,12 @@
break;
case LONG:
if (typeLattice.isWidePrimitive() && !typeLattice.isDouble()) {
- return TypeLatticeElement.LONG;
+ return TypeLatticeElement.getLong();
}
break;
case DOUBLE:
if (typeLattice.isWidePrimitive() && !typeLattice.isLong()) {
- return TypeLatticeElement.DOUBLE;
+ return TypeLatticeElement.getDouble();
}
break;
case LONG_OR_DOUBLE:
@@ -213,7 +213,7 @@
public static final int UNDEFINED_NUMBER = -1;
public static final Value UNDEFINED =
- new Value(UNDEFINED_NUMBER, TypeLatticeElement.BOTTOM, null);
+ new Value(UNDEFINED_NUMBER, TypeLatticeElement.getBottom(), null);
protected final int number;
public Instruction definition = null;
@@ -232,6 +232,7 @@
protected TypeLatticeElement typeLattice;
public Value(int number, TypeLatticeElement typeLattice, DebugLocalInfo local) {
+ assert typeLattice != null;
this.number = number;
this.debugData = local == null ? null : new DebugData(local);
this.typeLattice = typeLattice;
@@ -1101,6 +1102,7 @@
* @param newType The new type lattice element
*/
public void setTypeLattice(TypeLatticeElement newType) {
+ assert newType != null;
typeLattice = newType;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ValueType.java b/src/main/java/com/android/tools/r8/ir/code/ValueType.java
index e3926c5..865c7d4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ValueType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ValueType.java
@@ -124,13 +124,13 @@
public PrimitiveTypeLatticeElement toPrimitiveTypeLattice() {
switch (this) {
case INT:
- return TypeLatticeElement.INT;
+ return TypeLatticeElement.getInt();
case FLOAT:
- return TypeLatticeElement.FLOAT;
+ return TypeLatticeElement.getFloat();
case LONG:
- return TypeLatticeElement.LONG;
+ return TypeLatticeElement.getLong();
case DOUBLE:
- return TypeLatticeElement.DOUBLE;
+ return TypeLatticeElement.getDouble();
default:
throw new Unreachable("Unexpected type in conversion to primitive: " + this);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ValueTypeConstraint.java b/src/main/java/com/android/tools/r8/ir/code/ValueTypeConstraint.java
index eb0ba83..1f7f97b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ValueTypeConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ValueTypeConstraint.java
@@ -160,13 +160,13 @@
public PrimitiveTypeLatticeElement toPrimitiveTypeLattice() {
switch (this) {
case INT:
- return TypeLatticeElement.INT;
+ return TypeLatticeElement.getInt();
case FLOAT:
- return TypeLatticeElement.FLOAT;
+ return TypeLatticeElement.getFloat();
case LONG:
- return TypeLatticeElement.LONG;
+ return TypeLatticeElement.getLong();
case DOUBLE:
- return TypeLatticeElement.DOUBLE;
+ return TypeLatticeElement.getDouble();
default:
throw new Unreachable("Unexpected type in conversion to primitive: " + this);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 682804e..83ffaeb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -385,7 +385,7 @@
|| constNumber == null
|| add == null
|| store == null
- || constNumber.outValue().getTypeLattice() != TypeLatticeElement.INT) {
+ || constNumber.outValue().getTypeLattice() != TypeLatticeElement.getInt()) {
it.next();
continue;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index a8b0e60..8af9977 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -606,9 +606,6 @@
public void add(com.android.tools.r8.ir.code.Instruction instr, Instruction dex) {
assert !instr.isGoto();
- assert isBuildingForComparison()
- || !instr.isDexItemBasedConstString()
- || ir.method.getOptimizationInfo().useIdentifierNameString();
add(instr, new FixedSizeInfo(instr, dex));
}
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 6233a57..42728d4 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
@@ -6,6 +6,14 @@
import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getBottom;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getDouble;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getFloat;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getInt;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getLong;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getNull;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getSingle;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getWide;
import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.errors.CompilationError;
@@ -139,12 +147,6 @@
*/
public class IRBuilder {
- private static final TypeLatticeElement INT = TypeLatticeElement.INT;
- private static final TypeLatticeElement FLOAT = TypeLatticeElement.FLOAT;
- private static final TypeLatticeElement LONG = TypeLatticeElement.LONG;
- private static final TypeLatticeElement DOUBLE = TypeLatticeElement.DOUBLE;
- private static final TypeLatticeElement NULL = TypeLatticeElement.NULL;
-
public static final int INITIAL_BLOCK_OFFSET = -1;
private static TypeLatticeElement fromMemberType(MemberType type) {
@@ -153,20 +155,20 @@
case CHAR:
case SHORT:
case INT:
- return INT;
+ return getInt();
case FLOAT:
- return FLOAT;
+ return getFloat();
case INT_OR_FLOAT:
- return TypeLatticeElement.SINGLE;
+ return getSingle();
case LONG:
- return LONG;
+ return getLong();
case DOUBLE:
- return DOUBLE;
+ return getDouble();
case LONG_OR_DOUBLE:
- return TypeLatticeElement.WIDE;
+ return getWide();
case OBJECT:
// For object types, we delay the exact type computation until done building.
- return TypeLatticeElement.BOTTOM;
+ return getBottom();
default:
throw new Unreachable("Unexpected member type: " + type);
}
@@ -895,7 +897,7 @@
RemovedArgumentInfo removedArgumentInfo = getRemovedArgumentInfo();
if (removedArgumentInfo == null) {
DebugLocalInfo local = getOutgoingLocal(register);
- Value value = writeRegister(register, INT, ThrowingInfo.NO_THROW, local);
+ Value value = writeRegister(register, getInt(), ThrowingInfo.NO_THROW, local);
addNonThisArgument(new Argument(value, true));
} else {
assert removedArgumentInfo.isNeverUsed();
@@ -922,7 +924,7 @@
pendingArgumentInstructions = new ArrayList<>();
}
DebugLocalInfo local = getOutgoingLocal(register);
- Value value = writeRegister(register, TypeLatticeElement.NULL, ThrowingInfo.NO_THROW, local);
+ Value value = writeRegister(register, getNull(), ThrowingInfo.NO_THROW, local);
pendingArgumentInstructions.add(new ConstNumber(value, 0));
} else {
assert removedArgumentInfo.isNeverUsed();
@@ -982,6 +984,12 @@
if (currentBlock.getInstructions().isEmpty()) {
addInstruction(new DebugLocalRead());
} else {
+ // We do not want to add the out value of an instructions as a debug value for
+ // the same instruction. Debug values are there to keep values alive until that
+ // instruction. Therefore, they make no sense on the instruction that defines
+ // the value. There should always be a DebugLocalRead in the IR for situations
+ // where an introduced local's scope ends immediately.
+ assert !debugLocalEnds.contains(currentBlock.getInstructions().getLast().outValue());
attachLocalValues(currentBlock.getInstructions().getLast());
}
}
@@ -1043,7 +1051,7 @@
public void addArrayLength(int dest, int array) {
Value in = readRegister(array, ValueTypeConstraint.OBJECT);
- Value out = writeRegister(dest, INT, ThrowingInfo.CAN_THROW);
+ Value out = writeRegister(dest, getInt(), ThrowingInfo.CAN_THROW);
ArrayLength instruction = new ArrayLength(out, in);
assert instruction.instructionTypeCanThrow();
add(instruction);
@@ -1073,7 +1081,7 @@
public void addCmp(NumericType type, Bias bias, int dest, int left, int right) {
Value in1 = readNumericRegister(left, type);
Value in2 = readNumericRegister(right, type);
- Value out = writeRegister(dest, INT, ThrowingInfo.NO_THROW);
+ Value out = writeRegister(dest, getInt(), ThrowingInfo.NO_THROW);
Cmp instruction = new Cmp(type, bias, out, in1, in2);
assert !instruction.instructionTypeCanThrow();
add(instruction);
@@ -1087,23 +1095,23 @@
}
public void addLongConst(int dest, long value) {
- add(new ConstNumber(writeRegister(dest, LONG, ThrowingInfo.NO_THROW), value));
+ add(new ConstNumber(writeRegister(dest, getLong(), ThrowingInfo.NO_THROW), value));
}
public void addDoubleConst(int dest, long value) {
- add(new ConstNumber(writeRegister(dest, DOUBLE, ThrowingInfo.NO_THROW), value));
+ add(new ConstNumber(writeRegister(dest, getDouble(), ThrowingInfo.NO_THROW), value));
}
public void addIntConst(int dest, long value) {
- add(new ConstNumber(writeRegister(dest, INT, ThrowingInfo.NO_THROW), value));
+ add(new ConstNumber(writeRegister(dest, getInt(), ThrowingInfo.NO_THROW), value));
}
public void addFloatConst(int dest, long value) {
- add(new ConstNumber(writeRegister(dest, FLOAT, ThrowingInfo.NO_THROW), value));
+ add(new ConstNumber(writeRegister(dest, getFloat(), ThrowingInfo.NO_THROW), value));
}
public void addNullConst(int dest) {
- add(new ConstNumber(writeRegister(dest, NULL, ThrowingInfo.NO_THROW), 0L));
+ add(new ConstNumber(writeRegister(dest, getNull(), ThrowingInfo.NO_THROW), 0L));
}
public void addConstClass(int dest, DexType type) {
@@ -1160,7 +1168,6 @@
public void addDexItemBasedConstString(
int dest, DexReference item, NameComputationInfo<?> nameComputationInfo) {
- assert method.getOptimizationInfo().useIdentifierNameString();
TypeLatticeElement typeLattice =
TypeLatticeElement.stringClassType(appView, definitelyNotNull());
ThrowingInfo throwingInfo = throwingInfoForConstStrings();
@@ -1212,6 +1219,11 @@
addInstruction(new DebugLocalWrite(out, in));
return;
}
+ // If this move ends locals, add a DebugLocalRead to make sure the end point
+ // is registered in the right place.
+ if (!debugLocalEnds.isEmpty()) {
+ addInstruction(new DebugLocalRead());
+ }
}
currentBlock.writeCurrentDefinition(dest, in, ThrowingInfo.NO_THROW);
}
@@ -1235,6 +1247,15 @@
addInstruction(instruction);
}
+ public void addNop() {
+ // If locals end on a nop, insert a debug local read as the ending point.
+ // This avoids situations where the end of the local could be the instruction
+ // that introduced it when the local only spans a nop in the input.
+ if (!debugLocalEnds.isEmpty()) {
+ addInstruction(new DebugLocalRead());
+ }
+ }
+
public void addRem(NumericType type, int dest, int left, int right) {
boolean canThrow = type != NumericType.DOUBLE && type != NumericType.FLOAT;
Value in1 = readNumericRegister(left, type);
@@ -1358,7 +1379,7 @@
public void addInstanceOf(int dest, int value, DexType type) {
Value in = readRegister(value, ValueTypeConstraint.OBJECT);
- Value out = writeRegister(dest, INT, ThrowingInfo.CAN_THROW);
+ Value out = writeRegister(dest, getInt(), ThrowingInfo.CAN_THROW);
InstanceOf instruction = new InstanceOf(out, in, type);
assert instruction.instructionTypeCanThrow();
addInstruction(instruction);
@@ -2098,9 +2119,7 @@
// A debug initiated value must have a precise type constraint.
assert typeConstraint.isPrecise();
TypeLatticeElement type =
- typeConstraint.isObject()
- ? TypeLatticeElement.NULL
- : typeConstraint.toPrimitiveTypeLattice();
+ typeConstraint.isObject() ? getNull() : typeConstraint.toPrimitiveTypeLattice();
if (uninitializedDebugLocalValues == null) {
uninitializedDebugLocalValues = new Int2ReferenceOpenHashMap<>();
}
@@ -2137,14 +2156,14 @@
}
private Value readLongLiteral(long constant) {
- Value value = new Value(valueNumberGenerator.next(), LONG, null);
+ Value value = new Value(valueNumberGenerator.next(), getLong(), null);
ConstNumber number = new ConstNumber(value, constant);
add(number);
return number.outValue();
}
private Value readIntLiteral(long constant) {
- Value value = new Value(valueNumberGenerator.next(), INT, null);
+ Value value = new Value(valueNumberGenerator.next(), getInt(), null);
ConstNumber number = new ConstNumber(value, constant);
add(number);
return number.outValue();
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 028a397..2c9a53c 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
@@ -15,7 +15,6 @@
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexApplication.Builder;
-import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
@@ -44,6 +43,7 @@
import com.android.tools.r8.ir.desugar.D8NestBasedAccessDesugaring;
import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
+import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor;
import com.android.tools.r8.ir.desugar.LambdaRewriter;
import com.android.tools.r8.ir.desugar.StringConcatRewriter;
import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
@@ -51,11 +51,13 @@
import com.android.tools.r8.ir.optimize.AssertionsRewriter;
import com.android.tools.r8.ir.optimize.Assumer;
import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization;
+import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
import com.android.tools.r8.ir.optimize.CodeRewriter;
import com.android.tools.r8.ir.optimize.ConstantCanonicalizer;
import com.android.tools.r8.ir.optimize.DeadCodeRemover;
import com.android.tools.r8.ir.optimize.Devirtualizer;
import com.android.tools.r8.ir.optimize.DynamicTypeOptimization;
+import com.android.tools.r8.ir.optimize.EnumUnboxer;
import com.android.tools.r8.ir.optimize.IdempotentFunctionCallCanonicalizer;
import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -100,10 +102,10 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Sets;
-import com.google.common.collect.Streams;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -156,6 +158,7 @@
private final TypeChecker typeChecker;
private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
private final ServiceLoaderRewriter serviceLoaderRewriter;
+ private final EnumUnboxer enumUnboxer;
// Assumers that will insert Assume instructions.
public final Collection<Assumer> assumers = new ArrayList<>();
@@ -187,7 +190,8 @@
assert appView.appInfo().hasLiveness() || appView.graphLense().isIdentityLense();
assert appView.options() != null;
assert appView.options().programConsumer != null;
- this.timing = timing != null ? timing : new Timing("internal");
+ assert timing != null;
+ this.timing = timing;
this.appView = appView;
this.options = appView.options();
this.printer = printer;
@@ -195,7 +199,7 @@
this.codeRewriter = new CodeRewriter(appView, this);
this.constantCanonicalizer = new ConstantCanonicalizer(codeRewriter);
this.classInitializerDefaultsOptimization =
- options.debug ? null : new ClassInitializerDefaultsOptimization(appView, this);
+ new ClassInitializerDefaultsOptimization(appView, this);
this.stringConcatRewriter = new StringConcatRewriter(appView);
this.stringOptimizer = new StringOptimizer(appView);
this.stringBuilderOptimizer = new StringBuilderOptimizer(appView);
@@ -243,10 +247,13 @@
this.stringSwitchRemover = null;
this.serviceLoaderRewriter = null;
this.methodOptimizationInfoCollector = null;
+ this.enumUnboxer = null;
return;
}
this.lambdaRewriter =
- options.desugarState == DesugarState.ON ? new LambdaRewriter(appView) : null;
+ (options.desugarState == DesugarState.ON && !appView.enableWholeProgramOptimizations())
+ ? new LambdaRewriter(appView)
+ : null;
this.interfaceMethodRewriter =
options.isInterfaceMethodDesugaringEnabled()
? new InterfaceMethodRewriter(appView, this)
@@ -313,12 +320,13 @@
options.enableUninstantiatedTypeOptimization
? new UninstantiatedTypeOptimization(appViewWithLiveness)
: null;
- this.typeChecker = new TypeChecker(appView.withLiveness());
+ this.typeChecker = new TypeChecker(appViewWithLiveness);
this.d8NestBasedAccessDesugaring = null;
this.serviceLoaderRewriter =
options.enableServiceLoaderRewriting
- ? new ServiceLoaderRewriter(appView.withLiveness())
+ ? new ServiceLoaderRewriter(appViewWithLiveness)
: null;
+ this.enumUnboxer = options.enableEnumUnboxing ? new EnumUnboxer(appViewWithLiveness) : null;
} else {
this.classInliner = null;
this.classStaticizer = null;
@@ -339,6 +347,7 @@
options.shouldDesugarNests() ? new D8NestBasedAccessDesugaring(appView) : null;
this.serviceLoaderRewriter = null;
this.methodOptimizationInfoCollector = null;
+ this.enumUnboxer = null;
}
this.stringSwitchRemover =
options.isStringSwitchConversionEnabled()
@@ -346,14 +355,6 @@
: null;
}
- public Set<DexCallSite> getDesugaredCallSites() {
- if (lambdaRewriter != null) {
- return lambdaRewriter.getDesugaredCallSites();
- } else {
- return Collections.emptySet();
- }
- }
-
/** Create an IR converter for processing methods with full program optimization disabled. */
public IRConverter(AppView<?> appView, Timing timing) {
this(appView, timing, null, MainDexClasses.NONE);
@@ -399,11 +400,8 @@
private void synthesizeLambdaClasses(Builder<?> builder, ExecutorService executorService)
throws ExecutionException {
if (lambdaRewriter != null) {
- if (appView.enableWholeProgramOptimizations()) {
- lambdaRewriter.finalizeLambdaDesugaringForR8(builder);
- } else {
- lambdaRewriter.finalizeLambdaDesugaringForD8(builder, this, executorService);
- }
+ assert !appView.enableWholeProgramOptimizations();
+ lambdaRewriter.finalizeLambdaDesugaringForD8(builder, this, executorService);
}
}
@@ -422,12 +420,13 @@
private void desugarInterfaceMethods(
Builder<?> builder,
- InterfaceMethodRewriter.Flavor includeAllResources,
+ OptimizationFeedback feedback,
+ Flavor includeAllResources,
ExecutorService executorService)
throws ExecutionException {
if (interfaceMethodRewriter != null) {
interfaceMethodRewriter.desugarInterfaceMethods(
- builder, includeAllResources, executorService);
+ builder, feedback, includeAllResources, executorService);
}
}
@@ -463,7 +462,7 @@
desugarNestBasedAccess(builder, executor);
synthesizeLambdaClasses(builder, executor);
- desugarInterfaceMethods(builder, ExcludeDexResources, executor);
+ desugarInterfaceMethods(builder, simpleOptimizationFeedback, ExcludeDexResources, executor);
synthesizeTwrCloseResourceUtilityClass(builder, executor);
synthesizeJava8UtilityClass(builder, executor);
processCovariantReturnTypeAnnotations(builder);
@@ -622,22 +621,12 @@
}
public DexApplication optimize(ExecutorService executorService) throws ExecutionException {
- if (options.isShrinking()) {
- assert !removeLambdaDeserializationMethods();
- } else {
- removeLambdaDeserializationMethods();
- }
-
DexApplication application = appView.appInfo().app();
computeReachabilitySensitivity(application);
collectLambdaMergingCandidates(application);
collectStaticizerCandidates(application);
- if (lambdaRewriter != null) {
- lambdaRewriter.installGraphLens();
- }
-
// The process is in two phases in general.
// 1) Subject all DexEncodedMethods to optimization, except some optimizations that require
// reprocessing IR code of methods, e.g., outlining, double-inlining, class staticizer, etc.
@@ -665,6 +654,7 @@
method -> processMethod(method, feedback, primaryMethodProcessor),
this::waveStart,
this::waveDone,
+ timing,
executorService);
timing.end();
assert graphLenseForIR == appView.graphLense();
@@ -678,6 +668,10 @@
libraryMethodOverrideAnalysis.finish();
}
+ if (enumUnboxer != null) {
+ enumUnboxer.finishEnumAnalysis();
+ }
+
// Post processing:
// 1) Second pass for methods whose collected call site information become more precise.
// 2) Second inlining pass for dealing with double inline callers.
@@ -715,7 +709,8 @@
synthesizeLambdaClasses(builder, executorService);
printPhase("Interface method desugaring");
- desugarInterfaceMethods(builder, IncludeAllResources, executorService);
+ desugarInterfaceMethods(builder, feedback, IncludeAllResources, executorService);
+ feedback.updateVisibleOptimizationInfo();
printPhase("Twr close resource utility class synthesis");
synthesizeTwrCloseResourceUtilityClass(builder, executorService);
@@ -756,7 +751,8 @@
code -> {
outliner.applyOutliningCandidate(code);
printMethod(code, "IR after outlining (SSA)", null);
- finalizeIR(code.method, code, OptimizationFeedbackIgnore.getInstance());
+ finalizeIR(
+ code.method, code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
},
executorService);
feedback.updateVisibleOptimizationInfo();
@@ -812,11 +808,6 @@
private void waveStart(Collection<DexEncodedMethod> wave, ExecutorService executorService)
throws ExecutionException {
onWaveDoneActions = Collections.synchronizedList(new ArrayList<>());
-
- if (lambdaRewriter != null) {
- lambdaRewriter.synthesizeLambdaClassesForWave(
- wave, this, executorService, delayedOptimizationFeedback, lensCodeRewriter);
- }
}
private void waveDone() {
@@ -882,7 +873,7 @@
IRCode code = method.buildIR(appView, appView.appInfo().originFor(method.method.holder));
assert code != null;
codeRewriter.rewriteMoveResult(code);
- finalizeIR(method, code, OptimizationFeedbackIgnore.getInstance());
+ finalizeIR(method, code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
}
private void collectLambdaMergingCandidates(DexApplication application) {
@@ -934,7 +925,8 @@
}
assert code.isConsistentSSA();
code.traceBlocks();
- RegisterAllocator registerAllocator = performRegisterAllocation(code, method);
+ Timing timing = Timing.empty();
+ RegisterAllocator registerAllocator = performRegisterAllocation(code, method, timing);
method.setCode(code, registerAllocator, appView);
if (Log.ENABLED) {
Log.debug(getClass(), "Resulting dex code for %s:\n%s",
@@ -982,22 +974,6 @@
processMethodsConcurrently(methods, executorService);
}
- public void optimizeSynthesizedLambdaClasses(
- Collection<DexProgramClass> classes, ExecutorService executorService)
- throws ExecutionException {
- assert appView.enableWholeProgramOptimizations();
- Set<DexEncodedMethod> methods = Sets.newIdentityHashSet();
- for (DexProgramClass clazz : classes) {
- clazz.forEachMethod(methods::add);
- }
- LambdaMethodProcessor processor =
- new LambdaMethodProcessor(appView.withLiveness(), methods, executorService, timing);
- processor.forEachMethod(
- method -> processMethod(method, delayedOptimizationFeedback, processor),
- delayedOptimizationFeedback::updateVisibleOptimizationInfo,
- executorService);
- }
-
public void optimizeSynthesizedMethod(DexEncodedMethod method) {
if (!method.isProcessed()) {
// Process the generated method, but don't apply any outlining.
@@ -1031,18 +1007,17 @@
}
// TODO(b/140766440): Make this receive a list of CodeOptimizations to conduct.
- public void processMethod(
- DexEncodedMethod method,
- OptimizationFeedback feedback,
- MethodProcessor methodProcessor) {
+ public Timing processMethod(
+ DexEncodedMethod method, OptimizationFeedback feedback, MethodProcessor methodProcessor) {
Code code = method.getCode();
boolean matchesMethodFilter = options.methodMatchesFilter(method);
if (code != null && matchesMethodFilter) {
- rewriteCode(method, feedback, methodProcessor);
+ return rewriteCode(method, feedback, methodProcessor);
} else {
// Mark abstract methods as processed as well.
method.markProcessed(ConstraintWithTarget.NEVER);
}
+ return Timing.empty();
}
private static void invertConditionalsForTesting(IRCode code) {
@@ -1053,11 +1028,11 @@
}
}
- private void rewriteCode(
+ private Timing rewriteCode(
DexEncodedMethod method, OptimizationFeedback feedback, MethodProcessor methodProcessor) {
Origin origin = appView.appInfo().originFor(method.method.holder);
try {
- rewriteCodeInternal(method, feedback, methodProcessor, origin);
+ return rewriteCodeInternal(method, feedback, methodProcessor, origin);
} catch (CompilationError e) {
// If rewriting throws a compilation error, attach the origin and method if missing.
throw e.withAdditionalOriginAndPositionInfo(origin, new MethodPosition(method.method));
@@ -1070,7 +1045,7 @@
}
}
- private void rewriteCodeInternal(
+ private Timing rewriteCodeInternal(
DexEncodedMethod method,
OptimizationFeedback feedback,
MethodProcessor methodProcessor,
@@ -1086,22 +1061,21 @@
}
if (options.skipIR) {
feedback.markProcessed(method, ConstraintWithTarget.NEVER);
- return;
+ return Timing.empty();
}
IRCode code = method.buildIR(appView, origin);
if (code == null) {
feedback.markProcessed(method, ConstraintWithTarget.NEVER);
- return;
+ return Timing.empty();
}
- optimize(code, feedback, methodProcessor);
+ return optimize(code, feedback, methodProcessor);
}
// TODO(b/140766440): Convert all sub steps an implementer of CodeOptimization
- private void optimize(
- IRCode code,
- OptimizationFeedback feedback,
- MethodProcessor methodProcessor) {
+ private Timing optimize(
+ IRCode code, OptimizationFeedback feedback, MethodProcessor methodProcessor) {
DexEncodedMethod method = code.method;
+ Timing timing = Timing.create(method.qualifiedName(), options);
if (Log.ENABLED) {
Log.debug(getClass(), "Initial (SSA) flow graph for %s:\n%s", method.toSourceString(), code);
@@ -1115,7 +1089,9 @@
}
if (options.canHaveArtStringNewInitBug()) {
+ timing.begin("Check for new-init issue");
CodeRewriter.ensureDirectStringNewToInit(code, appView.dexItemFactory());
+ timing.end();
}
boolean isDebugMode = options.debug || method.getOptimizationInfo().isReachabilitySensitive();
@@ -1126,19 +1102,25 @@
if (!method.isProcessed()) {
if (lensCodeRewriter != null) {
+ timing.begin("Lens rewrite");
lensCodeRewriter.rewrite(code, method);
+ timing.end();
} else {
assert appView.graphLense().isIdentityLense();
}
if (lambdaRewriter != null) {
+ timing.begin("Desugar lambdas");
lambdaRewriter.desugarLambdas(method, code);
+ timing.end();
assert code.isConsistentSSA();
}
}
if (lambdaMerger != null) {
+ timing.begin("Merge lambdas");
lambdaMerger.rewriteCode(method, code, inliner);
+ timing.end();
assert code.isConsistentSSA();
}
@@ -1152,7 +1134,7 @@
+ "` does not type check and will be assumed to be unreachable.");
options.reporter.warning(warning);
finalizeEmptyThrowingCode(method, feedback);
- return;
+ return timing;
}
// This is the first point in time where we can assert that the types are sound. If this
@@ -1163,41 +1145,55 @@
if (serviceLoaderRewriter != null) {
assert appView.appInfo().hasLiveness();
+ timing.begin("Rewrite service loaders");
serviceLoaderRewriter.rewrite(code);
+ timing.end();
}
if (identifierNameStringMarker != null) {
+ timing.begin("Decouple identifier-name strings");
identifierNameStringMarker.decoupleIdentifierNameStringsInMethod(method, code);
+ timing.end();
assert code.isConsistentSSA();
}
if (memberValuePropagation != null) {
+ timing.begin("Propagate member values");
memberValuePropagation.rewriteWithConstantValues(code, method.method.holder);
+ timing.end();
}
if (options.enableEnumValueOptimization) {
assert appView.enableWholeProgramOptimizations();
+ timing.begin("Remove switch maps");
codeRewriter.removeSwitchMaps(code);
+ timing.end();
}
- assertionsRewriter.run(method, code);
+ assertionsRewriter.run(method, code, timing);
previous = printMethod(code, "IR after disable assertions (SSA)", previous);
+ timing.begin("Insert assume instructions");
CodeRewriter.insertAssumeInstructions(code, assumers);
+ timing.end();
previous = printMethod(code, "IR after inserting assume instructions (SSA)", previous);
+ timing.begin("Run proto shrinking tasks");
appView.withGeneratedExtensionRegistryShrinker(shrinker -> shrinker.rewriteCode(method, code));
previous = printMethod(code, "IR after generated extension registry shrinking (SSA)", previous);
appView.withGeneratedMessageLiteShrinker(shrinker -> shrinker.run(method, code));
+ timing.end();
previous = printMethod(code, "IR after generated message lite shrinking (SSA)", previous);
if (!isDebugMode && options.enableInlining && inliner != null) {
+ timing.begin("Inlining");
inliner.performInlining(method, code, feedback, methodProcessor);
+ timing.end();
assert code.verifyTypes(appView);
}
@@ -1205,77 +1201,124 @@
if (appView.appInfo().hasLiveness()) {
// Reflection optimization 1. getClass() / forName() -> const-class
+ timing.begin("Rewrite to const class");
ReflectionOptimizer.rewriteGetClassOrForNameToConstClass(appView.withLiveness(), code);
+ timing.end();
}
if (!isDebugMode) {
// Reflection optimization 2. get*Name() with const-class -> const-string
if (options.enableNameReflectionOptimization
|| options.testing.forceNameReflectionOptimization) {
+ timing.begin("Rewrite Class.getName");
stringOptimizer.rewriteClassGetName(appView, code);
+ timing.end();
}
// Reflection/string optimization 3. trivial conversion/computation on const-string
+ timing.begin("Optimize const strings");
stringOptimizer.computeTrivialOperationsOnConstString(code);
stringOptimizer.removeTrivialConversions(code);
+ timing.end();
if (libraryMethodOptimizer != null) {
+ timing.begin("Optimize library methods");
libraryMethodOptimizer.optimize(code, feedback, methodProcessor);
+ timing.end();
}
assert code.isConsistentSSA();
}
if (devirtualizer != null) {
assert code.verifyTypes(appView);
+ timing.begin("Devirtualize invoke interface");
devirtualizer.devirtualizeInvokeInterface(code, method.method.holder);
+ timing.end();
}
if (uninstantiatedTypeOptimization != null) {
+ timing.begin("Rewrite uninstantiated types");
uninstantiatedTypeOptimization.rewrite(code);
+ timing.end();
}
assert code.verifyTypes(appView);
+ timing.begin("Remove trivial type checks/casts");
codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(code);
+ timing.end();
if (options.enableEnumValueOptimization) {
assert appView.enableWholeProgramOptimizations();
+ timing.begin("Rewrite constant enum methods");
codeRewriter.rewriteConstantEnumMethodCalls(code);
+ timing.end();
}
+ timing.begin("Rewrite array length");
codeRewriter.rewriteKnownArrayLengthCalls(code);
+ timing.end();
+ timing.begin("Rewrite AssertionError");
codeRewriter.rewriteAssertionErrorTwoArgumentConstructor(code, options);
+ timing.end();
+ timing.begin("Run CSE");
codeRewriter.commonSubexpressionElimination(code);
+ timing.end();
+ timing.begin("Simplify arrays");
codeRewriter.simplifyArrayConstruction(code);
+ timing.end();
+ timing.begin("Rewrite move result");
codeRewriter.rewriteMoveResult(code);
+ timing.end();
// TODO(b/114002137): for now, string concatenation depends on rewriteMoveResult.
if (options.enableStringConcatenationOptimization
&& !isDebugMode
&& options.isGeneratingDex()) {
+ timing.begin("Rewrite string concat");
stringBuilderOptimizer.computeTrivialStringConcatenation(code);
+ timing.end();
}
+ timing.begin("Split range invokes");
codeRewriter.splitRangeInvokeConstants(code);
+ timing.end();
+ timing.begin("Propogate sparse conditionals");
new SparseConditionalConstantPropagation(code).run();
+ timing.end();
if (stringSwitchRemover != null) {
+ timing.begin("Remove string switch");
stringSwitchRemover.run(method, code);
+ timing.end();
}
+ timing.begin("Rewrite always throwing invokes");
codeRewriter.processMethodsNeverReturningNormally(code);
+ timing.end();
+ timing.begin("Simplify control flow");
if (codeRewriter.simplifyControlFlow(code)) {
+ timing.begin("Remove trivial type checks/casts");
codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(code);
+ timing.end();
}
+ timing.end();
if (options.enableRedundantConstNumberOptimization) {
+ timing.begin("Remove const numbers");
codeRewriter.redundantConstNumberRemoval(code);
+ timing.end();
}
if (RedundantFieldLoadElimination.shouldRun(appView, code)) {
+ timing.begin("Remove field loads");
new RedundantFieldLoadElimination(appView, code).run();
+ timing.end();
}
if (options.testing.invertConditionals) {
invertConditionalsForTesting(code);
}
+ timing.begin("Rewrite throw NPE");
codeRewriter.rewriteThrowNullPointerException(code);
+ timing.end();
- if (classInitializerDefaultsOptimization != null && !isDebugMode) {
- classInitializerDefaultsOptimization.optimize(method, code);
- }
+ timing.begin("Optimize class initializers");
+ ClassInitializerDefaultsResult classInitializerDefaultsResult =
+ classInitializerDefaultsOptimization.optimize(method, code);
+ timing.end();
if (Log.ENABLED) {
Log.debug(getClass(), "Intermediate (SSA) flow graph for %s:\n%s",
@@ -1284,15 +1327,23 @@
// Dead code removal. Performed after simplifications to remove code that becomes dead
// as a result of those simplifications. The following optimizations could reveal more
// dead code which is removed right before register allocation in performRegisterAllocation.
+ timing.begin("Remove dead code");
deadCodeRemover.run(code);
+ timing.end();
assert code.isConsistentSSA();
if (options.desugarState == DesugarState.ON && enableTryWithResourcesDesugaring()) {
+ timing.begin("Rewrite Throwable suppresed methods");
codeRewriter.rewriteThrowableAddAndGetSuppressed(code);
+ timing.end();
}
+ timing.begin("Rewrite backport methods");
backportedMethodRewriter.desugar(code);
+ timing.end();
+ timing.begin("Desugar string concat");
stringConcatRewriter.desugarStringConcats(method.method, code);
+ timing.end();
previous = printMethod(code, "IR after lambda desugaring (SSA)", previous);
@@ -1301,6 +1352,7 @@
previous = printMethod(code, "IR before class inlining (SSA)", previous);
if (classInliner != null) {
+ timing.begin("Inline classes");
// Class inliner should work before lambda merger, so if it inlines the
// lambda, it does not get collected by merger.
assert options.enableInlining && inliner != null;
@@ -1323,6 +1375,7 @@
// Inlining instruction allowance is not needed for the class inliner since it
// always uses a force inlining oracle for inlining.
-1)));
+ timing.end();
assert code.isConsistentSSA();
assert code.verifyTypes(appView);
}
@@ -1330,30 +1383,42 @@
previous = printMethod(code, "IR after class inlining (SSA)", previous);
if (d8NestBasedAccessDesugaring != null) {
+ timing.begin("Desugar nest access");
d8NestBasedAccessDesugaring.rewriteNestBasedAccesses(method, code, appView);
+ timing.end();
assert code.isConsistentSSA();
}
previous = printMethod(code, "IR after nest based access desugaring (SSA)", previous);
if (interfaceMethodRewriter != null) {
+ timing.begin("Rewrite interface methods");
interfaceMethodRewriter.rewriteMethodReferences(method, code);
+ timing.end();
assert code.isConsistentSSA();
}
previous = printMethod(code, "IR after interface method rewriting (SSA)", previous);
+ if (enumUnboxer != null && methodProcessor.isPost()) {
+ enumUnboxer.unboxEnums(code);
+ }
+
// This pass has to be after interfaceMethodRewriter and BackportedMethodRewriter.
if (desugaredLibraryAPIConverter != null
&& (!appView.enableWholeProgramOptimizations() || methodProcessor.isPrimary())) {
+ timing.begin("Desugar library API");
desugaredLibraryAPIConverter.desugar(code);
+ timing.end();
assert code.isConsistentSSA();
}
previous = printMethod(code, "IR after desugared library API Conversion (SSA)", previous);
if (twrCloseResourceRewriter != null) {
+ timing.begin("Rewrite TWR close");
twrCloseResourceRewriter.rewriteMethodCode(code);
+ timing.end();
}
assert code.verifyTypes(appView);
@@ -1361,7 +1426,9 @@
previous = printMethod(code, "IR after twr close resource rewriter (SSA)", previous);
if (lambdaMerger != null) {
+ timing.begin("Analyze lambda merging");
lambdaMerger.analyzeCode(method, code);
+ timing.end();
assert code.isConsistentSSA();
}
@@ -1370,7 +1437,9 @@
// TODO(b/140766440): an ideal solution would be puttting CodeOptimization for this into
// the list for primary processing only.
if (options.outline.enabled && outliner != null && methodProcessor.isPrimary()) {
+ timing.begin("Identify outlines");
outliner.getOutlineMethodIdentifierGenerator().accept(code);
+ timing.end();
assert code.isConsistentSSA();
}
@@ -1380,11 +1449,19 @@
// TODO(mkroghj) Test if shorten live ranges is worth it.
if (!options.isGeneratingClassFiles()) {
+ timing.begin("Canonicalize constants");
constantCanonicalizer.canonicalize(appView, code);
+ timing.end();
+ timing.begin("Create constants for literal instructions");
codeRewriter.useDedicatedConstantForLitInstruction(code);
+ timing.end();
+ timing.begin("Shorten live ranges");
codeRewriter.shortenLiveRanges(code);
+ timing.end();
}
+ timing.begin("Canonicalize idempotent calls");
idempotentFunctionCallCanonicalizer.canonicalize(code);
+ timing.end();
previous =
printMethod(code, "IR after idempotent function call canonicalization (SSA)", previous);
@@ -1398,30 +1475,46 @@
previous = printMethod(code, "IR after argument type logging (SSA)", previous);
if (classStaticizer != null) {
+ timing.begin("Identify staticizing candidates");
classStaticizer.examineMethodCode(method, code);
+ timing.end();
+ }
+
+ if (enumUnboxer != null && methodProcessor.isPrimary()) {
+ enumUnboxer.analyzeEnums(code);
}
assert code.verifyTypes(appView);
if (appView.enableWholeProgramOptimizations()) {
if (libraryMethodOverrideAnalysis != null) {
+ timing.begin("Analyze library method overrides");
libraryMethodOverrideAnalysis.analyze(code);
+ timing.end();
}
if (fieldBitAccessAnalysis != null) {
+ timing.begin("Record field access");
fieldBitAccessAnalysis.recordFieldAccesses(code, feedback);
+ timing.end();
}
// Arguments can be changed during the debug mode.
if (!isDebugMode && appView.callSiteOptimizationInfoPropagator() != null) {
+ timing.begin("Collect call-site info");
appView.callSiteOptimizationInfoPropagator().collectCallSiteOptimizationInfo(code);
+ timing.end();
}
- collectOptimizationInfo(code, feedback);
+ timing.begin("Collect optimization info");
+ collectOptimizationInfo(code, classInitializerDefaultsResult, feedback);
+ timing.end();
}
if (!assumers.isEmpty()) {
+ timing.begin("Remove assume instructions");
CodeRewriter.removeAssumeInstructions(appView, code);
+ timing.end();
assert code.isConsistentSSA();
}
@@ -1434,47 +1527,58 @@
printMethod(code, "IR after computation of optimization info summary (SSA)", previous);
if (options.canHaveNumberConversionRegisterAllocationBug()) {
+ timing.begin("Check number conversion issue");
codeRewriter.workaroundNumberConversionRegisterAllocationBug(code);
+ timing.end();
}
- // Either marked by IdentifierNameStringMarker or name reflection, or propagated from inlinee,
- // Then, make it visible to IdentifierMinifier.
- // Note that we place this at the end of IR processing because inlinee can be inlined by
- // Inliner, ClassInliner, or future optimizations that use the inlining machinery.
- if (method.getOptimizationInfo().useIdentifierNameString()) {
- // If it is optimized, e.g., moved to default values of static fields or even removed by dead
- // code remover, we can save future computation in IdentifierMinifier.
- if (Streams.stream(code.instructionIterator())
- .anyMatch(Instruction::isDexItemBasedConstString)) {
- feedback.markUseIdentifierNameString(method);
- }
- } else {
- assert Streams.stream(code.instructionIterator())
- .noneMatch(Instruction::isDexItemBasedConstString);
+ // IR processing should not need to handle identifier name strings, so after the first round
+ // there should be no information for that.
+ if (methodProcessor.isPrimary()) {
+ assert !method.getOptimizationInfo().useIdentifierNameString();
}
printMethod(code, "Optimized IR (SSA)", previous);
- finalizeIR(method, code, feedback);
+ timing.begin("Finalize IR");
+ finalizeIR(method, code, feedback, timing);
+ timing.end();
+ return timing;
+ }
+
+ public void collectIdentifierNameStringUse(
+ DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+ Iterator<Instruction> iterator = code.instructionIterator();
+ while (iterator.hasNext()) {
+ if (iterator.next().isDexItemBasedConstString()) {
+ feedback.markUseIdentifierNameString(method);
+ break;
+ }
+ }
}
// 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).
- public void collectOptimizationInfo(IRCode code, OptimizationFeedback feedback) {
+ public void collectOptimizationInfo(
+ IRCode code,
+ ClassInitializerDefaultsResult classInitializerDefaultsResult,
+ OptimizationFeedback feedback) {
if (appView.appInfo().withLiveness().isPinned(code.method.method)) {
return;
}
methodOptimizationInfoCollector
.collectMethodOptimizationInfo(code.method, code, feedback, dynamicTypeOptimization);
- FieldValueAnalysis.run(appView, code, feedback, code.method);
+ FieldValueAnalysis.run(appView, code, classInitializerDefaultsResult, feedback, code.method);
}
- public void finalizeIR(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+ public void finalizeIR(
+ DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
+ collectIdentifierNameStringUse(method, code, feedback);
code.traceBlocks();
if (options.isGeneratingClassFiles()) {
finalizeToCf(method, code, feedback);
} else {
assert options.isGeneratingDex();
- finalizeToDex(method, code, feedback);
+ finalizeToDex(method, code, feedback, timing);
}
}
@@ -1496,19 +1600,24 @@
markProcessed(method, code, feedback);
}
- private void finalizeToDex(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+ private void finalizeToDex(
+ DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
// Workaround massive dex2oat memory use for self-recursive methods.
CodeRewriter.disableDex2OatInliningForSelfRecursiveMethods(appView, code);
// Perform register allocation.
- RegisterAllocator registerAllocator = performRegisterAllocation(code, method);
+ RegisterAllocator registerAllocator = performRegisterAllocation(code, method, timing);
+ timing.begin("Build DEX code");
method.setCode(code, registerAllocator, appView);
+ timing.end();
updateHighestSortingStrings(method);
if (Log.ENABLED) {
Log.debug(getClass(), "Resulting dex code for %s:\n%s",
method.toSourceString(), logCode(options, method));
}
printMethod(code, "Final IR (non-SSA)", null);
+ timing.begin("Marking processed");
markProcessed(method, code, feedback);
+ timing.end();
}
private void markProcessed(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
@@ -1534,24 +1643,33 @@
}
}
- private RegisterAllocator performRegisterAllocation(IRCode code, DexEncodedMethod method) {
+ private RegisterAllocator performRegisterAllocation(
+ IRCode code, DexEncodedMethod method, Timing timing) {
// Always perform dead code elimination before register allocation. The register allocator
// does not allow dead code (to make sure that we do not waste registers for unneeded values).
+ timing.begin("Remove dead code");
deadCodeRemover.run(code);
+ timing.end();
materializeInstructionBeforeLongOperationsWorkaround(code);
workaroundForwardingInitializerBug(code);
+ timing.begin("Allocate registers");
LinearScanRegisterAllocator registerAllocator = new LinearScanRegisterAllocator(appView, code);
registerAllocator.allocateRegisters();
+ timing.end();
if (options.canHaveExceptionTargetingLoopHeaderBug()) {
codeRewriter.workaroundExceptionTargetingLoopHeaderBug(code);
}
printMethod(code, "After register allocation (non-SSA)", null);
+ timing.begin("Peephole optimize");
for (int i = 0; i < PEEPHOLE_OPTIMIZATION_PASSES; i++) {
CodeRewriter.collapseTrivialGotos(code);
PeepholeOptimizer.optimize(code, registerAllocator);
}
+ timing.end();
+ timing.begin("Clean up");
CodeRewriter.removeUnneededMovesOnExitingPaths(code, registerAllocator);
CodeRewriter.collapseTrivialGotos(code);
+ timing.end();
if (Log.ENABLED) {
Log.debug(getClass(), "Final (non-SSA) flow graph for %s:\n%s",
method.toSourceString(), code);
@@ -1713,7 +1831,7 @@
Instruction check = it.previous();
assert addBefore == check;
// Forced definition of const-zero
- Value fixitValue = code.createValue(TypeLatticeElement.INT);
+ Value fixitValue = code.createValue(TypeLatticeElement.getInt());
Instruction fixitDefinition = new AlwaysMaterializingDefinition(fixitValue);
fixitDefinition.setBlock(addBefore.getBlock());
fixitDefinition.setPosition(addBefore.getPosition());
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LambdaMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/LambdaMethodProcessor.java
deleted file mode 100644
index a6a9b94..0000000
--- a/src/main/java/com/android/tools/r8/ir/conversion/LambdaMethodProcessor.java
+++ /dev/null
@@ -1,69 +0,0 @@
-// 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.Action;
-import com.android.tools.r8.utils.IROrdering;
-import com.android.tools.r8.utils.ThreadUtils;
-import com.android.tools.r8.utils.ThrowingConsumer;
-import com.android.tools.r8.utils.Timing;
-import java.util.ArrayDeque;
-import java.util.Collection;
-import java.util.Deque;
-import java.util.Set;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-
-class LambdaMethodProcessor implements MethodProcessor {
-
- private final Deque<Collection<DexEncodedMethod>> waves;
- private Collection<DexEncodedMethod> wave;
-
- LambdaMethodProcessor(
- AppView<AppInfoWithLiveness> appView,
- Set<DexEncodedMethod> methods,
- ExecutorService executorService,
- Timing timing)
- throws ExecutionException {
- CallGraph callGraph =
- new PartialCallGraphBuilder(appView, methods).build(executorService, timing);
- this.waves = createWaves(appView, callGraph);
- }
-
- @Override
- public Phase getPhase() {
- return Phase.LAMBDA_PROCESSING;
- }
-
- private Deque<Collection<DexEncodedMethod>> createWaves(AppView<?> appView, CallGraph callGraph) {
- IROrdering shuffle = appView.options().testing.irOrdering;
- Deque<Collection<DexEncodedMethod>> waves = new ArrayDeque<>();
- while (!callGraph.isEmpty()) {
- waves.addLast(shuffle.order(callGraph.extractLeaves()));
- }
- return waves;
- }
-
- @Override
- public boolean isProcessedConcurrently(DexEncodedMethod method) {
- return wave.contains(method);
- }
-
- <E extends Exception> void forEachMethod(
- ThrowingConsumer<DexEncodedMethod, E> consumer,
- Action waveDone,
- ExecutorService executorService)
- throws ExecutionException {
- while (!waves.isEmpty()) {
- wave = waves.removeFirst();
- assert wave.size() > 0;
- ThreadUtils.processItems(wave, consumer, executorService);
- waveDone.execute();
- }
- }
-}
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 d6e2ff3..34609c9 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
@@ -9,7 +9,6 @@
enum Phase {
ONE_TIME,
- LAMBDA_PROCESSING,
PRIMARY,
POST
}
@@ -20,6 +19,10 @@
return getPhase() == Phase.PRIMARY;
}
+ default boolean isPost() {
+ return getPhase() == Phase.POST;
+ }
+
default CallSiteInformation getCallSiteInformation() {
return CallSiteInformation.empty();
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
index be529f4..6c42599 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
@@ -12,9 +12,11 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Action;
import com.android.tools.r8.utils.IROrdering;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ThreadUtils;
-import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.ThrowingFunction;
import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.Timing.TimingMerger;
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
import java.util.Collection;
@@ -73,7 +75,8 @@
private Deque<Collection<DexEncodedMethod>> createWaves(
AppView<?> appView, CallGraph callGraph, CallSiteInformation callSiteInformation) {
- IROrdering shuffle = appView.options().testing.irOrdering;
+ InternalOptions options = appView.options();
+ IROrdering shuffle = options.testing.irOrdering;
Deque<Collection<DexEncodedMethod>> waves = new ArrayDeque<>();
Set<Node> nodes = callGraph.nodes;
@@ -100,6 +103,7 @@
if (!reprocessing.isEmpty()) {
postMethodProcessorBuilder.put(reprocessing);
}
+ options.testing.waveModifier.accept(waves);
return waves;
}
@@ -135,17 +139,29 @@
* processed at the same time is passed. This can be used to avoid races in concurrent processing.
*/
<E extends Exception> void forEachMethod(
- ThrowingConsumer<DexEncodedMethod, E> consumer,
+ ThrowingFunction<DexEncodedMethod, Timing, E> consumer,
WaveStartAction waveStartAction,
Action waveDone,
+ Timing timing,
ExecutorService executorService)
throws ExecutionException {
+ TimingMerger merger =
+ timing.beginMerger("primary-processor", ThreadUtils.getNumberOfThreads(executorService));
while (!waves.isEmpty()) {
wave = waves.removeFirst();
assert wave.size() > 0;
waveStartAction.notifyWaveStart(wave, executorService);
- ThreadUtils.processItems(wave, consumer, executorService);
+ merger.add(
+ ThreadUtils.processItemsWithResults(
+ wave,
+ method -> {
+ Timing time = consumer.apply(method);
+ time.end();
+ return time;
+ },
+ executorService));
waveDone.execute();
}
+ merger.end();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
index b54b406..41f22a4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
@@ -107,7 +107,7 @@
InvokeVirtual invokeInstruction =
new InvokeVirtual(
appView.dexItemFactory().stringMethods.equals,
- code.createValue(PrimitiveTypeLatticeElement.INT),
+ code.createValue(PrimitiveTypeLatticeElement.getInt()),
ImmutableList.of(theSwitch.value(), constStringInstruction.outValue()));
invokeInstruction.setPosition(Position.syntheticNone());
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java b/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java
index 6f01dd7..2c41a2a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java
@@ -68,25 +68,25 @@
public static TypeLatticeElement typeForConstraint(ValueTypeConstraint constraint) {
switch (constraint) {
case INT_OR_FLOAT_OR_OBJECT:
- return TypeLatticeElement.TOP;
+ return TypeLatticeElement.getTop();
case OBJECT:
// If the constraint is object the concrete lattice type will need to be computed.
// We mark the object type as bottom for now, with the implication that it is of type
// reference but that it should not contribute to the computation of its join
// (in potentially self-referencing phis).
- return TypeLatticeElement.BOTTOM;
+ return TypeLatticeElement.getBottom();
case INT:
- return TypeLatticeElement.INT;
+ return TypeLatticeElement.getInt();
case FLOAT:
- return TypeLatticeElement.FLOAT;
+ return TypeLatticeElement.getFloat();
case INT_OR_FLOAT:
- return TypeLatticeElement.SINGLE;
+ return TypeLatticeElement.getSingle();
case LONG:
- return TypeLatticeElement.LONG;
+ return TypeLatticeElement.getLong();
case DOUBLE:
- return TypeLatticeElement.DOUBLE;
+ return TypeLatticeElement.getDouble();
case LONG_OR_DOUBLE:
- return TypeLatticeElement.WIDE;
+ return TypeLatticeElement.getWide();
default:
throw new Unreachable("Unexpected constraint type: " + constraint);
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 09cd2aa..21b182e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -38,6 +38,7 @@
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.desugar.DefaultMethodsHelper.Collection;
import com.android.tools.r8.ir.desugar.InterfaceProcessor.InterfaceProcessorNestedGraphLense;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.SynthesizedOrigin;
import com.android.tools.r8.position.MethodPosition;
@@ -913,6 +914,7 @@
*/
public void desugarInterfaceMethods(
Builder<?> builder,
+ OptimizationFeedback feedback,
Flavor flavour,
ExecutorService executorService)
throws ExecutionException {
@@ -930,7 +932,8 @@
// make original default methods abstract, remove bridge methods, create dispatch
// classes if needed.
AppInfo appInfo = appView.appInfo();
- for (Entry<DexType, DexProgramClass> entry : processInterfaces(builder, flavour).entrySet()) {
+ for (Entry<DexType, DexProgramClass> entry :
+ processInterfaces(builder, feedback, flavour).entrySet()) {
// Don't need to optimize synthesized class since all of its methods
// are just moved from interfaces and don't need to be re-processed.
DexProgramClass synthesizedClass = entry.getValue();
@@ -960,9 +963,10 @@
&& clazz.isInterface() == mustBeInterface;
}
- private Map<DexType, DexProgramClass> processInterfaces(Builder<?> builder, Flavor flavour) {
+ private Map<DexType, DexProgramClass> processInterfaces(
+ Builder<?> builder, OptimizationFeedback feedback, Flavor flavour) {
NestedGraphLense.Builder graphLensBuilder = InterfaceProcessorNestedGraphLense.builder();
- InterfaceProcessor processor = new InterfaceProcessor(appView, this);
+ InterfaceProcessor processor = new InterfaceProcessor(appView, this, feedback);
for (DexProgramClass clazz : builder.getProgramClasses()) {
if (shouldProcess(clazz, flavour, true)) {
processor.process(clazz, graphLensBuilder);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index ccb0f3c..ef9b9c9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -31,6 +31,7 @@
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
import com.android.tools.r8.origin.SynthesizedOrigin;
@@ -56,13 +57,16 @@
private final AppView<?> appView;
private final InterfaceMethodRewriter rewriter;
+ private final OptimizationFeedback feedback;
// All created companion and dispatch classes indexed by interface type.
final Map<DexType, DexProgramClass> syntheticClasses = new IdentityHashMap<>();
- InterfaceProcessor(AppView<?> appView, InterfaceMethodRewriter rewriter) {
+ InterfaceProcessor(
+ AppView<?> appView, InterfaceMethodRewriter rewriter, OptimizationFeedback feedback) {
this.appView = appView;
this.rewriter = rewriter;
+ this.feedback = feedback;
}
void process(DexProgramClass iface, NestedGraphLense.Builder graphLensBuilder) {
@@ -95,6 +99,7 @@
code, companionMethod.getArity(), appView);
DexEncodedMethod implMethod = new DexEncodedMethod(
companionMethod, newFlags, virtual.annotations, virtual.parameterAnnotationsList, code, true);
+ implMethod.copyMetadata(virtual, feedback);
virtual.setDefaultInterfaceMethodImplementation(implMethod);
companionMethods.add(implMethod);
graphLensBuilder.move(virtual.method, implMethod.method);
@@ -127,8 +132,17 @@
: "Static interface method " + direct.toSourceString() + " is expected to "
+ "either be public or private in " + iface.origin;
DexMethod companionMethod = rewriter.staticAsMethodOfCompanionClass(oldMethod);
- companionMethods.add(new DexEncodedMethod(companionMethod, newFlags,
- direct.annotations, direct.parameterAnnotationsList, direct.getCode(),true));
+ Code code = direct.getCode();
+ DexEncodedMethod implMethod =
+ new DexEncodedMethod(
+ companionMethod,
+ newFlags,
+ direct.annotations,
+ direct.parameterAnnotationsList,
+ direct.getCode(),
+ true);
+ implMethod.copyMetadata(direct, feedback);
+ companionMethods.add(implMethod);
graphLensBuilder.move(oldMethod, companionMethod);
} else {
if (originalFlags.isPrivate()) {
@@ -146,8 +160,16 @@
}
DexEncodedMethod.setDebugInfoWithFakeThisParameter(
code, companionMethod.getArity(), appView);
- companionMethods.add(new DexEncodedMethod(companionMethod,
- newFlags, direct.annotations, direct.parameterAnnotationsList, code,true));
+ DexEncodedMethod implMethod =
+ new DexEncodedMethod(
+ companionMethod,
+ newFlags,
+ direct.annotations,
+ direct.parameterAnnotationsList,
+ code,
+ true);
+ implMethod.copyMetadata(direct, feedback);
+ companionMethods.add(implMethod);
graphLensBuilder.move(oldMethod, companionMethod);
} else {
// Since there are no interface constructors at this point,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaAccessorMethodWithSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaAccessorMethodWithSynthesizedCode.java
new file mode 100644
index 0000000..a733540
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaAccessorMethodWithSynthesizedCode.java
@@ -0,0 +1,44 @@
+// Copyright (c) 2020, 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.desugar;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.UseRegistry;
+import java.util.function.Consumer;
+
+public class LambdaAccessorMethodWithSynthesizedCode extends LambdaSynthesizedCode {
+
+ public LambdaAccessorMethodWithSynthesizedCode(LambdaClass lambda) {
+ super(lambda);
+ }
+
+ @Override
+ public SourceCodeProvider getSourceCodeProvider() {
+ return callerPosition -> new AccessorMethodSourceCode(lambda, callerPosition);
+ }
+
+ @Override
+ public Consumer<UseRegistry> getRegistryCallback() {
+ return registry -> {
+ DexMethodHandle handle = lambda.descriptor.implHandle;
+ DexMethod target = handle.asMethod();
+ switch (handle.type) {
+ case INVOKE_STATIC:
+ registry.registerInvokeStatic(target);
+ break;
+ case INVOKE_INSTANCE:
+ registry.registerInvokeVirtual(target);
+ break;
+ case INVOKE_CONSTRUCTOR:
+ case INVOKE_DIRECT:
+ registry.registerInvokeDirect(target);
+ break;
+ default:
+ throw new Unreachable("Unexpected handle type: " + handle.type);
+ }
+ };
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index e7d2f34..330d72a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -29,6 +29,7 @@
import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
import com.android.tools.r8.origin.SynthesizedOrigin;
import com.android.tools.r8.utils.InternalOptions;
@@ -58,16 +59,16 @@
* <p>Another reason is that if we generate an accessor, we generate it in the class referencing the
* call site, and thus two such classes will require two separate lambda classes.
*/
-final class LambdaClass {
+public final class LambdaClass {
final LambdaRewriter rewriter;
- final DexType type;
- final LambdaDescriptor descriptor;
- final DexMethod constructor;
+ public final DexType type;
+ public LambdaDescriptor descriptor;
+ public final DexMethod constructor;
final DexMethod classConstructor;
- final DexField lambdaField;
- final Target target;
- final AtomicBoolean addToMainDexList = new AtomicBoolean(false);
+ public final DexField lambdaField;
+ public final Target target;
+ public final AtomicBoolean addToMainDexList = new AtomicBoolean(false);
private final Collection<DexProgramClass> synthesizedFrom = new ArrayList<>(1);
private final Supplier<DexProgramClass> lazyDexClass =
Suppliers.memoize(this::synthesizeLambdaClass); // NOTE: thread-safe.
@@ -136,7 +137,7 @@
return factory.createType(lambdaClassDescriptor.toString());
}
- final DexProgramClass getOrCreateLambdaClass() {
+ public final DexProgramClass getOrCreateLambdaClass() {
return lazyDexClass.get();
}
@@ -204,7 +205,7 @@
rewriter.getFactory().createString("f$" + index));
}
- final boolean isStateless() {
+ public final boolean isStateless() {
return descriptor.isStateless();
}
@@ -505,7 +506,7 @@
// Represents information about the method lambda class need to delegate the call to. It may
// be the same method as specified in lambda descriptor or a newly synthesized accessor.
// Also provides action for ensuring accessibility of the referenced symbols.
- abstract class Target {
+ public abstract class Target {
final DexMethod callTarget;
final Invoke.Type invokeType;
@@ -521,12 +522,12 @@
}
// Ensure access of the referenced symbol(s).
- abstract DexEncodedMethod ensureAccessibility();
+ abstract DexEncodedMethod ensureAccessibility(boolean allowMethodModification);
// Ensure access of the referenced symbol(s).
- DexEncodedMethod ensureAccessibilityIfNeeded() {
+ public DexEncodedMethod ensureAccessibilityIfNeeded(boolean allowMethodModification) {
if (!hasEnsuredAccessibility) {
- accessibilityBridge = ensureAccessibility();
+ accessibilityBridge = ensureAccessibility(allowMethodModification);
hasEnsuredAccessibility = true;
}
return accessibilityBridge;
@@ -573,7 +574,7 @@
}
@Override
- DexEncodedMethod ensureAccessibility() {
+ DexEncodedMethod ensureAccessibility(boolean allowMethodModification) {
return null;
}
}
@@ -589,7 +590,7 @@
}
@Override
- DexEncodedMethod ensureAccessibility() {
+ DexEncodedMethod ensureAccessibility(boolean allowMethodModification) {
// We already found the static method to be called, just relax its accessibility.
target.method.accessFlags.unsetPrivate();
if (target.holder.isInterface()) {
@@ -608,7 +609,7 @@
}
@Override
- DexEncodedMethod ensureAccessibility() {
+ DexEncodedMethod ensureAccessibility(boolean allowMethodModification) {
// For all instantiation points for which the compiler creates lambda$
// methods, it creates these methods in the same class/interface.
DexMethod implMethod = descriptor.implHandle.asMethod();
@@ -658,12 +659,20 @@
}
@Override
- DexEncodedMethod ensureAccessibility() {
+ DexEncodedMethod ensureAccessibility(boolean allowMethodModification) {
+ // When compiling with whole program optimization, check that we are not inplace modifying.
+ assert !(rewriter.getAppView().enableWholeProgramOptimizations() && allowMethodModification);
// For all instantiation points for which the compiler creates lambda$
// methods, it creates these methods in the same class/interface.
DexMethod implMethod = descriptor.implHandle.asMethod();
DexClass implMethodHolder = definitionFor(implMethod.holder);
+ return allowMethodModification
+ ? modifyLambdaImplementationMethod(implMethod, implMethodHolder)
+ : createSyntheticAccessor(implMethod, implMethodHolder);
+ }
+ private DexEncodedMethod modifyLambdaImplementationMethod(
+ DexMethod implMethod, DexClass implMethodHolder) {
List<DexEncodedMethod> oldDirectMethods = implMethodHolder.directMethods();
for (int i = 0; i < oldDirectMethods.size(); i++) {
DexEncodedMethod encodedMethod = oldDirectMethods.get(i);
@@ -692,6 +701,35 @@
}
return null;
}
+
+ private DexEncodedMethod createSyntheticAccessor(
+ DexMethod implMethod, DexClass implMethodHolder) {
+ MethodAccessFlags accessorFlags =
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC, false);
+
+ ForwardMethodSourceCode.Builder forwardSourceCodeBuilder =
+ ForwardMethodSourceCode.builder(callTarget)
+ .setReceiver(implMethod.holder)
+ .setTargetReceiver(implMethod.holder)
+ .setTarget(implMethod)
+ .setInvokeType(Type.DIRECT)
+ .setIsInterface(false);
+
+ DexEncodedMethod accessorEncodedMethod =
+ new DexEncodedMethod(
+ callTarget,
+ accessorFlags,
+ DexAnnotationSet.empty(),
+ ParameterAnnotationsList.empty(),
+ new SynthesizedCode(
+ forwardSourceCodeBuilder::build,
+ registry -> registry.registerInvokeDirect(implMethod)),
+ true);
+
+ implMethodHolder.appendVirtualMethod(accessorEncodedMethod);
+ return accessorEncodedMethod;
+ }
}
// Used for instance/static methods or constructors accessed via
@@ -703,7 +741,7 @@
}
@Override
- DexEncodedMethod ensureAccessibility() {
+ DexEncodedMethod ensureAccessibility(boolean allowMethodModification) {
// Create a static accessor with proper accessibility.
DexProgramClass accessorClass = programDefinitionFor(callTarget.holder);
assert accessorClass != null;
@@ -720,9 +758,7 @@
accessorFlags,
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
- new SynthesizedCode(
- callerPosition ->
- new AccessorMethodSourceCode(LambdaClass.this, callerPosition)),
+ new LambdaAccessorMethodWithSynthesizedCode(LambdaClass.this),
true);
// We may arrive here concurrently so we need must update the methods of the class atomically.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index 2eb7b03..56270d5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -40,9 +40,9 @@
final DexProto enforcedProto;
public final DexMethodHandle implHandle;
- final List<DexType> interfaces = new ArrayList<>();
+ public final List<DexType> interfaces = new ArrayList<>();
final Set<DexProto> bridges = Sets.newIdentityHashSet();
- final DexTypeList captures;
+ public final DexTypeList captures;
// Used for accessibility analysis and few assertions only.
private final MethodAccessFlags targetAccessFlags;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSynthesizedCode.java
index 3e7e3f5..7c24b63 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSynthesizedCode.java
@@ -9,9 +9,10 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.Invoke.Type;
import java.util.function.Consumer;
-class LambdaMainMethodSynthesizedCode extends LambdaSynthesizedCode {
+public class LambdaMainMethodSynthesizedCode extends LambdaSynthesizedCode {
private final DexMethod mainMethod;
@@ -34,7 +35,10 @@
|| target.invokeType == Invoke.Type.DIRECT
|| target.invokeType == Invoke.Type.INTERFACE;
- registry.registerNewInstance(target.callTarget.holder);
+ boolean constructorTarget = target.invokeType == Type.DIRECT;
+ if (constructorTarget) {
+ registry.registerNewInstance(target.callTarget.holder);
+ }
DexType[] capturedTypes = captures();
for (int i = 0; i < capturedTypes.length; i++) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 6ce1f5f..608fd4a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -6,8 +6,6 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DefaultUseRegistry;
import com.android.tools.r8.graph.DexApplication.Builder;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexEncodedField;
@@ -30,9 +28,6 @@
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.conversion.LensCodeRewriter;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.DescriptorUtils;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
@@ -47,7 +42,6 @@
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
-import java.util.function.Consumer;
/**
* Lambda desugaring rewriter.
@@ -67,7 +61,6 @@
final DexString instanceFieldName;
- private final LambdaRewriterGraphLense graphLens;
final BiMap<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
// Maps call sites seen so far to inferred lambda descriptor. It is intended
@@ -90,7 +83,6 @@
assert appView.appInfo().hasClassHierarchy()
: "Lambda desugaring is not available without class hierarchy.";
this.appView = appView.withClassHierarchy();
- this.graphLens = new LambdaRewriterGraphLense(appView);
this.instanceFieldName = getFactory().createString(LAMBDA_INSTANCE_FIELD_NAME);
}
@@ -106,125 +98,36 @@
return getAppView().dexItemFactory();
}
- public void installGraphLens() {
- appView.setGraphLense(graphLens);
- }
-
- public void synthesizeLambdaClassesForWave(
- Collection<DexEncodedMethod> wave,
- IRConverter converter,
- ExecutorService executorService,
- OptimizationFeedbackDelayed feedback,
- LensCodeRewriter lensCodeRewriter)
- throws ExecutionException {
- Set<LambdaClass> synthesizedLambdaClasses = Sets.newIdentityHashSet();
- Set<DexProgramClass> synthesizedLambdaProgramClasses = Sets.newIdentityHashSet();
- for (DexEncodedMethod method : wave) {
- synthesizeLambdaClassesForMethod(
- method,
- lambdaClass -> {
- synthesizedLambdaClasses.add(lambdaClass);
- synthesizedLambdaProgramClasses.add(lambdaClass.getOrCreateLambdaClass());
- },
- lensCodeRewriter);
- }
-
- if (synthesizedLambdaClasses.isEmpty()) {
- return;
- }
-
- synthesizeAccessibilityBridgesForLambdaClasses(
- synthesizedLambdaClasses, converter, executorService, feedback, lensCodeRewriter);
-
+ public Map<DexEncodedField, Set<DexEncodedMethod>> getWritesWithContexts(
+ DexProgramClass synthesizedLambdaClass) {
// Record that the static fields on each lambda class are only written inside the static
// initializer of the lambdas.
Map<DexEncodedField, Set<DexEncodedMethod>> writesWithContexts = new IdentityHashMap<>();
- for (DexProgramClass synthesizedLambdaClass : synthesizedLambdaProgramClasses) {
DexEncodedMethod clinit = synthesizedLambdaClass.getClassInitializer();
if (clinit != null) {
for (DexEncodedField field : synthesizedLambdaClass.staticFields()) {
writesWithContexts.put(field, ImmutableSet.of(clinit));
}
}
- }
-
- AppView<AppInfoWithLiveness> appViewWithLiveness = getAppView().withLiveness();
- appViewWithLiveness.setAppInfo(
- appViewWithLiveness.appInfo().withStaticFieldWrites(writesWithContexts));
-
- converter.optimizeSynthesizedLambdaClasses(synthesizedLambdaProgramClasses, executorService);
- feedback.updateVisibleOptimizationInfo();
+ return writesWithContexts;
}
- private void synthesizeAccessibilityBridgesForLambdaClasses(
+ private void synthesizeAccessibilityBridgesForLambdaClassesD8(
Collection<LambdaClass> lambdaClasses, IRConverter converter, ExecutorService executorService)
throws ExecutionException {
- synthesizeAccessibilityBridgesForLambdaClasses(
- lambdaClasses, converter, executorService, null, null);
- }
-
- private void synthesizeAccessibilityBridgesForLambdaClasses(
- Collection<LambdaClass> lambdaClasses,
- IRConverter converter,
- ExecutorService executorService,
- OptimizationFeedbackDelayed feedback,
- LensCodeRewriter lensCodeRewriter)
- throws ExecutionException {
Set<DexEncodedMethod> nonDexAccessibilityBridges = Sets.newIdentityHashSet();
for (LambdaClass lambdaClass : lambdaClasses) {
// This call may cause originalMethodSignatures to be updated.
- DexEncodedMethod accessibilityBridge = lambdaClass.target.ensureAccessibilityIfNeeded();
+ DexEncodedMethod accessibilityBridge = lambdaClass.target.ensureAccessibilityIfNeeded(true);
if (accessibilityBridge != null && !accessibilityBridge.getCode().isDexCode()) {
nonDexAccessibilityBridges.add(accessibilityBridge);
}
}
- if (appView.enableWholeProgramOptimizations()) {
- if (!originalMethodSignatures.isEmpty()) {
- graphLens.addOriginalMethodSignatures(originalMethodSignatures);
- originalMethodSignatures.clear();
- }
-
- // Ensure that all lambda classes referenced from accessibility bridges are synthesized
- // prior to the IR processing of these accessibility bridges.
- synthesizeLambdaClassesForWave(
- nonDexAccessibilityBridges, converter, executorService, feedback, lensCodeRewriter);
- }
if (!nonDexAccessibilityBridges.isEmpty()) {
converter.processMethodsConcurrently(nonDexAccessibilityBridges, executorService);
}
}
- public void synthesizeLambdaClassesForMethod(
- DexEncodedMethod method, Consumer<LambdaClass> consumer, LensCodeRewriter lensCodeRewriter) {
- if (!method.hasCode() || method.isProcessed()) {
- // Nothing to desugar.
- return;
- }
-
- Code code = method.getCode();
- if (!code.isCfCode()) {
- // Nothing to desugar.
- return;
- }
-
- // Introduce a lambda class in AppInfo for each call site such that we do not modify the
- // application (and, in particular, the class hierarchy) during wave processing.
- code.registerCodeReferences(
- method,
- new DefaultUseRegistry(getFactory()) {
-
- @Override
- public void registerCallSite(DexCallSite callSite) {
- LambdaDescriptor descriptor =
- inferLambdaDescriptor(
- lensCodeRewriter.rewriteCallSite(callSite, method), method.method.holder);
- if (descriptor != LambdaDescriptor.MATCH_FAILED) {
- consumer.accept(getOrCreateLambdaClass(descriptor, method.method.holder));
- }
- }
- });
- }
-
/**
* Detect and desugar lambdas and method references found in the code.
*
@@ -296,7 +199,7 @@
public void finalizeLambdaDesugaringForD8(
Builder<?> builder, IRConverter converter, ExecutorService executorService)
throws ExecutionException {
- synthesizeAccessibilityBridgesForLambdaClasses(
+ synthesizeAccessibilityBridgesForLambdaClassesD8(
knownLambdaClasses.values(), converter, executorService);
for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
DexProgramClass synthesizedClass = lambdaClass.getOrCreateLambdaClass();
@@ -315,18 +218,6 @@
executorService);
}
- /** Generates lambda classes and adds them to the builder. */
- public void finalizeLambdaDesugaringForR8(Builder<?> builder) {
- // Verify that all mappings have been published to the LambdaRewriterGraphLense.
- assert originalMethodSignatures.isEmpty();
- graphLens.markShouldMapInvocationType();
- for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
- DexProgramClass synthesizedClass = lambdaClass.getOrCreateLambdaClass();
- getAppInfo().addSynthesizedClass(synthesizedClass);
- builder.addSynthesizedClass(synthesizedClass, lambdaClass.addToMainDexList.get());
- }
- }
-
public Set<DexCallSite> getDesugaredCallSites() {
synchronized (knownCallSites) {
return knownCallSites.keySet();
@@ -357,7 +248,7 @@
// Returns a lambda class corresponding to the lambda descriptor and context,
// creates the class if it does not yet exist.
- private LambdaClass getOrCreateLambdaClass(LambdaDescriptor descriptor, DexType accessedFrom) {
+ public LambdaClass getOrCreateLambdaClass(LambdaDescriptor descriptor, DexType accessedFrom) {
DexType lambdaClassType = LambdaClass.createLambdaClassType(this, accessedFrom, descriptor);
// We check the map twice to to minimize time spent in synchronized block.
LambdaClass lambdaClass = getKnown(knownLambdaClasses, lambdaClassType);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriterGraphLense.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriterGraphLense.java
deleted file mode 100644
index 9903607..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriterGraphLense.java
+++ /dev/null
@@ -1,57 +0,0 @@
-// 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.ir.desugar;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
-import com.android.tools.r8.ir.code.Invoke;
-import com.google.common.collect.HashBiMap;
-import com.google.common.collect.ImmutableBiMap;
-import com.google.common.collect.ImmutableMap;
-import java.util.Map;
-
-class LambdaRewriterGraphLense extends NestedGraphLense {
-
- // We don't map the invocation type in the lens code rewriter for lambda accessibility bridges to
- // ensure that we always compute the same unique id for invoke-custom instructions.
- //
- // TODO(b/129458850): It might be possible to avoid this by performing lambda desugaring prior to
- // lens code rewriting in the IR converter.
- private boolean shouldMapInvocationType = false;
-
- LambdaRewriterGraphLense(AppView<?> appView) {
- super(
- ImmutableMap.of(),
- ImmutableMap.of(),
- ImmutableMap.of(),
- ImmutableBiMap.of(),
- HashBiMap.create(),
- appView.graphLense(),
- appView.dexItemFactory());
- }
-
- @Override
- protected boolean isLegitimateToHaveEmptyMappings() {
- return true;
- }
-
- void addOriginalMethodSignatures(Map<DexMethod, DexMethod> originalMethodSignatures) {
- this.originalMethodSignatures.putAll(originalMethodSignatures);
- }
-
- void markShouldMapInvocationType() {
- shouldMapInvocationType = true;
- }
-
- @Override
- protected Invoke.Type mapInvocationType(
- DexMethod newMethod, DexMethod originalMethod, Invoke.Type type) {
- if (shouldMapInvocationType && methodMap.get(originalMethod) == newMethod) {
- assert type == Invoke.Type.VIRTUAL || type == Invoke.Type.DIRECT;
- return Invoke.Type.STATIC;
- }
- return super.mapInvocationType(newMethod, originalMethod, type);
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
index a344095..b19e557 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ThrowingCharIterator;
+import com.android.tools.r8.utils.Timing;
import java.io.UTFDataFormatException;
import java.util.List;
import java.util.stream.Collectors;
@@ -216,10 +217,15 @@
* }
* </pre>
*/
- public void run(DexEncodedMethod method, IRCode code) {
- if (!enabled) {
- return;
+ public void run(DexEncodedMethod method, IRCode code, Timing timing) {
+ if (enabled) {
+ timing.begin("Rewrite assertions");
+ runInternal(method, code);
+ timing.end();
}
+ }
+
+ private void runInternal(DexEncodedMethod method, IRCode code) {
AssertionTransformation transformation = getTransformationForMethod(method);
if (transformation == AssertionTransformation.PASSTHROUGH) {
return;
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 fb75832..52d047e 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
@@ -60,6 +60,30 @@
public class ClassInitializerDefaultsOptimization {
+ public static class ClassInitializerDefaultsResult {
+
+ private static final ClassInitializerDefaultsResult EMPTY =
+ new ClassInitializerDefaultsResult(null);
+
+ private final Map<DexEncodedField, DexValue> fieldsWithStaticValues;
+
+ ClassInitializerDefaultsResult(Map<DexEncodedField, DexValue> fieldsWithStaticValues) {
+ this.fieldsWithStaticValues = fieldsWithStaticValues;
+ }
+
+ public static ClassInitializerDefaultsResult empty() {
+ return EMPTY;
+ }
+
+ public boolean hasStaticValue(DexEncodedField field) {
+ if (field.isStatic()) {
+ return (fieldsWithStaticValues != null && fieldsWithStaticValues.containsKey(field))
+ || field.getStaticValue() != null;
+ }
+ return false;
+ }
+ }
+
private class WaveDoneAction implements Action {
private final Map<DexEncodedField, DexValue> fieldsWithStaticValues;
@@ -103,14 +127,18 @@
this.dexItemFactory = appView.dexItemFactory();
}
- public void optimize(DexEncodedMethod method, IRCode code) {
+ public ClassInitializerDefaultsResult optimize(DexEncodedMethod method, IRCode code) {
+ if (appView.options().debug || method.getOptimizationInfo().isReachabilitySensitive()) {
+ return ClassInitializerDefaultsResult.empty();
+ }
+
if (!method.isClassInitializer()) {
- return;
+ return ClassInitializerDefaultsResult.empty();
}
DexClass clazz = appView.definitionFor(method.method.holder);
if (clazz == null) {
- return;
+ return ClassInitializerDefaultsResult.empty();
}
// Collect straight-line static puts up to the first side-effect that is not
@@ -123,7 +151,7 @@
// Return eagerly if there is nothing to optimize.
if (finalFieldPuts.isEmpty()) {
assert unnecessaryStaticPuts.isEmpty();
- return;
+ return ClassInitializerDefaultsResult.empty();
}
Map<DexEncodedField, DexValue> fieldsWithStaticValues = new IdentityHashMap<>();
@@ -253,6 +281,8 @@
} else {
fieldsWithStaticValues.forEach(DexEncodedField::setStaticValue);
}
+
+ return new ClassInitializerDefaultsResult(fieldsWithStaticValues);
}
private DexValue getDexStringValue(Value inValue, DexType holder) {
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 65e86b7..51fe0fe 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
@@ -1477,7 +1477,7 @@
new ConstNumber(
new Value(
code.valueNumberGenerator.next(),
- TypeLatticeElement.INT,
+ TypeLatticeElement.getInt(),
instanceOf.outValue().getLocalInfo()),
result == InstanceOfResult.TRUE ? 1 : 0);
it.replaceCurrentInstruction(newInstruction);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java
new file mode 100644
index 0000000..88166e5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java
@@ -0,0 +1,323 @@
+// Copyright (c) 2020, 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;
+
+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.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.ArrayTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.FieldInstruction;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class EnumUnboxer {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final Set<DexType> enumsToUnbox;
+
+ private final boolean debugLogEnabled;
+ private final Map<DexType, Reason> debugLogs;
+ private final DexItemFactory factory;
+
+ public EnumUnboxer(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ this.factory = appView.dexItemFactory();
+ if (appView.options().testing.enableEnumUnboxingDebugLogs) {
+ debugLogEnabled = true;
+ debugLogs = new ConcurrentHashMap<>();
+ } else {
+ debugLogEnabled = false;
+ debugLogs = null;
+ }
+ enumsToUnbox = new EnumUnboxingCandidateAnalysis(appView, this).findCandidates();
+ }
+
+ public void unboxEnums(IRCode code) {
+ // TODO(b/147860220): To implement.
+ // Do not forget static get, which is implicitly valid (no inValue).
+ }
+
+ public void analyzeEnums(IRCode code) {
+ // Enum <clinit> and <init> are analyzed in between the two processing phases using optimization
+ // feedback.
+ DexClass dexClass = appView.definitionFor(code.method.method.holder);
+ if (dexClass.isEnum() && code.method.isInitializer()) {
+ return;
+ }
+ analyzeEnumsInMethod(code);
+ }
+
+ private void markEnumAsUnboxable(Reason reason, DexProgramClass enumClass) {
+ assert enumClass.isEnum();
+ reportFailure(enumClass.type, reason);
+ enumsToUnbox.remove(enumClass.type);
+ }
+
+ private DexProgramClass getEnumUnboxingCandidateOrNull(TypeLatticeElement lattice) {
+ if (lattice.isClassType()) {
+ DexType classType = lattice.asClassTypeLatticeElement().getClassType();
+ return getEnumUnboxingCandidateOrNull(classType);
+ }
+ if (lattice.isArrayType()) {
+ ArrayTypeLatticeElement arrayLattice = lattice.asArrayTypeLatticeElement();
+ if (arrayLattice.getArrayBaseTypeLattice().isClassType()) {
+ DexType classType =
+ arrayLattice.getArrayBaseTypeLattice().asClassTypeLatticeElement().getClassType();
+ return getEnumUnboxingCandidateOrNull(classType);
+ }
+ }
+ return null;
+ }
+
+ private DexProgramClass getEnumUnboxingCandidateOrNull(DexType anyType) {
+ if (!enumsToUnbox.contains(anyType)) {
+ return null;
+ }
+ return appView.definitionForProgramType(anyType);
+ }
+
+ private void analyzeEnumsInMethod(IRCode code) {
+ for (BasicBlock block : code.blocks) {
+ for (Instruction instruction : block.getInstructions()) {
+ Value outValue = instruction.outValue();
+ DexProgramClass enumClass =
+ outValue == null ? null : getEnumUnboxingCandidateOrNull(outValue.getTypeLattice());
+ if (enumClass != null) {
+ validateEnumUsages(code, outValue.uniqueUsers(), outValue.uniquePhiUsers(), enumClass);
+ }
+ }
+ for (Phi phi : block.getPhis()) {
+ DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(phi.getTypeLattice());
+ if (enumClass != null) {
+ validateEnumUsages(code, phi.uniqueUsers(), phi.uniquePhiUsers(), enumClass);
+ }
+ }
+ }
+ }
+
+ private Reason validateEnumUsages(
+ IRCode code, Set<Instruction> uses, Set<Phi> phiUses, DexProgramClass enumClass) {
+ for (Instruction user : uses) {
+ Reason reason = instructionAllowEnumUnboxing(user, code, enumClass);
+ if (reason != Reason.ELIGIBLE) {
+ markEnumAsUnboxable(reason, enumClass);
+ return reason;
+ }
+ }
+ for (Phi phi : phiUses) {
+ for (Value operand : phi.getOperands()) {
+ if (getEnumUnboxingCandidateOrNull(operand.getTypeLattice()) != enumClass) {
+ markEnumAsUnboxable(Reason.INVALID_PHI, enumClass);
+ return Reason.INVALID_PHI;
+ }
+ }
+ }
+ return Reason.ELIGIBLE;
+ }
+
+ public void finishEnumAnalysis() {
+ for (DexType toUnbox : enumsToUnbox) {
+ DexProgramClass enumClass = appView.definitionForProgramType(toUnbox);
+ assert enumClass != null;
+
+ DexEncodedMethod initializer = enumClass.lookupDirectMethod(factory.enumMethods.constructor);
+ assert initializer != null;
+ if (initializer.getOptimizationInfo().mayHaveSideEffects()) {
+ markEnumAsUnboxable(Reason.INVALID_INIT, enumClass);
+ continue;
+ }
+
+ if (enumClass.classInitializationMayHaveSideEffects(appView)) {
+ markEnumAsUnboxable(Reason.INVALID_CLINIT, enumClass);
+ continue;
+ }
+
+ Map<DexField, EnumValueInfo> enumValueInfoMapFor =
+ appView.appInfo().withLiveness().getEnumValueInfoMapFor(enumClass.type);
+ if (enumValueInfoMapFor == null) {
+ markEnumAsUnboxable(Reason.MISSING_INFO_MAP, enumClass);
+ continue;
+ }
+ if (enumValueInfoMapFor.size() != enumClass.staticFields().size() - 1) {
+ markEnumAsUnboxable(Reason.UNEXPECTED_STATIC_FIELD, enumClass);
+ }
+ }
+ if (debugLogEnabled) {
+ reportEnumsAnalysis();
+ }
+ }
+
+ private Reason instructionAllowEnumUnboxing(
+ Instruction instruction, IRCode code, DexProgramClass enumClass) {
+
+ // All invokes in the library are invalid, besides a few cherry picked cases such as ordinal().
+ if (instruction.isInvokeMethod()) {
+ InvokeMethod invokeMethod = instruction.asInvokeMethod();
+ if (invokeMethod.getInvokedMethod().holder.isArrayType()) {
+ // The only valid methods is clone for values() to be correct.
+ if (invokeMethod.getInvokedMethod().name == factory.cloneMethodName) {
+ return Reason.ELIGIBLE;
+ }
+ return Reason.INVALID_INVOKE_ON_ARRAY;
+ }
+ DexEncodedMethod invokedEncodedMethod =
+ invokeMethod.lookupSingleTarget(appView, code.method.method.holder);
+ if (invokedEncodedMethod == null) {
+ return Reason.INVALID_INVOKE;
+ }
+ DexMethod invokedMethod = invokedEncodedMethod.method;
+ DexClass dexClass = appView.definitionFor(invokedMethod.holder);
+ if (dexClass == null) {
+ return Reason.INVALID_INVOKE;
+ }
+ if (dexClass.isProgramClass()) {
+ // All invokes in the program are generally valid, but specific care is required
+ // for values() and valueOf().
+ if (dexClass.isEnum() && factory.enumMethods.isValuesMethod(invokedMethod, dexClass)) {
+ return Reason.VALUES_INVOKE;
+ }
+ if (dexClass.isEnum() && factory.enumMethods.isValueOfMethod(invokedMethod, dexClass)) {
+ return Reason.VALUE_OF_INVOKE;
+ }
+ return Reason.ELIGIBLE;
+ }
+ if (dexClass.isClasspathClass()) {
+ return Reason.INVALID_INVOKE;
+ }
+ assert dexClass.isLibraryClass();
+ if (dexClass.type != factory.enumType) {
+ return Reason.UNSUPPORTED_LIBRARY_CALL;
+ }
+ // TODO(b/147860220): Methods toString(), name(), compareTo(), EnumSet and EnumMap may be
+ // interesting to model. A the moment rewrite only Enum#ordinal().
+ if (debugLogEnabled) {
+ if (invokedMethod == factory.enumMethods.compareTo) {
+ return Reason.COMPARE_TO_INVOKE;
+ }
+ if (invokedMethod == factory.enumMethods.name) {
+ return Reason.NAME_INVOKE;
+ }
+ if (invokedMethod == factory.enumMethods.toString) {
+ return Reason.TO_STRING_INVOKE;
+ }
+ }
+ if (invokedMethod != factory.enumMethods.ordinal) {
+ return Reason.UNSUPPORTED_LIBRARY_CALL;
+ }
+ return Reason.ELIGIBLE;
+ }
+
+ // A field put is valid only if the field is not on an enum, and the field type and the valuePut
+ // have identical enum type.
+ if (instruction.isFieldPut()) {
+ FieldInstruction fieldInstruction = instruction.asFieldInstruction();
+ DexEncodedField field = appView.appInfo().resolveField(fieldInstruction.getField());
+ if (field == null) {
+ return Reason.INVALID_FIELD_PUT;
+ }
+ DexProgramClass dexClass = appView.definitionForProgramType(field.field.holder);
+ if (dexClass == null) {
+ return Reason.INVALID_FIELD_PUT;
+ }
+ if (dexClass.isEnum()) {
+ return Reason.FIELD_PUT_ON_ENUM;
+ }
+ // The put value has to be of the field type.
+ if (field.field.type != enumClass.type) {
+ return Reason.TYPE_MISSMATCH_FIELD_PUT;
+ }
+ return Reason.ELIGIBLE;
+ }
+
+ if (instruction.isAssume()) {
+ Value outValue = instruction.outValue();
+ return validateEnumUsages(code, outValue.uniqueUsers(), outValue.uniquePhiUsers(), enumClass);
+ }
+
+ // Return is used for valueOf methods.
+ if (instruction.isReturn()) {
+ DexType returnType = code.method.method.proto.returnType;
+ if (returnType != enumClass.type && returnType.toBaseType(factory) != enumClass.type) {
+ return Reason.IMPLICIT_UP_CAST_IN_RETURN;
+ }
+ return Reason.ELIGIBLE;
+ }
+
+ return Reason.OTHER_UNSUPPORTED_INSTRUCTION;
+ }
+
+ private void reportEnumsAnalysis() {
+ assert debugLogEnabled;
+ Reporter reporter = appView.options().reporter;
+ reporter.info(
+ new StringDiagnostic(
+ "Unboxed enums (Unboxing succeeded "
+ + enumsToUnbox.size()
+ + "): "
+ + Arrays.toString(enumsToUnbox.toArray())));
+ StringBuilder sb = new StringBuilder();
+ sb.append("Boxed enums (Unboxing failed ").append(debugLogs.size()).append("):\n");
+ for (DexType enumType : debugLogs.keySet()) {
+ sb.append("- ")
+ .append(enumType)
+ .append(": ")
+ .append(debugLogs.get(enumType).toString())
+ .append('\n');
+ }
+ reporter.info(new StringDiagnostic(sb.toString()));
+ }
+
+ void reportFailure(DexType enumType, Reason reason) {
+ if (debugLogEnabled) {
+ debugLogs.put(enumType, reason);
+ }
+ }
+
+ public enum Reason {
+ ELIGIBLE,
+ SUBTYPES,
+ INTERFACE,
+ INSTANCE_FIELD,
+ UNEXPECTED_STATIC_FIELD,
+ VIRTUAL_METHOD,
+ UNEXPECTED_DIRECT_METHOD,
+ INVALID_PHI,
+ INVALID_INIT,
+ INVALID_CLINIT,
+ INVALID_INVOKE,
+ INVALID_INVOKE_ON_ARRAY,
+ IMPLICIT_UP_CAST_IN_RETURN,
+ VALUE_OF_INVOKE,
+ VALUES_INVOKE,
+ COMPARE_TO_INVOKE,
+ TO_STRING_INVOKE,
+ NAME_INVOKE,
+ UNSUPPORTED_LIBRARY_CALL,
+ MISSING_INFO_MAP,
+ INVALID_FIELD_PUT,
+ FIELD_PUT_ON_ENUM,
+ TYPE_MISSMATCH_FIELD_PUT,
+ OTHER_UNSUPPORTED_INSTRUCTION;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxingCandidateAnalysis.java
new file mode 100644
index 0000000..3fb84e1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxingCandidateAnalysis.java
@@ -0,0 +1,114 @@
+// Copyright (c) 2020, 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;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.optimize.EnumUnboxer.Reason;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+class EnumUnboxingCandidateAnalysis {
+
+ private final AppView<?> appView;
+ private final EnumUnboxer enumUnboxer;
+ private final DexItemFactory factory;
+
+ EnumUnboxingCandidateAnalysis(AppView<?> appView, EnumUnboxer enumUnboxer) {
+ this.appView = appView;
+ this.enumUnboxer = enumUnboxer;
+ factory = appView.dexItemFactory();
+ }
+
+ Set<DexType> findCandidates() {
+ Set<DexType> enums = Sets.newConcurrentHashSet();
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ if (isEnumUnboxingCandidate(clazz)) {
+ enums.add(clazz.type);
+ }
+ }
+ return enums;
+ }
+
+ private boolean isEnumUnboxingCandidate(DexProgramClass clazz) {
+ if (!clazz.isEnum()) {
+ return false;
+ }
+ if (!clazz.isEffectivelyFinal(appView)) {
+ enumUnboxer.reportFailure(clazz.type, Reason.SUBTYPES);
+ return false;
+ }
+ // TODO(b/147860220): interfaces without default methods should be acceptable if the build setup
+ // is correct (all abstract methods are implemented).
+ if (!clazz.interfaces.isEmpty()) {
+ enumUnboxer.reportFailure(clazz.type, Reason.INTERFACE);
+ return false;
+ }
+ if (!clazz.instanceFields().isEmpty()) {
+ enumUnboxer.reportFailure(clazz.type, Reason.INSTANCE_FIELD);
+ return false;
+ }
+ if (!enumHasBasicStaticFields(clazz)) {
+ enumUnboxer.reportFailure(clazz.type, Reason.UNEXPECTED_STATIC_FIELD);
+ return false;
+ }
+ if (!clazz.virtualMethods().isEmpty()) {
+ enumUnboxer.reportFailure(clazz.type, Reason.VIRTUAL_METHOD);
+ return false;
+ }
+ // Methods values, valueOf, init, clinit are present on each enum.
+ // Methods init and clinit are required if the enum is used.
+ // Methods valueOf and values are normally kept by the commonly used/recommended enum keep rule
+ // -keepclassmembers,allowoptimization enum * {
+ // public static **[] values();
+ // public static ** valueOf(java.lang.String);
+ // }
+ // In general there will be 4 methods, unless the enum keep rule is not present.
+ if (clazz.directMethods().size() > 4) {
+ enumUnboxer.reportFailure(clazz.type, Reason.UNEXPECTED_DIRECT_METHOD);
+ return false;
+ }
+ for (DexEncodedMethod directMethod : clazz.directMethods()) {
+ if (!(factory.enumMethods.isValuesMethod(directMethod.method, clazz)
+ || factory.enumMethods.isValueOfMethod(directMethod.method, clazz)
+ || isStandardEnumInitializer(directMethod)
+ || directMethod.isClassInitializer())) {
+ enumUnboxer.reportFailure(clazz.type, Reason.UNEXPECTED_DIRECT_METHOD);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean isStandardEnumInitializer(DexEncodedMethod method) {
+ return method.isInstanceInitializer()
+ && method.method.proto == factory.enumMethods.constructor.proto;
+ }
+
+ // The enum should have the $VALUES static field and only fields directly referencing the enum
+ // instances.
+ private boolean enumHasBasicStaticFields(DexProgramClass clazz) {
+ for (DexEncodedField staticField : clazz.staticFields()) {
+ if (staticField.field.type == clazz.type
+ && staticField.accessFlags.isEnum()
+ && staticField.accessFlags.isFinal()) {
+ // Enum field, valid, do nothing.
+ } else if (staticField.field.type.isArrayType()
+ && staticField.field.type.toArrayElementType(factory) == clazz.type
+ && staticField.accessFlags.isSynthetic()
+ && staticField.accessFlags.isFinal()
+ && staticField.field.name == factory.enumValuesFieldName) {
+ // Field $VALUES, valid, do nothing.
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+}
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 5df7550..722625f 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
@@ -17,13 +17,12 @@
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.analysis.value.SingleValue;
import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.IRMetadata;
-import com.android.tools.r8.ir.code.InstanceGet;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
@@ -161,9 +160,6 @@
code.origin));
return null;
}
- if (replacement.isDexItemBasedConstString()) {
- code.method.getMutableOptimizationInfo().markUseIdentifierNameString();
- }
return replacement;
}
@@ -294,16 +290,16 @@
}
}
- private void rewriteStaticGetWithConstantValues(
+ private void rewriteFieldGetWithConstantValues(
IRCode code,
Set<Value> affectedValues,
ListIterator<BasicBlock> blocks,
InstructionListIterator iterator,
- StaticGet current) {
+ FieldInstruction current) {
DexField field = current.getField();
// TODO(b/123857022): Should be able to use definitionFor().
- DexEncodedField target = appView.appInfo().lookupStaticTarget(field.holder, field);
+ DexEncodedField target = appView.appInfo().resolveField(field);
if (target == null) {
boolean replaceCurrentInstructionWithConstNull =
appView.withGeneratedExtensionRegistryShrinker(
@@ -314,6 +310,10 @@
return;
}
+ if (target.isStatic() != current.isStaticGet()) {
+ return;
+ }
+
if (!mayPropagateValueFor(target)) {
return;
}
@@ -336,9 +336,9 @@
target.valueAsConstInstruction(code, current.outValue().getLocalInfo(), appView);
if (replacement != null) {
affectedValues.addAll(current.outValue().affectedValues());
- if (target.mayTriggerClassInitializationSideEffects(appView, code.method.method.holder)) {
- // To preserve class initialization side effects, original static-get remains as-is, but its
- // value is replaced with constant.
+ if (current.instructionMayHaveSideEffects(appView, code.method.method.holder)) {
+ // To preserve class initialization/NPE side effects, original field-get remains as-is,
+ // but its value is replaced with constant.
replacement.setPosition(current.getPosition());
current.outValue().replaceUsers(replacement.outValue());
if (current.getBlock().hasCatchHandlers()) {
@@ -349,39 +349,6 @@
} else {
iterator.replaceCurrentInstruction(replacement);
}
- if (replacement.isDexItemBasedConstString()) {
- code.method.getMutableOptimizationInfo().markUseIdentifierNameString();
- }
- feedback.markFieldAsPropagated(target);
- }
- }
-
- private void rewriteInstanceGetWithConstantValues(
- IRCode code,
- Set<Value> affectedValues,
- InstructionListIterator iterator,
- InstanceGet current) {
- if (current.object().getTypeLattice().isNullable()) {
- return;
- }
-
- DexField field = current.getField();
-
- // TODO(b/123857022): Should be able to use definitionFor().
- DexEncodedField target = appView.appInfo().lookupInstanceTarget(field.holder, field);
- if (target == null || !mayPropagateValueFor(target)) {
- return;
- }
-
- // Check if a this value is known const.
- Instruction replacement =
- target.valueAsConstInstruction(code, current.outValue().getLocalInfo(), appView);
- if (replacement != null) {
- affectedValues.add(replacement.outValue());
- iterator.replaceCurrentInstruction(replacement);
- if (replacement.isDexItemBasedConstString()) {
- code.method.getMutableOptimizationInfo().markUseIdentifierNameString();
- }
feedback.markFieldAsPropagated(target);
}
}
@@ -407,16 +374,9 @@
if (current.isInvokeMethod()) {
rewriteInvokeMethodWithConstantValues(
code, callingContext, affectedValues, blocks, iterator, current.asInvokeMethod());
- } else if (current.isStaticGet()) {
- rewriteStaticGetWithConstantValues(
- code,
- affectedValues,
- blocks,
- iterator,
- current.asStaticGet());
- } else if (current.isInstanceGet()) {
- rewriteInstanceGetWithConstantValues(
- code, affectedValues, iterator, current.asInstanceGet());
+ } else if (current.isFieldGet()) {
+ rewriteFieldGetWithConstantValues(
+ code, affectedValues, blocks, iterator, current.asFieldInstruction());
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java
index c6aecbb..b679383 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java
@@ -47,7 +47,7 @@
if (group.isSingletonLambda(lambda)) {
int id = group.lambdaId(lambda);
add(builder -> builder.addNewInstance(instance, groupClassType));
- add(builder -> builder.addConst(TypeLatticeElement.INT, lambdaId, id));
+ add(builder -> builder.addConst(TypeLatticeElement.getInt(), lambdaId, id));
add(
builder ->
builder.addInvoke(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
index 9e07c44..49eaa46 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
@@ -232,7 +232,7 @@
@Override
void prepareSuperConstructorCall(int receiverRegister) {
int arityRegister = nextRegister(ValueType.INT);
- add(builder -> builder.addConst(TypeLatticeElement.INT, arityRegister, arity));
+ add(builder -> builder.addConst(TypeLatticeElement.getInt(), arityRegister, arity));
add(
builder ->
builder.addInvoke(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
index f17535a..e54d241 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
@@ -213,7 +213,7 @@
DexType lambda = method.holder;
// Create constant with lambda id.
- Value lambdaIdValue = context.code.createValue(TypeLatticeElement.INT);
+ Value lambdaIdValue = context.code.createValue(TypeLatticeElement.getInt());
ConstNumber lambdaId = new ConstNumber(lambdaIdValue, group.lambdaId(lambda));
lambdaId.setPosition(invoke.getPosition());
context.instructions().previous();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
index 9c5a8d1..91e952a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
@@ -32,6 +32,10 @@
public LibraryMethodOptimizer(AppView<? extends AppInfoWithSubtyping> appView) {
this.appView = appView;
register(new BooleanMethodOptimizer(appView));
+
+ if (LogMethodOptimizer.isEnabled(appView)) {
+ register(new LogMethodOptimizer(appView));
+ }
}
private void register(LibraryMethodModelCollection optimizer) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java
new file mode 100644
index 0000000..f65b57a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java
@@ -0,0 +1,154 @@
+// Copyright (c) 2020, 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.library;
+
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
+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.DexType;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import java.util.Set;
+
+public class LogMethodOptimizer implements LibraryMethodModelCollection {
+
+ private static final int VERBOSE = 2;
+ private static final int DEBUG = 3;
+ private static final int INFO = 4;
+ private static final int WARN = 5;
+ private static final int ERROR = 6;
+ private static final int ASSERT = 7;
+
+ private final AppView<? extends AppInfoWithSubtyping> appView;
+
+ private final DexType logType;
+
+ private final DexMethod isLoggableMethod;
+ private final DexMethod vMethod;
+ private final DexMethod dMethod;
+ private final DexMethod iMethod;
+ private final DexMethod wMethod;
+ private final DexMethod eMethod;
+ private final DexMethod wtfMethod;
+
+ LogMethodOptimizer(AppView<? extends AppInfoWithSubtyping> appView) {
+ this.appView = appView;
+
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ DexType logType = dexItemFactory.createType("Landroid/util/Log;");
+ this.logType = logType;
+ this.isLoggableMethod =
+ dexItemFactory.createMethod(
+ logType,
+ dexItemFactory.createProto(
+ dexItemFactory.booleanType, dexItemFactory.stringType, dexItemFactory.intType),
+ "isLoggable");
+ this.vMethod =
+ dexItemFactory.createMethod(
+ logType,
+ dexItemFactory.createProto(
+ dexItemFactory.intType, dexItemFactory.stringType, dexItemFactory.stringType),
+ "v");
+ this.dMethod =
+ dexItemFactory.createMethod(
+ logType,
+ dexItemFactory.createProto(
+ dexItemFactory.intType, dexItemFactory.stringType, dexItemFactory.stringType),
+ "d");
+ this.iMethod =
+ dexItemFactory.createMethod(
+ logType,
+ dexItemFactory.createProto(
+ dexItemFactory.intType, dexItemFactory.stringType, dexItemFactory.stringType),
+ "i");
+ this.wMethod =
+ dexItemFactory.createMethod(
+ logType,
+ dexItemFactory.createProto(
+ dexItemFactory.intType, dexItemFactory.stringType, dexItemFactory.stringType),
+ "w");
+ this.eMethod =
+ dexItemFactory.createMethod(
+ logType,
+ dexItemFactory.createProto(
+ dexItemFactory.intType, dexItemFactory.stringType, dexItemFactory.stringType),
+ "e");
+ this.wtfMethod =
+ dexItemFactory.createMethod(
+ logType,
+ dexItemFactory.createProto(
+ dexItemFactory.intType, dexItemFactory.stringType, dexItemFactory.stringType),
+ "wtf");
+ }
+
+ public static boolean isEnabled(AppView<?> appView) {
+ return appView.options().getProguardConfiguration().getMaxRemovedAndroidLogLevel() >= VERBOSE;
+ }
+
+ @Override
+ public DexType getType() {
+ return logType;
+ }
+
+ @Override
+ public void optimize(
+ IRCode code,
+ InstructionListIterator instructionIterator,
+ InvokeMethod invoke,
+ DexEncodedMethod singleTarget,
+ Set<Value> affectedValues) {
+ int maxRemovedAndroidLogLevel =
+ appView.options().getProguardConfiguration().getMaxRemovedAndroidLogLevel();
+ if (singleTarget.method == isLoggableMethod) {
+ Value logLevelValue = invoke.arguments().get(1).getAliasedValue();
+ if (!logLevelValue.isPhi() && !logLevelValue.hasLocalInfo()) {
+ Instruction definition = logLevelValue.definition;
+ if (definition.isConstNumber()) {
+ int logLevel = definition.asConstNumber().getIntValue();
+ replaceInvokeWithConstNumber(
+ code, instructionIterator, invoke, maxRemovedAndroidLogLevel >= logLevel ? 0 : 1);
+ }
+ }
+ } else if (singleTarget.method == vMethod) {
+ if (maxRemovedAndroidLogLevel >= VERBOSE) {
+ replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
+ }
+ } else if (singleTarget.method == dMethod) {
+ if (maxRemovedAndroidLogLevel >= DEBUG) {
+ replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
+ }
+ } else if (singleTarget.method == iMethod) {
+ if (maxRemovedAndroidLogLevel >= INFO) {
+ replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
+ }
+ } else if (singleTarget.method == wMethod) {
+ if (maxRemovedAndroidLogLevel >= WARN) {
+ replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
+ }
+ } else if (singleTarget.method == eMethod) {
+ if (maxRemovedAndroidLogLevel >= ERROR) {
+ replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
+ }
+ } else if (singleTarget.method == wtfMethod) {
+ if (maxRemovedAndroidLogLevel >= ASSERT) {
+ replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
+ }
+ }
+ }
+
+ private void replaceInvokeWithConstNumber(
+ IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke, int value) {
+ if (invoke.hasOutValue() && invoke.outValue().hasAnyUsers()) {
+ instructionIterator.replaceCurrentInstructionWithConstInt(appView, code, value);
+ } else {
+ instructionIterator.removeOrReplaceByDebugLocalRead();
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/MoveLoadUpPeephole.java b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/MoveLoadUpPeephole.java
index c50791e..49a74d1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/MoveLoadUpPeephole.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/MoveLoadUpPeephole.java
@@ -113,7 +113,7 @@
Instruction current = it.next();
if (position != current.getPosition()
|| !current.isConstNumber()
- || current.outValue().getTypeLattice() != TypeLatticeElement.INT
+ || current.outValue().getTypeLattice() != TypeLatticeElement.getInt()
|| current.asConstNumber().getIntValue() < -128
|| current.asConstNumber().getIntValue() > 127
|| !it.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index e019acd..247f13e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -25,6 +25,7 @@
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
import com.android.tools.r8.ir.optimize.CodeRewriter;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.staticizer.ClassStaticizer.CandidateInfo;
@@ -32,6 +33,7 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
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.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableList;
@@ -298,7 +300,7 @@
IRCode code = method.buildIR(appView, origin);
codeOptimizations.forEach(codeOptimization -> codeOptimization.accept(code));
CodeRewriter.removeAssumeInstructions(appView, code);
- converter.finalizeIR(method, code, feedback);
+ converter.finalizeIR(method, code, feedback, Timing.empty());
}
private void insertAssumeInstructions(IRCode code) {
@@ -306,7 +308,8 @@
}
private Consumer<IRCode> collectOptimizationInfo(OptimizationFeedback feedback) {
- return code -> converter.collectOptimizationInfo(code, feedback);
+ return code ->
+ converter.collectOptimizationInfo(code, ClassInitializerDefaultsResult.empty(), feedback);
}
private void removeCandidateInstantiation(IRCode code) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index 41e1264..072f762 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -305,7 +305,6 @@
return;
}
Set<Value> affectedValues = Sets.newIdentityHashSet();
- boolean markUseIdentifierNameString = false;
InstructionListIterator it = code.instructionListIterator();
while (it.hasNext()) {
Instruction instr = it.next();
@@ -452,7 +451,6 @@
} else if (deferred != null) {
affectedValues.addAll(invoke.outValue().affectedValues());
it.replaceCurrentInstruction(deferred);
- markUseIdentifierNameString = true;
logHistogramOfNames(deferred);
}
}
@@ -461,9 +459,6 @@
if (!affectedValues.isEmpty()) {
new TypeAnalysis(appView).narrowing(affectedValues);
}
- if (markUseIdentifierNameString) {
- code.method.getMutableOptimizationInfo().markUseIdentifierNameString();
- }
}
private void logNameComputation(ClassNameMapping kind) {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
index 25a35ad..f9da7c6 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
@@ -85,6 +85,6 @@
@Override
public final String toString(DexEncodedMethod method, ClassNameMapper naming) {
- return "SynthesizedCode";
+ return this.getClass().getSimpleName();
}
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
index c63f22e..0edb554 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -6,8 +6,10 @@
import static com.android.tools.r8.kotlin.Kotlin.addKotlinPrefix;
import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toKmType;
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedClassifier;
import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmConstructor;
import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmType;
+import static kotlinx.metadata.Flag.IS_SEALED;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
@@ -80,6 +82,17 @@
}
rewriteDeclarationContainer(kmClass, appView, lens);
+
+ List<String> sealedSubclasses = kmClass.getSealedSubclasses();
+ sealedSubclasses.clear();
+ if (IS_SEALED.invoke(kmClass.getFlags())) {
+ for (DexType subtype : appView.appInfo().allImmediateSubtypes(clazz.type)) {
+ String classifier = toRenamedClassifier(subtype, appView, lens);
+ if (classifier != null) {
+ sealedSubclasses.add(classifier);
+ }
+ }
+ }
}
@Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
index ebd5524..7a1a343 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
@@ -7,14 +7,20 @@
import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmFunction;
import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmFunctionAsExtension;
+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.kotlin.KotlinMetadataSynthesizer.KmPropertyGroup;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import kotlinx.metadata.KmDeclarationContainer;
import kotlinx.metadata.KmFunction;
+import kotlinx.metadata.KmProperty;
import kotlinx.metadata.jvm.KotlinClassHeader;
import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -105,9 +111,9 @@
NamingLens lens) {
assert clazz != null;
+ Map<String, KmPropertyGroup.Builder> propertyGroupBuilderMap = new HashMap<>();
List<KmFunction> functions = kmDeclarationContainer.getFunctions();
functions.clear();
- // TODO(b/70169921): clear property
for (DexEncodedMethod method : clazz.methods()) {
if (method.isInitializer()) {
continue;
@@ -127,17 +133,62 @@
}
continue;
}
- if (method.isKotlinExtensionProperty()) {
- // TODO(b/70169921): (extension) property
- continue;
- }
if (method.isKotlinProperty()) {
- // TODO(b/70169921): (extension) property
+ String name = method.getKotlinMemberInfo().propertyName;
+ assert name != null;
+ KmPropertyGroup.Builder builder =
+ propertyGroupBuilderMap.computeIfAbsent(
+ name, k -> KmPropertyGroup.builder(method.getKotlinMemberInfo().flag, name));
+ switch (method.getKotlinMemberInfo().memberKind) {
+ case EXTENSION_PROPERTY_GETTER:
+ builder.isExtensionGetter();
+ // fallthrough;
+ case PROPERTY_GETTER:
+ builder.foundGetter(method);
+ break;
+ case EXTENSION_PROPERTY_SETTER:
+ builder.isExtensionSetter();
+ // fallthrough;
+ case PROPERTY_SETTER:
+ builder.foundSetter(method);
+ break;
+ case EXTENSION_PROPERTY_ANNOTATIONS:
+ builder.isExtensionAnnotations();
+ // fallthrough;
+ case PROPERTY_ANNOTATIONS:
+ builder.foundAnnotations(method);
+ break;
+ default:
+ throw new Unreachable("Not a Kotlin property: " + method.getKotlinMemberInfo());
+ }
continue;
}
// TODO(b/70169921): What should we do for methods that fall into this category---no mark?
}
- // TODO(b/70169921): (extension) companion
+
+ for (DexEncodedField field : clazz.fields()) {
+ if (field.isKotlinBackingField()) {
+ String name = field.getKotlinMemberInfo().propertyName;
+ assert name != null;
+ KmPropertyGroup.Builder builder =
+ propertyGroupBuilderMap.computeIfAbsent(
+ name, k -> KmPropertyGroup.builder(field.getKotlinMemberInfo().flag, name));
+ builder.foundBackingField(field);
+ }
+ }
+
+ List<KmProperty> properties = kmDeclarationContainer.getProperties();
+ properties.clear();
+ for (KmPropertyGroup.Builder builder : propertyGroupBuilderMap.values()) {
+ KmPropertyGroup group = builder.build();
+ if (group == null) {
+ continue;
+ }
+ KmProperty property = group.toRenamedKmProperty(appView, lens);
+ if (property != null) {
+ properties.add(property);
+ }
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
index 82bafe6..5382e44 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
@@ -28,13 +28,19 @@
}
public final MemberKind memberKind;
- public final int flag;
- // TODO(b/70169921): for (extension) property, we may need to group/track the original shape of
- // the property. Then, at then end, metadata for only live ones will be synthesized.
+ // Original flag. May be necessary to keep Kotlin-specific flag, e.g., inline function.
+ final int flag;
+ // Original property name for (extension) property. Otherwise, null.
+ final String propertyName;
private KotlinMemberInfo(MemberKind memberKind, int flag) {
+ this(memberKind, flag, null);
+ }
+
+ private KotlinMemberInfo(MemberKind memberKind, int flag, String propertyName) {
this.memberKind = memberKind;
this.flag = flag;
+ this.propertyName = propertyName;
}
public enum MemberKind {
@@ -53,7 +59,7 @@
EXTENSION_PROPERTY_SETTER,
EXTENSION_PROPERTY_ANNOTATIONS;
- // TODO(b/70169921): (extension) companion
+ // TODO(b/70169921): companion
public boolean isFunction() {
return this == FUNCTION || isExtensionFunction();
@@ -129,7 +135,8 @@
if (kmPropertyFieldMap.containsKey(key)) {
KmProperty kmProperty = kmPropertyFieldMap.get(key);
field.setKotlinMemberInfo(
- new KotlinMemberInfo(MemberKind.PROPERTY_BACKING_FIELD, kmProperty.getFlags()));
+ new KotlinMemberInfo(
+ MemberKind.PROPERTY_BACKING_FIELD, kmProperty.getFlags(), kmProperty.getName()));
}
}
@@ -151,19 +158,27 @@
KmProperty kmProperty = kmPropertyGetterMap.get(key);
if (isExtension(kmProperty)) {
method.setKotlinMemberInfo(
- new KotlinMemberInfo(MemberKind.EXTENSION_PROPERTY_GETTER, kmProperty.getFlags()));
+ new KotlinMemberInfo(
+ MemberKind.EXTENSION_PROPERTY_GETTER,
+ kmProperty.getFlags(),
+ kmProperty.getName()));
} else {
method.setKotlinMemberInfo(
- new KotlinMemberInfo(MemberKind.PROPERTY_GETTER, kmProperty.getFlags()));
+ new KotlinMemberInfo(
+ MemberKind.PROPERTY_GETTER, kmProperty.getFlags(), kmProperty.getName()));
}
} else if (kmPropertySetterMap.containsKey(key)) {
KmProperty kmProperty = kmPropertySetterMap.get(key);
if (isExtension(kmProperty)) {
method.setKotlinMemberInfo(
- new KotlinMemberInfo(MemberKind.EXTENSION_PROPERTY_SETTER, kmProperty.getFlags()));
+ new KotlinMemberInfo(
+ MemberKind.EXTENSION_PROPERTY_SETTER,
+ kmProperty.getFlags(),
+ kmProperty.getName()));
} else {
method.setKotlinMemberInfo(
- new KotlinMemberInfo(MemberKind.PROPERTY_SETTER, kmProperty.getFlags()));
+ new KotlinMemberInfo(
+ MemberKind.PROPERTY_SETTER, kmProperty.getFlags(), kmProperty.getName()));
}
}
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
index a3e62c5..8a8773a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -3,15 +3,19 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.kotlin;
-import static com.android.tools.r8.kotlin.Kotlin.addKotlinPrefix;
+import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmFieldSignature;
import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmMethodSignature;
-import static com.android.tools.r8.utils.DescriptorUtils.descriptorToInternalName;
+import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKmType;
+import static com.android.tools.r8.kotlin.Kotlin.NAME;
+import static com.android.tools.r8.utils.DescriptorUtils.descriptorToKotlinClassifier;
import static kotlinx.metadata.FlagsKt.flagsOf;
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.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.naming.NamingLens;
@@ -36,23 +40,21 @@
static KmType toKmType(String descriptor) {
KmType kmType = new KmType(flagsOf());
- kmType.visitClass(descriptorToInternalName(descriptor));
+ kmType.visitClass(descriptorToKotlinClassifier(descriptor));
return kmType;
}
- static KmType toRenamedKmType(
+ static String toRenamedClassifier(
DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
- // E.g., [Ljava/lang/String; -> Lkotlin/Array;
+ // E.g., [Ljava/lang/String; -> kotlin/Array
if (type.isArrayType()) {
- return toKmType(addKotlinPrefix("Array;"));
+ return NAME + "/Array";
}
- // E.g., void -> Lkotlin/Unit;
+ // E.g., void -> kotlin/Unit
if (appView.dexItemFactory().kotlin.knownTypeConversion.containsKey(type)) {
- KmType kmType = new KmType(flagsOf());
DexType convertedType = appView.dexItemFactory().kotlin.knownTypeConversion.get(type);
assert convertedType != null;
- kmType.visitClass(descriptorToInternalName(convertedType.toDescriptorString()));
- return kmType;
+ return descriptorToKotlinClassifier(convertedType.toDescriptorString());
}
// For library or classpath class, synthesize @Metadata always.
// For a program class, make sure it is live.
@@ -64,10 +66,19 @@
DexClass clazz = appView.definitionFor(type);
assert clazz == null || clazz.isProgramClass() || renamedType == type
: type.toSourceString() + " -> " + renamedType.toSourceString();
+ return descriptorToKotlinClassifier(renamedType.toDescriptorString());
+ }
+
+ static KmType toRenamedKmType(
+ DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+ String classifier = toRenamedClassifier(type, appView, lens);
+ if (classifier == null) {
+ return null;
+ }
// TODO(b/70169921): Mysterious, why attempts to properly set flags bothers kotlinc?
// and/or why wiping out flags works for KmType but not KmFunction?!
KmType kmType = new KmType(flagsOf());
- kmType.visitClass(descriptorToInternalName(renamedType.toDescriptorString()));
+ kmType.visitClass(classifier);
return kmType;
}
@@ -109,8 +120,9 @@
NamingLens lens,
boolean isExtension) {
// For library overrides, synthesize @Metadata always.
- // For regular methods, make sure it is live.
+ // For regular methods, make sure it is live or pinned.
if (!method.isLibraryMethodOverride().isTrue()
+ && !appView.appInfo().isPinned(method.method)
&& !appView.appInfo().liveMethods.contains(method.method)) {
return null;
}
@@ -118,8 +130,13 @@
// For a library method override, we should not have renamed it.
assert !method.isLibraryMethodOverride().isTrue() || renamedMethod == method.method
: method.toSourceString() + " -> " + renamedMethod.toSourceString();
- KmFunction kmFunction =
- new KmFunction(method.accessFlags.getAsKotlinFlags(), renamedMethod.name.toString());
+ // TODO(b/70169921): Should we keep kotlin-specific flags only while synthesizing the base
+ // value from general JVM flags?
+ int flag =
+ appView.appInfo().isPinned(method.method) && method.getKotlinMemberInfo() != null
+ ? method.getKotlinMemberInfo().flag
+ : method.accessFlags.getAsKotlinFlags();
+ KmFunction kmFunction = new KmFunction(flag, renamedMethod.name.toString());
JvmExtensionsKt.setSignature(kmFunction, toJvmMethodSignature(method.method));
KmType kmReturnType = toRenamedKmType(method.method.proto.returnType, appView, lens);
if (kmReturnType == null) {
@@ -164,4 +181,327 @@
}
return true;
}
+
+ /**
+ * A group of a field, and methods that correspond to a Kotlin property.
+ *
+ * <p>
+ * va(l|r) prop: T
+ *
+ * is converted to a backing field with signature `T prop` if necessary. If the property is a pure
+ * computation, e.g., return `this` or access to the status of other properties, a field is not
+ * needed for the property.
+ * <p>
+ * Depending on property visibility, getter is defined with signature `T getProp()` (or `isProp`
+ * for boolean property).
+ * <p>
+ * If it's editable, i.e., declared as `var`, setter is defined with signature `void setProp(T)`.
+ * <p>
+ * Yet another addition, `void prop$annotations()`, seems(?) to be used to store associated
+ * annotations.
+ *
+ * <p>
+ * Currently, it's unclear how users can selectively keep each. Assuming every piece is kept by
+ * more general rules, such as keeping non-private members, we expect to find those as a whole.
+ * When rewriting a corresponding property, each information is used to update corresponding part,
+ * e.g., field to update the return type of the property, getter to update jvmMethodSignature of
+ * getter, and so on.
+ */
+ static class KmPropertyGroup {
+ final int flag;
+ final String name;
+ final DexEncodedField field;
+ final DexEncodedMethod getter;
+ final DexEncodedMethod setter;
+ final DexEncodedMethod annotations;
+ final boolean isExtension;
+
+ private KmPropertyGroup(
+ int flag,
+ String name,
+ DexEncodedField field,
+ DexEncodedMethod getter,
+ DexEncodedMethod setter,
+ DexEncodedMethod annotations,
+ boolean isExtension) {
+ this.flag = flag;
+ this.name = name;
+ this.field = field;
+ this.getter = getter;
+ this.setter = setter;
+ this.annotations = annotations;
+ this.isExtension = isExtension;
+ }
+
+ static Builder builder(int flag, String name) {
+ return new Builder(flag, name);
+ }
+
+ static class Builder {
+ private final int flag;
+ private final String name;
+ private DexEncodedField field;
+ private DexEncodedMethod getter;
+ private DexEncodedMethod setter;
+ private DexEncodedMethod annotations;
+
+ private boolean isExtensionGetter;
+ private boolean isExtensionSetter;
+ private boolean isExtensionAnnotations;
+
+ private Builder(int flag, String name) {
+ this.flag = flag;
+ this.name = name;
+ }
+
+ Builder foundBackingField(DexEncodedField field) {
+ this.field = field;
+ return this;
+ }
+
+ Builder foundGetter(DexEncodedMethod getter) {
+ this.getter = getter;
+ return this;
+ }
+
+ Builder foundSetter(DexEncodedMethod setter) {
+ this.setter = setter;
+ return this;
+ }
+
+ Builder foundAnnotations(DexEncodedMethod annotations) {
+ this.annotations = annotations;
+ return this;
+ }
+
+ Builder isExtensionGetter() {
+ this.isExtensionGetter = true;
+ return this;
+ }
+
+ Builder isExtensionSetter() {
+ this.isExtensionSetter = true;
+ return this;
+ }
+
+ Builder isExtensionAnnotations() {
+ this.isExtensionAnnotations = true;
+ return this;
+ }
+
+ KmPropertyGroup build() {
+ boolean isExtension = isExtensionGetter || isExtensionSetter || isExtensionAnnotations;
+ // If this is an extension property, everything should be an extension.
+ if (isExtension) {
+ if (getter != null && !isExtensionGetter) {
+ return null;
+ }
+ if (setter != null && !isExtensionSetter) {
+ return null;
+ }
+ if (annotations != null && !isExtensionAnnotations) {
+ return null;
+ }
+ }
+ return new KmPropertyGroup(flag, name, field, getter, setter, annotations, isExtension);
+ }
+ }
+
+ KmProperty toRenamedKmProperty(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+ KmProperty kmProperty = new KmProperty(flag, name, flagsOf(), flagsOf());
+ KmType kmPropertyType = null;
+ KmType kmReceiverType = null;
+
+ if (field != null) {
+ DexField renamedField = lens.lookupField(field.field, appView.dexItemFactory());
+ if (renamedField == null) {
+ // Bail out if we can't find a renamed backing field.
+ return null;
+ }
+ kmProperty.setName(renamedField.name.toString());
+ kmPropertyType = toRenamedKmType(field.field.type, appView, lens);
+ if (kmPropertyType != null) {
+ kmProperty.setReturnType(kmPropertyType);
+ }
+ JvmExtensionsKt.setFieldSignature(kmProperty, toJvmFieldSignature(field.field));
+ }
+
+ GetterSetterCriteria criteria = checkGetterCriteria();
+ if (criteria == GetterSetterCriteria.VIOLATE) {
+ return null;
+ }
+
+ if (criteria == GetterSetterCriteria.MET) {
+ assert getter != null
+ : "checkGetterCriteria: " + this.toString();
+ if (isExtension) {
+ assert getter.method.proto.parameters.size() == 1
+ : "checkGetterCriteria: " + this.toString();
+ kmReceiverType = toRenamedKmType(getter.method.proto.parameters.values[0], appView, lens);
+ if (kmReceiverType != null) {
+ kmProperty.setReceiverParameterType(kmReceiverType);
+ }
+ }
+ if (kmPropertyType == null) {
+ // The property type is not set yet.
+ kmPropertyType = toRenamedKmType(getter.method.proto.returnType, appView, lens);
+ if (kmPropertyType != null) {
+ kmProperty.setReturnType(kmPropertyType);
+ }
+ } else {
+ // If property type is set already (via backing field), make sure it's consistent.
+ KmType kmPropertyTypeFromGetter =
+ toRenamedKmType(getter.method.proto.returnType, appView, lens);
+ if (!getDescriptorFromKmType(kmPropertyType)
+ .equals(getDescriptorFromKmType(kmPropertyTypeFromGetter))) {
+ return null;
+ }
+ }
+ kmProperty.setGetterFlags(getter.accessFlags.getAsKotlinFlags());
+ JvmExtensionsKt.setGetterSignature(kmProperty, toJvmMethodSignature(getter.method));
+ }
+
+ criteria = checkSetterCriteria();
+ if (criteria == GetterSetterCriteria.VIOLATE) {
+ return null;
+ }
+
+ if (criteria == GetterSetterCriteria.MET) {
+ assert setter != null && setter.method.proto.parameters.size() >= 1
+ : "checkSetterCriteria: " + this.toString();
+ if (isExtension) {
+ assert setter.method.proto.parameters.size() == 2
+ : "checkSetterCriteria: " + this.toString();
+ if (kmReceiverType == null) {
+ kmReceiverType =
+ toRenamedKmType(setter.method.proto.parameters.values[0], appView, lens);
+ if (kmReceiverType != null) {
+ kmProperty.setReceiverParameterType(kmReceiverType);
+ }
+ } else {
+ // If the receiver type for the extension property is set already (via getter),
+ // make sure it's consistent.
+ KmType kmReceiverTypeFromSetter =
+ toRenamedKmType(setter.method.proto.parameters.values[0], appView, lens);
+ if (!getDescriptorFromKmType(kmReceiverType)
+ .equals(getDescriptorFromKmType(kmReceiverTypeFromSetter))) {
+ return null;
+ }
+ }
+ }
+ int valueIndex = isExtension ? 1 : 0;
+ if (kmPropertyType == null) {
+ // The property type is not set yet.
+ kmPropertyType =
+ toRenamedKmType(setter.method.proto.parameters.values[valueIndex], appView, lens);
+ if (kmPropertyType != null) {
+ kmProperty.setReturnType(kmPropertyType);
+ }
+ } else {
+ // If property type is set already (via either backing field or getter),
+ // make sure it's consistent.
+ KmType kmPropertyTypeFromSetter =
+ toRenamedKmType(setter.method.proto.parameters.values[valueIndex], appView, lens);
+ if (!getDescriptorFromKmType(kmPropertyType)
+ .equals(getDescriptorFromKmType(kmPropertyTypeFromSetter))) {
+ return null;
+ }
+ }
+ kmProperty.setSetterFlags(setter.accessFlags.getAsKotlinFlags());
+ JvmExtensionsKt.setSetterSignature(kmProperty, toJvmMethodSignature(setter.method));
+ }
+
+ // If the property type remains null at the end, bail out to synthesize this property.
+ if (kmPropertyType == null) {
+ return null;
+ }
+ // For extension property, if the receiver type remains null at the end, bail out too.
+ if (isExtension && kmReceiverType == null) {
+ return null;
+ }
+ return kmProperty;
+ }
+
+ enum GetterSetterCriteria {
+ NOT_AVAILABLE,
+ MET,
+ VIOLATE
+ }
+
+ // Getter should look like:
+ // 1) T getProp(); for regular property, or
+ // 2) T getProp(R); for extension property, where
+ // T is a property type, and R is a receiver type.
+ private GetterSetterCriteria checkGetterCriteria() {
+ if (getter == null) {
+ return GetterSetterCriteria.NOT_AVAILABLE;
+ }
+ // Property type will be checked against a backing field type if any.
+ // And if they're different, we won't synthesize KmProperty for that.
+ // Checking parameter size.
+ if (isExtension) {
+ return getter.method.proto.parameters.size() == 1
+ ? GetterSetterCriteria.MET : GetterSetterCriteria.VIOLATE;
+ } else {
+ return getter.method.proto.parameters.size() == 0
+ ? GetterSetterCriteria.MET : GetterSetterCriteria.VIOLATE;
+ }
+ }
+
+ // Setter should look like:
+ // 1) void setProp(T); for regular property, or
+ // 2) void setProp(R, T); for extension property, where
+ // T is a property type, and R is a receiver type.
+ private GetterSetterCriteria checkSetterCriteria() {
+ if (setter == null) {
+ return GetterSetterCriteria.NOT_AVAILABLE;
+ }
+ if (!setter.method.proto.returnType.isVoidType()) {
+ return GetterSetterCriteria.VIOLATE;
+ }
+ // Property type will be checked against a backing field type if any.
+ // And if they're different, we won't synthesize KmProperty for that.
+ // Plus, receiver type will be checked, too, against a getter.
+ if (isExtension) {
+ return setter.method.proto.parameters.size() == 2
+ ? GetterSetterCriteria.MET : GetterSetterCriteria.VIOLATE;
+ } else {
+ return setter.method.proto.parameters.size() == 1
+ ? GetterSetterCriteria.MET : GetterSetterCriteria.VIOLATE;
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("KmPropertyGroup {").append(System.lineSeparator());
+ builder.append(" name: ").append(name).append(System.lineSeparator());
+ builder.append(" isExtension: ").append(isExtension).append(System.lineSeparator());
+ if (field != null) {
+ builder.append(" field: ")
+ .append(field.toSourceString())
+ .append(System.lineSeparator());
+ }
+ if (getter != null) {
+ builder
+ .append(" getter: ")
+ .append(getter.toSourceString())
+ .append(System.lineSeparator());
+ }
+ if (setter != null) {
+ builder
+ .append(" setter: ")
+ .append(setter.toSourceString())
+ .append(System.lineSeparator());
+ }
+ if (annotations != null) {
+ builder
+ .append(" annotations: ")
+ .append(annotations.toSourceString())
+ .append(System.lineSeparator());
+ }
+ builder.append("}");
+ return builder.toString();
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
index 147c251..1d3718b 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -415,7 +415,12 @@
} else {
// "x:y:a():u:v -> b"
assert originalRange instanceof Range;
- return ((Range) originalRange).from + lineNumberAfterMinification - minifiedRange.from;
+ Range originalRange = (Range) this.originalRange;
+ if (originalRange.to == originalRange.from) {
+ // This is a single line mapping which we should report as the actual line.
+ return originalRange.to;
+ }
+ return originalRange.from + lineNumberAfterMinification - minifiedRange.from;
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index de60241..47d6431 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -195,7 +195,6 @@
InstancePut instancePut = instruction.asInstancePut();
iterator.replaceCurrentInstruction(new InstancePut(field, instancePut.object(), newIn));
}
- method.getMutableOptimizationInfo().markUseIdentifierNameString();
return iterator;
}
@@ -335,7 +334,6 @@
iterator.replaceCurrentInstruction(
Invoke.create(
invoke.getType(), invokedMethod, invokedMethod.proto, invoke.outValue(), newIns));
- method.getMutableOptimizationInfo().markUseIdentifierNameString();
}
return iterator;
}
diff --git a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
index 6c097a4..8b662b9 100644
--- a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
@@ -10,6 +10,7 @@
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.naming.MethodNameMinifier.State;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.DisjointSets;
import com.android.tools.r8.utils.MethodJavaSignatureEquivalence;
@@ -18,7 +19,6 @@
import com.google.common.base.Equivalence;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
@@ -359,7 +359,6 @@
}
private final AppView<AppInfoWithLiveness> appView;
- private final Set<DexCallSite> desugaredCallSites;
private final Equivalence<DexMethod> equivalence;
private final MethodNameMinifier.State minifierState;
@@ -371,12 +370,8 @@
/** A map for caching all interface states. */
private final Map<DexType, InterfaceReservationState> interfaceStateMap = new HashMap<>();
- InterfaceMethodNameMinifier(
- AppView<AppInfoWithLiveness> appView,
- Set<DexCallSite> desugaredCallSites,
- MethodNameMinifier.State minifierState) {
+ InterfaceMethodNameMinifier(AppView<AppInfoWithLiveness> appView, State minifierState) {
this.appView = appView;
- this.desugaredCallSites = desugaredCallSites;
this.minifierState = minifierState;
this.equivalence =
appView.options().getProguardConfiguration().isOverloadAggressively()
@@ -433,7 +428,7 @@
// desugared lambdas this is a conservative estimate, as we don't track if the generated
// lambda classes survive into the output. As multi-interface lambda expressions are rare
// this is not a big deal.
- Set<DexCallSite> liveCallSites = Sets.union(desugaredCallSites, appView.appInfo().callSites);
+ Set<DexCallSite> liveCallSites = appView.appInfo().callSites;
// If the input program contains a multi-interface lambda expression that implements
// interface methods with different protos, we need to make sure tha the implemented lambda
// methods are renamed to the same name.
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index 8d2e79f..d506d84 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -21,7 +21,6 @@
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
-import java.util.Set;
import java.util.function.Function;
/**
@@ -174,8 +173,7 @@
}
}
- MethodRenaming computeRenaming(
- Collection<DexClass> interfaces, Set<DexCallSite> desugaredCallSites, Timing timing) {
+ MethodRenaming computeRenaming(Collection<DexClass> interfaces, Timing timing) {
// Phase 1: Reserve all the names that need to be kept and allocate linked state in the
// library part.
timing.begin("Phase 1");
@@ -186,7 +184,7 @@
// states that may hold an implementation.
timing.begin("Phase 2");
InterfaceMethodNameMinifier interfaceMethodNameMinifier =
- new InterfaceMethodNameMinifier(appView, desugaredCallSites, minifierState);
+ new InterfaceMethodNameMinifier(appView, minifierState);
timing.end();
timing.begin("Phase 3");
interfaceMethodNameMinifier.assignNamesToInterfaceMethods(timing, interfaces);
@@ -244,7 +242,7 @@
private void reserveNamesInClasses(
DexType type, DexType libraryFrontier, MethodReservationState<?> parentReservationState) {
- assert appView.isInterface(type).isFalse();
+ assert !appView.isInterface(type).isTrue();
MethodReservationState<?> reservationState =
allocateReservationStateAndReserve(type, libraryFrontier, parentReservationState);
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index eb3a614..e768589 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -8,7 +8,6 @@
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;
@@ -39,18 +38,16 @@
public class Minifier {
private final AppView<AppInfoWithLiveness> appView;
- private final Set<DexCallSite> desugaredCallSites;
- public Minifier(AppView<AppInfoWithLiveness> appView, Set<DexCallSite> desugaredCallSites) {
+ public Minifier(AppView<AppInfoWithLiveness> appView) {
this.appView = appView;
- this.desugaredCallSites = desugaredCallSites;
}
public NamingLens run(ExecutorService executorService, Timing timing) throws ExecutionException {
assert appView.options().isMinifying();
timing.begin("ComputeInterfaces");
Set<DexClass> interfaces = new TreeSet<>((a, b) -> a.type.slowCompareTo(b.type));
- interfaces.addAll(appView.appInfo().computeReachableInterfaces(desugaredCallSites));
+ interfaces.addAll(appView.appInfo().computeReachableInterfaces());
timing.end();
timing.begin("MinifyClasses");
ClassNameMinifier classNameMinifier =
@@ -70,8 +67,7 @@
MemberNamingStrategy minifyMembers = new MinifierMemberNamingStrategy(appView);
timing.begin("MinifyMethods");
MethodRenaming methodRenaming =
- new MethodNameMinifier(appView, minifyMembers)
- .computeRenaming(interfaces, desugaredCallSites, timing);
+ new MethodNameMinifier(appView, minifyMembers).computeRenaming(interfaces, timing);
timing.end();
assert new MinifiedRenaming(appView, classRenaming, methodRenaming, FieldRenaming.empty())
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index 4fd6274..814ef4c 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.naming;
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.DexDefinition;
import com.android.tools.r8.graph.DexEncodedField;
@@ -68,7 +67,6 @@
private final AppView<AppInfoWithLiveness> appView;
private final DexItemFactory factory;
private final SeedMapper seedMapper;
- private final Set<DexCallSite> desugaredCallSites;
private final BiMap<DexType, DexString> mappedNames = HashBiMap.create();
// To keep the order deterministic, we sort the classes by their type, which is a unique key.
private final Set<DexClass> mappedClasses = new TreeSet<>((a, b) -> a.type.slowCompareTo(b.type));
@@ -79,14 +77,10 @@
private final Map<DexMethod, DexString> additionalMethodNamings = Maps.newIdentityHashMap();
private final Map<DexField, DexString> additionalFieldNamings = Maps.newIdentityHashMap();
- public ProguardMapMinifier(
- AppView<AppInfoWithLiveness> appView,
- SeedMapper seedMapper,
- Set<DexCallSite> desugaredCallSites) {
+ public ProguardMapMinifier(AppView<AppInfoWithLiveness> appView, SeedMapper seedMapper) {
this.appView = appView;
this.factory = appView.dexItemFactory();
this.seedMapper = seedMapper;
- this.desugaredCallSites = desugaredCallSites;
}
public NamingLens run(ExecutorService executorService, Timing timing) throws ExecutionException {
@@ -145,8 +139,7 @@
new ApplyMappingMemberNamingStrategy(appView, memberNames);
timing.begin("MinifyMethods");
MethodRenaming methodRenaming =
- new MethodNameMinifier(appView, nameStrategy)
- .computeRenaming(interfaces, desugaredCallSites, timing);
+ new MethodNameMinifier(appView, nameStrategy).computeRenaming(interfaces, timing);
// Amend the method renamings with the default interface methods.
methodRenaming.renaming.putAll(defaultInterfaceMethodImplementationNames);
methodRenaming.renaming.putAll(additionalMethodNamings);
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index af3d41e..317cb3b 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -19,7 +19,6 @@
import com.android.tools.r8.optimize.PublicizerLense.PublicizedLenseBuilder;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Timing;
-import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.ExecutionException;
@@ -64,7 +63,7 @@
// Phase 2: Visit classes and promote class/member to public if possible.
timing.begin("Phase 2: promoteToPublic");
- for (DexClass iface : appView.appInfo().computeReachableInterfaces(Collections.emptySet())) {
+ for (DexClass iface : appView.appInfo().computeReachableInterfaces()) {
publicizeType(iface.type);
}
publicizeType(appView.dexItemFactory().objectType);
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
index 3b68d3d..8d3e956 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
@@ -458,6 +458,69 @@
}
}
+ static class CircularReferenceLine extends StackTraceLine {
+
+ private final String startWhitespace;
+ private final String exceptionClass;
+ private final String endBracketAndWhitespace;
+
+ private static final String CIRCULAR_REFERENCE = "[CIRCULAR REFERENCE:";
+
+ public CircularReferenceLine(
+ String startWhitespace, String exceptionClass, String endBracketAndWhitespace) {
+ this.startWhitespace = startWhitespace;
+ this.exceptionClass = exceptionClass;
+ this.endBracketAndWhitespace = endBracketAndWhitespace;
+ }
+
+ static StackTraceLine tryParse(String line) {
+ // Check that the line is indented with some amount of white space.
+ if (line.length() == 0 || !Character.isWhitespace(line.charAt(0))) {
+ return null;
+ }
+ // Find the first non-white space character and check that we have the sequence
+ // '[CIRCULAR REFERENCE:Exception]'.
+ int firstNonWhiteSpace = firstNonWhiteSpaceCharacterFromIndex(line, 0);
+ if (!line.startsWith(CIRCULAR_REFERENCE, firstNonWhiteSpace)) {
+ return null;
+ }
+ int exceptionStartIndex = firstNonWhiteSpace + CIRCULAR_REFERENCE.length();
+ int lastBracketPosition = firstCharFromIndex(line, exceptionStartIndex, ']');
+ if (lastBracketPosition == line.length()) {
+ return null;
+ }
+ int onlyWhitespaceFromLastBracket =
+ firstNonWhiteSpaceCharacterFromIndex(line, lastBracketPosition + 1);
+ if (onlyWhitespaceFromLastBracket != line.length()) {
+ return null;
+ }
+ return new CircularReferenceLine(
+ line.substring(0, firstNonWhiteSpace),
+ line.substring(exceptionStartIndex, lastBracketPosition),
+ line.substring(lastBracketPosition));
+ }
+
+ @Override
+ List<StackTraceLine> retrace(RetraceBase retraceBase, boolean verbose) {
+ List<StackTraceLine> exceptionLines = new ArrayList<>();
+ retraceBase
+ .retrace(Reference.classFromTypeName(exceptionClass))
+ .forEach(
+ element ->
+ exceptionLines.add(
+ new CircularReferenceLine(
+ startWhitespace,
+ element.getClassReference().getTypeName(),
+ endBracketAndWhitespace)));
+ return exceptionLines;
+ }
+
+ @Override
+ public String toString() {
+ return startWhitespace + CIRCULAR_REFERENCE + exceptionClass + endBracketAndWhitespace;
+ }
+ }
+
static class UnknownLine extends StackTraceLine {
private final String line;
@@ -490,6 +553,10 @@
if (parsedLine != null) {
return parsedLine;
}
+ parsedLine = CircularReferenceLine.tryParse(line);
+ if (parsedLine != null) {
+ return parsedLine;
+ }
parsedLine = MoreLine.tryParse(line);
if (parsedLine == null) {
diagnosticsHandler.warning(
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index ac4f95f..89cf63c 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -179,7 +179,7 @@
final Set<DexType> instantiatedLambdas;
// TODO(zerny): Clean up the constructors so we have just one.
- private AppInfoWithLiveness(
+ AppInfoWithLiveness(
DexApplication application,
Set<DexType> liveTypes,
Set<DexType> instantiatedAnnotationTypes,
@@ -579,19 +579,13 @@
return clazz == null || !clazz.isProgramClass();
}
- public Collection<DexClass> computeReachableInterfaces(Set<DexCallSite> desugaredCallSites) {
+ public Collection<DexClass> computeReachableInterfaces() {
Set<DexClass> interfaces = Sets.newIdentityHashSet();
Set<DexType> seen = Sets.newIdentityHashSet();
Deque<DexType> worklist = new ArrayDeque<>();
for (DexProgramClass clazz : classes()) {
worklist.add(clazz.type);
}
- // TODO(b/129458850): Remove this once desugared classes are made part of the program classes.
- for (DexCallSite callSite : desugaredCallSites) {
- for (DexEncodedMethod method : lookupLambdaImplementedMethods(callSite)) {
- worklist.add(method.method.holder);
- }
- }
for (DexCallSite callSite : callSites) {
for (DexEncodedMethod method : lookupLambdaImplementedMethods(callSite)) {
worklist.add(method.method.holder);
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 4a8cdf3..b90d37e 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -10,14 +10,26 @@
import static com.android.tools.r8.shaking.EnqueuerUtils.toImmutableSortedMap;
import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfStore;
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
import com.android.tools.r8.graph.AccessControl;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.Descriptor;
import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexApplication.Builder;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinition;
@@ -54,8 +66,11 @@
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
+import com.android.tools.r8.ir.desugar.LambdaClass;
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
+import com.android.tools.r8.ir.desugar.LambdaRewriter;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.DelayedRootSetActionItem.InterfaceMethodSyntheticBridgeAction;
@@ -67,6 +82,7 @@
import com.android.tools.r8.utils.Action;
import com.android.tools.r8.utils.DequeUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.DesugarState;
import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.StringDiagnostic;
@@ -98,6 +114,7 @@
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
+import org.objectweb.asm.Opcodes;
/**
* Approximates the runtime dependencies for the given set of roots.
@@ -247,7 +264,9 @@
* Set of interface types for which there may be instantiations, such as lambda expressions or
* explicit keep rules.
*/
- private final SetWithReason<DexProgramClass> instantiatedInterfaceTypes;
+ private final Set<DexProgramClass> instantiatedInterfaceTypes;
+ /** Subset of the above that are marked instantiated by usages that are not desugared lambdas. */
+ private final SetWithReason<DexProgramClass> unknownInstantiatedInterfaceTypes;
/** A queue of items that need processing. Different items trigger different actions. */
private final EnqueuerWorklist workList;
@@ -297,6 +316,12 @@
private final GraphReporter graphReporter;
+ private final LambdaRewriter lambdaRewriter;
+ private final Map<DexType, LambdaClass> lambdaClasses = new IdentityHashMap<>();
+ private final Map<DexEncodedMethod, Map<DexCallSite, LambdaClass>> lambdaCallSites =
+ new IdentityHashMap<>();
+ private final Set<DexProgramClass> classesWithSerializableLambdas = Sets.newIdentityHashSet();
+
Enqueuer(
AppView<? extends AppInfoWithSubtyping> appView,
GraphConsumer keptGraphConsumer,
@@ -328,7 +353,9 @@
failedResolutionTargets = SetUtils.newIdentityHashSet(2);
liveMethods = new LiveMethodsSet(graphReporter::registerMethod);
liveFields = new SetWithReason<>(graphReporter::registerField);
- instantiatedInterfaceTypes = new SetWithReason<>(graphReporter::registerInterface);
+ unknownInstantiatedInterfaceTypes = new SetWithReason<>(graphReporter::registerInterface);
+ instantiatedInterfaceTypes = Sets.newIdentityHashSet();
+ lambdaRewriter = options.desugarState == DesugarState.ON ? new LambdaRewriter(appView) : null;
}
public Mode getMode() {
@@ -483,7 +510,8 @@
private void markInterfaceAsInstantiated(DexProgramClass clazz, KeepReasonWitness witness) {
assert clazz.isInterface() && !clazz.accessFlags.isAnnotation();
- if (!instantiatedInterfaceTypes.add(clazz, witness)) {
+ unknownInstantiatedInterfaceTypes.add(clazz, witness);
+ if (!instantiatedInterfaceTypes.add(clazz)) {
return;
}
populateInstantiatedTypesCache(clazz);
@@ -588,24 +616,6 @@
}
void traceCallSite(DexCallSite callSite, ProgramMethod context) {
- callSites.add(callSite);
-
- List<DexType> directInterfaces = LambdaDescriptor.getInterfaces(callSite, appInfo);
- if (directInterfaces != null) {
- for (DexType lambdaInstantiatedInterface : directInterfaces) {
- markLambdaInstantiated(lambdaInstantiatedInterface, context.method);
- }
- } else {
- if (!appInfo.isStringConcat(callSite.bootstrapMethod)) {
- if (options.reporter != null) {
- Diagnostic message =
- new StringDiagnostic(
- "Unknown bootstrap method " + callSite.bootstrapMethod, context.holder.origin);
- options.reporter.warning(message);
- }
- }
- }
-
DexProgramClass bootstrapClass =
getProgramClassOrNull(callSite.bootstrapMethod.asMethod().holder);
if (bootstrapClass != null) {
@@ -614,9 +624,41 @@
LambdaDescriptor descriptor = LambdaDescriptor.tryInfer(callSite, appInfo, context.holder);
if (descriptor == null) {
+ if (!appInfo.isStringConcat(callSite.bootstrapMethod)) {
+ if (options.reporter != null) {
+ Diagnostic message =
+ new StringDiagnostic(
+ "Unknown bootstrap method " + callSite.bootstrapMethod, context.holder.origin);
+ options.reporter.warning(message);
+ }
+ }
+
+ callSites.add(callSite);
return;
}
+ for (DexType lambdaInstantiatedInterface : descriptor.interfaces) {
+ markLambdaInstantiated(lambdaInstantiatedInterface, context.method);
+ }
+
+ if (lambdaRewriter != null) {
+ assert context.method.getCode().isCfCode() : "Unexpected input type with lambdas";
+ CfCode code = context.method.getCode().asCfCode();
+ if (code != null) {
+ LambdaClass lambdaClass =
+ lambdaRewriter.getOrCreateLambdaClass(descriptor, context.method.method.holder);
+ lambdaClasses.put(lambdaClass.type, lambdaClass);
+ lambdaCallSites
+ .computeIfAbsent(context.method, k -> new IdentityHashMap<>())
+ .put(callSite, lambdaClass);
+ if (lambdaClass.descriptor.interfaces.contains(appView.dexItemFactory().serializableType)) {
+ classesWithSerializableLambdas.add(context.holder);
+ }
+ }
+ } else {
+ callSites.add(callSite);
+ }
+
// For call sites representing a lambda, we link the targeted method
// or field as if it were referenced from the current method.
@@ -657,10 +699,6 @@
// We make an assumption that such classes are inherited directly from java.lang.Object
// and implement all lambda interfaces.
- if (directInterfaces == null) {
- return;
- }
-
// The set now contains all virtual methods on the type and its supertype that are reachable.
// In a second step, we now look at interfaces. We have to do this in this order due to JVM
// semantics for default methods. A default method is only reachable if it is not overridden
@@ -670,7 +708,7 @@
// the shadowing of other interface chains into account.
// See https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3
ScopedDexMethodSet seen = new ScopedDexMethodSet();
- for (DexType iface : directInterfaces) {
+ for (DexType iface : descriptor.interfaces) {
DexProgramClass ifaceClazz = getProgramClassOrNull(iface);
if (ifaceClazz != null) {
transitionDefaultMethodsForInstantiatedClass(iface, seen);
@@ -1856,10 +1894,14 @@
options.reporter.warning(message);
return;
}
- if (clazz.isProgramClass()) {
- KeepReason reason = KeepReason.instantiatedIn(method);
- if (instantiatedInterfaceTypes.add(clazz.asProgramClass(), reason)) {
- populateInstantiatedTypesCache(clazz.asProgramClass());
+ DexProgramClass programClass = clazz.asProgramClass();
+ if (programClass != null) {
+ // When not desugaring, we need to mark the instantiation point as unknown for now.
+ if (lambdaRewriter == null) {
+ unknownInstantiatedInterfaceTypes.add(programClass, KeepReason.instantiatedIn(method));
+ }
+ if (instantiatedInterfaceTypes.add(programClass)) {
+ populateInstantiatedTypesCache(programClass);
}
}
}
@@ -2270,9 +2312,11 @@
liveMethods.add(bridge.holder, bridge.method, graphReporter.fakeReportShouldNotBeUsed());
}
+ DexApplication app = postProcessLambdaDesugaring(appInfo);
+
AppInfoWithLiveness appInfoWithLiveness =
new AppInfoWithLiveness(
- appInfo,
+ app,
SetUtils.mapIdentityHashSet(liveTypes.getItems(), DexProgramClass::getType),
SetUtils.mapIdentityHashSet(
liveAnnotations.getItems(), DexAnnotation::getAnnotationType),
@@ -2313,12 +2357,114 @@
Collections.emptyMap(),
Collections.emptyMap(),
SetUtils.mapIdentityHashSet(
- instantiatedInterfaceTypes.getItems(), DexProgramClass::getType),
+ unknownInstantiatedInterfaceTypes.getItems(), DexProgramClass::getType),
constClassReferences);
appInfo.markObsolete();
return appInfoWithLiveness;
}
+ private DexApplication postProcessLambdaDesugaring(AppInfoWithSubtyping appInfo) {
+ if (lambdaRewriter == null || lambdaClasses.isEmpty()) {
+ return appInfo.app();
+ }
+ Builder<?> appBuilder = appInfo.app().builder();
+ for (LambdaClass lambdaClass : lambdaClasses.values()) {
+ // Add all desugared classes to the application, main-dex list, and mark them instantiated.
+ DexProgramClass programClass = lambdaClass.getOrCreateLambdaClass();
+ appBuilder.addProgramClass(programClass);
+ if (lambdaClass.addToMainDexList.get()) {
+ appBuilder.addToMainDexList(Collections.singletonList(programClass.type));
+ }
+ liveTypes.add(programClass, graphReporter.fakeReportShouldNotBeUsed());
+ instantiatedTypes.add(programClass, graphReporter.fakeReportShouldNotBeUsed());
+
+ // Register all of the field writes in the lambda constructors.
+ // This is needed to ensure that the initializers can be optimized.
+ Map<DexEncodedField, Set<DexEncodedMethod>> writes =
+ lambdaRewriter.getWritesWithContexts(programClass);
+ writes.forEach(
+ (field, contexts) -> {
+ for (DexEncodedMethod context : contexts) {
+ registerFieldWrite(field.field, context);
+ }
+ });
+
+ // Mark all methods on the desugared lambda classes as live.
+ for (DexEncodedMethod method : programClass.methods()) {
+ targetedMethods.add(method, graphReporter.fakeReportShouldNotBeUsed());
+ liveMethods.add(programClass, method, graphReporter.fakeReportShouldNotBeUsed());
+ }
+
+ // Ensure accessors if needed and mark them live too.
+ DexEncodedMethod accessor = lambdaClass.target.ensureAccessibilityIfNeeded(false);
+ if (accessor != null) {
+ DexProgramClass clazz = getProgramClassOrNull(accessor.method.holder);
+ targetedMethods.add(accessor, graphReporter.fakeReportShouldNotBeUsed());
+ liveMethods.add(clazz, accessor, graphReporter.fakeReportShouldNotBeUsed());
+ }
+ }
+
+ // Rewrite all of the invoke-dynamic instructions to lambda class instantiations.
+ lambdaCallSites.forEach(this::rewriteLambdaCallSites);
+
+ // Remove all '$deserializeLambda$' methods which are not supported by desugaring.
+ for (DexProgramClass clazz : classesWithSerializableLambdas) {
+ clazz.removeDirectMethod(appView.dexItemFactory().deserializeLambdaMethod);
+ }
+
+ return appBuilder.build();
+ }
+
+ private void rewriteLambdaCallSites(
+ DexEncodedMethod method, Map<DexCallSite, LambdaClass> callSites) {
+ assert !callSites.isEmpty();
+ CfCode code = method.getCode().asCfCode();
+ List<CfInstruction> instructions = code.instructions;
+ int replaced = 0;
+ int maxTemp = 0;
+ for (int i = 0; i < instructions.size(); i++) {
+ CfInstruction instruction = instructions.get(i);
+ if (instruction instanceof CfInvokeDynamic) {
+ LambdaClass lambdaClass = callSites.get(((CfInvokeDynamic) instruction).getCallSite());
+ if (lambdaClass == null) {
+ continue;
+ }
+ if (lambdaClass.isStateless()) {
+ CfFieldInstruction getStaticLambdaInstance =
+ new CfFieldInstruction(
+ Opcodes.GETSTATIC, lambdaClass.lambdaField, lambdaClass.lambdaField);
+ instructions.set(i, getStaticLambdaInstance);
+ } else {
+ List<CfInstruction> replacement = new ArrayList<>();
+ int arguments = lambdaClass.descriptor.captures.size();
+ int temp = code.getMaxLocals();
+ for (int j = arguments - 1; j >= 0; j--) {
+ ValueType type = ValueType.fromDexType(lambdaClass.descriptor.captures.values[j]);
+ replacement.add(new CfStore(type, temp));
+ temp += type.requiredRegisters();
+ }
+ maxTemp = Math.max(temp, maxTemp);
+ replacement.add(new CfNew(lambdaClass.type));
+ replacement.add(new CfStackInstruction(Opcode.Dup));
+ for (int j = 0; j < arguments; j++) {
+ ValueType type = ValueType.fromDexType(lambdaClass.descriptor.captures.values[j]);
+ temp -= type.requiredRegisters();
+ replacement.add(new CfLoad(type, temp));
+ }
+ replacement.add(new CfInvoke(Opcodes.INVOKESPECIAL, lambdaClass.constructor, false));
+ instructions.remove(i);
+ instructions.addAll(i, replacement);
+ }
+ ++replaced;
+ }
+ }
+ if (maxTemp > 0) {
+ assert maxTemp > code.getMaxLocals();
+ code.setMaxLocals(maxTemp);
+ }
+ assert replaced == callSites.size();
+ }
+
private static <T extends PresortedComparable<T>> SortedSet<T> toSortedDescriptorSet(
Set<? extends KeyedDexItem<T>> set) {
ImmutableSortedSet.Builder<T> builder =
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index bfad990..3c91bc8 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -70,6 +70,7 @@
private boolean keepRuleSynthesisForRecompilation = false;
private boolean configurationDebugging = false;
private boolean dontUseMixedCaseClassnames = false;
+ private int maxRemovedAndroidLogLevel = 1;
private Builder(DexItemFactory dexItemFactory, Reporter reporter) {
this.dexItemFactory = dexItemFactory;
@@ -288,6 +289,14 @@
this.dontUseMixedCaseClassnames = dontUseMixedCaseClassnames;
}
+ public int getMaxRemovedAndroidLogLevel() {
+ return maxRemovedAndroidLogLevel;
+ }
+
+ public void setMaxRemovedAndroidLogLevel(int maxRemovedAndroidLogLevel) {
+ this.maxRemovedAndroidLogLevel = maxRemovedAndroidLogLevel;
+ }
+
/**
* This synthesizes a set of keep rules that are necessary in order to be able to successfully
* recompile the generated dex files with the same keep rules.
@@ -346,7 +355,8 @@
adaptResourceFileContents.build(),
keepDirectories.build(),
configurationDebugging,
- dontUseMixedCaseClassnames);
+ dontUseMixedCaseClassnames,
+ maxRemovedAndroidLogLevel);
reporter.failIfPendingErrors();
@@ -415,6 +425,7 @@
private final ProguardPathFilter keepDirectories;
private final boolean configurationDebugging;
private final boolean dontUseMixedCaseClassnames;
+ private final int maxRemovedAndroidLogLevel;
private ProguardConfiguration(
String parsedConfiguration,
@@ -454,7 +465,8 @@
ProguardPathFilter adaptResourceFileContents,
ProguardPathFilter keepDirectories,
boolean configurationDebugging,
- boolean dontUseMixedCaseClassnames) {
+ boolean dontUseMixedCaseClassnames,
+ int maxRemovedAndroidLogLevel) {
this.parsedConfiguration = parsedConfiguration;
this.dexItemFactory = factory;
this.injars = ImmutableList.copyOf(injars);
@@ -493,6 +505,7 @@
this.keepDirectories = keepDirectories;
this.configurationDebugging = configurationDebugging;
this.dontUseMixedCaseClassnames = dontUseMixedCaseClassnames;
+ this.maxRemovedAndroidLogLevel = maxRemovedAndroidLogLevel;
}
/**
@@ -659,6 +672,10 @@
return dontUseMixedCaseClassnames;
}
+ public int getMaxRemovedAndroidLogLevel() {
+ return maxRemovedAndroidLogLevel;
+ }
+
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index a589f9a..cc05e23 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -58,7 +58,7 @@
"maximuminlinedcodelength");
private static final List<String> IGNORED_OPTIONAL_SINGLE_ARG_OPTIONS =
- ImmutableList.of("runtype", "laststageoutput", "maximumremovedandroidloglevel");
+ ImmutableList.of("runtype", "laststageoutput");
private static final List<String> IGNORED_FLAG_OPTIONS = ImmutableList.of(
"forceprocessing",
@@ -398,6 +398,14 @@
configurationBuilder.setConfigurationDebugging(true);
} else if (acceptString("dontusemixedcaseclassnames")) {
configurationBuilder.setDontUseMixedCaseClassnames(true);
+ } else if (acceptString("maximumremovedandroidloglevel")) {
+ skipWhitespace();
+ Integer maxRemovedAndroidLogLevel = acceptInteger();
+ if (maxRemovedAndroidLogLevel != null) {
+ configurationBuilder.setMaxRemovedAndroidLogLevel(maxRemovedAndroidLogLevel);
+ } else {
+ throw parseError("Expected integer", getPosition());
+ }
} else {
String unknownOption = acceptString();
String devMessage = "";
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
index 93f36a2..4be540a 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
@@ -74,8 +74,9 @@
return this;
}
- public void setArguments(List<ProguardTypeMatcher> arguments) {
+ public Builder setArguments(List<ProguardTypeMatcher> arguments) {
this.arguments = arguments;
+ return this;
}
public Builder setReturnValue(ProguardMemberRuleReturnValue value) {
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 62241d5..6ab0836 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -923,6 +923,12 @@
if (virtualMethod.accessFlags.isAbstract()) {
// Remove abstract/interface methods that are shadowed.
deferredRenamings.map(virtualMethod.method, shadowedBy.method);
+
+ // The override now corresponds to the method in the parent, so unset its synthetic flag
+ // if the method in the parent is not synthetic.
+ if (!virtualMethod.isSyntheticMethod() && shadowedBy.isSyntheticMethod()) {
+ shadowedBy.accessFlags.demoteFromSynthetic();
+ }
continue;
}
} else {
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index fd51973..70d6df0 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -178,6 +178,16 @@
}
/**
+ * Convert a descriptor to a classifier in Kotlin metadata
+ * @param descriptor like "Lorg/foo/bar/Baz$Nested;"
+ * @return className "org/foo/bar/Baz.Nested"
+ */
+ public static String descriptorToKotlinClassifier(String descriptor) {
+ return getBinaryNameFromDescriptor(descriptor)
+ .replace(INNER_CLASS_SEPARATOR, JAVA_PACKAGE_SEPARATOR);
+ }
+
+ /**
* Convert a type descriptor to a Java type name. Will also deobfuscate class names if a
* class mapper is provided.
*
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 d168a64..9eb8b6c 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -53,7 +53,9 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Comparator;
+import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -202,6 +204,7 @@
public boolean enableInlining =
!Version.isDevelopmentVersion()
|| System.getProperty("com.android.tools.r8.disableinlining") == null;
+ public boolean enableEnumUnboxing = false;
// TODO(b/141451716): Evaluate the effect of allowing inlining in the inlinee.
public boolean applyInliningToInlinee =
System.getProperty("com.android.tools.r8.applyInliningToInlinee") != null;
@@ -979,6 +982,8 @@
? NondeterministicIROrdering.getInstance()
: IdentityIROrdering.getInstance();
+ public Consumer<Deque<Collection<DexEncodedMethod>>> waveModifier = waves -> {};
+
/**
* If this flag is enabled, we will also compute the set of possible targets for invoke-
* interface and invoke-virtual instructions that target a library method, and add the
@@ -1006,6 +1011,7 @@
public boolean enableCheckCastAndInstanceOfRemoval = true;
public boolean enableDeadSwitchCaseElimination = true;
public boolean enableSwitchToIfRewriting = true;
+ public boolean enableEnumUnboxingDebugLogs = false;
public boolean forceRedundantConstNumberRemoval = false;
public boolean forceAssumeNoneInsertion = false;
public boolean invertConditionals = false;
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index df13ac6..e9eb31f 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -378,11 +378,23 @@
for (; j < mappedPositions.size(); j++) {
// Break if this position cannot be merged with lastPosition.
MappedPosition mp = mappedPositions.get(j);
+ // We allow for ranges being mapped to the same line but not to other ranges:
+ // 1:10:void foo():42:42 -> a
+ // is OK since retrace(a(:7)) = 42, however, the following is not OK:
+ // 1:10:void foo():42:43 -> a
+ // since retrace(a(:7)) = 49, which is not correct.
+ boolean isSingleLine = mp.originalLine == firstPosition.originalLine;
+ boolean differentDelta =
+ mp.originalLine - lastPosition.originalLine
+ != mp.obfuscatedLine - lastPosition.obfuscatedLine;
+ boolean isMappingRangeToSingleLine =
+ firstPosition.obfuscatedLine != lastPosition.obfuscatedLine
+ && firstPosition.originalLine == lastPosition.originalLine;
// Note that mp.caller and lastPosition.class must be deep-compared since multiple
// inlining passes lose the canonical property of the positions.
- if ((mp.method != lastPosition.method)
- || (mp.originalLine - lastPosition.originalLine
- != mp.obfuscatedLine - lastPosition.obfuscatedLine)
+ if (mp.method != lastPosition.method
+ || (!isSingleLine && differentDelta)
+ || (!isSingleLine && isMappingRangeToSingleLine)
|| !Objects.equals(mp.caller, lastPosition.caller)) {
break;
}
diff --git a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
index 02b5fbb..40232ca 100644
--- a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
@@ -4,29 +4,39 @@
package com.android.tools.r8.utils;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
public class ThreadUtils {
public static final int NOT_SPECIFIED = -1;
+ public static <T, R, E extends Exception> Collection<R> processItemsWithResults(
+ Iterable<T> items, ThrowingFunction<T, R, E> consumer, ExecutorService executorService)
+ throws ExecutionException {
+ List<Future<R>> futures = new ArrayList<>();
+ for (T item : items) {
+ futures.add(executorService.submit(() -> consumer.apply(item)));
+ }
+ return awaitFuturesWithResults(futures);
+ }
+
public static <T, E extends Exception> void processItems(
Iterable<T> items, ThrowingConsumer<T, E> consumer, ExecutorService executorService)
throws ExecutionException {
- List<Future<?>> futures = new ArrayList<>();
- for (T item : items) {
- futures.add(executorService.submit(
- () -> {
- consumer.accept(item);
- return null; // we want a Callable not a Runnable to be able to throw
- }));
- }
- awaitFutures(futures);
+ processItemsWithResults(
+ items,
+ arg -> {
+ consumer.accept(arg);
+ return null;
+ },
+ executorService);
}
public static void awaitFutures(Iterable<? extends Future<?>> futures)
@@ -52,6 +62,31 @@
}
}
+ public static <R> Collection<R> awaitFuturesWithResults(Collection<? extends Future<R>> futures)
+ throws ExecutionException {
+ List<R> results = new ArrayList<>(futures.size());
+ Iterator<? extends Future<R>> futureIterator = futures.iterator();
+ try {
+ while (futureIterator.hasNext()) {
+ results.add(futureIterator.next().get());
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Interrupted while waiting for future.", e);
+ } finally {
+ // In case we get interrupted or one of the threads throws an exception, still wait for all
+ // further work to make sure synchronization guarantees are met. Calling cancel unfortunately
+ // does not guarantee that the task at hand actually terminates before cancel returns.
+ while (futureIterator.hasNext()) {
+ try {
+ futureIterator.next().get();
+ } catch (Throwable t) {
+ // Ignore any new Exception.
+ }
+ }
+ }
+ return results;
+ }
+
static ExecutorService getExecutorServiceForProcessors(int processors) {
// This heuristic is based on measurements on a 32 core (hyper-threaded) machine.
int threads = processors <= 2 ? processors : (int) Math.ceil(Integer.min(processors, 16) / 2.0);
@@ -68,4 +103,11 @@
public static ExecutorService getExecutorService(InternalOptions options) {
return getExecutorService(options.numberOfThreads);
}
+
+ public static int getNumberOfThreads(ExecutorService service) {
+ if (service instanceof ForkJoinPool) {
+ return ((ForkJoinPool) service).getParallelism();
+ }
+ return -1;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/ThrowingFunction.java b/src/main/java/com/android/tools/r8/utils/ThrowingFunction.java
new file mode 100644
index 0000000..e65d42c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ThrowingFunction.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import java.util.function.Function;
+
+/**
+ * Similar to a {@link Function} but throws a single {@link Throwable}.
+ *
+ * @param <T> the type of the input
+ * @param <E> the type of the {@link Throwable}
+ */
+@FunctionalInterface
+public interface ThrowingFunction<T, R, E extends Throwable> {
+ R apply(T t) throws E;
+}
diff --git a/src/main/java/com/android/tools/r8/utils/Timing.java b/src/main/java/com/android/tools/r8/utils/Timing.java
index 7d68954..78c1b44 100644
--- a/src/main/java/com/android/tools/r8/utils/Timing.java
+++ b/src/main/java/com/android/tools/r8/utils/Timing.java
@@ -13,6 +13,10 @@
// Finally a report is printed by:
// t.report();
+import com.google.common.base.Strings;
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
@@ -20,21 +24,71 @@
public class Timing {
+ private static final int MINIMUM_REPORT_PERCENTAGE = 2;
+
+ private static final Timing EMPTY =
+ new Timing("<empty>", false) {
+ @Override
+ public TimingMerger beginMerger(String title, int numberOfThreads) {
+ return new TimingMerger(null, -1, this) {
+ @Override
+ public void add(Collection<Timing> timings) {
+ // Ignore.
+ }
+
+ @Override
+ public void end() {
+ // Ignore.
+ }
+ };
+ }
+
+ @Override
+ public void begin(String title) {
+ // Ignore.
+ }
+
+ @Override
+ public void end() {
+ // Ignore.
+ }
+
+ @Override
+ public void report() {
+ // Ignore.
+ }
+
+ @Override
+ public void scope(String title, TimingScope fn) {
+ // Ignore.
+ }
+ };
+
+ public static Timing empty() {
+ return Timing.EMPTY;
+ }
+
+ public static Timing create(String title, InternalOptions options) {
+ // We also create a timer when running assertions to validate wellformedness of the node stack.
+ return options.printTimes || InternalOptions.assertionsEnabled()
+ ? new Timing(title, options.printMemory)
+ : Timing.empty();
+ }
+
+ private final Node top;
private final Stack<Node> stack;
private final boolean trackMemory;
- public Timing() {
- this("<no title>");
- }
-
+ @Deprecated
public Timing(String title) {
this(title, false);
}
- public Timing(String title, boolean trackMemory) {
+ private Timing(String title, boolean trackMemory) {
this.trackMemory = trackMemory;
stack = new Stack<>();
- stack.push(new Node("Recorded timings for " + title));
+ top = new Node(title, trackMemory);
+ stack.push(top);
}
private static class MemInfo {
@@ -53,8 +107,9 @@
}
}
- class Node {
+ static class Node {
final String title;
+ final boolean trackMemory;
final Map<String, Node> children = new LinkedHashMap<>();
long duration = 0;
@@ -62,8 +117,9 @@
Map<String, MemInfo> startMemory;
Map<String, MemInfo> endMemory;
- Node(String title) {
+ Node(String title, boolean trackMemory) {
this.title = title;
+ this.trackMemory = trackMemory;
if (trackMemory) {
startMemory = computeMemoryInformation();
}
@@ -98,25 +154,50 @@
public String toString(Node top) {
if (this == top) return toString();
- return toString() + " (" + prettyPercentage(duration(), top.duration()) + ")";
+ return "(" + prettyPercentage(duration(), top.duration()) + ") " + toString();
}
public void report(int depth, Node top) {
assert duration() >= 0;
- if (depth > 0) {
- for (int i = 0; i < depth; i++) {
- System.out.print(" ");
- }
- System.out.print("- ");
+ if (percentage(duration(), top.duration()) < MINIMUM_REPORT_PERCENTAGE) {
+ return;
}
+ printPrefix(depth);
System.out.println(toString(top));
if (trackMemory) {
printMemory(depth);
}
- children.values().forEach(p -> p.report(depth + 1, top));
+ if (children.isEmpty()) {
+ return;
+ }
+ Collection<Node> childNodes = children.values();
+ long childTime = 0;
+ for (Node childNode : childNodes) {
+ childTime += childNode.duration();
+ }
+ if (childTime < duration()) {
+ long unaccounted = duration() - childTime;
+ if (percentage(unaccounted, top.duration()) >= MINIMUM_REPORT_PERCENTAGE) {
+ printPrefix(depth + 1);
+ System.out.println(
+ "("
+ + prettyPercentage(unaccounted, top.duration())
+ + ") Unaccounted: "
+ + prettyTime(unaccounted));
+ }
+ }
+ childNodes.forEach(p -> p.report(depth + 1, top));
+
}
- private void printMemory(int depth) {
+ void printPrefix(int depth) {
+ if (depth > 0) {
+ System.out.print(Strings.repeat(" ", depth));
+ System.out.print("- ");
+ }
+ }
+
+ void printMemory(int depth) {
for (Entry<String, MemInfo> start : startMemory.entrySet()) {
if (start.getKey().equals("Memory")) {
for (int i = 0; i <= depth; i++) {
@@ -137,8 +218,111 @@
}
}
+ public static class TimingMerger {
+ final Node parent;
+ final Node merged;
+
+ private int taskCount = 0;
+ private Node slowest = new Node("<zero>", false);
+
+ private TimingMerger(String title, int numberOfThreads, Timing timing) {
+ parent = timing.stack.peek();
+ merged =
+ new Node(title, timing.trackMemory) {
+ @Override
+ public void report(int depth, Node top) {
+ assert duration() >= 0;
+ printPrefix(depth);
+ System.out.print(toString());
+ if (numberOfThreads <= 0) {
+ System.out.println(" (unknown thread count)");
+ } else {
+ long walltime = parent.duration();
+ long perThreadTime = duration() / numberOfThreads;
+ System.out.println(
+ ", tasks: "
+ + taskCount
+ + ", threads: "
+ + numberOfThreads
+ + ", utilization: "
+ + prettyPercentage(perThreadTime, walltime));
+ }
+ if (trackMemory) {
+ printMemory(depth);
+ }
+ // Report children with this merge node as "top" so times are relative to the total
+ // merge.
+ children.forEach((title, node) -> node.report(depth + 1, this));
+ // Print the slowest entry if one was found.
+ if (slowest.duration > 0) {
+ printPrefix(depth);
+ System.out.println("SLOWEST " + slowest.toString(this));
+ slowest.children.forEach((title, node) -> node.report(depth + 1, this));
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "MERGE " + super.toString();
+ }
+ };
+ }
+
+ private static class Item {
+ final Node mergeTarget;
+ final Node mergeSource;
+
+ public Item(Node mergeTarget, Node mergeSource) {
+ this.mergeTarget = mergeTarget;
+ this.mergeSource = mergeSource;
+ }
+ }
+
+ public void add(Collection<Timing> timings) {
+ final boolean trackMemory = merged.trackMemory;
+ Deque<Item> worklist = new ArrayDeque<>();
+ for (Timing timing : timings) {
+ if (timing == empty()) {
+ continue;
+ }
+ assert timing.stack.isEmpty() : "Expected sub-timing to have completed prior to merge";
+ ++taskCount;
+ merged.duration += timing.top.duration;
+ if (timing.top.duration > slowest.duration) {
+ slowest = timing.top;
+ }
+ worklist.addLast(new Item(merged, timing.top));
+ }
+ while (!worklist.isEmpty()) {
+ Item item = worklist.pollFirst();
+ item.mergeSource.children.forEach(
+ (title, child) -> {
+ Node mergeTarget =
+ item.mergeTarget.children.computeIfAbsent(title, t -> new Node(t, trackMemory));
+ mergeTarget.duration += child.duration;
+ if (!child.children.isEmpty()) {
+ worklist.addLast(new Item(mergeTarget, child));
+ }
+ });
+ }
+ }
+
+ public void end() {
+ assert !parent.children.containsKey(merged.title);
+ parent.children.put(merged.title, merged);
+ }
+ }
+
+ public TimingMerger beginMerger(String title, int numberOfThreads) {
+ return new TimingMerger(title, numberOfThreads, this);
+ }
+
+ private static long percentage(long part, long total) {
+ return part * 100 / total;
+ }
+
private static String prettyPercentage(long part, long total) {
- return (part * 100 / total) + "%";
+ return percentage(part, total) + "%";
}
private static String prettyTime(long value) {
@@ -176,7 +360,7 @@
child = parent.children.get(title);
child.restart();
} else {
- child = new Node(title);
+ child = new Node(title, trackMemory);
parent.children.put(title, child);
}
stack.push(child);
@@ -188,9 +372,11 @@
}
public void report() {
+ assert stack.size() == 1;
Node top = stack.peek();
+ assert top == this.top;
top.end();
- System.out.println();
+ System.out.println("Recorded timings:");
top.report(0, top);
}
@@ -207,7 +393,7 @@
void apply();
}
- private Map<String, MemInfo> computeMemoryInformation() {
+ private static Map<String, MemInfo> computeMemoryInformation() {
System.gc();
Map<String, MemInfo> info = new LinkedHashMap<>();
info.put(
diff --git a/src/test/examplesAndroidO/classmerging/LambdaRewritingTest.java b/src/test/examplesAndroidO/classmerging/LambdaRewritingTest.java
index 8357eee..621ae07 100644
--- a/src/test/examplesAndroidO/classmerging/LambdaRewritingTest.java
+++ b/src/test/examplesAndroidO/classmerging/LambdaRewritingTest.java
@@ -11,13 +11,19 @@
// Leads to an invoke-custom instruction that mentions the type of `obj` since it is captured.
invoke(() -> obj.foo());
+
+ FunctionImpl functionImpl = new FunctionImpl();
+ if (System.currentTimeMillis() < 0) {
+ System.out.println(functionImpl);
+ }
}
+ @NeverInline
private static void invoke(Function f) {
f.accept();
}
- // Must not be merged into FunctionImpl as it is instantiated by a lambda.
+ // Cannot be merged as it has two subtypes: FunctionImpl and a lambda.
public interface Function {
void accept();
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index d740e54..d6732bd 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import java.nio.file.Path;
+import java.util.Arrays;
import java.util.Collection;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -35,11 +36,17 @@
return builder;
}
- public D8TestBuilder addProgramResourceProvider(ProgramResourceProvider provider) {
- builder.addProgramResourceProvider(provider);
+ public D8TestBuilder addProgramResourceProviders(Collection<ProgramResourceProvider> providers) {
+ for (ProgramResourceProvider provider : providers) {
+ builder.addProgramResourceProvider(provider);
+ }
return self();
}
+ public D8TestBuilder addProgramResourceProviders(ProgramResourceProvider... providers) {
+ return addProgramResourceProviders(Arrays.asList(providers));
+ }
+
@Override
public D8TestBuilder addClasspathClasses(Collection<Class<?>> classes) {
builder.addClasspathResourceProvider(ClassFileResourceProviderFromClasses(classes));
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 92d02ee..39ac985 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -102,7 +102,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 137, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 119, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -110,7 +110,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = true)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 8, "lambdadesugaring"))
.run();
}
@@ -142,7 +142,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 137, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 119, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -150,7 +150,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = true)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 8, "lambdadesugaring"))
.run();
}
@@ -164,7 +164,7 @@
.withBuilderTransformation(ToolHelper::allowTestProguardOptions)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 31, "lambdadesugaringnplus"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 33, "lambdadesugaringnplus"))
.run();
test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -174,7 +174,7 @@
.withBuilderTransformation(ToolHelper::allowTestProguardOptions)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaringnplus"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 2, "lambdadesugaringnplus"))
.run();
}
@@ -188,7 +188,7 @@
.withBuilderTransformation(ToolHelper::allowTestProguardOptions)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 31, "lambdadesugaringnplus"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 33, "lambdadesugaringnplus"))
.run();
test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -198,7 +198,7 @@
.withBuilderTransformation(ToolHelper::allowTestProguardOptions)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 5, "lambdadesugaringnplus"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 2, "lambdadesugaringnplus"))
.run();
}
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 59ae602..1dca05c 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -119,11 +119,17 @@
return builder;
}
- public T addProgramResourceProvider(ProgramResourceProvider provider) {
- builder.addProgramResourceProvider(provider);
+ public T addProgramResourceProviders(Collection<ProgramResourceProvider> providers) {
+ for (ProgramResourceProvider provider : providers) {
+ builder.addProgramResourceProvider(provider);
+ }
return self();
}
+ public T addProgramResourceProviders(ProgramResourceProvider... providers) {
+ return addProgramResourceProviders(Arrays.asList(providers));
+ }
+
@Override
public T addClasspathClasses(Collection<Class<?>> classes) {
builder.addClasspathResourceProvider(ClassFileResourceProviderFromClasses(classes));
diff --git a/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java b/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
index 80fe207..9cb139c 100644
--- a/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
+++ b/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
@@ -38,7 +38,7 @@
.addProgramFiles(SMALI_DIR.resolve(name).resolve(name + ".dex"))
.build();
ExecutorService executorService = Executors.newSingleThreadExecutor();
- Timing timing = new Timing("R8UnreachableCodeTest");
+ Timing timing = Timing.empty();
InternalOptions options = new InternalOptions();
options.programConsumer = DexIndexedConsumer.emptyConsumer();
DirectMappedDexApplication application =
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 203a07b..c1a612f 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -48,6 +48,8 @@
import com.android.tools.r8.shaking.ProguardKeepRule;
import com.android.tools.r8.shaking.ProguardKeepRule.Builder;
import com.android.tools.r8.shaking.ProguardKeepRuleType;
+import com.android.tools.r8.shaking.ProguardMemberRule;
+import com.android.tools.r8.shaking.ProguardMemberType;
import com.android.tools.r8.shaking.ProguardTypeMatcher;
import com.android.tools.r8.shaking.RootSetBuilder;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
@@ -423,6 +425,10 @@
return buildClasses(programClasses, libraryClasses).build();
}
+ protected static AndroidApp.Builder buildClasses(Class<?>... programClasses) throws IOException {
+ return buildClasses(Arrays.asList(programClasses), Collections.emptyList());
+ }
+
protected static AndroidApp.Builder buildClasses(Collection<Class<?>> programClasses)
throws IOException {
return buildClasses(programClasses, Collections.emptyList());
@@ -559,9 +565,7 @@
protected static AppInfo computeAppInfo(AndroidApp application) {
try {
DexApplication dexApplication =
- new ApplicationReader(
- application, new InternalOptions(), new Timing("TestBase.computeAppInfo"))
- .read();
+ new ApplicationReader(application, new InternalOptions(), Timing.empty()).read();
return new AppInfo(dexApplication);
} catch (IOException | ExecutionException e) {
throw new RuntimeException(e);
@@ -572,7 +576,7 @@
AndroidApp application) {
try {
DexApplication dexApplication =
- new ApplicationReader(application, new InternalOptions(), new Timing()).read();
+ new ApplicationReader(application, new InternalOptions(), Timing.empty()).read();
return new AppInfoWithClassHierarchy(dexApplication);
} catch (IOException | ExecutionException e) {
throw new RuntimeException(e);
@@ -581,7 +585,7 @@
protected static AppView<AppInfoWithSubtyping> computeAppViewWithSubtyping(AndroidApp app)
throws Exception {
- Timing timing = new Timing();
+ Timing timing = Timing.empty();
InternalOptions options = new InternalOptions();
DexApplication application = new ApplicationReader(app, options, timing).read().toDirect();
AppView<AppInfoWithSubtyping> appView =
@@ -598,7 +602,9 @@
DexApplication application = appView.appInfo().app();
RootSet rootSet =
new RootSetBuilder(
- appView, application, buildKeepRuleForClass(mainClass, application.dexItemFactory))
+ appView,
+ application,
+ buildKeepRuleForClassAndMethods(mainClass, application.dexItemFactory))
.run(executor);
AppInfoWithLiveness appInfoWithLiveness =
EnqueuerFactory.createForInitialTreeShaking(appView)
@@ -638,6 +644,13 @@
method.getMethodName());
}
+ protected static DexMethod buildNullaryVoidMethod(
+ Class clazz, String name, DexItemFactory factory) {
+ return buildMethod(
+ Reference.method(Reference.classFromClass(clazz), name, Collections.emptyList(), null),
+ factory);
+ }
+
protected static DexProto buildProto(
TypeReference returnType, List<TypeReference> formalTypes, DexItemFactory factory) {
return factory.createProto(
@@ -657,6 +670,21 @@
return Collections.singletonList(keepRuleBuilder.build());
}
+ private static List<ProguardConfigurationRule> buildKeepRuleForClassAndMethods(
+ Class clazz, DexItemFactory factory) {
+ Builder keepRuleBuilder = ProguardKeepRule.builder();
+ keepRuleBuilder.setSource("buildKeepRuleForClass " + clazz.getTypeName());
+ keepRuleBuilder.setType(ProguardKeepRuleType.KEEP);
+ keepRuleBuilder.setClassNames(
+ ProguardClassNameList.singletonList(
+ ProguardTypeMatcher.create(
+ factory.createType(DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName())))));
+ keepRuleBuilder.setMemberRules(
+ Lists.newArrayList(
+ ProguardMemberRule.builder().setRuleType(ProguardMemberType.ALL_METHODS).build()));
+ return Collections.singletonList(keepRuleBuilder.build());
+ }
+
/** Returns a list containing all the data resources in the given app. */
public static List<DataEntryResource> getDataResources(AndroidApp app) throws ResourceException {
List<DataEntryResource> dataResources = new ArrayList<>();
diff --git a/src/test/java/com/android/tools/r8/TestParameters.java b/src/test/java/com/android/tools/r8/TestParameters.java
index fc9c8db..d494ea2 100644
--- a/src/test/java/com/android/tools/r8/TestParameters.java
+++ b/src/test/java/com/android/tools/r8/TestParameters.java
@@ -49,6 +49,10 @@
return runtime;
}
+ public boolean useRuntimeAsNoneRuntime() {
+ return isNoneRuntime() || (runtime != null && runtime.equals(TestRuntime.getCheckedInJdk9()));
+ }
+
// Helper function to get the "backend" for a given runtime target.
public Backend getBackend() {
return runtime.getBackend();
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 19c6edd..2defd19 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -150,6 +150,13 @@
return addKeepMainRule(mainClass.getTypeName());
}
+ public T addKeepMainRules(Class<?>[] mainClasses) {
+ for (Class<?> mainClass : mainClasses) {
+ this.addKeepMainRule(mainClass);
+ }
+ return self();
+ }
+
public T addKeepMainRule(String mainClass) {
return addKeepRules(
"-keep class " + mainClass + " { public static void main(java.lang.String[]); }");
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 87b2806..effe9d9 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -1066,9 +1066,7 @@
.addProgramFiles(ListUtils.map(fileNames, Paths::get))
.addLibraryFiles(androidJar)
.build();
- return new ApplicationReader(
- input, new InternalOptions(), new Timing("ToolHelper buildApplication"))
- .read().toDirect();
+ return new ApplicationReader(input, new InternalOptions(), Timing.empty()).read().toDirect();
}
public static ProguardConfiguration loadProguardConfiguration(
@@ -2085,7 +2083,7 @@
public static void disassemble(AndroidApp app, PrintStream ps)
throws IOException, ExecutionException {
DexApplication application =
- new ApplicationReader(app, new InternalOptions(), new Timing()).read().toDirect();
+ new ApplicationReader(app, new InternalOptions(), Timing.empty()).read().toDirect();
new AssemblyWriter(application, new InternalOptions(), true, false).write(ps);
}
diff --git a/src/test/java/com/android/tools/r8/bisect/BisectTest.java b/src/test/java/com/android/tools/r8/bisect/BisectTest.java
index 02f6b35..28ecca3 100644
--- a/src/test/java/com/android/tools/r8/bisect/BisectTest.java
+++ b/src/test/java/com/android/tools/r8/bisect/BisectTest.java
@@ -89,7 +89,7 @@
Result lastResult = Result.UNKNOWN;
while (clazz == null) {
InternalOptions options = new InternalOptions();
- Timing timing = new Timing("bisect-test");
+ Timing timing = Timing.empty();
DexApplication appGood = new ApplicationReader(goodInput, options, timing).read();
DexApplication appBad = new ApplicationReader(badInput, options, timing).read();
BisectState state = new BisectState(appGood, appBad, stateFile);
@@ -113,7 +113,7 @@
@Test
public void bisectWithInternalCommand() throws Exception {
InternalOptions options = new InternalOptions();
- Timing timing = new Timing("bisect-test");
+ Timing timing = Timing.empty();
DexApplication appGood = new ApplicationReader(buildGood(), options, timing).read();
DexApplication appBad = new ApplicationReader(buildBad(), options, timing).read();
ExecutorService executor = Executors.newWorkStealingPool();
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 cf013d1..09489c9 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
@@ -13,7 +13,8 @@
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.StringConsumer.FileConsumer;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
@@ -34,6 +35,7 @@
import com.android.tools.r8.smali.SmaliBuilder;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.AndroidApp.Builder;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -337,6 +339,7 @@
ImmutableSet.of(
"classmerging.LambdaRewritingTest",
"classmerging.LambdaRewritingTest$Function",
+ "classmerging.LambdaRewritingTest$FunctionImpl",
"classmerging.LambdaRewritingTest$InterfaceImpl");
runTestOnInput(
main,
@@ -1223,21 +1226,31 @@
VerticalClassMergerDebugTest debugTestRunner)
throws Throwable {
Path proguardMapPath = File.createTempFile("mapping", ".txt", temp.getRoot()).toPath();
- R8Command.Builder commandBuilder =
- ToolHelper.prepareR8CommandBuilder(input)
- .setDisableMinification(true)
- .addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown());
- ToolHelper.allowTestProguardOptions(commandBuilder);
- AndroidApp output =
- ToolHelper.runR8(
- commandBuilder.build(),
- options -> {
- optionsConsumer.accept(options);
- options.proguardMapConsumer =
- new FileConsumer(proguardMapPath, options.proguardMapConsumer);
- });
+ R8TestCompileResult compileResult =
+ testForR8(Backend.DEX)
+ .apply(
+ b -> {
+ // Some tests add DEX inputs, so circumvent the check by adding directly to the
+ // app.
+ Builder appBuilder = ToolHelper.getAppBuilder(b.getBuilder());
+ for (ProgramResourceProvider provider : input.getProgramResourceProviders()) {
+ appBuilder.addProgramResourceProvider(provider);
+ }
+ })
+ .noMinification()
+ .addKeepRules(proguardConfig)
+ .enableProguardTestOptions()
+ .addOptionsModification(
+ o -> {
+ optionsConsumer.accept(o);
+ o.testing.allowUnusedProguardConfigurationRules = true;
+ o.proguardMapConsumer = new FileConsumer(proguardMapPath, o.proguardMapConsumer);
+ })
+ .compile();
+
CodeInspector inputInspector = new CodeInspector(input);
- CodeInspector outputInspector = new CodeInspector(output);
+ CodeInspector outputInspector = compileResult.inspector();
+
// Check that all classes in [preservedClassNames] are in fact preserved.
for (FoundClassSubject classSubject : inputInspector.allClasses()) {
String className = classSubject.getOriginalName();
@@ -1247,12 +1260,19 @@
shouldBePresent,
outputInspector.clazz(className).isPresent());
}
+
// Check that the R8-generated code produces the same result as D8-generated code.
- assertEquals(runOnArt(compileWithD8(input), main), runOnArt(output, main));
+ String d8Result =
+ testForD8()
+ .addProgramResourceProviders(input.getProgramResourceProviders())
+ .run(main)
+ .assertSuccess()
+ .getStdOut();
+ compileResult.run(main).assertSuccessWithOutput(d8Result);
// Check that we never come across a method that has a name with "$classmerging$" in it during
// debugging.
if (debugTestRunner != null) {
- debugTestRunner.test(output, proguardMapPath);
+ debugTestRunner.test(compileResult.app, proguardMapPath);
}
return outputInspector;
}
diff --git a/src/test/java/com/android/tools/r8/d8/D8FrameworkDexPassthroughMarkerTest.java b/src/test/java/com/android/tools/r8/d8/D8FrameworkDexPassthroughMarkerTest.java
index 866dcf5..e17ac14 100644
--- a/src/test/java/com/android/tools/r8/d8/D8FrameworkDexPassthroughMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/d8/D8FrameworkDexPassthroughMarkerTest.java
@@ -67,10 +67,7 @@
Marker selfie = Marker.parse(markerDexString);
assert marker.equals(selfie);
AndroidApp app = ToolHelper.runD8(command, opts -> opts.setMarker(marker));
- DexApplication dexApp =
- new ApplicationReader(
- app, options, new Timing("D8FrameworkDexPassthroughMarkerTest"))
- .read();
+ DexApplication dexApp = new ApplicationReader(app, options, Timing.empty()).read();
Collection<Marker> markers = dexApp.dexItemFactory.extractMarkers();
assertEquals(1, markers.size());
Marker readMarker = markers.iterator().next();
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultInterfaceWithIdentifierNameString.java b/src/test/java/com/android/tools/r8/desugar/DefaultInterfaceWithIdentifierNameString.java
new file mode 100644
index 0000000..211050e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultInterfaceWithIdentifierNameString.java
@@ -0,0 +1,112 @@
+// Copyright (c) 2020, 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;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+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.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DefaultInterfaceWithIdentifierNameString extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ // Test expect that default interface method desugaring is enabled.
+ return getTestParameters()
+ .withDexRuntimes()
+ .withApiLevelsEndingAtExcluding(AndroidApiLevel.N)
+ .build();
+ }
+
+ public DefaultInterfaceWithIdentifierNameString(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private boolean isInvokeClassForName(InstructionSubject instruction) {
+ if (!instruction.isInvokeStatic()) {
+ return false;
+ }
+ return ((InvokeInstructionSubject) instruction)
+ .invokedMethod()
+ .name
+ .toString()
+ .equals("forName");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject classSubject = inspector.clazz(A.class);
+ assertThat(classSubject, isRenamed());
+ ClassSubject companionClassSubject = inspector.companionClassFor(I.class);
+ assertThat(companionClassSubject, isRenamed());
+ companionClassSubject
+ .allMethods()
+ .forEach(
+ method -> assertTrue(method.streamInstructions().anyMatch(this::isInvokeClassForName)));
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(DefaultInterfaceWithIdentifierNameString.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableInliningAnnotations()
+ .enableMergeAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("new A", "new A", "DONE");
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) throws Exception {
+ I.newInstance().antoherInstance();
+ System.out.println("DONE");
+ }
+ }
+
+ @NeverMerge
+ interface I {
+ @NeverInline
+ static I newInstance() throws Exception {
+ return (I)
+ Class.forName("com.android.tools.r8.desugar.DefaultInterfaceWithIdentifierNameString$A")
+ .newInstance();
+ }
+
+ @NeverInline
+ default I antoherInstance() throws Exception {
+ return (I)
+ Class.forName("com.android.tools.r8.desugar.DefaultInterfaceWithIdentifierNameString$A")
+ .newInstance();
+ }
+ }
+
+ @NeverClassInline
+ static class A implements I {
+ A() {
+ System.out.println("new A");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarInstanceLambdaWithReadsTest.java b/src/test/java/com/android/tools/r8/desugar/DesugarInstanceLambdaWithReadsTest.java
new file mode 100644
index 0000000..07e4f62
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarInstanceLambdaWithReadsTest.java
@@ -0,0 +1,112 @@
+// Copyright (c) 2020, 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;
+
+import static org.junit.Assert.assertEquals;
+
+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.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DesugarInstanceLambdaWithReadsTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("false");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ public DesugarInstanceLambdaWithReadsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(Main.class, A.class, B.class, Consumer.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkJustOneLambdaImplementationMethod);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class, A.class, B.class, Consumer.class)
+ .addKeepClassRules(Consumer.class)
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkJustOneLambdaImplementationMethod);
+ }
+
+ private void checkJustOneLambdaImplementationMethod(CodeInspector inspector) {
+ List<FoundMethodSubject> lambdaImplementationMethods =
+ inspector.clazz(Main.class).allMethods(m -> m.getOriginalName().startsWith("lambda$"));
+ assertEquals(1, lambdaImplementationMethods.size());
+ }
+
+ private interface Consumer {
+ void accept(String arg);
+ }
+
+ abstract static class A {
+ abstract boolean contains(String item);
+ }
+
+ // A is expected to be merged into B.
+ static class B extends A {
+ final List<String> items;
+
+ public B(List<String> items) {
+ this.items = items;
+ }
+
+ @NeverInline
+ @Override
+ boolean contains(String item) {
+ return items.contains(item);
+ }
+ }
+
+ static class Main {
+ // Field that is read from the lambda$ method.
+ A filter;
+
+ public Main(A filter) {
+ this.filter = filter;
+ }
+
+ @NeverInline
+ public static void forEach(List<String> args, Consumer fn) {
+ for (String arg : args) {
+ fn.accept(arg);
+ }
+ }
+
+ public void foo(List<String> args) {
+ forEach(args, arg -> System.out.println(filter.contains(arg)));
+ }
+
+ public static void main(String[] args) {
+ new Main(new B(Arrays.asList(args))).foo(Collections.singletonList("hello!"));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugaredLambdaImplementationInliningTest.java b/src/test/java/com/android/tools/r8/desugar/DesugaredLambdaImplementationInliningTest.java
index 9453575..cb1b96e 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugaredLambdaImplementationInliningTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugaredLambdaImplementationInliningTest.java
@@ -6,40 +6,59 @@
import static org.junit.Assert.assertEquals;
-import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
// Test if the static method of the lambda implementation (SINGLE_CALLER) is inlined into the
// interface method of the lambda class.
+@RunWith(Parameterized.class)
public class DesugaredLambdaImplementationInliningTest extends TestBase {
+
+ static final String EXPECTED =
+ StringUtils.lines(
+ "Running desugared stateless lambda.",
+ "Running desugared stateful lambda: lambda-state.");
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ final TestParameters parameters;
+
+ public DesugaredLambdaImplementationInliningTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
@Test
public void test() throws Exception {
class Counter {
private int i = 0;
}
Counter counter = new Counter();
- R8TestRunResult result =
- testForR8Compat(Backend.DEX)
- .addInnerClasses(DesugaredLambdaImplementationInliningTest.class)
- .addKeepMainRule(DesugaredLambdaImplementationInliningTest.TestClass.class)
- .noMinification()
- .run(TestClass.class)
- .assertSuccess()
- .inspect(
- inspector -> {
- inspector
- .clazz(DesugaredLambdaImplementationInliningTest.TestClass.class)
- .forAllMethods(
- fms -> {
- if (fms.isStatic() && !fms.getOriginalName().equals("main")) {
- ++counter.i;
- }
- });
- });
-
- // TODO(b/126323172) Change expected value to zero after fixed.
- assertEquals(2, counter.i);
+ testForR8Compat(parameters.getBackend())
+ .addProgramClasses(TestClass.class)
+ .addKeepMainRule(TestClass.class)
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspector()
+ .clazz(TestClass.class)
+ .forAllMethods(
+ fms -> {
+ if (fms.isStatic() && !fms.getOriginalName().equals("main")) {
+ ++counter.i;
+ }
+ });
+ // On CF the lambdas remain.
+ assertEquals(parameters.isDexRuntime() ? 0 : 2, counter.i);
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DumpCoreLibUsage.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DumpCoreLibUsage.java
index 7513e50..62d2d3a 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DumpCoreLibUsage.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DumpCoreLibUsage.java
@@ -66,7 +66,7 @@
AndroidApp input =
AndroidApp.builder().addLibraryFiles(ToolHelper.getAndroidJar(apiLevel)).build();
DirectMappedDexApplication dexApplication =
- new ApplicationReader(input, new InternalOptions(factory, new Reporter()), new Timing())
+ new ApplicationReader(input, new InternalOptions(factory, new Reporter()), Timing.empty())
.read()
.toDirect();
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11AtomicTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11AtomicTests.java
index b161386..2801eb9 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11AtomicTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11AtomicTests.java
@@ -15,7 +15,6 @@
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
-import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
@@ -31,8 +30,7 @@
private static final Path ATOMIC_TESTS_FOLDER =
Paths.get(ToolHelper.JDK_11_TESTS_DIR + "java/util/concurrent/atomic/");
- private static final Path ATOMIC_COMPILED_TESTS_FOLDER =
- Paths.get(ToolHelper.JDK_11_TESTS_CLASSES_DIR + "Atomic/");
+ private static Path ATOMIC_COMPILED_TESTS_FOLDER;
private static final String ATOMIC_REFERENCE_TEST = "AtomicReferenceTest";
private static final String ATOMIC_UPDATERS = "AtomicUpdaters";
private static final Path[] ATOMIC_TESTS_FILES =
@@ -63,8 +61,7 @@
@BeforeClass
public static void compileAtomicClasses() throws Exception {
- File atomicClassesDir = new File(ATOMIC_COMPILED_TESTS_FOLDER.toString());
- assert atomicClassesDir.exists() || atomicClassesDir.mkdirs();
+ ATOMIC_COMPILED_TESTS_FOLDER = getStaticTemp().newFolder("atomic").toPath();
javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
.addClasspathFiles(
Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")))
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ConcurrentMapTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ConcurrentMapTests.java
index fa595cb..7d0fc6b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ConcurrentMapTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ConcurrentMapTests.java
@@ -17,7 +17,6 @@
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
-import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
@@ -37,10 +36,6 @@
Paths.get(ToolHelper.JDK_11_TESTS_DIR + "java/util/concurrent/ConcurrentMap/");
private static final Path CONCURRENT_HASH_TESTS_FOLDER =
Paths.get(ToolHelper.JDK_11_TESTS_DIR + "java/util/concurrent/ConcurrentHashMap/");
- private static final Path CONCURRENT_COMPILED_TESTS_FOLDER =
- Paths.get(ToolHelper.JDK_11_TESTS_CLASSES_DIR + "ConcurrentMap/");
- private static final Path CONCURRENT_HASH_COMPILED_TESTS_FOLDER =
- Paths.get(ToolHelper.JDK_11_TESTS_CLASSES_DIR + "ConcurrentHashMap/");
private static Path[] CONCURRENT_COMPILED_TESTS_FILES;
private static Path[] CONCURRENT_HASH_COMPILED_TESTS_FILES;
private static final Path[] SUPPORT_LIBS =
@@ -71,16 +66,15 @@
@BeforeClass
public static void compileConcurrentClasses() throws Exception {
- File concurrentClassesDir = new File(CONCURRENT_COMPILED_TESTS_FOLDER.toString());
- assert concurrentClassesDir.exists() || concurrentClassesDir.mkdirs();
+ Path concurrentCompiledTestsFolder = getStaticTemp().newFolder("concurrentmap").toPath();
javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
.addClasspathFiles(
Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")))
.addSourceFiles(getAllFilesWithSuffixInDirectory(CONCURRENT_TESTS_FOLDER, JAVA_EXTENSION))
- .setOutputPath(CONCURRENT_COMPILED_TESTS_FOLDER)
+ .setOutputPath(concurrentCompiledTestsFolder)
.compile();
CONCURRENT_COMPILED_TESTS_FILES =
- getAllFilesWithSuffixInDirectory(CONCURRENT_COMPILED_TESTS_FOLDER, CLASS_EXTENSION);
+ getAllFilesWithSuffixInDirectory(concurrentCompiledTestsFolder, CLASS_EXTENSION);
assert CONCURRENT_COMPILED_TESTS_FILES.length > 0;
List<Path> concurrentHashFilesAndDependencies = new ArrayList<>();
@@ -89,16 +83,16 @@
getAllFilesWithSuffixInDirectory(CONCURRENT_HASH_TESTS_FOLDER, JAVA_EXTENSION));
Collections.addAll(concurrentHashFilesAndDependencies, SUPPORT_LIBS);
Path[] classesToCompile = concurrentHashFilesAndDependencies.toArray(new Path[0]);
- File concurrentHashClassesDir = new File(CONCURRENT_HASH_COMPILED_TESTS_FOLDER.toString());
- assert concurrentHashClassesDir.exists() || concurrentHashClassesDir.mkdirs();
+ Path concurrentHashCompiledTestsFolder =
+ getStaticTemp().newFolder("concurrenthashmap").toPath();
javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
.addClasspathFiles(
Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")))
.addSourceFiles(classesToCompile)
- .setOutputPath(CONCURRENT_HASH_COMPILED_TESTS_FOLDER)
+ .setOutputPath(concurrentHashCompiledTestsFolder)
.compile();
CONCURRENT_HASH_COMPILED_TESTS_FILES =
- getAllFilesWithSuffixInDirectory(CONCURRENT_HASH_COMPILED_TESTS_FOLDER, CLASS_EXTENSION);
+ getAllFilesWithSuffixInDirectory(concurrentHashCompiledTestsFolder, CLASS_EXTENSION);
assert CONCURRENT_HASH_COMPILED_TESTS_FILES.length > 0;
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11DesugaredLibraryTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11DesugaredLibraryTestBase.java
index 94c128e..248302a 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11DesugaredLibraryTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11DesugaredLibraryTestBase.java
@@ -13,7 +13,6 @@
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.google.common.collect.ImmutableList;
-import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
@@ -27,8 +26,7 @@
public class Jdk11DesugaredLibraryTestBase extends DesugaredLibraryTestBase {
protected static Path[] JDK_11_JAVA_BASE_EXTENSION_COMPILED_FILES;
- protected static final Path JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR =
- Paths.get(ToolHelper.JDK_11_TESTS_CLASSES_DIR + "Bootlib");
+ static Path JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR;
private static final Path JDK_11_JAVA_BASE_EXTENSION_FILES_DIR =
Paths.get("third_party/openjdk/jdk-11-test/lib/testlibrary/bootlib/java.base");
@@ -72,8 +70,7 @@
@BeforeClass
public static void compileJavaBaseExtensions() throws Exception {
- File extensionClassesDir = new File(JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR.toString());
- assert extensionClassesDir.exists() || extensionClassesDir.mkdirs();
+ JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR = getStaticTemp().newFolder("jdk11JavaBaseExt").toPath();
List<String> options =
Arrays.asList(
"--add-reads",
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11MathTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11MathTests.java
index 713061e..8a07522 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11MathTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11MathTests.java
@@ -13,7 +13,6 @@
import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.ToolHelper;
-import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.junit.Assume;
@@ -28,18 +27,8 @@
private static final String DIVMOD = "DivModTests";
private static final String EXACTARITH = "ExactArithTests";
- // Build test constants.
- private static final Path JDK_11_MATH_TESTS_DIR =
- Paths.get(ToolHelper.JDK_11_TESTS_CLASSES_DIR + "Math");
- private static final Path JDK_11_STRICT_MATH_TESTS_DIR =
- Paths.get(ToolHelper.JDK_11_TESTS_CLASSES_DIR + "StrictMath");
- private static final Path[] JDK_11_MATH_TEST_CLASS_FILES =
- new Path[] {
- JDK_11_MATH_TESTS_DIR.resolve(DIVMOD + CLASS_EXTENSION),
- JDK_11_MATH_TESTS_DIR.resolve(EXACTARITH + CLASS_EXTENSION)
- };
- private static final Path[] JDK_11_STRICT_MATH_TEST_CLASS_FILES =
- new Path[] {JDK_11_STRICT_MATH_TESTS_DIR.resolve(EXACTARITH + CLASS_EXTENSION)};
+ private static Path[] JDK_11_MATH_TEST_CLASS_FILES;
+ private static Path[] JDK_11_STRICT_MATH_TEST_CLASS_FILES;
// JDK 11 test constants.
private static final Path JDK_11_MATH_JAVA_DIR =
@@ -67,19 +56,25 @@
@BeforeClass
public static void compileMathClasses() throws Exception {
- File mathClassesDir = new File(JDK_11_MATH_TESTS_DIR.toString());
- assert mathClassesDir.exists() || mathClassesDir.mkdirs();
+ // Build test constants.
+ Path jdk11MathTestsDir = getStaticTemp().newFolder("math").toPath();
javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
.addSourceFiles(JDK_11_MATH_JAVA_FILES)
- .setOutputPath(JDK_11_MATH_TESTS_DIR)
+ .setOutputPath(jdk11MathTestsDir)
.compile();
+ JDK_11_MATH_TEST_CLASS_FILES =
+ new Path[] {
+ jdk11MathTestsDir.resolve(DIVMOD + CLASS_EXTENSION),
+ jdk11MathTestsDir.resolve(EXACTARITH + CLASS_EXTENSION)
+ };
- File strictMathClassesDir = new File(JDK_11_STRICT_MATH_TESTS_DIR.toString());
- assert strictMathClassesDir.exists() || strictMathClassesDir.mkdirs();
+ Path jdk11StrictMathTestsDir = getStaticTemp().newFolder("strictmath").toPath();
javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
.addSourceFiles(JDK_11_STRICT_MATH_JAVA_FILES)
- .setOutputPath(JDK_11_STRICT_MATH_TESTS_DIR)
+ .setOutputPath(jdk11StrictMathTestsDir)
.compile();
+ JDK_11_STRICT_MATH_TEST_CLASS_FILES =
+ new Path[] {jdk11StrictMathTestsDir.resolve(EXACTARITH + CLASS_EXTENSION)};
}
@Test
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ObjectsTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ObjectsTests.java
index c24839f..c49e10c 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ObjectsTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ObjectsTests.java
@@ -14,7 +14,6 @@
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm;
-import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.junit.Assume;
@@ -26,13 +25,8 @@
@RunWith(Parameterized.class)
public class Jdk11ObjectsTests extends TestBase {
- private static final Path JDK_11_OBJECTS_TESTS_DIR =
- Paths.get(ToolHelper.JDK_11_TESTS_CLASSES_DIR + "Objects");
private static final String BASIC_OBJECTS_TEST = "BasicObjectsTest";
- private static final Path[] JDK_11_OBJECTS_TEST_CLASS_FILES =
- new Path[] {
- JDK_11_OBJECTS_TESTS_DIR.resolve(BASIC_OBJECTS_TEST + CLASS_EXTENSION),
- };
+ private static Path[] JDK_11_OBJECTS_TEST_CLASS_FILES;
private static final Path JDK_11_OBJECTS_JAVA_DIR =
Paths.get(ToolHelper.JDK_11_TESTS_DIR + "java/util/Objects");
@@ -54,12 +48,15 @@
@BeforeClass
public static void compileObjectsClass() throws Exception {
- File objectsDir = new File(JDK_11_OBJECTS_TESTS_DIR.toString());
- assert objectsDir.exists() || objectsDir.mkdirs();
+ Path jdk11ObjectsTestsDir = getStaticTemp().newFolder("objects").toPath();
javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
.addSourceFiles(JDK_11_OBJECTS_JAVA_DIR.resolve(BASIC_OBJECTS_TEST + JAVA_EXTENSION))
- .setOutputPath(JDK_11_OBJECTS_TESTS_DIR)
+ .setOutputPath(jdk11ObjectsTestsDir)
.compile();
+ JDK_11_OBJECTS_TEST_CLASS_FILES =
+ new Path[] {
+ jdk11ObjectsTestsDir.resolve(BASIC_OBJECTS_TEST + CLASS_EXTENSION),
+ };
}
@Test
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
index 3f4a1ee..9d9b5cb 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
@@ -19,7 +19,6 @@
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
-import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
@@ -58,8 +57,7 @@
this.parameters = parameters;
}
- private static final Path JDK_11_STREAM_TEST_CLASSES_DIR =
- Paths.get(ToolHelper.JDK_11_TESTS_CLASSES_DIR + "Stream");
+ private static Path JDK_11_STREAM_TEST_CLASSES_DIR;
private static final Path JDK_11_STREAM_TEST_FILES_DIR =
Paths.get("third_party/openjdk/jdk-11-test/java/util/stream/test");
private static Path[] JDK_11_STREAM_TEST_COMPILED_FILES;
@@ -162,8 +160,7 @@
@BeforeClass
public static void compileJdk11StreamTests() throws Exception {
- File streamClassesDir = new File(JDK_11_STREAM_TEST_CLASSES_DIR.toString());
- assert streamClassesDir.exists() || streamClassesDir.mkdirs();
+ JDK_11_STREAM_TEST_CLASSES_DIR = getStaticTemp().newFolder("stream").toPath();
List<String> options =
Arrays.asList(
"--add-reads",
diff --git a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
index 5b94aac..8542a76 100644
--- a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
+++ b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
@@ -141,8 +141,7 @@
DexProgramClass sharedSynthesizedClass =
makeClass(options, "SharedSynthesized", 100, Constants.MAX_NON_JUMBO_INDEX - 1, classes);
- DexApplication.Builder builder =
- DirectMappedDexApplication.builder(options, new Timing("SharedClassWritingTest"));
+ DexApplication.Builder builder = DirectMappedDexApplication.builder(options, Timing.empty());
builder.addSynthesizedClass(sharedSynthesizedClass, false);
classes.forEach(builder::addProgramClass);
DexApplication application = builder.build();
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
new file mode 100644
index 0000000..332ad2e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2020, 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.enumunboxing;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertTrue;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+
+public class EnumUnboxingTestBase extends TestBase {
+
+ static final String KEEP_ENUM =
+ "-keepclassmembers enum * { public static **[] values(); public static **"
+ + " valueOf(java.lang.String); }";
+
+ public void assertLines2By2Correct(String string) {
+ List<String> lines = StringUtils.splitLines(string);
+ assert lines.size() % 2 == 0;
+ for (int i = 0; i < lines.size(); i += 2) {
+ assertEquals(
+ "Different lines: " + lines.get(i) + " || " + lines.get(i + 1) + "\n" + string,
+ lines.get(i),
+ lines.get(i + 1));
+ }
+ }
+
+ void enableEnumOptions(InternalOptions options) {
+ options.enableEnumUnboxing = true;
+ options.testing.enableEnumUnboxingDebugLogs = true;
+ }
+
+ void assertEnumIsUnboxed(Class<?> enumClass, String testName, TestDiagnosticMessages m) {
+ Diagnostic diagnostic = m.getInfos().get(0);
+ assertTrue(diagnostic.getDiagnosticMessage().startsWith("Unboxed enums"));
+ assertTrue(
+ "Expected enum to be removed (" + testName + "): ",
+ diagnostic.getDiagnosticMessage().contains(enumClass.getSimpleName()));
+ }
+
+ void assertEnumIsBoxed(Class<?> enumClass, String testName, TestDiagnosticMessages m) {
+ Diagnostic diagnostic = m.getInfos().get(1);
+ assertTrue(diagnostic.getDiagnosticMessage().startsWith("Boxed enums"));
+ assertTrue(
+ "Expected enum NOT to be removed (" + testName + "): ",
+ diagnostic.getDiagnosticMessage().contains(enumClass.getSimpleName()));
+ }
+
+ static TestParametersCollection enumUnboxingTestParameters() {
+ return getTestParameters()
+ .withCfRuntime(CfVm.JDK9)
+ .withDexRuntime(DexVm.Version.first())
+ .withDexRuntime(DexVm.Version.last())
+ .withAllApiLevels()
+ .build();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java
new file mode 100644
index 0000000..247e8a1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java
@@ -0,0 +1,191 @@
+// Copyright (c) 2020, 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.enumunboxing;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.enumunboxing.FailingEnumUnboxingAnalysisTest.EnumInstanceFieldMain.EnumInstanceField;
+import com.android.tools.r8.enumunboxing.FailingEnumUnboxingAnalysisTest.EnumInterfaceMain.EnumInterface;
+import com.android.tools.r8.enumunboxing.FailingEnumUnboxingAnalysisTest.EnumStaticFieldMain.EnumStaticField;
+import com.android.tools.r8.enumunboxing.FailingEnumUnboxingAnalysisTest.EnumStaticMethodMain.EnumStaticMethod;
+import com.android.tools.r8.enumunboxing.FailingEnumUnboxingAnalysisTest.EnumVirtualMethodMain.EnumVirtualMethod;
+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 FailingEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+
+ private static final Class<?>[] FAILURES = {
+ EnumInterface.class,
+ EnumStaticField.class,
+ EnumInstanceField.class,
+ EnumStaticMethod.class,
+ EnumVirtualMethod.class
+ };
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public FailingEnumUnboxingAnalysisTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testEnumUnboxingFailure() throws Exception {
+ R8FullTestBuilder r8FullTestBuilder =
+ testForR8(parameters.getBackend()).addInnerClasses(FailingEnumUnboxingAnalysisTest.class);
+ for (Class<?> failure : FAILURES) {
+ r8FullTestBuilder.addKeepMainRule(failure.getEnclosingClass());
+ }
+ R8TestCompileResult compile =
+ r8FullTestBuilder
+ .noTreeShaking() // Disabled to avoid merging Itf into EnumInterface.
+ .enableInliningAnnotations()
+ .addKeepRules(KEEP_ENUM)
+ .addOptionsModification(this::enableEnumOptions)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::assertEnumsAsExpected);
+ for (Class<?> failure : FAILURES) {
+ R8TestRunResult run =
+ compile
+ .inspectDiagnosticMessages(
+ m -> assertEnumIsBoxed(failure, failure.getSimpleName(), m))
+ .run(parameters.getRuntime(), failure.getEnclosingClass())
+ .assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
+ }
+ }
+
+ private void assertEnumsAsExpected(CodeInspector inspector) {
+ assertEquals(1, inspector.clazz(EnumInterface.class).getDexClass().interfaces.size());
+
+ assertTrue(inspector.clazz(EnumStaticField.class).uniqueFieldWithName("X").isPresent());
+ assertTrue(inspector.clazz(EnumInstanceField.class).uniqueFieldWithName("a").isPresent());
+
+ assertEquals(5, inspector.clazz(EnumStaticMethod.class).getDexClass().directMethods().size());
+ assertEquals(1, inspector.clazz(EnumVirtualMethod.class).virtualMethods().size());
+ }
+
+ static class EnumInterfaceMain {
+
+ public static void main(String[] args) {
+ System.out.println(EnumInterface.A.ordinal());
+ System.out.println(0);
+ }
+
+ enum EnumInterface implements Itf {
+ A,
+ B,
+ C
+ }
+
+ interface Itf {
+
+ default int ordinal() {
+ return -1;
+ }
+ }
+ }
+
+ static class EnumStaticFieldMain {
+
+ public static void main(String[] args) {
+ System.out.println(EnumStaticField.A.ordinal());
+ System.out.println(0);
+ System.out.println(EnumStaticField.X.ordinal());
+ System.out.println(0);
+ }
+
+ enum EnumStaticField {
+ A,
+ B,
+ C;
+ static EnumStaticField X = A;
+ }
+ }
+
+ static class EnumInstanceFieldMain {
+
+ enum EnumInstanceField {
+ A(10),
+ B(20),
+ C(30);
+ private int a;
+
+ EnumInstanceField(int i) {
+ this.a = i;
+ }
+ }
+
+ public static void main(String[] args) {
+ System.out.println(EnumInstanceField.A.ordinal());
+ System.out.println(0);
+ System.out.println(EnumInstanceField.A.a);
+ System.out.println(10);
+ }
+ }
+
+ static class EnumStaticMethodMain {
+
+ enum EnumStaticMethod {
+ A,
+ B,
+ C;
+
+ // Enum cannot be unboxed if it has a static method, we do not inline so the method is
+ // present.
+ @NeverInline
+ static int foo() {
+ return Math.addExact(-1, 0);
+ }
+ }
+
+ public static void main(String[] args) {
+ System.out.println(EnumStaticMethod.A.ordinal());
+ System.out.println(0);
+ System.out.println(EnumStaticMethod.foo());
+ System.out.println(-1);
+ }
+ }
+
+ static class EnumVirtualMethodMain {
+
+ public static void main(String[] args) {
+ EnumVirtualMethod e1 = EnumVirtualMethod.A;
+ System.out.println(e1.ordinal());
+ System.out.println(0);
+ System.out.println(e1.valueOf());
+ System.out.println(-1);
+ }
+
+ enum EnumVirtualMethod {
+ A,
+ B,
+ C;
+
+ // Enum cannot be unboxed if it has a virtual method, we do not inline so the method is
+ // present.
+ @NeverInline
+ int valueOf() {
+ return Math.addExact(-1, 0);
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java
new file mode 100644
index 0000000..762380a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java
@@ -0,0 +1,241 @@
+// Copyright (c) 2020, 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.enumunboxing;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.EnumSet;
+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 FailingMethodEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+
+ private static final Class<?>[] FAILURES = {
+ NullCheck.class,
+ Check.class,
+ InstanceFieldPutObject.class,
+ StaticFieldPutObject.class,
+ ToString.class,
+ EnumSetTest.class,
+ FailingPhi.class
+ };
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public FailingMethodEnumUnboxingAnalysisTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testEnumUnboxingFailure() throws Exception {
+ R8FullTestBuilder r8FullTestBuilder =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(FailingMethodEnumUnboxingAnalysisTest.class);
+ for (Class<?> failure : FAILURES) {
+ r8FullTestBuilder.addKeepMainRule(failure);
+ }
+ R8TestCompileResult compile =
+ r8FullTestBuilder
+ .addKeepRules(KEEP_ENUM)
+ .addOptionsModification(this::enableEnumOptions)
+ .enableInliningAnnotations()
+ .addOptionsModification(
+ // Disabled to avoid toString() being removed.
+ opt -> opt.enableEnumValueOptimization = false)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::assertEnumsAsExpected);
+ for (Class<?> failure : FAILURES) {
+ R8TestRunResult run =
+ compile
+ .inspectDiagnosticMessages(
+ m ->
+ assertEnumIsBoxed(
+ failure.getDeclaredClasses()[0], failure.getSimpleName(), m))
+ .run(parameters.getRuntime(), failure)
+ .assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
+ }
+ }
+
+ private void assertEnumsAsExpected(CodeInspector inspector) {
+ // Check all as expected (else we test nothing)
+ assertTrue(inspector.clazz(NullCheck.class).uniqueMethodWithName("nullCheck").isPresent());
+ assertTrue(inspector.clazz(Check.class).uniqueMethodWithName("check").isPresent());
+
+ assertEquals(
+ 1, inspector.clazz(InstanceFieldPutObject.class).getDexClass().instanceFields().size());
+ assertEquals(
+ 1, inspector.clazz(StaticFieldPutObject.class).getDexClass().staticFields().size());
+
+ assertTrue(inspector.clazz(FailingPhi.class).uniqueMethodWithName("switchOn").isPresent());
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ static class NullCheck {
+
+ enum MyEnum {
+ A,
+ B,
+ C
+ }
+
+ public static void main(String[] args) {
+ System.out.println(nullCheck(MyEnum.A));
+ System.out.println(false);
+ System.out.println(nullCheck(null));
+ System.out.println(true);
+ }
+
+ // Do not resolve the == with constants after inlining.
+ @NeverInline
+ static boolean nullCheck(MyEnum e) {
+ return e == null;
+ }
+ }
+
+ static class Check {
+
+ enum MyEnum {
+ A,
+ B,
+ C
+ }
+
+ public static void main(String[] args) {
+ MyEnum e1 = MyEnum.A;
+ System.out.println(check(e1));
+ System.out.println(false);
+ }
+
+ // Do not resolve the == with constants after inlining.
+ @NeverInline
+ static boolean check(MyEnum e) {
+ return e == MyEnum.B;
+ }
+ }
+
+ static class InstanceFieldPutObject {
+
+ enum MyEnum {
+ A,
+ B,
+ C
+ }
+
+ Object e;
+
+ public static void main(String[] args) {
+ InstanceFieldPutObject fieldPut = new InstanceFieldPutObject();
+ fieldPut.setA();
+ Object obj = new Object();
+ fieldPut.e = obj;
+ System.out.println(fieldPut.e);
+ System.out.println(obj);
+ }
+
+ void setA() {
+ e = MyEnum.A;
+ }
+ }
+
+ static class StaticFieldPutObject {
+
+ enum MyEnum {
+ A,
+ B,
+ C
+ }
+
+ static Object e;
+
+ public static void main(String[] args) {
+ setA();
+ Object obj = new Object();
+ e = obj;
+ System.out.println(e);
+ System.out.println(obj);
+ }
+
+ static void setA() {
+ e = MyEnum.A;
+ }
+ }
+
+ static class ToString {
+
+ enum MyEnum {
+ A,
+ B,
+ C
+ }
+
+ public static void main(String[] args) {
+ MyEnum e1 = MyEnum.A;
+ System.out.println(e1.toString());
+ System.out.println("A");
+ }
+ }
+
+ static class EnumSetTest {
+
+ enum MyEnum {
+ A,
+ B,
+ C
+ }
+
+ public static void main(String[] args) {
+ EnumSet<MyEnum> es = EnumSet.allOf(MyEnum.class);
+ System.out.println(es.size());
+ System.out.println("3");
+ }
+ }
+
+ static class FailingPhi {
+
+ enum MyEnum {
+ A,
+ B,
+ C
+ }
+
+ public static void main(String[] args) {
+ System.out.println(switchOn(1));
+ System.out.println("B");
+ System.out.println(switchOn(2));
+ System.out.println("class java.lang.Object");
+ }
+
+ // Avoid removing the switch entirely.
+ @NeverInline
+ static Object switchOn(int i) {
+ switch (i) {
+ case 0:
+ return MyEnum.A;
+ case 1:
+ return MyEnum.B;
+ default:
+ return Object.class;
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java
new file mode 100644
index 0000000..2841689
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java
@@ -0,0 +1,124 @@
+// Copyright (c) 2020, 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.enumunboxing;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+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 FieldPutEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+
+ private final TestParameters parameters;
+ private static final Class<?>[] INPUTS =
+ new Class<?>[] {InstanceFieldPut.class, StaticFieldPut.class};
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public FieldPutEnumUnboxingAnalysisTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ R8TestCompileResult compile =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(FieldPutEnumUnboxingAnalysisTest.class)
+ .addKeepMainRules(INPUTS)
+ .addKeepRules(KEEP_ENUM)
+ .addOptionsModification(this::enableEnumOptions)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .noMinification()
+ .compile()
+ .inspect(
+ i -> {
+ assertEquals(
+ 1, i.clazz(InstanceFieldPut.class).getDexClass().instanceFields().size());
+ assertEquals(
+ 1, i.clazz(StaticFieldPut.class).getDexClass().staticFields().size());
+ });
+
+ for (Class<?> input : INPUTS) {
+ R8TestRunResult run =
+ compile
+ .inspectDiagnosticMessages(
+ m -> assertEnumIsUnboxed(input.getDeclaredClasses()[0], input.getSimpleName(), m))
+ .run(parameters.getRuntime(), input)
+ .assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
+ }
+ }
+
+ static class InstanceFieldPut {
+
+ enum MyEnum {
+ A,
+ B,
+ C
+ }
+
+ MyEnum e;
+
+ public static void main(String[] args) {
+ InstanceFieldPut fieldPut = new InstanceFieldPut();
+ fieldPut.setA();
+ System.out.println(fieldPut.e.ordinal());
+ System.out.println(0);
+ fieldPut.setB();
+ System.out.println(fieldPut.e.ordinal());
+ System.out.println(1);
+ }
+
+ void setA() {
+ e = MyEnum.A;
+ }
+
+ void setB() {
+ e = MyEnum.B;
+ }
+ }
+
+ static class StaticFieldPut {
+
+ enum MyEnum {
+ A,
+ B,
+ C
+ }
+
+ static MyEnum e;
+
+ public static void main(String[] args) {
+ setA();
+ System.out.println(StaticFieldPut.e.ordinal());
+ System.out.println(0);
+ setB();
+ System.out.println(StaticFieldPut.e.ordinal());
+ System.out.println(1);
+ }
+
+ @NeverInline
+ static void setA() {
+ StaticFieldPut.e = MyEnum.A;
+ }
+
+ @NeverInline
+ static void setB() {
+ StaticFieldPut.e = MyEnum.B;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java
new file mode 100644
index 0000000..079c9f7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java
@@ -0,0 +1,62 @@
+// Copyright (c) 2020, 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.enumunboxing;
+
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+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 OrdinalEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public OrdinalEnumUnboxingAnalysisTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private static final Class<?> ENUM_CLASS = MyEnum.class;
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ Class<Ordinal> classToTest = Ordinal.class;
+ R8TestRunResult run =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(classToTest, ENUM_CLASS)
+ .addKeepMainRule(classToTest)
+ .addKeepRules(KEEP_ENUM)
+ .addOptionsModification(this::enableEnumOptions)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectDiagnosticMessages(
+ m -> assertEnumIsUnboxed(ENUM_CLASS, classToTest.getSimpleName(), m))
+ .run(parameters.getRuntime(), classToTest)
+ .assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
+ }
+
+ enum MyEnum {
+ A,
+ B,
+ C
+ }
+
+ static class Ordinal {
+
+ public static void main(String[] args) {
+ System.out.println(MyEnum.A.ordinal());
+ System.out.println(0);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java
new file mode 100644
index 0000000..87cacd5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java
@@ -0,0 +1,79 @@
+// Copyright (c) 2020, 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.enumunboxing;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+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 PhiEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public PhiEnumUnboxingAnalysisTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private static final Class<?> ENUM_CLASS = MyEnum.class;
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ Class<?> classToTest = Phi.class;
+ R8TestRunResult run =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(classToTest, ENUM_CLASS)
+ .addKeepMainRule(classToTest)
+ .addKeepRules(KEEP_ENUM)
+ .enableInliningAnnotations()
+ .addOptionsModification(this::enableEnumOptions)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectDiagnosticMessages(
+ m -> assertEnumIsUnboxed(ENUM_CLASS, classToTest.getSimpleName(), m))
+ .run(parameters.getRuntime(), classToTest)
+ .assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
+ }
+
+ enum MyEnum {
+ A,
+ B,
+ C
+ }
+
+ static class Phi {
+
+ public static void main(String[] args) {
+ System.out.println(switchOn(1).ordinal());
+ System.out.println(1);
+ System.out.println(switchOn(2).ordinal());
+ System.out.println(2);
+ }
+
+ // Avoid removing the switch entirely.
+ @NeverInline
+ static MyEnum switchOn(int i) {
+ switch (i) {
+ case 0:
+ return MyEnum.A;
+ case 1:
+ return MyEnum.B;
+ default:
+ return MyEnum.C;
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java
new file mode 100644
index 0000000..699c72d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java
@@ -0,0 +1,79 @@
+// Copyright (c) 2020, 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.enumunboxing;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+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 SwitchEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public SwitchEnumUnboxingAnalysisTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private static final Class<?> ENUM_CLASS = MyEnum.class;
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ Class<Switch> classToTest = Switch.class;
+ R8TestRunResult run =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(SwitchEnumUnboxingAnalysisTest.class)
+ .addKeepMainRule(classToTest)
+ .addKeepRules(KEEP_ENUM)
+ .enableInliningAnnotations()
+ .addOptionsModification(this::enableEnumOptions)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectDiagnosticMessages(
+ m -> assertEnumIsBoxed(ENUM_CLASS, classToTest.getSimpleName(), m))
+ .run(parameters.getRuntime(), classToTest)
+ .assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
+ }
+
+ enum MyEnum {
+ A,
+ B,
+ C
+ }
+
+ static class Switch {
+
+ public static void main(String[] args) {
+ System.out.println(switchOnEnum(MyEnum.A));
+ System.out.println(0xC0FFEE);
+ System.out.println(switchOnEnum(MyEnum.B));
+ System.out.println(0xBABE);
+ }
+
+ // Avoid removing the switch entirely.
+ @NeverInline
+ static int switchOnEnum(MyEnum e) {
+ switch (e) {
+ case A:
+ return 0xC0FFEE;
+ case B:
+ return 0xBABE;
+ default:
+ return 0xDEADBEEF;
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/DexTypeTest.java b/src/test/java/com/android/tools/r8/graph/DexTypeTest.java
index f482d5c..90218e0 100644
--- a/src/test/java/com/android/tools/r8/graph/DexTypeTest.java
+++ b/src/test/java/com/android/tools/r8/graph/DexTypeTest.java
@@ -26,14 +26,14 @@
InternalOptions options = new InternalOptions();
DexApplication application =
new ApplicationReader(
- AndroidApp.builder()
- .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
- .addLibraryFiles(ToolHelper.getKotlinStdlibJar())
- .build(),
- options,
- new Timing(DexType.class.getName()))
- .read()
- .toDirect();
+ AndroidApp.builder()
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
+ .addLibraryFiles(ToolHelper.getKotlinStdlibJar())
+ .build(),
+ options,
+ Timing.empty())
+ .read()
+ .toDirect();
factory = options.itemFactory;
appInfo = new AppInfoWithSubtyping(application);
}
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
index cffd09c..78585b8 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
@@ -45,7 +45,7 @@
proguardMap = StringResource.fromFile(mapFile);
}
ExecutorService executorService = Executors.newSingleThreadExecutor();
- Timing timing = new Timing("ReadGMSCore");
+ Timing timing = Timing.empty();
program =
new ApplicationReader(app, new InternalOptions(), timing)
.read(proguardMap, executorService)
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
index eb62734..558664f 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
@@ -179,14 +179,11 @@
usedRootClassSubject.uniqueFieldWithName("recursiveWithRequiredField_"), isPresent());
assertThat(usedRootClassSubject.uniqueFieldWithName("hasMapField_"), isPresent());
- // TODO(b/112437944): Should be present.
assertThat(
- usedRootClassSubject.uniqueFieldWithName("isExtendedWithRequiredField_"),
- not(isPresent()));
- // TODO(b/112437944): Should be present.
+ usedRootClassSubject.uniqueFieldWithName("isExtendedWithRequiredField_"), isPresent());
assertThat(
usedRootClassSubject.uniqueFieldWithName("isRepeatedlyExtendedWithRequiredField_"),
- not(isPresent()));
+ isPresent());
ClassSubject hasRequiredFieldClassSubject = outputInspector.clazz(HAS_REQUIRED_FIELD);
assertThat(hasRequiredFieldClassSubject, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java b/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
index 9394934..586129b 100644
--- a/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
@@ -54,7 +54,7 @@
AndroidApp application = buildApplication(builder);
InternalOptions options = new InternalOptions();
DexApplication dexApplication =
- new ApplicationReader(application, options, new Timing("BasicBlockIteratorTest")).read();
+ new ApplicationReader(application, options, Timing.empty()).read();
AppView<?> appView = AppView.createForD8(new AppInfo(dexApplication), options);
// Build the code, and split the code into three blocks.
diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java
index e20e882..34d7c61 100644
--- a/src/test/java/com/android/tools/r8/ir/InlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java
@@ -10,6 +10,7 @@
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.DexItemFactory;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
@@ -17,11 +18,13 @@
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.EnqueuerFactory;
import com.android.tools.r8.shaking.ProguardClassFilter;
+import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.shaking.ProguardKeepRule;
import com.android.tools.r8.shaking.RootSetBuilder;
import com.android.tools.r8.smali.SmaliBuilder;
import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -54,7 +57,7 @@
application,
ImmutableList.of(ProguardKeepRule.defaultKeepAllRule(unused -> {})))
.run(executorService));
- Timing timing = new Timing(getClass().getSimpleName());
+ Timing timing = Timing.empty();
Enqueuer enqueuer = EnqueuerFactory.createForInitialTreeShaking(appView);
appView.setAppInfo(
enqueuer.traceApplication(
@@ -62,6 +65,13 @@
return new TestApplication(appView, method, additionalCode);
}
+ private static InternalOptions createOptions() {
+ Reporter reporter = new Reporter();
+ ProguardConfiguration proguardConfiguration =
+ ProguardConfiguration.builder(new DexItemFactory(), reporter).build();
+ return new InternalOptions(proguardConfiguration, reporter);
+ }
+
private TestApplication codeForMethodReplaceTest(int a, int b) throws ExecutionException {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
@@ -108,7 +118,7 @@
" return-void"
);
- InternalOptions options = new InternalOptions();
+ InternalOptions options = createOptions();
DexApplication application = buildApplication(builder, options).toDirect();
// Return the processed method for inspection.
@@ -190,7 +200,7 @@
" return-void"
);
- InternalOptions options = new InternalOptions();
+ InternalOptions options = createOptions();
DexApplication application = buildApplication(builder, options).toDirect();
// Return the processed method for inspection.
@@ -267,7 +277,7 @@
" return-void"
);
- InternalOptions options = new InternalOptions();
+ InternalOptions options = createOptions();
DexApplication application = buildApplication(builder, options).toDirect();
// Return the processed method for inspection.
@@ -400,7 +410,7 @@
" return-void"
);
- InternalOptions options = new InternalOptions();
+ InternalOptions options = createOptions();
DexApplication application = buildApplication(builder, options).toDirect();
// Return the processed method for inspection.
@@ -513,7 +523,7 @@
" goto :print_result"
);
- InternalOptions options = new InternalOptions();
+ InternalOptions options = createOptions();
DexApplication application = buildApplication(builder, options).toDirect();
// Return the processed method for inspection.
@@ -624,7 +634,7 @@
" goto :print_result"
);
- InternalOptions options = new InternalOptions();
+ InternalOptions options = createOptions();
DexApplication application = buildApplication(builder, options).toDirect();
// Return the processed method for inspection.
@@ -737,7 +747,7 @@
" goto :print_result"
);
- InternalOptions options = new InternalOptions();
+ InternalOptions options = createOptions();
DexApplication application = buildApplication(builder, options).toDirect();
// Return the processed method for inspection.
@@ -893,7 +903,7 @@
" goto :print_result"
);
- InternalOptions options = new InternalOptions();
+ InternalOptions options = createOptions();
DexApplication application = buildApplication(builder, options).toDirect();
// Return the processed method for inspection.
@@ -1139,7 +1149,7 @@
" goto :print_result"
);
- InternalOptions options = new InternalOptions();
+ InternalOptions options = createOptions();
DexApplication application = buildApplication(builder, options).toDirect();
// Return the processed method for inspection.
diff --git a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
index fc61691..8d73fad 100644
--- a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
@@ -45,7 +45,7 @@
protected DexApplication buildApplication(AndroidApp input, InternalOptions options) {
try {
options.itemFactory.resetSortedIndices();
- return new ApplicationReader(input, options, new Timing("IrInjectionTest")).read();
+ return new ApplicationReader(input, options, Timing.empty()).read();
} catch (IOException | ExecutionException e) {
throw new RuntimeException(e);
}
@@ -123,7 +123,7 @@
}
public String run() throws IOException {
- Timing timing = new Timing(getClass().getSimpleName());
+ Timing timing = Timing.empty();
IRConverter converter = new IRConverter(appView, timing, null, MainDexClasses.NONE);
converter.replaceCodeForTesting(method, code);
AndroidApp app = writeDex(application, appView.options());
diff --git a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
index 4b0d1ed..e53db5e 100644
--- a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
+++ b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
@@ -364,9 +364,9 @@
// Modify the code to make the inserted block add the constant 10 to the original return
// value.
Value newConstValue =
- new Value(test.valueNumberGenerator.next(), TypeLatticeElement.INT, null);
+ new Value(test.valueNumberGenerator.next(), TypeLatticeElement.getInt(), null);
Value newReturnValue =
- new Value(test.valueNumberGenerator.next(), TypeLatticeElement.INT, null);
+ new Value(test.valueNumberGenerator.next(), TypeLatticeElement.getInt(), null);
Value oldReturnValue = newReturnBlock.iterator().next().asReturn().returnValue();
newReturnBlock.iterator().next().asReturn().returnValue().replaceUsers(newReturnValue);
Instruction constInstruction = new ConstNumber(newConstValue, 10);
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/AnalysisTestBase.java b/src/test/java/com/android/tools/r8/ir/analysis/AnalysisTestBase.java
index c7e8270..7321e79 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/AnalysisTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/AnalysisTestBase.java
@@ -65,9 +65,7 @@
@Before
public void setup() throws Exception {
DexApplication application =
- new ApplicationReader(app, options, new Timing("AnalysisTestBase.appReader"))
- .read()
- .toDirect();
+ new ApplicationReader(app, options, Timing.empty()).read().toDirect();
appView = AppView.createForR8(new AppInfoWithSubtyping(application), options);
}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
index d1e44d0..02395ef 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
@@ -118,7 +118,7 @@
parameters.isCfRuntime()
? ClassFileConsumer.emptyConsumer()
: DexIndexedConsumer.emptyConsumer();
- Timing timing = new Timing("FieldBitAccessInfoTest");
+ Timing timing = Timing.empty();
DexApplication application =
new ApplicationReader(
AndroidApp.builder()
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/ArrayTypeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/ArrayTypeTest.java
index e5e0a86..74a49ed 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/ArrayTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/ArrayTypeTest.java
@@ -4,8 +4,8 @@
package com.android.tools.r8.ir.analysis.type;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.FLOAT;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.INT;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getFloat;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getInt;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -74,8 +74,8 @@
ArrayTypeLatticeElement arrayType = array.getTypeLattice().asArrayTypeLatticeElement();
TypeLatticeElement elementType = arrayType.getArrayMemberTypeAsMemberType();
- assertEquals(FLOAT, elementType);
- assertEquals(FLOAT, value.getTypeLattice());
+ assertEquals(getFloat(), elementType);
+ assertEquals(getFloat(), value.getTypeLattice());
}
}
};
@@ -94,8 +94,8 @@
ArrayTypeLatticeElement arrayType = array.getTypeLattice().asArrayTypeLatticeElement();
TypeLatticeElement elementType = arrayType.getArrayMemberTypeAsMemberType();
- assertEquals(FLOAT, elementType);
- assertEquals(FLOAT, value.getTypeLattice());
+ assertEquals(getFloat(), elementType);
+ assertEquals(getFloat(), value.getTypeLattice());
}
{
@@ -104,7 +104,7 @@
code,
instruction ->
instruction.isConstNumber() && instruction.asConstNumber().getRawValue() != 0);
- assertEquals(FLOAT, constNumberInstruction.outValue().getTypeLattice());
+ assertEquals(getFloat(), constNumberInstruction.outValue().getTypeLattice());
}
};
}
@@ -115,7 +115,7 @@
for (BasicBlock block : code.blocks) {
for (Phi phi : block.getPhis()) {
phiCount++;
- assertEquals(INT, phi.getTypeLattice());
+ assertEquals(getInt(), phi.getTypeLattice());
}
}
assertEquals(2, phiCount);
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/ConstrainedPrimitiveTypeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/ConstrainedPrimitiveTypeTest.java
index dd0b5e2..9471d46 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/ConstrainedPrimitiveTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/ConstrainedPrimitiveTypeTest.java
@@ -4,10 +4,10 @@
package com.android.tools.r8.ir.analysis.type;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.DOUBLE;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.FLOAT;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.INT;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.LONG;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getDouble;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getFloat;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getInt;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getLong;
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.NeverInline;
@@ -54,42 +54,42 @@
@Test
public void testIntWithInvokeUser() throws Exception {
- buildAndCheckIR("intWithInvokeUserTest", testInspector(INT, 1));
+ buildAndCheckIR("intWithInvokeUserTest", testInspector(getInt(), 1));
}
@Test
public void testIntWithIndirectInvokeUser() throws Exception {
- buildAndCheckIR("intWithIndirectInvokeUserTest", testInspector(INT, 2));
+ buildAndCheckIR("intWithIndirectInvokeUserTest", testInspector(getInt(), 2));
}
@Test
public void testFloatWithInvokeUser() throws Exception {
- buildAndCheckIR("floatWithInvokeUserTest", testInspector(FLOAT, 1));
+ buildAndCheckIR("floatWithInvokeUserTest", testInspector(getFloat(), 1));
}
@Test
public void testFloatWithIndirectInvokeUser() throws Exception {
- buildAndCheckIR("floatWithIndirectInvokeUserTest", testInspector(FLOAT, 2));
+ buildAndCheckIR("floatWithIndirectInvokeUserTest", testInspector(getFloat(), 2));
}
@Test
public void testLongWithInvokeUser() throws Exception {
- buildAndCheckIR("longWithInvokeUserTest", testInspector(LONG, 1));
+ buildAndCheckIR("longWithInvokeUserTest", testInspector(getLong(), 1));
}
@Test
public void testLongWithIndirectInvokeUser() throws Exception {
- buildAndCheckIR("longWithIndirectInvokeUserTest", testInspector(LONG, 2));
+ buildAndCheckIR("longWithIndirectInvokeUserTest", testInspector(getLong(), 2));
}
@Test
public void testDoubleWithInvokeUser() throws Exception {
- buildAndCheckIR("doubleWithInvokeUserTest", testInspector(DOUBLE, 1));
+ buildAndCheckIR("doubleWithInvokeUserTest", testInspector(getDouble(), 1));
}
@Test
public void testDoubleWithIndirectInvokeUser() throws Exception {
- buildAndCheckIR("doubleWithIndirectInvokeUserTest", testInspector(DOUBLE, 2));
+ buildAndCheckIR("doubleWithIndirectInvokeUserTest", testInspector(getDouble(), 2));
}
private static Consumer<IRCode> testInspector(
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
index 91fdee5..198ffa3 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
@@ -58,10 +58,10 @@
@RunWith(Parameterized.class)
public class TypeAnalysisTest extends SmaliTestBase {
private static final InternalOptions TEST_OPTIONS = new InternalOptions();
- private static final TypeLatticeElement NULL = TypeLatticeElement.NULL;
- private static final TypeLatticeElement SINGLE = TypeLatticeElement.SINGLE;
- private static final TypeLatticeElement INT = TypeLatticeElement.INT;
- private static final TypeLatticeElement LONG = TypeLatticeElement.LONG;
+ private static final TypeLatticeElement NULL = TypeLatticeElement.getNull();
+ private static final TypeLatticeElement SINGLE = TypeLatticeElement.getSingle();
+ private static final TypeLatticeElement INT = TypeLatticeElement.getInt();
+ private static final TypeLatticeElement LONG = TypeLatticeElement.getLong();
private final String dirName;
private final String smaliFileName;
@@ -113,8 +113,7 @@
byte[] content = Smali.compile(smaliStringBuilder.toString());
AndroidApp app = AndroidApp.builder().addDexProgramData(content, Origin.unknown()).build();
DexApplication dexApplication =
- new ApplicationReader(app, TEST_OPTIONS, new Timing("TypeAnalysisTest.appReader"))
- .read().toDirect();
+ new ApplicationReader(app, TEST_OPTIONS, Timing.empty()).read().toDirect();
inspection.accept(
AppView.createForD8(new AppInfo(dexApplication), TEST_OPTIONS),
new CodeInspector(dexApplication));
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeConstraintOnTrivialPhiTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeConstraintOnTrivialPhiTest.java
index ad428dd..80d752e 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeConstraintOnTrivialPhiTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeConstraintOnTrivialPhiTest.java
@@ -4,10 +4,6 @@
package com.android.tools.r8.ir.analysis.type;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.DOUBLE;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.FLOAT;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.INT;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.LONG;
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.TestParameters;
@@ -94,22 +90,24 @@
@Test
public void testIntConstraintOnTrivialPhi() throws Exception {
- buildAndCheckIR("intConstraintOnTrivialPhiTest", testInspector(INT));
+ buildAndCheckIR("intConstraintOnTrivialPhiTest", testInspector(TypeLatticeElement.getInt()));
}
@Test
public void testFloatConstraintOnTrivialPhi() throws Exception {
- buildAndCheckIR("floatConstraintOnTrivialPhiTest", testInspector(FLOAT));
+ buildAndCheckIR(
+ "floatConstraintOnTrivialPhiTest", testInspector(TypeLatticeElement.getFloat()));
}
@Test
public void testLongConstraintOnTrivialPhi() throws Exception {
- buildAndCheckIR("longConstraintOnTrivialPhiTest", testInspector(LONG));
+ buildAndCheckIR("longConstraintOnTrivialPhiTest", testInspector(TypeLatticeElement.getLong()));
}
@Test
public void testDoubleConstraintOnTrivialPhi() throws Exception {
- buildAndCheckIR("doubleConstraintOnTrivialPhiTest", testInspector(DOUBLE));
+ buildAndCheckIR(
+ "doubleConstraintOnTrivialPhiTest", testInspector(TypeLatticeElement.getDouble()));
}
private static Consumer<IRCode> testInspector(TypeLatticeElement expectedType) {
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElementWidthTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElementWidthTest.java
index d6b9598..f2a20a6 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElementWidthTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElementWidthTest.java
@@ -18,8 +18,7 @@
@Test
public void testArrayWidth() {
ArrayTypeLatticeElement arrayType =
- ArrayTypeLatticeElement.create(
- IntTypeLatticeElement.getInstance(), Nullability.maybeNull());
+ ArrayTypeLatticeElement.create(TypeLatticeElement.getInt(), Nullability.maybeNull());
assertFalse(arrayType.isSinglePrimitive());
assertFalse(arrayType.isWidePrimitive());
assertEquals(1, arrayType.requiredRegisters());
@@ -27,51 +26,51 @@
@Test
public void testBooleanWidth() {
- assertTrue(BooleanTypeLatticeElement.getInstance().isSinglePrimitive());
- assertFalse(BooleanTypeLatticeElement.getInstance().isWidePrimitive());
- assertEquals(1, BooleanTypeLatticeElement.getInstance().requiredRegisters());
+ assertTrue(TypeLatticeElement.getBoolean().isSinglePrimitive());
+ assertFalse(TypeLatticeElement.getBoolean().isWidePrimitive());
+ assertEquals(1, TypeLatticeElement.getBoolean().requiredRegisters());
}
@Test
public void testByteWidth() {
- assertTrue(ByteTypeLatticeElement.getInstance().isSinglePrimitive());
- assertFalse(ByteTypeLatticeElement.getInstance().isWidePrimitive());
- assertEquals(1, ByteTypeLatticeElement.getInstance().requiredRegisters());
+ assertTrue(TypeLatticeElement.getByte().isSinglePrimitive());
+ assertFalse(TypeLatticeElement.getByte().isWidePrimitive());
+ assertEquals(1, TypeLatticeElement.getByte().requiredRegisters());
}
@Test
public void testCharWidth() {
- assertTrue(CharTypeLatticeElement.getInstance().isSinglePrimitive());
- assertFalse(CharTypeLatticeElement.getInstance().isWidePrimitive());
- assertEquals(1, CharTypeLatticeElement.getInstance().requiredRegisters());
+ assertTrue(TypeLatticeElement.getChar().isSinglePrimitive());
+ assertFalse(TypeLatticeElement.getChar().isWidePrimitive());
+ assertEquals(1, TypeLatticeElement.getChar().requiredRegisters());
}
@Test
public void testDoubleWidth() {
- assertTrue(DoubleTypeLatticeElement.getInstance().isWidePrimitive());
- assertFalse(DoubleTypeLatticeElement.getInstance().isSinglePrimitive());
- assertEquals(2, DoubleTypeLatticeElement.getInstance().requiredRegisters());
+ assertTrue(TypeLatticeElement.getDouble().isWidePrimitive());
+ assertFalse(TypeLatticeElement.getDouble().isSinglePrimitive());
+ assertEquals(2, TypeLatticeElement.getDouble().requiredRegisters());
}
@Test
public void testFloatWidth() {
- assertTrue(FloatTypeLatticeElement.getInstance().isSinglePrimitive());
- assertFalse(FloatTypeLatticeElement.getInstance().isWidePrimitive());
- assertEquals(1, FloatTypeLatticeElement.getInstance().requiredRegisters());
+ assertTrue(TypeLatticeElement.getFloat().isSinglePrimitive());
+ assertFalse(TypeLatticeElement.getFloat().isWidePrimitive());
+ assertEquals(1, TypeLatticeElement.getFloat().requiredRegisters());
}
@Test
public void testIntWidth() {
- assertTrue(IntTypeLatticeElement.getInstance().isSinglePrimitive());
- assertFalse(IntTypeLatticeElement.getInstance().isWidePrimitive());
- assertEquals(1, IntTypeLatticeElement.getInstance().requiredRegisters());
+ assertTrue(TypeLatticeElement.getInt().isSinglePrimitive());
+ assertFalse(TypeLatticeElement.getInt().isWidePrimitive());
+ assertEquals(1, TypeLatticeElement.getInt().requiredRegisters());
}
@Test
public void testLongWidth() {
- assertTrue(LongTypeLatticeElement.getInstance().isWidePrimitive());
- assertFalse(LongTypeLatticeElement.getInstance().isSinglePrimitive());
- assertEquals(2, LongTypeLatticeElement.getInstance().requiredRegisters());
+ assertTrue(TypeLatticeElement.getLong().isWidePrimitive());
+ assertFalse(TypeLatticeElement.getLong().isSinglePrimitive());
+ assertEquals(2, TypeLatticeElement.getLong().requiredRegisters());
}
@Test
@@ -87,8 +86,8 @@
@Test
public void testShortWidth() {
- assertTrue(ShortTypeLatticeElement.getInstance().isSinglePrimitive());
- assertFalse(ShortTypeLatticeElement.getInstance().isWidePrimitive());
- assertEquals(1, ShortTypeLatticeElement.getInstance().requiredRegisters());
+ assertTrue(TypeLatticeElement.getShort().isSinglePrimitive());
+ assertFalse(TypeLatticeElement.getShort().isWidePrimitive());
+ assertEquals(1, TypeLatticeElement.getShort().requiredRegisters());
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
index 3c47534..e0465b3 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
@@ -4,9 +4,9 @@
package com.android.tools.r8.ir.analysis.type;
import static com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement.computeLeastUpperBoundOfInterfaces;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.BOTTOM;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.TOP;
import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.fromDexType;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getBottom;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getTop;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -56,7 +56,7 @@
.addLibraryFiles(ToolHelper.getDefaultAndroidJar())
.build(),
options,
- new Timing(TypeLatticeTest.class.getName()))
+ Timing.empty())
.read()
.toDirect();
factory = options.itemFactory;
@@ -64,19 +64,19 @@
}
private TopTypeLatticeElement top() {
- return TypeLatticeElement.TOP;
+ return getTop();
}
private BottomTypeLatticeElement bottom() {
- return TypeLatticeElement.BOTTOM;
+ return getBottom();
}
private SinglePrimitiveTypeLatticeElement single() {
- return TypeLatticeElement.SINGLE;
+ return TypeLatticeElement.getSingle();
}
private WidePrimitiveTypeLatticeElement wide() {
- return TypeLatticeElement.WIDE;
+ return TypeLatticeElement.getWide();
}
private TypeLatticeElement element(DexType type) {
@@ -510,9 +510,7 @@
assertTrue(strictlyLessThan(
array(2, factory.objectType),
array(1, factory.objectType)));
- assertTrue(strictlyLessThan(
- ReferenceTypeLatticeElement.getNullTypeLatticeElement(),
- array(1, factory.classType)));
+ assertTrue(strictlyLessThan(TypeLatticeElement.getNull(), array(1, factory.classType)));
}
@Test
@@ -527,10 +525,10 @@
element(factory.objectType, Nullability.maybeNull())));
assertFalse(
lessThanOrEqualUpToNullability(array(3, factory.stringType), array(4, factory.stringType)));
- assertTrue(lessThanOrEqualUpToNullability(BOTTOM, element(factory.objectType)));
- assertFalse(lessThanOrEqualUpToNullability(element(factory.objectType), BOTTOM));
- assertFalse(lessThanOrEqualUpToNullability(TOP, element(factory.objectType)));
- assertTrue(lessThanOrEqualUpToNullability(element(factory.objectType), TOP));
+ assertTrue(lessThanOrEqualUpToNullability(getBottom(), element(factory.objectType)));
+ assertFalse(lessThanOrEqualUpToNullability(element(factory.objectType), getBottom()));
+ assertFalse(lessThanOrEqualUpToNullability(getTop(), element(factory.objectType)));
+ assertTrue(lessThanOrEqualUpToNullability(element(factory.objectType), getTop()));
}
@Test
@@ -545,10 +543,10 @@
assertFalse(lessThanOrEqual(nullableType, nonNullType));
// Check that the class-type null is also more specific than nullableType.
- assertTrue(strictlyLessThan(TypeLatticeElement.NULL, nullableType));
+ assertTrue(strictlyLessThan(TypeLatticeElement.getNull(), nullableType));
assertTrue(
strictlyLessThan(
- TypeLatticeElement.NULL,
+ TypeLatticeElement.getNull(),
nullableType.getOrCreateVariant(Nullability.definitelyNull())));
}
@@ -556,7 +554,7 @@
public void testNotNullOfNullGivesBottom() {
assertEquals(
Nullability.bottom(),
- ReferenceTypeLatticeElement.NULL.asMeetWithNotNull().nullability());
+ ReferenceTypeLatticeElement.getNull().asMeetWithNotNull().nullability());
}
@Test
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/UnconstrainedPrimitiveTypeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/UnconstrainedPrimitiveTypeTest.java
index 09c27ea..d08b323 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/UnconstrainedPrimitiveTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/UnconstrainedPrimitiveTypeTest.java
@@ -4,8 +4,6 @@
package com.android.tools.r8.ir.analysis.type;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.INT;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.LONG;
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.TestParameters;
@@ -88,27 +86,32 @@
@Test
public void testUnconstrainedSingleWithNoUsers() throws Exception {
- buildAndCheckIR("unconstrainedSingleWithNoUsersTest", testInspector(INT, 1));
+ buildAndCheckIR(
+ "unconstrainedSingleWithNoUsersTest", testInspector(TypeLatticeElement.getInt(), 1));
}
@Test
public void testUnconstrainedSingleWithIfUser() throws Exception {
- buildAndCheckIR("unconstrainedSingleWithIfUserTest", testInspector(INT, 2));
+ buildAndCheckIR(
+ "unconstrainedSingleWithIfUserTest", testInspector(TypeLatticeElement.getInt(), 2));
}
@Test
public void testUnconstrainedSingleWithIfZeroUser() throws Exception {
- buildAndCheckIR("unconstrainedSingleWithIfZeroUserTest", testInspector(INT, 1));
+ buildAndCheckIR(
+ "unconstrainedSingleWithIfZeroUserTest", testInspector(IntTypeLatticeElement.getInt(), 1));
}
@Test
public void testUnconstrainedWideWithNoUsers() throws Exception {
- buildAndCheckIR("unconstrainedWideWithNoUsersTest", testInspector(LONG, 1));
+ buildAndCheckIR(
+ "unconstrainedWideWithNoUsersTest", testInspector(TypeLatticeElement.getLong(), 1));
}
@Test
public void testUnconstrainedWideWithIfUser() throws Exception {
- buildAndCheckIR("unconstrainedWideWithIfUserTest", testInspector(LONG, 2));
+ buildAndCheckIR(
+ "unconstrainedWideWithIfUserTest", testInspector(TypeLatticeElement.getLong(), 2));
}
private static Consumer<IRCode> testInspector(
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
index 8b027e4..6b18b6b 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
@@ -40,7 +40,7 @@
private final ExecutorService executorService = ThreadUtils.getExecutorService(options);
public PartialCallGraphTest() throws Exception {
- Timing timing = new Timing("PartialCallGraphTest.setup");
+ Timing timing = Timing.empty();
AndroidApp app = testForD8().addProgramClasses(TestClass.class).compile().app;
DexApplication application = new ApplicationReader(app, options, timing).read().toDirect();
AppView<AppInfoWithSubtyping> appView =
@@ -66,8 +66,7 @@
@Test
public void testFullGraph() throws Exception {
- CallGraph cg =
- new CallGraphBuilder(appView).build(executorService, new Timing("testFullGraph"));
+ CallGraph cg = new CallGraphBuilder(appView).build(executorService, Timing.empty());
Node m1 = findNode(cg.nodes, "m1");
Node m2 = findNode(cg.nodes, "m2");
Node m3 = findNode(cg.nodes, "m3");
@@ -115,7 +114,7 @@
CallGraph pg =
new PartialCallGraphBuilder(appView, ImmutableSet.of(em1, em2, em4, em5))
- .build(executorService, new Timing("tetPartialGraph"));
+ .build(executorService, Timing.empty());
Node m1 = findNode(pg.nodes, "m1");
Node m2 = findNode(pg.nodes, "m2");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
index 2d626a2..607839e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
@@ -76,14 +76,14 @@
IRMetadata metadata = IRMetadata.unknown();
Position position = Position.testingPosition();
- Value v3 = new Value(3, TypeLatticeElement.LONG, null);
+ Value v3 = new Value(3, TypeLatticeElement.getLong(), null);
v3.setNeedsRegister(true);
new MockLiveIntervals(v3);
Instruction instruction = new ConstNumber(v3, 0);
instruction.setPosition(position);
block.add(instruction, metadata);
- Value v0 = new Value(0, TypeLatticeElement.LONG, null);
+ Value v0 = new Value(0, TypeLatticeElement.getLong(), null);
v0.setNeedsRegister(true);
new MockLiveIntervals(v0);
instruction = new ConstNumber(v0, 10);
@@ -94,14 +94,14 @@
instruction.setPosition(position);
block.add(instruction, metadata);
- Value v2 = new Value(2, TypeLatticeElement.INT, null);
+ Value v2 = new Value(2, TypeLatticeElement.getInt(), null);
v2.setNeedsRegister(true);
new MockLiveIntervals(v2);
instruction = new ConstNumber(v2, 10);
instruction.setPosition(position);
block.add(instruction, metadata);
- Value v1 = new Value(1, TypeLatticeElement.INT, null);
+ Value v1 = new Value(1, TypeLatticeElement.getInt(), null);
v1.setNeedsRegister(true);
new MockLiveIntervals(v1);
instruction = new Move(v1 ,v2);
@@ -112,7 +112,7 @@
instruction.setPosition(position);
block.add(instruction, metadata);
- Value v0_2 = new Value(0, TypeLatticeElement.LONG, null);
+ Value v0_2 = new Value(0, TypeLatticeElement.getLong(), null);
v0_2.setNeedsRegister(true);
new MockLiveIntervals(v0_2);
instruction = new ConstNumber(v0_2, 10);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstraintWithTargetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstraintWithTargetTest.java
index ee19ffd..a24de2f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstraintWithTargetTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstraintWithTargetTest.java
@@ -29,11 +29,9 @@
InternalOptions options = new InternalOptions();
DexApplication application =
new ApplicationReader(
- AndroidApp.builder()
- .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
- .build(),
+ AndroidApp.builder().addLibraryFiles(ToolHelper.getDefaultAndroidJar()).build(),
options,
- new Timing(ConstraintWithTargetTest.class.getName()))
+ Timing.empty())
.read()
.toDirect();
factory = options.itemFactory;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
index 26535f1..838a681 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
@@ -17,7 +17,7 @@
public abstract class NonNullTrackerTestBase extends TestBase {
protected AppView<?> build(Class<?> mainClass) throws Exception {
- Timing timing = new Timing(getClass().getSimpleName());
+ Timing timing = Timing.empty();
AndroidApp app = buildAndroidApp(ToolHelper.getClassAsBytes(mainClass));
InternalOptions options = new InternalOptions();
DexApplication dexApplication = new ApplicationReader(app, options, timing).read().toDirect();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
index fdb982f..5d48fa6 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
@@ -59,7 +59,7 @@
block2.setFilledForTesting();
BasicBlock block1 = new BasicBlock();
block1.setNumber(1);
- Value value = new Value(0, TypeLatticeElement.INT, null);
+ Value value = new Value(0, TypeLatticeElement.getInt(), null);
Instruction number = new ConstNumber(value, 0);
number.setPosition(position);
block1.add(number, metadata);
@@ -94,7 +94,7 @@
@Test
public void trivialGotoLoopAsFallthrough() {
InternalOptions options = new InternalOptions();
- DexApplication app = DexApplication.builder(new InternalOptions(), new Timing("")).build();
+ DexApplication app = DexApplication.builder(new InternalOptions(), Timing.empty()).build();
AppView<AppInfo> appView = AppView.createForD8(new AppInfo(app), options);
// Setup block structure:
// block0:
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/StaticFinalLibraryFieldCanonicalizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/StaticFinalLibraryFieldCanonicalizationTest.java
new file mode 100644
index 0000000..7da9e81
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/StaticFinalLibraryFieldCanonicalizationTest.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2020, 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.canonicalization;
+
+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.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableSet;
+import java.util.List;
+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 StaticFinalLibraryFieldCanonicalizationTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final boolean systemHasClassInitializationSideEffects;
+
+ @Parameters(name = "{0}, System has class initialization side effects: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+ }
+
+ public StaticFinalLibraryFieldCanonicalizationTest(
+ TestParameters parameters, boolean systemHasClassInitializationSideEffects) {
+ this.systemHasClassInitializationSideEffects = systemHasClassInitializationSideEffects;
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class)
+ .addKeepMainRule(TestClass.class)
+ .addOptionsModification(
+ options -> {
+ if (!systemHasClassInitializationSideEffects) {
+ options.itemFactory.libraryClassesWithoutStaticInitialization =
+ ImmutableSet.of(options.itemFactory.createType("Ljava/lang/System;"));
+ }
+ })
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+ assertThat(testClassSubject, isPresent());
+
+ MethodSubject mainMethodSubject = testClassSubject.mainMethod();
+ assertThat(mainMethodSubject, isPresent());
+ assertEquals(
+ systemHasClassInitializationSideEffects || parameters.isCfRuntime() ? 2 : 1,
+ mainMethodSubject.streamInstructions().filter(InstructionSubject::isStaticGet).count());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.print("Hello");
+ System.out.println(" world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/NoLongerSyntheticAfterVerticalClassMergingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/NoLongerSyntheticAfterVerticalClassMergingTest.java
new file mode 100644
index 0000000..6fe9d09
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/NoLongerSyntheticAfterVerticalClassMergingTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2020, 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.classmerger.vertical;
+
+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.assertFalse;
+
+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.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NoLongerSyntheticAfterVerticalClassMergingTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public NoLongerSyntheticAfterVerticalClassMergingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class, A.class)
+ .addProgramClassFileData(
+ transformer(B.class).setSynthetic(B.class.getDeclaredMethod("m")).transform())
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("B.m()");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ assertThat(inspector.clazz(A.class), not(isPresent()));
+
+ ClassSubject bClassSubject = inspector.clazz(B.class);
+ assertThat(bClassSubject, isPresent());
+
+ MethodSubject mMethodSubject = bClassSubject.uniqueMethodWithName("m");
+ assertThat(mMethodSubject, isPresent());
+ assertFalse(mMethodSubject.getMethod().accessFlags.isSynthetic());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ A a = new B();
+ a.m();
+ }
+ }
+
+ abstract static class A {
+
+ public abstract void m();
+ }
+
+ @NeverClassInline
+ static class B extends A {
+
+ @NeverInline
+ @Override
+ public void m() {
+ System.out.println("B.m()");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/lambda/LambdaMethodInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/lambda/LambdaMethodInliningTest.java
new file mode 100644
index 0000000..c90173e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/lambda/LambdaMethodInliningTest.java
@@ -0,0 +1,133 @@
+// Copyright (c) 2020, 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.lambda;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.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 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;
+
+@RunWith(Parameterized.class)
+public class LambdaMethodInliningTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public LambdaMethodInliningTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(LambdaMethodInliningTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .enableMergeAnnotations()
+ .addOptionsModification(options -> options.enableClassInlining = false)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello", "Hello");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject classSubject = inspector.clazz(TestClass.class);
+ assertThat(classSubject, isPresent());
+
+ MethodSubject testClassMethodSubject = classSubject.uniqueMethodWithName("testClass");
+ assertThat(testClassMethodSubject, isPresent());
+ assertTrue(
+ testClassMethodSubject
+ .streamInstructions()
+ .noneMatch(InstructionSubject::isInvokeInterface));
+ assertTrue(
+ testClassMethodSubject
+ .streamInstructions()
+ .anyMatch(
+ instruction ->
+ instruction.isInvokeVirtual()
+ && instruction.getMethod().toSourceString().contains("println")));
+
+ MethodSubject testLambdaMethodSubject = classSubject.uniqueMethodWithName("testLambda");
+ assertThat(testLambdaMethodSubject, isPresent());
+ assertTrue(
+ testLambdaMethodSubject
+ .streamInstructions()
+ .noneMatch(InstructionSubject::isInvokeInterface));
+ assertTrue(
+ testLambdaMethodSubject
+ .streamInstructions()
+ .anyMatch(
+ instruction ->
+ instruction.isInvokeVirtual()
+ && instruction.getMethod().toSourceString().contains("println")));
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ testClass(new A());
+ testLambda(
+ () -> {
+ System.out.print("H");
+ System.out.print("e");
+ System.out.print("l");
+ System.out.print("l");
+ System.out.println("o");
+ });
+ }
+
+ @NeverInline
+ static void testClass(ImplementedByClass obj) {
+ obj.m();
+ }
+
+ @NeverInline
+ static void testLambda(ImplementedByLambda obj) {
+ obj.m();
+ }
+ }
+
+ @NeverMerge
+ interface ImplementedByClass {
+
+ void m();
+ }
+
+ interface ImplementedByLambda {
+
+ void m();
+ }
+
+ static class A implements ImplementedByClass {
+
+ @Override
+ public void m() {
+ System.out.print("H");
+ System.out.print("e");
+ System.out.print("l");
+ System.out.print("l");
+ System.out.println("o");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/logging/AndroidLogRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/logging/AndroidLogRemovalTest.java
new file mode 100644
index 0000000..1314274
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/logging/AndroidLogRemovalTest.java
@@ -0,0 +1,195 @@
+// Copyright (c) 2020, 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.logging;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.List;
+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 AndroidLogRemovalTest extends TestBase {
+
+ private final int maxRemovedAndroidLogLevel;
+ private final TestParameters parameters;
+
+ @Parameters(name = "{1}, log level: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ ImmutableList.of(1, 2, 3, 4, 5, 6, 7),
+ getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
+
+ public AndroidLogRemovalTest(int maxRemovedAndroidLogLevel, TestParameters parameters) {
+ this.maxRemovedAndroidLogLevel = maxRemovedAndroidLogLevel;
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ Path libraryFile =
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(
+ transformer(Log.class).setClassDescriptor("Landroid/util/Log;").transform())
+ .addKeepAllClassesRule()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .writeToZip();
+
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(
+ transformer(TestClass.class)
+ .transformMethodInsnInMethod(
+ "main",
+ (opcode, owner, name, descriptor, isInterface, continuation) ->
+ continuation.apply(
+ opcode,
+ owner.endsWith("$Log") ? "android/util/Log" : owner,
+ name,
+ descriptor,
+ isInterface))
+ .transform())
+ .addLibraryFiles(libraryFile, runtimeJar(parameters.getBackend()))
+ .addKeepMainRule(TestClass.class)
+ .addKeepRules("-maximumremovedandroidloglevel " + maxRemovedAndroidLogLevel)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .addRunClasspathFiles(libraryFile)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(getExpectedOutputForMaxLogLevel());
+ }
+
+ private String getExpectedOutputForMaxLogLevel() {
+ switch (maxRemovedAndroidLogLevel) {
+ case 1:
+ return StringUtils.join(
+ "",
+ StringUtils.times(StringUtils.lines("[R8] VERBOSE."), 2),
+ StringUtils.times(StringUtils.lines("[R8] DEBUG."), 2),
+ StringUtils.times(StringUtils.lines("[R8] INFO."), 2),
+ StringUtils.times(StringUtils.lines("[R8] WARN."), 2),
+ StringUtils.times(StringUtils.lines("[R8] ERROR."), 2),
+ StringUtils.times(StringUtils.lines("[R8] ASSERT."), 2));
+ case 2:
+ return StringUtils.join(
+ "",
+ StringUtils.times(StringUtils.lines("[R8] DEBUG."), 2),
+ StringUtils.times(StringUtils.lines("[R8] INFO."), 2),
+ StringUtils.times(StringUtils.lines("[R8] WARN."), 2),
+ StringUtils.times(StringUtils.lines("[R8] ERROR."), 2),
+ StringUtils.times(StringUtils.lines("[R8] ASSERT."), 2));
+ case 3:
+ return StringUtils.join(
+ "",
+ StringUtils.times(StringUtils.lines("[R8] INFO."), 2),
+ StringUtils.times(StringUtils.lines("[R8] WARN."), 2),
+ StringUtils.times(StringUtils.lines("[R8] ERROR."), 2),
+ StringUtils.times(StringUtils.lines("[R8] ASSERT."), 2));
+ case 4:
+ return StringUtils.join(
+ "",
+ StringUtils.times(StringUtils.lines("[R8] WARN."), 2),
+ StringUtils.times(StringUtils.lines("[R8] ERROR."), 2),
+ StringUtils.times(StringUtils.lines("[R8] ASSERT."), 2));
+ case 5:
+ return StringUtils.join(
+ "",
+ StringUtils.times(StringUtils.lines("[R8] ERROR."), 2),
+ StringUtils.times(StringUtils.lines("[R8] ASSERT."), 2));
+ case 6:
+ return StringUtils.join("", StringUtils.times(StringUtils.lines("[R8] ASSERT."), 2));
+ case 7:
+ return "";
+ default:
+ throw new Unreachable();
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ Log.v("R8", "VERBOSE.");
+ if (Log.isLoggable("R8", Log.VERBOSE)) {
+ System.out.println("[R8] VERBOSE.");
+ }
+
+ Log.d("R8", "DEBUG.");
+ if (Log.isLoggable("R8", Log.DEBUG)) {
+ System.out.println("[R8] DEBUG.");
+ }
+
+ Log.i("R8", "INFO.");
+ if (Log.isLoggable("R8", Log.INFO)) {
+ System.out.println("[R8] INFO.");
+ }
+
+ Log.w("R8", "WARN.");
+ if (Log.isLoggable("R8", Log.WARN)) {
+ System.out.println("[R8] WARN.");
+ }
+
+ Log.e("R8", "ERROR.");
+ if (Log.isLoggable("R8", Log.ERROR)) {
+ System.out.println("[R8] ERROR.");
+ }
+
+ Log.wtf("R8", "ASSERT.");
+ if (Log.isLoggable("R8", Log.ASSERT)) {
+ System.out.println("[R8] ASSERT.");
+ }
+ }
+ }
+
+ public static class Log {
+
+ public static final int VERBOSE = 2;
+ public static final int DEBUG = 3;
+ public static final int INFO = 4;
+ public static final int WARN = 5;
+ public static final int ERROR = 6;
+ public static final int ASSERT = 7;
+
+ public static boolean isLoggable(String tag, int level) {
+ return true;
+ }
+
+ public static int v(String tag, String message) {
+ System.out.println("[" + tag + "] " + message);
+ return 42;
+ }
+
+ public static int d(String tag, String message) {
+ System.out.println("[" + tag + "] " + message);
+ return 42;
+ }
+
+ public static int i(String tag, String message) {
+ System.out.println("[" + tag + "] " + message);
+ return 42;
+ }
+
+ public static int w(String tag, String message) {
+ System.out.println("[" + tag + "] " + message);
+ return 42;
+ }
+
+ public static int e(String tag, String message) {
+ System.out.println("[" + tag + "] " + message);
+ return 42;
+ }
+
+ public static int wtf(String tag, String message) {
+ System.out.println("[" + tag + "] " + message);
+ return 42;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeInstanceFieldValueTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeInstanceFieldValueTest.java
index 32d6962..e1e6b4b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeInstanceFieldValueTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeInstanceFieldValueTest.java
@@ -5,9 +5,9 @@
package com.android.tools.r8.ir.optimize.membervaluepropagation;
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 static org.junit.Assert.assertFalse;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -51,8 +51,7 @@
.compile()
.inspect(this::inspect)
.run(parameters.getRuntime(), TestClass.class)
- // TODO(b/147835946): Should be "Hello world!".
- .assertSuccessWithOutput("");
+ .assertSuccessWithOutputLines("Hello world!");
}
private void inspect(CodeInspector inspector) {
@@ -64,7 +63,7 @@
FieldSubject alwaysTrueNoSideEffectsFieldSubject =
configClassSubject.uniqueFieldWithName("alwaysTrueNoSideEffects");
- assertThat(alwaysTrueNoSideEffectsFieldSubject, isPresent());
+ assertThat(alwaysTrueNoSideEffectsFieldSubject, not(isPresent()));
ClassSubject testClassSubject = inspector.clazz(TestClass.class);
assertThat(testClassSubject, isPresent());
@@ -79,13 +78,6 @@
.map(InstructionSubject::getField)
.filter(alwaysTrueFieldSubject.getField().field::equals)
.count());
- // TODO(b/147835946): Should be true.
- assertFalse(
- mainMethodSubject
- .streamInstructions()
- .filter(InstructionSubject::isInstanceGet)
- .map(InstructionSubject::getField)
- .noneMatch(alwaysTrueNoSideEffectsFieldSubject.getField().field::equals));
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/ConstructorWithExceptionalControlFlowTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/ConstructorWithExceptionalControlFlowTest.java
new file mode 100644
index 0000000..e662d29
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/ConstructorWithExceptionalControlFlowTest.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2020, 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.membervaluepropagation;
+
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ConstructorWithExceptionalControlFlowTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection params() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ConstructorWithExceptionalControlFlowTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ConstructorWithExceptionalControlFlowTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("false");
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println(new A().alwaysFalse);
+ }
+ }
+
+ static class A {
+
+ boolean alwaysFalse;
+
+ A() {
+ try {
+ throwRuntimeException();
+ } catch (Exception e) {
+ return;
+ }
+ alwaysFalse = true;
+ }
+
+ @NeverInline
+ static void throwRuntimeException() {
+ if (System.currentTimeMillis() >= 0) {
+ throw new RuntimeException();
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWithDefaultValueAssignmentAfterDefaultsOptimizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWithDefaultValueAssignmentAfterDefaultsOptimizationTest.java
new file mode 100644
index 0000000..df33e2b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWithDefaultValueAssignmentAfterDefaultsOptimizationTest.java
@@ -0,0 +1,79 @@
+// Copyright (c) 2020, 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.membervaluepropagation;
+
+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.graph.DexEncodedMethod;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.Optional;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class FieldWithDefaultValueAssignmentAfterDefaultsOptimizationTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public FieldWithDefaultValueAssignmentAfterDefaultsOptimizationTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(FieldWithDefaultValueAssignmentAfterDefaultsOptimizationTest.class)
+ .addKeepMainRule(TestClass.class)
+ .addOptionsModification(options -> options.testing.waveModifier = this::waveModifier)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("42");
+ }
+
+ private void waveModifier(Deque<Collection<DexEncodedMethod>> waves) {
+ Collection<DexEncodedMethod> initialWave = waves.getFirst();
+ Optional<DexEncodedMethod> printFieldMethod =
+ initialWave.stream()
+ .filter(method -> method.method.name.toSourceString().equals("printField"))
+ .findFirst();
+ assertTrue(printFieldMethod.isPresent());
+ initialWave.remove(printFieldMethod.get());
+
+ ArrayList<DexEncodedMethod> lastWave = new ArrayList<>();
+ lastWave.add(printFieldMethod.get());
+ waves.addLast(lastWave);
+ }
+
+ static class TestClass {
+
+ static int field = 42;
+
+ static {
+ printField();
+ field = 0;
+ }
+
+ @NeverInline
+ static void printField() {
+ System.out.println(field); // Should print 42.
+ }
+
+ public static void main(String[] args) {}
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FinalFieldWithDefaultValueAssignmentPropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FinalFieldWithDefaultValueAssignmentPropagationTest.java
index 0f714ef..8710a6a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FinalFieldWithDefaultValueAssignmentPropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FinalFieldWithDefaultValueAssignmentPropagationTest.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.optimize.membervaluepropagation;
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.assertFalse;
@@ -51,8 +52,7 @@
private void inspect(CodeInspector inspector) {
ClassSubject testClassSubject = inspector.clazz(TestClass.class);
assertThat(testClassSubject, isPresent());
- // TODO(b/147799637): Should be absent.
- assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+ assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
ClassSubject configClassSubject = inspector.clazz(Config.class);
assertThat(configClassSubject, isPresent());
@@ -67,7 +67,8 @@
static class TestClass {
public static void main(String[] args) {
- if (new Config().alwaysFalse) {
+ Config nullableConfig = System.currentTimeMillis() >= 0 ? new Config() : null;
+ if (nullableConfig.alwaysFalse) {
dead();
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonFinalFieldWithDefaultValueAssignmentPropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonFinalFieldWithDefaultValueAssignmentPropagationTest.java
index 2a37168..f68a131 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonFinalFieldWithDefaultValueAssignmentPropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonFinalFieldWithDefaultValueAssignmentPropagationTest.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.optimize.membervaluepropagation;
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.assertFalse;
@@ -53,8 +54,7 @@
private void inspect(CodeInspector inspector) {
ClassSubject testClassSubject = inspector.clazz(TestClass.class);
assertThat(testClassSubject, isPresent());
- // TODO(b/147799637): Should be absent.
- assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+ assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
ClassSubject configClassSubject = inspector.clazz(Config.class);
assertThat(configClassSubject, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/StaticFieldAssignedOnlyInInstanceConstructorTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/StaticFieldAssignedOnlyInInstanceConstructorTest.java
new file mode 100644
index 0000000..6b69ae8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/StaticFieldAssignedOnlyInInstanceConstructorTest.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2020, 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.membervaluepropagation;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+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 StaticFieldAssignedOnlyInInstanceConstructorTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection params() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public StaticFieldAssignedOnlyInInstanceConstructorTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(StaticFieldAssignedOnlyInInstanceConstructorTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("0");
+ }
+
+ static class TestClass {
+
+ static int FIELD;
+
+ public static void main(String[] args) {
+ System.out.println(FIELD); // Should print 0.
+ new TestClass();
+ }
+
+ public TestClass() {
+ FIELD = 42;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/NestedStringBuilderTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/NestedStringBuilderTest.java
index 4c4951d..38ddc76 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/NestedStringBuilderTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/NestedStringBuilderTest.java
@@ -4,34 +4,19 @@
package com.android.tools.r8.ir.optimize.string;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.ForceInline;
-import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestCompileResult;
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 com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.Streams;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-class NestedStringBuilders {
-
- public static void main(String... args) {
- System.out.println(concat("one", args[0]) + "two");
- }
-
- @ForceInline
- public static String concat(String one, String two) {
- return one + two;
- }
-}
-
@RunWith(Parameterized.class)
public class NestedStringBuilderTest extends TestBase {
private static final Class<?> MAIN = NestedStringBuilders.class;
@@ -39,7 +24,7 @@
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
private final TestParameters parameters;
@@ -48,29 +33,37 @@
this.parameters = parameters;
}
- private void test(TestCompileResult result) throws Exception {
- result
- .run(parameters.getRuntime(), MAIN.getTypeName(), "$")
- .assertSuccessWithOutput(EXPECTED);
- CodeInspector codeInspector = result.inspector();
- ClassSubject mainClass = codeInspector.clazz(MAIN);
- MethodSubject main = mainClass.mainMethod();
- long count = Streams.stream(main.iterateInstructions(instructionSubject ->
- instructionSubject.isNewInstance(StringBuilder.class.getTypeName()))).count();
- // TODO(b/113859361): should be 1 after merging StringBuilder's
- assertEquals(2, count);
- }
-
@Test
public void b113859361() throws Exception {
- R8TestCompileResult result =
- testForR8(parameters.getBackend())
- .addProgramClasses(MAIN)
- .enableForceInliningAnnotations()
- .addKeepMainRule(MAIN)
- .setMinApi(parameters.getRuntime())
- .compile();
- test(result);
+ assumeTrue("CF does not rewrite move results.", parameters.isDexRuntime());
+
+ testForR8(parameters.getBackend())
+ .addProgramClasses(MAIN)
+ .enableForceInliningAnnotations()
+ .addKeepMainRule(MAIN)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), MAIN.getTypeName(), "$")
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(codeInspector -> {
+ ClassSubject mainClass = codeInspector.clazz(MAIN);
+ MethodSubject main = mainClass.mainMethod();
+ assertEquals(
+ // TODO(b/113859361): should be 1 after merging StringBuilder's
+ 2,
+ main.streamInstructions().filter(
+ i -> i.isNewInstance(StringBuilder.class.getTypeName())).count());
+ });
}
+ static class NestedStringBuilders {
+
+ public static void main(String... args) {
+ System.out.println(concat("one", args[0]) + "two");
+ }
+
+ @ForceInline
+ public static String concat(String one, String two) {
+ return one + two;
+ }
+ }
}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java
index e40b4e0..761856f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.ir.optimize.string;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -14,7 +16,9 @@
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.string.StringBuilderOptimizer.BuilderState;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
import java.util.function.Consumer;
import org.junit.Ignore;
import org.junit.Test;
@@ -152,26 +156,104 @@
for (Value builder : optimizer.analysis.builderStates.keySet()) {
Map<Instruction, BuilderState> perBuilderState =
optimizer.analysis.builderStates.get(builder);
- checkBuilderState(optimizer, perBuilderState, null, true);
+ checkBuilderState(optimizer, perBuilderState, "Hello,R8", true);
}
- assertEquals(0, optimizer.analysis.simplifiedBuilders.size());
+ assertEquals(1, optimizer.analysis.simplifiedBuilders.size());
+ // TODO(b/113859361): check # of merged builders.
+ // assertEquals(2, optimizer.analysis.mergedBuilders.size());
}));
}
+ @Ignore("TODO(b/113859361): merge builder.")
@Test
public void testNestedBuilders_appendBuilderResult() throws Exception {
buildAndCheckIR(
"nestedBuilders_appendBuilderResult",
checkOptimizerStates(appView, optimizer -> {
+ assertEquals(1, optimizer.analysis.builderStates.size());
+ for (Value builder : optimizer.analysis.builderStates.keySet()) {
+ Map<Instruction, BuilderState> perBuilderState =
+ optimizer.analysis.builderStates.get(builder);
+ checkBuilderState(optimizer, perBuilderState, "Hello,R8", true);
+ }
+ assertEquals(1, optimizer.analysis.simplifiedBuilders.size());
+ // TODO(b/113859361): check # of merged builders.
+ // assertEquals(2, optimizer.analysis.mergedBuilders.size());
+ }));
+ }
+
+ @Ignore("TODO(b/113859361): merge builder.")
+ @Test
+ public void testNestedBuilders_conditional() throws Exception {
+ buildAndCheckIR(
+ "nestedBuilders_conditional",
+ checkOptimizerStates(appView, optimizer -> {
+ assertEquals(1, optimizer.analysis.builderStates.size());
+ for (Value builder : optimizer.analysis.builderStates.keySet()) {
+ Map<Instruction, BuilderState> perBuilderState =
+ optimizer.analysis.builderStates.get(builder);
+ checkBuilderState(optimizer, perBuilderState, null, true);
+ }
+ assertEquals(0, optimizer.analysis.simplifiedBuilders.size());
+ // TODO(b/113859361): check # of merged builders.
+ // assertEquals(3, optimizer.analysis.mergedBuilders.size());
+ }));
+ }
+
+ @Ignore("TODO(b/113859361): merge builder.")
+ @Test
+ public void testConcatenatedBuilders_init() throws Exception {
+ buildAndCheckIR(
+ "concatenatedBuilders_init",
+ checkOptimizerStates(appView, optimizer -> {
+ assertEquals(1, optimizer.analysis.builderStates.size());
+ for (Value builder : optimizer.analysis.builderStates.keySet()) {
+ Map<Instruction, BuilderState> perBuilderState =
+ optimizer.analysis.builderStates.get(builder);
+ checkBuilderState(optimizer, perBuilderState, "Hello,R8", true);
+ }
+ assertEquals(1, optimizer.analysis.simplifiedBuilders.size());
+ // TODO(b/113859361): check # of merged builders.
+ // assertEquals(2, optimizer.analysis.mergedBuilders.size());
+ }));
+ }
+
+ @Ignore("TODO(b/113859361): merge builder.")
+ @Test
+ public void testConcatenatedBuilders_append() throws Exception {
+ buildAndCheckIR(
+ "concatenatedBuilders_append",
+ checkOptimizerStates(appView, optimizer -> {
+ assertEquals(1, optimizer.analysis.builderStates.size());
+ for (Value builder : optimizer.analysis.builderStates.keySet()) {
+ Map<Instruction, BuilderState> perBuilderState =
+ optimizer.analysis.builderStates.get(builder);
+ checkBuilderState(optimizer, perBuilderState, "Hello,R8", true);
+ }
+ assertEquals(1, optimizer.analysis.simplifiedBuilders.size());
+ // TODO(b/113859361): check # of merged builders.
+ // assertEquals(2, optimizer.analysis.mergedBuilders.size());
+ }));
+ }
+
+ @Ignore("TODO(b/113859361): merge builder.")
+ @Test
+ public void testConcatenatedBuilders_conditional() throws Exception {
+ final Set<String> expectedConstStrings = new HashSet<>();
+ expectedConstStrings.add("Hello,R8");
+ expectedConstStrings.add("D8");
+ buildAndCheckIR(
+ "concatenatedBuilders_conditional",
+ checkOptimizerStates(appView, optimizer -> {
assertEquals(2, optimizer.analysis.builderStates.size());
for (Value builder : optimizer.analysis.builderStates.keySet()) {
Map<Instruction, BuilderState> perBuilderState =
optimizer.analysis.builderStates.get(builder);
- String expectedResult =
- optimizer.analysis.simplifiedBuilders.contains(builder) ? "R8" : null;
- checkBuilderState(optimizer, perBuilderState, expectedResult, true);
+ checkBuilderStates(optimizer, perBuilderState, expectedConstStrings, true);
}
- assertEquals(1, optimizer.analysis.simplifiedBuilders.size());
+ assertEquals(2, optimizer.analysis.simplifiedBuilders.size());
+ // TODO(b/113859361): check # of merged builders.
+ // assertEquals(2, optimizer.analysis.mergedBuilders.size());
}));
}
@@ -282,16 +364,36 @@
StringBuilderOptimizer optimizer,
Map<Instruction, BuilderState> perBuilderState,
String expectedConstString,
- boolean expectToSeeToString) {
- boolean metToString = false;
+ boolean expectToSeeMethodToString) {
+ boolean seenMethodToString = false;
for (Map.Entry<Instruction, BuilderState> entry : perBuilderState.entrySet()) {
if (entry.getKey().isInvokeMethod()
&& optimizer.optimizationConfiguration.isToStringMethod(
entry.getKey().asInvokeMethod().getInvokedMethod())) {
- metToString = true;
+ seenMethodToString = true;
assertEquals(expectedConstString, optimizer.analysis.toCompileTimeString(entry.getValue()));
}
}
- assertEquals(expectToSeeToString, metToString);
+ assertEquals(expectToSeeMethodToString, seenMethodToString);
+ }
+
+ static void checkBuilderStates(
+ StringBuilderOptimizer optimizer,
+ Map<Instruction, BuilderState> perBuilderState,
+ Set<String> expectedConstStrings,
+ boolean expectToSeeMethodToString) {
+ boolean seenMethodToString = false;
+ for (Map.Entry<Instruction, BuilderState> entry : perBuilderState.entrySet()) {
+ if (entry.getKey().isInvokeMethod()
+ && optimizer.optimizationConfiguration.isToStringMethod(
+ entry.getKey().asInvokeMethod().getInvokedMethod())) {
+ seenMethodToString = true;
+ String computedString = optimizer.analysis.toCompileTimeString(entry.getValue());
+ assertNotNull(computedString);
+ assertTrue(expectedConstStrings.contains(computedString));
+ expectedConstStrings.remove(computedString);
+ }
+ }
+ assertEquals(expectToSeeMethodToString, seenMethodToString);
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java
index 0efe652..69c6355 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java
@@ -44,6 +44,14 @@
"Hello,R8",
// nestedBuilders_appendBuilderResult
"Hello,R8",
+ // nestedBuilders_conditional
+ "Hello,R8",
+ // concatenatedBuilders_init
+ "Hello,R8",
+ // concatenatedBuilders_append
+ "Hello,R8",
+ // concatenatedBuilders_conditional
+ "Hello,R8",
// simplePhi
"Hello,",
"Hello,D8",
@@ -142,6 +150,28 @@
expectedCount = 3;
assertEquals(expectedCount, countConstString(method));
+ method = mainClass.uniqueMethodWithName("nestedBuilders_conditional");
+ assertThat(method, isPresent());
+ assertEquals(4, countConstString(method));
+
+ method = mainClass.uniqueMethodWithName("concatenatedBuilders_init");
+ assertThat(method, isPresent());
+ // TODO(b/113859361): merge builders
+ expectedCount = 2;
+ assertEquals(expectedCount, countConstString(method));
+
+ method = mainClass.uniqueMethodWithName("concatenatedBuilders_append");
+ assertThat(method, isPresent());
+ // TODO(b/113859361): merge builders
+ expectedCount = 2;
+ assertEquals(expectedCount, countConstString(method));
+
+ method = mainClass.uniqueMethodWithName("concatenatedBuilders_conditional");
+ assertThat(method, isPresent());
+ // TODO(b/113859361): merge builders
+ expectedCount = 4;
+ assertEquals(expectedCount, countConstString(method));
+
method = mainClass.uniqueMethodWithName("simplePhi");
assertThat(method, isPresent());
assertEquals(4, countConstString(method));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTestClass.java
index 535fee9..88bb9d4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTestClass.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTestClass.java
@@ -105,6 +105,67 @@
}
@NeverInline
+ public static void nestedBuilders_conditional() {
+ StringBuilder b1 = new StringBuilder();
+ StringBuilder b2 = new StringBuilder();
+ StringBuilder b3 = new StringBuilder();
+ b1.append("Hello,");
+ if (System.currentTimeMillis() > 0) {
+ b2.append("R");
+ b2.append("8");
+ b1.append(b2);
+ } else {
+ // To avoid canonicalization
+ b3.append("D");
+ b3.append(8);
+ b1.append(b3.toString());
+ }
+ System.out.println(b1.toString());
+ }
+
+ @NeverInline
+ public static void concatenatedBuilders_init() {
+ StringBuilder b1 = new StringBuilder();
+ b1.append("Hello,");
+ b1.append("R");
+ StringBuilder b2 = new StringBuilder(b1);
+ b2.append(8);
+ System.out.println(b2.toString());
+ }
+
+ @NeverInline
+ public static void concatenatedBuilders_append() {
+ StringBuilder b1 = new StringBuilder();
+ b1.append("Hello,");
+ b1.append("R");
+ StringBuilder b2 = new StringBuilder();
+ b2.append(b1);
+ b2.append(8);
+ System.out.println(b2.toString());
+ }
+
+ @NeverInline
+ public static void concatenatedBuilders_conditional() {
+ String result;
+ StringBuilder b1 = new StringBuilder();
+ b1.append("Hello,");
+ if (System.currentTimeMillis() > 0) {
+ StringBuilder b2 = new StringBuilder();
+ b2.append(b1);
+ b2.append("R");
+ b2.append("8");
+ result = b2.toString();
+ } else {
+ StringBuilder b3 = new StringBuilder();
+ // To avoid canonicalization
+ b3.append("D");
+ b3.append(8);
+ result = b3.toString();
+ }
+ System.out.println(result);
+ }
+
+ @NeverInline
public static void simplePhi() {
StringBuilder builder = new StringBuilder();
builder.append("Hello");
@@ -176,6 +237,10 @@
typeConversion_withPhis();
nestedBuilders_appendBuilderItself();
nestedBuilders_appendBuilderResult();
+ nestedBuilders_conditional();
+ concatenatedBuilders_init();
+ concatenatedBuilders_append();
+ concatenatedBuilders_conditional();
simplePhi();
phiAtInit();
phiWithDifferentInits();
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
index d5a89c8..00ee4e7 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
@@ -65,13 +65,13 @@
@Test
public void equalityOfConstantOperands() {
RegisterAllocator allocator = new MockRegisterAllocator();
- Value value0 = new Value(0, TypeLatticeElement.INT, null);
+ Value value0 = new Value(0, TypeLatticeElement.getInt(), null);
ConstNumber const0 = new ConstNumber(value0, 0);
- Value value1 = new Value(1, TypeLatticeElement.INT, null);
+ Value value1 = new Value(1, TypeLatticeElement.getInt(), null);
ConstNumber const1 = new ConstNumber(value1, 1);
- Value value2 = new Value(2, TypeLatticeElement.INT, null);
+ Value value2 = new Value(2, TypeLatticeElement.getInt(), null);
ConstNumber const2 = new ConstNumber(value2, 2);
- Value value3 = new Value(2, TypeLatticeElement.INT, null);
+ Value value3 = new Value(2, TypeLatticeElement.getInt(), null);
Add add0 = new Add(NumericType.INT, value3, value0, value1);
add0.setPosition(Position.none());
Add add1 = new Add(NumericType.INT, value3, value0, value2);
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index a449c5d..f88783d 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -161,8 +161,8 @@
CollectMovesIterator moves = new CollectMovesIterator();
int temp = 42;
RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
- scheduler.addMove(new RegisterMove(0, 1, TypeLatticeElement.INT));
- scheduler.addMove(new RegisterMove(1, 0, TypeLatticeElement.INT));
+ scheduler.addMove(new RegisterMove(0, 1, TypeLatticeElement.getInt()));
+ scheduler.addMove(new RegisterMove(1, 0, TypeLatticeElement.getInt()));
scheduler.schedule();
assertEquals(3, moves.size());
Move tempMove = moves.get(0);
@@ -186,8 +186,8 @@
CollectMovesIterator moves = new CollectMovesIterator();
int temp = 42;
RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
- scheduler.addMove(new RegisterMove(0, 2, TypeLatticeElement.LONG));
- scheduler.addMove(new RegisterMove(2, 0, TypeLatticeElement.LONG));
+ scheduler.addMove(new RegisterMove(0, 2, TypeLatticeElement.getLong()));
+ scheduler.addMove(new RegisterMove(2, 0, TypeLatticeElement.getLong()));
scheduler.schedule();
assertEquals(3, moves.size());
Move tempMove = moves.get(0);
@@ -211,8 +211,8 @@
CollectMovesIterator moves = new CollectMovesIterator();
int temp = 42;
RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
- scheduler.addMove(new RegisterMove(1, 0, TypeLatticeElement.LONG));
- scheduler.addMove(new RegisterMove(0, 1, TypeLatticeElement.INT));
+ scheduler.addMove(new RegisterMove(1, 0, TypeLatticeElement.getLong()));
+ scheduler.addMove(new RegisterMove(0, 1, TypeLatticeElement.getInt()));
scheduler.schedule();
assertEquals(3, moves.size());
Move tempMove = moves.get(0).asMove();
@@ -236,8 +236,8 @@
CollectMovesIterator moves = new CollectMovesIterator();
int temp = 42;
RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
- scheduler.addMove(new RegisterMove(0, 1, TypeLatticeElement.INT));
- scheduler.addMove(new RegisterMove(1, 0, TypeLatticeElement.LONG));
+ scheduler.addMove(new RegisterMove(0, 1, TypeLatticeElement.getInt()));
+ scheduler.addMove(new RegisterMove(1, 0, TypeLatticeElement.getLong()));
scheduler.schedule();
assertEquals(3, moves.size());
Move tempMove = moves.get(0).asMove();
@@ -261,8 +261,8 @@
CollectMovesIterator moves = new CollectMovesIterator();
int temp = 42;
RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
- scheduler.addMove(new RegisterMove(0, 1, TypeLatticeElement.LONG));
- scheduler.addMove(new RegisterMove(2, 3, TypeLatticeElement.LONG));
+ scheduler.addMove(new RegisterMove(0, 1, TypeLatticeElement.getLong()));
+ scheduler.addMove(new RegisterMove(2, 3, TypeLatticeElement.getLong()));
scheduler.schedule();
assertEquals(2, moves.size());
Move firstMove = moves.get(0).asMove();
@@ -280,8 +280,8 @@
CollectMovesIterator moves = new CollectMovesIterator();
int temp = 42;
RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
- scheduler.addMove(new RegisterMove(2, 1, TypeLatticeElement.LONG));
- scheduler.addMove(new RegisterMove(0, 3, TypeLatticeElement.LONG));
+ scheduler.addMove(new RegisterMove(2, 1, TypeLatticeElement.getLong()));
+ scheduler.addMove(new RegisterMove(0, 3, TypeLatticeElement.getLong()));
scheduler.schedule();
assertEquals(3, moves.size());
Move firstMove = moves.get(0).asMove();
@@ -303,9 +303,9 @@
CollectMovesIterator moves = new CollectMovesIterator();
int temp = 42;
RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
- scheduler.addMove(new RegisterMove(2, 0, TypeLatticeElement.LONG));
- scheduler.addMove(new RegisterMove(0, 2, TypeLatticeElement.INT));
- scheduler.addMove(new RegisterMove(1, 3, TypeLatticeElement.INT));
+ scheduler.addMove(new RegisterMove(2, 0, TypeLatticeElement.getLong()));
+ scheduler.addMove(new RegisterMove(0, 2, TypeLatticeElement.getInt()));
+ scheduler.addMove(new RegisterMove(1, 3, TypeLatticeElement.getInt()));
scheduler.schedule();
assertEquals(4, moves.size());
Move firstMove = moves.get(0).asMove();
@@ -327,8 +327,8 @@
CollectMovesIterator moves = new CollectMovesIterator();
int temp = 42;
RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
- scheduler.addMove(new RegisterMove(0, 2, TypeLatticeElement.LONG));
- scheduler.addMove(new RegisterMove(3, 0, TypeLatticeElement.INT));
+ scheduler.addMove(new RegisterMove(0, 2, TypeLatticeElement.getLong()));
+ scheduler.addMove(new RegisterMove(3, 0, TypeLatticeElement.getInt()));
scheduler.schedule();
assertEquals(3, moves.size());
Move firstMove = moves.get(0).asMove();
@@ -350,10 +350,10 @@
CollectMovesIterator moves = new CollectMovesIterator();
int temp = 42;
RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
- scheduler.addMove(new RegisterMove(14, 11, TypeLatticeElement.LONG));
- scheduler.addMove(new RegisterMove(16, 13, TypeLatticeElement.LONG));
- scheduler.addMove(new RegisterMove(10, 17, TypeLatticeElement.LONG));
- scheduler.addMove(new RegisterMove(12, 19, TypeLatticeElement.LONG));
+ scheduler.addMove(new RegisterMove(14, 11, TypeLatticeElement.getLong()));
+ scheduler.addMove(new RegisterMove(16, 13, TypeLatticeElement.getLong()));
+ scheduler.addMove(new RegisterMove(10, 17, TypeLatticeElement.getLong()));
+ scheduler.addMove(new RegisterMove(12, 19, TypeLatticeElement.getLong()));
scheduler.schedule();
// In order to resolve these moves, we need to use two temporary register pairs.
assertEquals(6, moves.size());
@@ -375,10 +375,10 @@
CollectMovesIterator moves = new CollectMovesIterator();
int temp = 42;
RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
- scheduler.addMove(new RegisterMove(26, 22, TypeLatticeElement.INT));
- scheduler.addMove(new RegisterMove(29, 24, TypeLatticeElement.LONG));
+ scheduler.addMove(new RegisterMove(26, 22, TypeLatticeElement.getInt()));
+ scheduler.addMove(new RegisterMove(29, 24, TypeLatticeElement.getLong()));
scheduler.addMove(new RegisterMove(28, 26, objectType));
- scheduler.addMove(new RegisterMove(23, 28, TypeLatticeElement.LONG));
+ scheduler.addMove(new RegisterMove(23, 28, TypeLatticeElement.getLong()));
scheduler.schedule();
// For this example we need recursive unblocking.
assertEquals(6, moves.size());
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
index a5183d3..4700a44 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
@@ -63,20 +63,19 @@
// Setup live an inactive live interval with ranges [0, 10[ and [20, 30[ with only
// uses in the first interval and which is linked to another interval.
LiveIntervals inactiveIntervals =
- new LiveIntervals(new Value(0, TypeLatticeElement.INT, null));
+ new LiveIntervals(new Value(0, TypeLatticeElement.getInt(), null));
inactiveIntervals.addRange(new LiveRange(0, 10));
inactiveIntervals.addUse(new LiveIntervalsUse(0, 10));
inactiveIntervals.addUse(new LiveIntervalsUse(4, 10));
inactiveIntervals.addRange(new LiveRange(20, 30));
inactiveIntervals.setRegister(0);
- LiveIntervals linked =
- new LiveIntervals(new Value(1, TypeLatticeElement.INT, null));
+ LiveIntervals linked = new LiveIntervals(new Value(1, TypeLatticeElement.getInt(), null));
linked.setRegister(1);
inactiveIntervals.link(linked);
allocator.addInactiveIntervals(inactiveIntervals);
// Setup an unhandled interval that overlaps the inactive interval.
LiveIntervals unhandledIntervals =
- new LiveIntervals(new Value(2, TypeLatticeElement.INT, null));
+ new LiveIntervals(new Value(2, TypeLatticeElement.getInt(), null));
unhandledIntervals.addRange(new LiveRange(12, 24));
// Split the overlapping inactive intervals and check that after the split, the second
// part of the inactive interval is unhandled and will therefore get a new register
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index 26f2030..74d62e0 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -517,7 +517,7 @@
}
public DexApplication read(InternalOptions options) throws Exception {
- Timing timing = new Timing("JasminTest");
+ Timing timing = Timing.empty();
return new ApplicationReader(build(), options, timing).read();
}
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInCompanionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInCompanionTest.java
new file mode 100644
index 0000000..a5c01a5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInCompanionTest.java
@@ -0,0 +1,126 @@
+// Copyright (c) 2020, 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.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.List;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRenameInCompanionTest extends KotlinMetadataTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0} target: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+ }
+
+ public MetadataRenameInCompanionTest(
+ TestParameters parameters, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.parameters = parameters;
+ }
+
+ private static Path companionLibJar;
+
+ @BeforeClass
+ public static void createLibJar() throws Exception {
+ String companionLibFolder = PKG_PREFIX + "/companion_lib";
+ companionLibJar =
+ kotlinc(KOTLINC, KotlinTargetVersion.JAVA_8)
+ .addSourceFiles(getKotlinFileInTest(companionLibFolder, "lib"))
+ .compile();
+ }
+
+ @Test
+ public void testMetadataInCompanion_renamed() throws Exception {
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramFiles(companionLibJar)
+ // Keep the B class and its interface (which has the doStuff method).
+ .addKeepRules("-keep class **.B")
+ .addKeepRules("-keep class **.I { <methods>; }")
+ // Keep getters for B$Companion.(singleton|foo) which will be referenced at the app.
+ .addKeepRules("-keepclassmembers class **.B$* { *** get*(...); }")
+ // No rule for Super, but will be kept and renamed.
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .compile();
+ String pkg = getClass().getPackage().getName();
+ final String superClassName = pkg + ".companion_lib.Super";
+ final String bClassName = pkg + ".companion_lib.B";
+ final String companionClassName = pkg + ".companion_lib.B$Companion";
+ compileResult.inspect(inspector -> {
+ ClassSubject sup = inspector.clazz(superClassName);
+ assertThat(sup, isRenamed());
+
+ ClassSubject impl = inspector.clazz(bClassName);
+ assertThat(impl, isPresent());
+ assertThat(impl, not(isRenamed()));
+ // API entry is kept, hence the presence of Metadata.
+ KmClassSubject kmClass = impl.getKmClass();
+ assertThat(kmClass, isPresent());
+ List<ClassSubject> superTypes = kmClass.getSuperTypes();
+ assertTrue(superTypes.stream().noneMatch(
+ supertype -> supertype.getFinalDescriptor().contains("Super")));
+ assertTrue(superTypes.stream().anyMatch(
+ supertype -> supertype.getFinalDescriptor().equals(sup.getFinalDescriptor())));
+
+ // Bridge for the property in the companion that needs a backing field.
+ MethodSubject singletonBridge = impl.uniqueMethodWithName("access$getSingleton$cp");
+ assertThat(singletonBridge, isRenamed());
+
+ // For B$Companion.foo, no backing field needed, hence no bridge.
+ MethodSubject fooBridge = impl.uniqueMethodWithName("access$getFoo$cp");
+ assertThat(fooBridge, not(isPresent()));
+
+ ClassSubject companion = inspector.clazz(companionClassName);
+ assertThat(companion, isRenamed());
+
+ MethodSubject singletonGetter = companion.uniqueMethodWithName("getSingleton");
+ assertThat(singletonGetter, isPresent());
+
+ MethodSubject fooGetter = companion.uniqueMethodWithName("getFoo");
+ assertThat(fooGetter, isPresent());
+ });
+
+ Path libJar = compileResult.writeToZip();
+
+ String appFolder = PKG_PREFIX + "/companion_app";
+ ProcessResult kotlinTestCompileResult =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(getKotlinFileInTest(appFolder, "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ // TODO(b/143687784): update to just .compile() once fixed.
+ .compileRaw();
+
+ // TODO(b/70169921): should be able to compile!
+ assertNotEquals(0, kotlinTestCompileResult.exitCode);
+ assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: singleton"));
+ assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: foo"));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionPropertyTest.java
index 7ffa9ac..84d9dd2 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionPropertyTest.java
@@ -4,22 +4,23 @@
package com.android.tools.r8.kotlin.metadata;
import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionProperty;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
-import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
-import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.KmClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmFunctionSubject;
import com.android.tools.r8.utils.codeinspector.KmPackageSubject;
import com.android.tools.r8.utils.codeinspector.KmPropertySubject;
import java.nio.file.Path;
@@ -87,6 +88,9 @@
List<ClassSubject> superTypes = kmClass.getSuperTypes();
assertTrue(superTypes.stream().noneMatch(
supertype -> supertype.getFinalDescriptor().contains("Super")));
+ KmFunctionSubject kmFunction = kmClass.kmFunctionWithUniqueName("doStuff");
+ assertThat(kmFunction, isPresent());
+ assertThat(kmFunction, not(isExtensionFunction()));
ClassSubject bKt = inspector.clazz(bKtClassName);
assertThat(bKt, isPresent());
@@ -97,20 +101,24 @@
KmPropertySubject kmProperty = kmPackage.kmPropertyExtensionWithUniqueName("asI");
assertThat(kmProperty, isExtensionProperty());
+ assertNotNull(kmProperty.getterSignature());
});
Path libJar = compileResult.writeToZip();
String appFolder = PKG_PREFIX + "/extension_property_app";
- ProcessResult processResult =
+ Path output =
kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
.addClasspathFiles(libJar)
.addSourceFiles(getKotlinFileInTest(appFolder, "main"))
.setOutputPath(temp.newFolder().toPath())
- .compileRaw();
- // TODO(b/70169921): should be able to compile!
- assertNotEquals(0, processResult.exitCode);
- assertThat(processResult.stderr, containsString("unresolved reference: doStuff"));
+ .compile();
+
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), pkg + ".extension_property_app.MainKt")
+ .assertSuccessWithOutputLines("do stuff", "do stuff");
}
@Test
@@ -134,7 +142,6 @@
final String bKtClassName = pkg + ".extension_property_lib.BKt";
compileResult.inspect(inspector -> {
ClassSubject sup = inspector.clazz(superClassName);
- assertThat(sup, isPresent());
assertThat(sup, isRenamed());
ClassSubject impl = inspector.clazz(bClassName);
@@ -158,19 +165,23 @@
KmPropertySubject kmProperty = kmPackage.kmPropertyExtensionWithUniqueName("asI");
assertThat(kmProperty, isExtensionProperty());
+ assertNotNull(kmProperty.getterSignature());
});
Path libJar = compileResult.writeToZip();
String appFolder = PKG_PREFIX + "/extension_property_app";
- ProcessResult processResult =
+ Path output =
kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
.addClasspathFiles(libJar)
.addSourceFiles(getKotlinFileInTest(appFolder, "main"))
.setOutputPath(temp.newFolder().toPath())
- .compileRaw();
- // TODO(b/70169921): should be able to compile!
- assertNotEquals(0, processResult.exitCode);
- assertThat(processResult.stderr, containsString("unresolved reference: doStuff"));
+ .compile();
+
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), pkg + ".extension_property_app.MainKt")
+ .assertSuccessWithOutputLines("do stuff", "do stuff");
}
}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTest.java
index d0708f8..556780c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTest.java
@@ -81,8 +81,7 @@
assertThat(name, not(isExtensionProperty()));
assertNotNull(name.fieldSignature());
assertNotNull(name.getterSignature());
- // TODO(b/70169921): Can remove setter.
- assertNotNull(name.setterSignature());
+ assertNull(name.setterSignature());
KmPropertySubject familyName = kmClass.kmPropertyWithUniqueName("familyName");
assertThat(familyName, isPresent());
@@ -98,8 +97,7 @@
assertThat(age, not(isExtensionProperty()));
assertNotNull(age.fieldSignature());
assertNotNull(age.getterSignature());
- // TODO(b/70169921): Can remove setter.
- assertNotNull(name.setterSignature());
+ assertNull(name.setterSignature());
});
Path libJar = compileResult.writeToZip();
@@ -146,27 +144,19 @@
KmPropertySubject name = kmClass.kmPropertyWithUniqueName("name");
assertThat(name, isPresent());
assertThat(name, not(isExtensionProperty()));
- assertNotNull(name.fieldSignature());
- // TODO(b/70169921): Either remove getter or rewrite renamed signature.
- assertNotNull(name.getterSignature());
+ assertNull(name.fieldSignature());
+ assertNull(name.getterSignature());
assertNotNull(name.setterSignature());
KmPropertySubject familyName = kmClass.kmPropertyWithUniqueName("familyName");
- assertThat(familyName, isPresent());
- assertThat(familyName, not(isExtensionProperty()));
- // No backing field for property `familyName`
- assertNull(familyName.fieldSignature());
- assertNotNull(familyName.getterSignature());
- // No setter for property `familyName`
- assertNull(familyName.setterSignature());
+ assertThat(familyName, not(isPresent()));
KmPropertySubject age = kmClass.kmPropertyWithUniqueName("age");
assertThat(age, isPresent());
assertThat(age, not(isExtensionProperty()));
assertNotNull(age.fieldSignature());
- // TODO(b/70169921): Either remove getter or rewrite renamed signature.
- assertNotNull(age.getterSignature());
- assertNotNull(name.setterSignature());
+ assertNull(age.getterSignature());
+ assertNotNull(age.setterSignature());
});
Path libJar = compileResult.writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTypeTest.java
index 60a9732..78a1514 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTypeTest.java
@@ -6,16 +6,14 @@
import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
-import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
-import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.KmClassSubject;
@@ -82,25 +80,25 @@
supertype -> supertype.getFinalDescriptor().contains("Itf")));
assertTrue(superTypes.stream().anyMatch(
supertype -> supertype.getFinalDescriptor().equals(itf.getFinalDescriptor())));
- // TODO(b/70169921): should not refer to Itf
List<ClassSubject> propertyReturnTypes = kmClass.getReturnTypesInProperties();
assertTrue(propertyReturnTypes.stream().anyMatch(
- propertyType -> propertyType.getOriginalDescriptor().contains("Itf")));
+ propertyType -> propertyType.getFinalDescriptor().equals(itf.getFinalDescriptor())));
});
Path libJar = compileResult.writeToZip();
String appFolder = PKG_PREFIX + "/propertytype_app";
- ProcessResult processResult =
+ Path output =
kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
.addClasspathFiles(libJar)
.addSourceFiles(getKotlinFileInTest(appFolder, "main"))
.setOutputPath(temp.newFolder().toPath())
- .compileRaw();
- // TODO(b/70169921): should be able to compile!
- assertNotEquals(0, processResult.exitCode);
- assertThat(
- processResult.stderr,
- containsString("cannot access class '" + pkg + ".propertytype_lib.Itf'"));
+ .compile();
+
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), pkg + ".propertytype_app.MainKt")
+ .assertSuccessWithOutputLines("Impl::8");
}
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSealedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSealedClassTest.java
new file mode 100644
index 0000000..a2d4fa1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSealedClassTest.java
@@ -0,0 +1,183 @@
+// Copyright (c) 2020, 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.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmFunctionSubject;
+import com.android.tools.r8.utils.codeinspector.KmPackageSubject;
+import java.nio.file.Path;
+import java.util.Collection;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRenameInSealedClassTest extends KotlinMetadataTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0} target: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+ }
+
+ public MetadataRenameInSealedClassTest(
+ TestParameters parameters, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.parameters = parameters;
+ }
+
+ private static Path sealedLibJar;
+
+ @BeforeClass
+ public static void createLibJar() throws Exception {
+ String sealedLibFolder = PKG_PREFIX + "/sealed_lib";
+ sealedLibJar =
+ kotlinc(KOTLINC, KotlinTargetVersion.JAVA_8)
+ .addSourceFiles(getKotlinFileInTest(sealedLibFolder, "lib"))
+ .compile();
+ }
+
+ @Test
+ public void testMetadataInSealedClass_valid() throws Exception {
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramFiles(sealedLibJar)
+ // Keep the Expr class
+ .addKeepRules("-keep class **.Expr")
+ // Keep the extension function
+ .addKeepRules("-keep class **.LibKt { <methods>; }")
+ // Keep the factory object and utils
+ .addKeepRules("-keep class **.ExprFactory { *; }")
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .compile();
+ String pkg = getClass().getPackage().getName();
+ final String numClassName = pkg + ".sealed_lib.Num";
+ final String exprClassName = pkg + ".sealed_lib.Expr";
+ final String libClassName = pkg + ".sealed_lib.LibKt";
+ compileResult.inspect(inspector -> {
+ ClassSubject num = inspector.clazz(numClassName);
+ assertThat(num, isRenamed());
+
+ ClassSubject expr = inspector.clazz(exprClassName);
+ assertThat(expr, isPresent());
+ assertThat(expr, not(isRenamed()));
+
+ KmClassSubject kmClass = expr.getKmClass();
+ assertThat(kmClass, isPresent());
+
+ kmClass.getSealedSubclassDescriptors().forEach(sealedSubclassDescriptor -> {
+ ClassSubject sealedSubclass =
+ inspector.clazz(descriptorToJavaType(sealedSubclassDescriptor));
+ assertThat(sealedSubclass, isRenamed());
+ assertEquals(sealedSubclassDescriptor, sealedSubclass.getFinalDescriptor());
+ });
+
+ ClassSubject libKt = inspector.clazz(libClassName);
+ assertThat(expr, isPresent());
+ assertThat(expr, not(isRenamed()));
+
+ KmPackageSubject kmPackage = libKt.getKmPackage();
+ assertThat(kmPackage, isPresent());
+
+ KmFunctionSubject eval = kmPackage.kmFunctionExtensionWithUniqueName("eval");
+ assertThat(eval, isPresent());
+ assertThat(eval, isExtensionFunction());
+ });
+
+ Path libJar = compileResult.writeToZip();
+
+ String appFolder = PKG_PREFIX + "/sealed_app";
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(getKotlinFileInTest(appFolder, "valid"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), pkg + ".sealed_app.ValidKt")
+ .assertSuccessWithOutputLines("6");
+ }
+
+ @Test
+ public void testMetadataInSealedClass_invalid() throws Exception {
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramFiles(sealedLibJar)
+ // Keep the Expr class
+ .addKeepRules("-keep class **.Expr")
+ // Keep the extension function
+ .addKeepRules("-keep class **.LibKt { <methods>; }")
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .compile();
+ String pkg = getClass().getPackage().getName();
+ final String exprClassName = pkg + ".sealed_lib.Expr";
+ final String numClassName = pkg + ".sealed_lib.Num";
+ final String libClassName = pkg + ".sealed_lib.LibKt";
+ compileResult.inspect(inspector -> {
+ // Without any specific keep rule and no instantiation point, it's not necessary to keep
+ // sub classes of Expr.
+ ClassSubject num = inspector.clazz(numClassName);
+ assertThat(num, not(isPresent()));
+
+ ClassSubject expr = inspector.clazz(exprClassName);
+ assertThat(expr, isPresent());
+ assertThat(expr, not(isRenamed()));
+
+ KmClassSubject kmClass = expr.getKmClass();
+ assertThat(kmClass, isPresent());
+
+ assertTrue(kmClass.getSealedSubclassDescriptors().isEmpty());
+
+ ClassSubject libKt = inspector.clazz(libClassName);
+ assertThat(expr, isPresent());
+ assertThat(expr, not(isRenamed()));
+
+ KmPackageSubject kmPackage = libKt.getKmPackage();
+ assertThat(kmPackage, isPresent());
+
+ KmFunctionSubject eval = kmPackage.kmFunctionExtensionWithUniqueName("eval");
+ assertThat(eval, isPresent());
+ assertThat(eval, isExtensionFunction());
+ });
+
+ Path libJar = compileResult.writeToZip();
+
+ String appFolder = PKG_PREFIX + "/sealed_app";
+ ProcessResult kotlinTestCompileResult =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(getKotlinFileInTest(appFolder, "invalid"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compileRaw();
+
+ assertNotEquals(0, kotlinTestCompileResult.exitCode);
+ assertThat(kotlinTestCompileResult.stderr, containsString("cannot access"));
+ assertThat(kotlinTestCompileResult.stderr, containsString("private in 'Expr'"));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/companion_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_app/main.kt
new file mode 100644
index 0000000..55c61e3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_app/main.kt
@@ -0,0 +1,12 @@
+// Copyright (c) 2020, 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.kotlin.metadata.companion_app
+
+import com.android.tools.r8.kotlin.metadata.companion_lib.B
+
+fun main() {
+ B().doStuff()
+ B.singleton.doStuff()
+ println(B.foo)
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/companion_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_lib/lib.kt
new file mode 100644
index 0000000..9e9f2eb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_lib/lib.kt
@@ -0,0 +1,26 @@
+// Copyright (c) 2020, 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.kotlin.metadata.companion_lib
+
+interface I {
+ fun doStuff()
+}
+
+open class Super : I {
+ override fun doStuff() {
+ println("do stuff")
+ }
+}
+
+class B : Super() {
+ override fun doStuff() {
+ println(foo)
+ }
+
+ companion object {
+ val singleton: Super = B()
+ val foo: String
+ get() = "B.Companion:foo"
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_app/invalid.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_app/invalid.kt
new file mode 100644
index 0000000..88f401c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_app/invalid.kt
@@ -0,0 +1,14 @@
+// Copyright (c) 2020, 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.kotlin.metadata.sealed_app
+
+import com.android.tools.r8.kotlin.metadata.sealed_lib.Expr
+import com.android.tools.r8.kotlin.metadata.sealed_lib.eval
+
+// A sealed class can only be extended within that file.
+class MyExpr : Expr()
+
+fun main() {
+ println(MyExpr().eval())
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_app/valid.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_app/valid.kt
new file mode 100644
index 0000000..379c17e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_app/valid.kt
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, 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.kotlin.metadata.sealed_app
+
+import com.android.tools.r8.kotlin.metadata.sealed_lib.eval
+import com.android.tools.r8.kotlin.metadata.sealed_lib.ExprFactory
+
+fun main() {
+ println(ExprFactory.createSum(ExprFactory.createNum(4), ExprFactory.createNum(2)).eval())
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_lib/lib.kt
new file mode 100644
index 0000000..ea64d13
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_lib/lib.kt
@@ -0,0 +1,26 @@
+// Copyright (c) 2020, 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.kotlin.metadata.sealed_lib
+
+sealed class Expr
+
+sealed class BinOp(val e1: Expr, val e2: Expr, val op: String) : Expr() {
+ override fun toString() = "$e1 $op $e2"
+}
+
+data class Num(val num: Int) : Expr()
+
+data class Sum(val left: Expr, val right: Expr) : BinOp(left, right, "+")
+
+object ExprFactory {
+ fun createNum(i: Int): Expr = Num(i)
+ fun createSum(e1: Expr, e2: Expr): Expr = Sum(e1, e2)
+}
+
+fun Expr.eval(): Int =
+ when(this) {
+ is Num -> num
+ is Sum -> e1.eval() + e2.eval()
+ else -> throw IllegalArgumentException("Unknown Expr: $this")
+ }
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 6ebd77c..3f8c11f 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -798,7 +798,7 @@
int methodCount,
DiagnosticsHandler diagnosticsHandler)
throws IOException, ExecutionException {
- Timing timing = new Timing("MainDexListTests");
+ Timing timing = Timing.empty();
InternalOptions options =
new InternalOptions(new DexItemFactory(), new Reporter(diagnosticsHandler));
options.minApiLevel = minApi;
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierTest.java b/src/test/java/com/android/tools/r8/naming/MinifierTest.java
index 473ca53..1baffde 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierTest.java
@@ -11,7 +11,6 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.utils.ListUtils;
-import com.android.tools.r8.utils.Timing;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collection;
@@ -31,7 +30,7 @@
String test,
List<String> keepRulesFiles,
BiConsumer<DexItemFactory, NamingLens> inspection) {
- super(test, keepRulesFiles, inspection, new Timing("MinifierTest"));
+ super(test, keepRulesFiles, inspection);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
index b6db30f..b03aeaa 100644
--- a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
@@ -24,7 +24,6 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -46,14 +45,11 @@
protected DexItemFactory dexItemFactory;
protected NamingTestBase(
- String test,
- List<String> keepRulesFiles,
- BiConsumer<DexItemFactory, NamingLens> inspection,
- Timing timing) {
+ String test, List<String> keepRulesFiles, BiConsumer<DexItemFactory, NamingLens> inspection) {
appFileName = ToolHelper.EXAMPLES_BUILD_DIR + test + "/classes.dex";
this.keepRulesFiles = keepRulesFiles;
this.inspection = inspection;
- this.timing = timing;
+ this.timing = Timing.empty();
}
@Before
@@ -80,7 +76,7 @@
appView.setAppInfo(
enqueuer.traceApplication(
appView.rootSet(), configuration.getDontWarnPatterns(), executor, timing));
- return new Minifier(appView.withLiveness(), Collections.emptySet()).run(executor, timing);
+ return new Minifier(appView.withLiveness()).run(executor, timing);
}
protected static <T> Collection<Object[]> createTests(
diff --git a/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java b/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java
index 4209cb6..549b0e1 100644
--- a/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java
@@ -14,7 +14,6 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.utils.ListUtils;
-import com.android.tools.r8.utils.Timing;
import com.google.common.base.CharMatcher;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
@@ -39,7 +38,7 @@
String test,
List<String> keepRulesFiles,
BiConsumer<DexItemFactory, NamingLens> inspection) {
- super(test, keepRulesFiles, inspection, new Timing("PackageNamingTest"));
+ super(test, keepRulesFiles, inspection);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
index fbe1f34..9b2c7c4 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
@@ -44,9 +44,12 @@
}
private int expectedActualStackTraceHeight() {
- // In debug mode the expected stack trace height differs since there is no lambda desugaring
- // for CF.
- return mode == CompilationMode.RELEASE ? 2 : (parameters.isCfRuntime() ? 4 : 5);
+ // In DEX release the entire lambda is inlined.
+ if (parameters.isDexRuntime()) {
+ return mode == CompilationMode.RELEASE ? 1 : 5;
+ }
+ // In CF release it is not and in debug there is no lambda desugaring thus the shorter stack.
+ return mode == CompilationMode.RELEASE ? 2 : 4;
}
private boolean isSynthesizedLambdaFrame(StackTraceLine line) {
diff --git a/src/test/java/com/android/tools/r8/regress/b147865212/Flaf2Dump.java b/src/test/java/com/android/tools/r8/regress/b147865212/Flaf2Dump.java
new file mode 100644
index 0000000..aa2ac6a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b147865212/Flaf2Dump.java
@@ -0,0 +1,203 @@
+// Copyright (c) 2020, 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.regress.b147865212;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generate a class file with the following code, line number table,
+// and local variable table.
+//
+// public static final java.lang.String box(int);
+// descriptor: (I)Ljava/lang/String;
+// flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+// Code:
+// stack=1, locals=3, args_size=1
+// 0: nop
+// 1: ldc #9 // String OK
+// 3: astore_1
+// 4: nop
+// 5: iload_0
+// 6: istore_2
+// 7: nop
+// 8: nop
+// 9: iload_0
+// 10: istore_2
+// 11: nop
+// 12: aload_1
+// 13: areturn
+// 14: astore_1
+// 15: nop
+// 16: iload_0
+// 17: istore_2
+// 18: nop
+// 19: nop
+// 20: iload_0
+// 21: istore_2
+// 22: nop
+// 23: aload_1
+// 24: athrow
+// Exception table:
+// from to target type
+// 0 4 14 any
+// 14 15 14 any
+// StackMapTable: number_of_entries = 1
+// frame_type = 78 /* same_locals_1_stack_item */
+// stack = [ class java/lang/Throwable ]
+// LineNumberTable:
+// line 2: 0
+// line 3: 1
+// line 5: 4
+// line 6: 5
+// line 8: 8
+// line 9: 9
+// line 10: 11
+// line 3: 13
+// line 12: 14
+// line 5: 15
+// line 6: 16
+// line 8: 19
+// line 9: 20
+// line 10: 22
+// LocalVariableTable:
+// Start Length Slot Name Signature
+// 7 1 2 z I
+// 11 1 2 z I
+// 18 1 2 z I
+// 22 1 2 z I
+// 0 25 0 x I
+public class Flaf2Dump implements Opcodes {
+
+ public static byte[] dump() throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+ AnnotationVisitor annotationVisitor0;
+
+ classWriter.visit(
+ V1_6, ACC_PUBLIC | ACC_FINAL | ACC_SUPER, "FlafKt", null, "java/lang/Object", null);
+
+ classWriter.visitSource("Flaf.kt", null);
+
+ {
+ annotationVisitor0 = classWriter.visitAnnotation("Lkotlin/Metadata;", true);
+ annotationVisitor0.visit("mv", new int[] {1, 1, 17});
+ annotationVisitor0.visit("bv", new int[] {1, 0, 3});
+ annotationVisitor0.visit("k", new Integer(2));
+ {
+ AnnotationVisitor annotationVisitor1 = annotationVisitor0.visitArray("d1");
+ annotationVisitor1.visit(
+ null,
+ "\u0000\u0006\n\u0000\n\u0002\u0010\u000e\u001a\u0006\u0010\u0000\u001a\u00020\u0001");
+ annotationVisitor1.visitEnd();
+ }
+ {
+ AnnotationVisitor annotationVisitor1 = annotationVisitor0.visitArray("d2");
+ annotationVisitor1.visit(null, "box");
+ annotationVisitor1.visit(null, "");
+ annotationVisitor1.visitEnd();
+ }
+ annotationVisitor0.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "box", "()Ljava/lang/String;", null, null);
+ {
+ annotationVisitor0 =
+ methodVisitor.visitAnnotation("Lorg/jetbrains/annotations/NotNull;", false);
+ annotationVisitor0.visitEnd();
+ }
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ Label label1 = new Label();
+ Label label2 = new Label();
+ methodVisitor.visitTryCatchBlock(label0, label1, label2, null);
+ Label label3 = new Label();
+ methodVisitor.visitTryCatchBlock(label2, label3, label2, null);
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(2, label0);
+ methodVisitor.visitInsn(NOP);
+ Label label4 = new Label();
+ methodVisitor.visitLabel(label4);
+ methodVisitor.visitLineNumber(3, label4);
+ methodVisitor.visitLdcInsn("OK");
+ methodVisitor.visitVarInsn(ASTORE, 0);
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(5, label1);
+ methodVisitor.visitInsn(NOP);
+ Label label5 = new Label();
+ methodVisitor.visitLabel(label5);
+ methodVisitor.visitLineNumber(6, label5);
+ methodVisitor.visitInsn(ICONST_2);
+ methodVisitor.visitVarInsn(ISTORE, 1);
+ Label label6 = new Label();
+ methodVisitor.visitLabel(label6);
+ methodVisitor.visitInsn(NOP);
+ Label label7 = new Label();
+ methodVisitor.visitLabel(label7);
+ methodVisitor.visitLineNumber(8, label7);
+ methodVisitor.visitInsn(NOP);
+ Label label8 = new Label();
+ methodVisitor.visitLabel(label8);
+ methodVisitor.visitLineNumber(9, label8);
+ methodVisitor.visitInsn(ICONST_4);
+ methodVisitor.visitVarInsn(ISTORE, 1);
+ Label label9 = new Label();
+ methodVisitor.visitLabel(label9);
+ methodVisitor.visitInsn(NOP);
+ Label label10 = new Label();
+ methodVisitor.visitLabel(label10);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ Label label11 = new Label();
+ methodVisitor.visitLabel(label11);
+ methodVisitor.visitLineNumber(3, label11);
+ methodVisitor.visitInsn(ARETURN);
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(11, label2);
+ methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/Throwable"});
+ methodVisitor.visitVarInsn(ASTORE, 0);
+ methodVisitor.visitLabel(label3);
+ methodVisitor.visitLineNumber(5, label3);
+ methodVisitor.visitInsn(NOP);
+ Label label12 = new Label();
+ methodVisitor.visitLabel(label12);
+ methodVisitor.visitLineNumber(6, label12);
+ methodVisitor.visitInsn(ICONST_2);
+ methodVisitor.visitVarInsn(ISTORE, 1);
+ Label label13 = new Label();
+ methodVisitor.visitLabel(label13);
+ methodVisitor.visitInsn(NOP);
+ Label label14 = new Label();
+ methodVisitor.visitLabel(label14);
+ methodVisitor.visitLineNumber(8, label14);
+ methodVisitor.visitInsn(NOP);
+ Label label15 = new Label();
+ methodVisitor.visitLabel(label15);
+ methodVisitor.visitLineNumber(9, label15);
+ methodVisitor.visitInsn(ICONST_4);
+ methodVisitor.visitVarInsn(ISTORE, 1);
+ Label label16 = new Label();
+ methodVisitor.visitLabel(label16);
+ methodVisitor.visitInsn(NOP);
+ Label label17 = new Label();
+ methodVisitor.visitLabel(label17);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitInsn(ATHROW);
+ methodVisitor.visitLocalVariable("z", "I", null, label6, label7, 1);
+ methodVisitor.visitLocalVariable("z", "I", null, label9, label10, 1);
+ methodVisitor.visitLocalVariable("z", "I", null, label13, label14, 1);
+ methodVisitor.visitLocalVariable("z", "I", null, label16, label17, 1);
+ methodVisitor.visitMaxs(1, 2);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b147865212/FlafDump.java b/src/test/java/com/android/tools/r8/regress/b147865212/FlafDump.java
new file mode 100644
index 0000000..d0a4f6a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b147865212/FlafDump.java
@@ -0,0 +1,124 @@
+// Copyright (c) 2020, 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.regress.b147865212;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generate a class file containing the following method, line number table,
+// and local variable table. Conditionally add an extra entry to the line
+// number table for the nop at instruction 5. When generateLineNumberForLocal
+// is true, a `line 7: 5` entry is generated in the line number table
+// making the local observable in the debugger.
+//
+// public static final java.lang.String box();
+// descriptor: ()Ljava/lang/String;
+// flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+// Code:
+// stack=1, locals=1, args_size=0
+// 0: nop
+// 1: ldc #11 // String A
+// 3: areturn
+// 4: astore_0
+// 5: nop
+// 6: ldc #13 // String B
+// 8: areturn
+// Exception table:
+// from to target type
+// 0 4 4 Class java/lang/IllegalStateException
+// StackMapTable: number_of_entries = 1
+// frame_type = 68 /* same_locals_1_stack_item */
+// stack = [ class java/lang/IllegalStateException ]
+// LineNumberTable:
+// line 2: 0
+// line 3: 1
+// line 4: 4
+// line 5: 6
+// line 6: 6
+// LocalVariableTable:
+// Start Length Slot Name Signature
+// 5 1 0 e Ljava/lang/IllegalStateException;
+public class FlafDump implements Opcodes {
+ public static byte[] dump(boolean generateLineNumberForLocal) throws Exception {
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+ AnnotationVisitor annotationVisitor0;
+
+ classWriter.visit(
+ V1_6, ACC_PUBLIC | ACC_FINAL | ACC_SUPER, "FlafKt", null, "java/lang/Object", null);
+
+ classWriter.visitSource("Flaf.kt", null);
+
+ {
+ annotationVisitor0 = classWriter.visitAnnotation("Lkotlin/Metadata;", true);
+ annotationVisitor0.visit("mv", new int[] {1, 1, 16});
+ annotationVisitor0.visit("bv", new int[] {1, 0, 3});
+ annotationVisitor0.visit("k", new Integer(2));
+ {
+ AnnotationVisitor annotationVisitor1 = annotationVisitor0.visitArray("d1");
+ annotationVisitor1.visit(
+ null,
+ "\u0000\u0006\n\u0000\n\u0002\u0010\u000e\u001a\u0006\u0010\u0000\u001a\u00020\u0001");
+ annotationVisitor1.visitEnd();
+ }
+ {
+ AnnotationVisitor annotationVisitor1 = annotationVisitor0.visitArray("d2");
+ annotationVisitor1.visit(null, "box");
+ annotationVisitor1.visit(null, "");
+ annotationVisitor1.visitEnd();
+ }
+ annotationVisitor0.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "box", "()Ljava/lang/String;", null, null);
+ {
+ annotationVisitor0 =
+ methodVisitor.visitAnnotation("Lorg/jetbrains/annotations/NotNull;", false);
+ annotationVisitor0.visitEnd();
+ }
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ Label label1 = new Label();
+ methodVisitor.visitTryCatchBlock(label0, label1, label1, "java/lang/IllegalStateException");
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(2, label0);
+ methodVisitor.visitInsn(NOP);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(3, label2);
+ methodVisitor.visitLdcInsn("A");
+ methodVisitor.visitInsn(ARETURN);
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(4, label1);
+ methodVisitor.visitFrame(
+ Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/IllegalStateException"});
+ methodVisitor.visitVarInsn(ASTORE, 0);
+ Label label3 = new Label();
+ methodVisitor.visitLabel(label3);
+ if (generateLineNumberForLocal) {
+ methodVisitor.visitLineNumber(7, label3);
+ }
+ methodVisitor.visitInsn(NOP);
+ Label label4 = new Label();
+ methodVisitor.visitLabel(label4);
+ methodVisitor.visitLineNumber(5, label4);
+ methodVisitor.visitLineNumber(6, label4);
+ methodVisitor.visitLdcInsn("B");
+ methodVisitor.visitInsn(ARETURN);
+ methodVisitor.visitLocalVariable(
+ "e", "Ljava/lang/IllegalStateException;", null, label3, label4, 0);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b147865212/Regress147865212.java b/src/test/java/com/android/tools/r8/regress/b147865212/Regress147865212.java
new file mode 100644
index 0000000..d7133e7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b147865212/Regress147865212.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2020, 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.regress.b147865212;
+
+import static org.junit.Assert.assertFalse;
+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.graph.DexDebugEvent.StartLocal;
+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 java.util.Arrays;
+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 Regress147865212 extends TestBase {
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ private final TestParameters parameters;
+
+ public Regress147865212(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private boolean hasLocal(MethodSubject method) {
+ return Arrays.stream(method.getMethod().getCode().asDexCode().getDebugInfo().events)
+ .anyMatch(event -> event instanceof StartLocal);
+ }
+
+ private MethodSubject build(byte[] classFile) throws Exception {
+ CodeInspector inspector =
+ testForD8()
+ .addProgramClassFileData(classFile)
+ .setMinApi(parameters.getApiLevel())
+ .debug()
+ .compile()
+ .inspector();
+ ClassSubject clazz = inspector.clazz("FlafKt");
+ assertTrue(clazz.isPresent());
+ MethodSubject method = clazz.uniqueMethodWithName("box");
+ assertTrue(method.isPresent());
+ return method;
+ }
+
+ @Test
+ public void testFlafWithLineNumberForLocal() throws Exception {
+ // Generate the FlafKt class file adding a line number for the single nop instruction
+ // that a local spans. That will make the local observable in the debugger as there
+ // is now a breakpoint on the nop. Therefore, the local stays in the output.
+ MethodSubject method = build(FlafDump.dump(true));
+ assertTrue(hasLocal(method));
+ }
+
+ @Test
+ public void testFlafWithoutLineNumberForLocal() throws Exception {
+ // Generate the FlafKt class file where all locals are live only on a nop instruction
+ // with no line number. Therefore, the locals are not observable in the debugger and
+ // are removed.
+ MethodSubject method = build(FlafDump.dump(false));
+ assertFalse(hasLocal(method));
+ }
+
+ @Test
+ public void testFlaf2() throws Exception {
+ MethodSubject method = build(Flaf2Dump.dump());
+ assertFalse(hasLocal(method));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
index 6162b15..c7f8ef9 100644
--- a/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
@@ -29,7 +29,7 @@
@Test
public void testArrays() throws IOException, ExecutionException {
- Timing timing = new Timing(ArrayTargetLookupTest.class.getCanonicalName());
+ Timing timing = Timing.empty();
InternalOptions options = new InternalOptions();
AndroidApp app =
AndroidApp.builder()
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
index e290d51..b473a40 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -11,7 +11,6 @@
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.references.Reference;
import com.android.tools.r8.resolution.singletarget.Main;
import com.android.tools.r8.resolution.singletarget.one.AbstractSubClass;
import com.android.tools.r8.resolution.singletarget.one.AbstractTopClass;
@@ -192,12 +191,6 @@
});
}
- public static DexMethod buildNullaryVoidMethod(Class clazz, String name, AppInfo appInfo) {
- return buildMethod(
- Reference.method(Reference.classFromClass(clazz), name, Collections.emptyList(), null),
- appInfo.dexItemFactory());
- }
-
private static DexType toType(Class clazz, AppInfo appInfo) {
return buildType(clazz, appInfo.dexItemFactory());
}
@@ -209,7 +202,7 @@
@Test
public void lookupSingleTarget() {
- DexMethod method = buildNullaryVoidMethod(invokeReceiver, methodName, appInfo);
+ DexMethod method = buildNullaryVoidMethod(invokeReceiver, methodName, appInfo.dexItemFactory());
Assert.assertNotNull(
appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).getSingleTarget());
DexEncodedMethod singleVirtualTarget = appInfo.lookupSingleVirtualTarget(method, method.holder);
@@ -224,7 +217,7 @@
@Test
public void lookupVirtualTargets() {
- DexMethod method = buildNullaryVoidMethod(invokeReceiver, methodName, appInfo);
+ DexMethod method = buildNullaryVoidMethod(invokeReceiver, methodName, appInfo.dexItemFactory());
Assert.assertNotNull(
appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).getSingleTarget());
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
index eb4cd97..28b468e 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
@@ -61,7 +61,7 @@
}
private static DexMethod buildMethod(Class clazz, String name) {
- return SingleTargetLookupTest.buildNullaryVoidMethod(clazz, name, appInfo);
+ return buildNullaryVoidMethod(clazz, name, appInfo.dexItemFactory());
}
@Parameters(name = "{0}")
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
index 85ace0b..1da4ff7 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
@@ -82,7 +82,7 @@
}
private static DexMethod buildMethod(Class clazz, String name) {
- return SingleTargetLookupTest.buildNullaryVoidMethod(clazz, name, appInfo);
+ return buildNullaryVoidMethod(clazz, name, appInfo.dexItemFactory());
}
@Parameters(name = "{0}")
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
index c90b6e4..674c090 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
@@ -99,7 +99,7 @@
}
private static DexMethod buildMethod(Class clazz, String name) {
- return SingleTargetLookupTest.buildNullaryVoidMethod(clazz, name, appInfo);
+ return buildNullaryVoidMethod(clazz, name, appInfo.dexItemFactory());
}
@Parameters(name = "{0}")
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
index f41a60f..f9fb653 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
@@ -146,7 +146,7 @@
}
private static DexMethod buildMethod(Class clazz, String name) {
- return SingleTargetLookupTest.buildNullaryVoidMethod(clazz, name, appInfo);
+ return buildNullaryVoidMethod(clazz, name, appInfo.dexItemFactory());
}
@Parameters(name = "{0}")
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java
index 2117ed6..005f292 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java
@@ -9,11 +9,9 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.resolution.SingleTargetLookupTest;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.AndroidApp;
import com.google.common.collect.ImmutableList;
@@ -42,10 +40,10 @@
@Test
public void testResolution() throws Exception {
// The resolution is runtime independent, so just run it on the default CF VM.
- assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
AndroidApp app = readClasses(CLASSES);
AppInfoWithLiveness appInfo = computeAppViewWithLiveness(app, Main.class).appInfo();
- DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
+ DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
// Currently R8 will resolve to L::f as that is the first in the topological search.
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
index a9c79fc..0c3ed4c 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
@@ -9,11 +9,9 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.resolution.SingleTargetLookupTest;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
@@ -42,7 +40,7 @@
@Test
public void testResolution() throws Exception {
// The resolution is runtime independent, so just run it on the default CF VM.
- assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
AppInfoWithLiveness appInfo =
computeAppViewWithLiveness(
buildClasses(CLASSES)
@@ -50,7 +48,7 @@
.build(),
Main.class)
.appInfo();
- DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
+ DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
assertEquals(L.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
index a65c188..42f7600 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
@@ -9,11 +9,9 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.resolution.SingleTargetLookupTest;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
@@ -42,7 +40,7 @@
@Test
public void testResolution() throws Exception {
// The resolution is runtime independent, so just run it on the default CF VM.
- assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
AppInfoWithLiveness appInfo =
computeAppViewWithLiveness(
buildClasses(CLASSES)
@@ -50,7 +48,7 @@
.build(),
Main.class)
.appInfo();
- DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
+ DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
assertEquals(R.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
index 053fba0..c29509d 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
@@ -10,12 +10,10 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.resolution.SingleTargetLookupTest;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
@@ -45,7 +43,7 @@
@Test
public void testResolution() throws Exception {
// The resolution is runtime independent, so just run it on the default CF VM.
- assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
AppInfoWithLiveness appInfo =
computeAppViewWithLiveness(
buildClasses(CLASSES)
@@ -53,7 +51,7 @@
.build(),
Main.class)
.appInfo();
- DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
+ DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
assertEquals(L.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
index 5163bc2..e7c5ab8 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
@@ -10,12 +10,10 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.resolution.SingleTargetLookupTest;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
@@ -45,7 +43,7 @@
@Test
public void testResolution() throws Exception {
// The resolution is runtime independent, so just run it on the default CF VM.
- assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
AppInfoWithLiveness appInfo =
computeAppViewWithLiveness(
buildClasses(CLASSES)
@@ -53,7 +51,7 @@
.build(),
Main.class)
.appInfo();
- DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
+ DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
assertEquals(R.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
index 75f689f..3d7071d 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
@@ -10,10 +10,8 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.resolution.SingleTargetLookupTest;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -44,7 +42,7 @@
@Test
public void testResolution() throws Exception {
// The resolution is runtime independent, so just run it on the default CF VM.
- assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
AppInfoWithLiveness appInfo =
computeAppViewWithLiveness(
buildClasses(CLASSES)
@@ -52,7 +50,7 @@
.build(),
Main.class)
.appInfo();
- DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
+ DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
Set<String> holders = new HashSet<>();
resolutionResult
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
index 5772bdc..b5c2018 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
@@ -9,11 +9,9 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.resolution.SingleTargetLookupTest;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.google.common.collect.ImmutableList;
import java.util.List;
@@ -41,10 +39,10 @@
@Test
public void testResolution() throws Exception {
// The resolution is runtime independent, so just run it on the default CF VM.
- assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
AppInfoWithLiveness appInfo =
computeAppViewWithLiveness(readClasses(CLASSES), Main.class).appInfo();
- DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
+ DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
assertEquals(L.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
index 50e0f8d..867cfe9 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
@@ -9,11 +9,9 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.resolution.SingleTargetLookupTest;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.google.common.collect.ImmutableList;
import java.util.List;
@@ -41,10 +39,10 @@
@Test
public void testResolution() throws Exception {
// The resolution is runtime independent, so just run it on the default CF VM.
- assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
AppInfoWithLiveness appInfo =
computeAppViewWithLiveness(readClasses(CLASSES), Main.class).appInfo();
- DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
+ DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
assertEquals(R.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
index bb81c51..b2a4ce2 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
@@ -10,10 +10,8 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.resolution.SingleTargetLookupTest;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -45,7 +43,7 @@
@Test
public void testResolution() throws Exception {
// The resolution is runtime independent, so just run it on the default CF VM.
- assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
AppInfoWithLiveness appInfo =
computeAppViewWithLiveness(
buildClasses(CLASSES)
@@ -53,7 +51,7 @@
.build(),
Main.class)
.appInfo();
- DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
+ DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
Set<String> holders = new HashSet<>();
resolutionResult
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
new file mode 100644
index 0000000..d2a1ee6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
@@ -0,0 +1,138 @@
+// Copyright (c) 2020, 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.resolution.interfacetargets;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+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.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/** This is a reproduction of b/148168065 */
+@RunWith(Parameterized.class)
+public class DefaultMethodAsOverrideWithLambdaTest extends TestBase {
+
+ private static final String[] EXPECTED = new String[] {"Lambda.foo", "J.bar"};
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public DefaultMethodAsOverrideWithLambdaTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testResolution() throws Exception {
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
+ AppInfoWithLiveness appInfo =
+ computeAppViewWithLiveness(buildClasses(I.class, A.class, Main.class).build(), Main.class)
+ .appInfo();
+ DexMethod method = buildNullaryVoidMethod(I.class, "bar", appInfo.dexItemFactory());
+ ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
+ Set<String> targets =
+ resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+ .map(DexEncodedMethod::qualifiedName)
+ .collect(Collectors.toSet());
+ ImmutableSet<String> expected =
+ ImmutableSet.of(A.class.getTypeName() + ".bar", J.class.getTypeName() + ".bar");
+ // TODO(b/148168065): Correct incorrect target lookup.
+ // assertEquals(expected, targets);
+ }
+
+ @Test
+ public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+ testForRuntime(parameters)
+ .addInnerClasses(DefaultMethodAsOverrideWithLambdaTest.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(DefaultMethodAsOverrideWithLambdaTest.class)
+ .enableInliningAnnotations()
+ .enableMergeAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatMatches(containsString("AbstractMethodError"));
+ // TODO(b/148168065): Correct incorrect target lookup.
+ // .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @FunctionalInterface
+ @NeverMerge
+ public interface I {
+ void foo();
+
+ default void bar() {
+ System.out.println("I.bar");
+ }
+ }
+
+ @NeverMerge
+ public interface J extends I {
+
+ @Override
+ default void bar() {
+ System.out.println("J.bar");
+ }
+ }
+
+ @NeverClassInline
+ public static class A implements I {
+
+ @Override
+ @NeverInline
+ public void foo() {
+ System.out.println("A.foo");
+ }
+
+ @Override
+ public void bar() {
+ System.out.println("A.bar");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ if (args.length == 0) {
+ callI((J) () -> System.out.println("Lambda.foo"));
+ } else {
+ callI(new A());
+ }
+ }
+
+ private static void callI(I i) {
+ i.foo();
+ i.bar();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
new file mode 100644
index 0000000..37f346b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2020, 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.resolution.interfacetargets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+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.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+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 DefaultMethodLambdaTest extends TestBase {
+
+ private static final String[] EXPECTED = new String[] {"Lambda.foo", "I.bar"};
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public DefaultMethodLambdaTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testResolution() throws Exception {
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
+ AppInfoWithLiveness appInfo =
+ computeAppViewWithLiveness(buildClasses(I.class, A.class, Main.class).build(), Main.class)
+ .appInfo();
+ DexMethod method = buildNullaryVoidMethod(I.class, "bar", appInfo.dexItemFactory());
+ ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
+ Set<String> targets =
+ resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+ .map(DexEncodedMethod::qualifiedName)
+ .collect(Collectors.toSet());
+ ImmutableSet<String> expected =
+ ImmutableSet.of(A.class.getTypeName() + ".bar", I.class.getTypeName() + ".bar");
+ assertEquals(expected, targets);
+ }
+
+ @Test
+ public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+ testForRuntime(parameters)
+ .addInnerClasses(DefaultMethodLambdaTest.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(DefaultMethodLambdaTest.class)
+ .enableInliningAnnotations()
+ .enableMergeAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @FunctionalInterface
+ @NeverMerge
+ public interface I {
+ void foo();
+
+ default void bar() {
+ System.out.println("I.bar");
+ }
+ }
+
+ @NeverClassInline
+ public static class A implements I {
+
+ @Override
+ @NeverInline
+ public void foo() {
+ System.out.println("A.foo");
+ }
+
+ @Override
+ public void bar() {
+ System.out.println("A.bar");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ if (args.length == 0) {
+ callI(() -> System.out.println("Lambda.foo"));
+ } else {
+ callI(new A());
+ }
+ }
+
+ private static void callI(I i) {
+ i.foo();
+ i.bar();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
new file mode 100644
index 0000000..3a455cc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
@@ -0,0 +1,135 @@
+// Copyright (c) 2020, 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.resolution.interfacetargets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+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.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+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 LambdaMultipleInterfacesTest extends TestBase {
+
+ private static final String[] EXPECTED = new String[] {"Lambda.foo", "J.bar"};
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public LambdaMultipleInterfacesTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testResolution() throws Exception {
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
+ AppInfoWithLiveness appInfo =
+ computeAppViewWithLiveness(
+ buildClasses(I.class, J.class, A.class, Main.class).build(), Main.class)
+ .appInfo();
+ DexMethod method = buildNullaryVoidMethod(J.class, "bar", appInfo.dexItemFactory());
+ ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
+ Set<String> targets =
+ resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+ .map(DexEncodedMethod::qualifiedName)
+ .collect(Collectors.toSet());
+ ImmutableSet<String> expected =
+ ImmutableSet.of(A.class.getTypeName() + ".bar", J.class.getTypeName() + ".bar");
+ assertEquals(expected, targets);
+ }
+
+ @Test
+ public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+ testForRuntime(parameters)
+ .addInnerClasses(LambdaMultipleInterfacesTest.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(LambdaMultipleInterfacesTest.class)
+ .enableInliningAnnotations()
+ .enableMergeAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @FunctionalInterface
+ @NeverMerge
+ public interface I {
+ void foo();
+ }
+
+ public interface J {
+ default void bar() {
+ System.out.println("J.bar");
+ }
+ }
+
+ @NeverClassInline
+ public static class A implements I, J {
+
+ @Override
+ public void foo() {
+ System.out.println("A.foo");
+ }
+
+ @Override
+ public void bar() {
+ System.out.println("A.bar");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ I i;
+ if (args.length == 0) {
+ i =
+ (I & J)
+ () -> {
+ System.out.println("Lambda.foo");
+ };
+ } else {
+ i = new A();
+ }
+ callIJ(i);
+ }
+
+ @NeverInline
+ private static void callIJ(I i) {
+ i.foo();
+ ((I & J) i).bar();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java
new file mode 100644
index 0000000..8b96a84
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java
@@ -0,0 +1,130 @@
+// Copyright (c) 2020, 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.resolution.interfacetargets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+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.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+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 MultipleImplementsTest extends TestBase {
+
+ private static final String[] EXPECTED = new String[] {"B.foo"};
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public MultipleImplementsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testResolution() throws Exception {
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
+ AppInfoWithLiveness appInfo =
+ computeAppViewWithLiveness(
+ buildClasses(I.class, A.class, B.class, C.class, Main.class).build(), Main.class)
+ .appInfo();
+ DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
+ ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
+ Set<String> targets =
+ resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+ .map(DexEncodedMethod::qualifiedName)
+ .collect(Collectors.toSet());
+ ImmutableSet<String> expected =
+ ImmutableSet.of(B.class.getTypeName() + ".foo", C.class.getTypeName() + ".foo");
+ assertEquals(expected, targets);
+ }
+
+ @Test
+ public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+ testForRuntime(parameters)
+ .addInnerClasses(MultipleImplementsTest.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(MultipleImplementsTest.class)
+ .enableInliningAnnotations()
+ .enableMergeAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @NeverMerge
+ public interface I {
+ void foo();
+ }
+
+ @NeverMerge
+ public interface J {
+ void foo();
+ }
+
+ @NeverMerge
+ public abstract static class A implements I, J {}
+
+ @NeverClassInline
+ public static class B extends A {
+
+ @Override
+ @NeverInline
+ public void foo() {
+ System.out.println("B.foo");
+ }
+ }
+
+ @NeverClassInline
+ public static class C extends A {
+
+ @Override
+ public void foo() {
+ System.out.println("C.foo");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ A a = args.length == 0 ? new B() : new C();
+ callJ(a);
+ }
+
+ @NeverInline
+ private static void callJ(J j) {
+ j.foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java
new file mode 100644
index 0000000..60ed217
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2020, 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.resolution.interfacetargets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+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.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+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 SimpleInterfaceInvokeTest extends TestBase {
+
+ private static final String[] EXPECTED = new String[] {"A.foo", "B.foo"};
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public SimpleInterfaceInvokeTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testResolution() throws Exception {
+ // The resolution is runtime independent, so just run it on the default CF VM.
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
+ AppInfoWithLiveness appInfo =
+ computeAppViewWithLiveness(
+ buildClasses(I.class, A.class, B.class, Main.class).build(), Main.class)
+ .appInfo();
+ DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
+ ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
+ Set<String> targets =
+ resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+ .map(DexEncodedMethod::qualifiedName)
+ .collect(Collectors.toSet());
+ ImmutableSet<String> expected =
+ ImmutableSet.of(A.class.getTypeName() + ".foo", B.class.getTypeName() + ".foo");
+ assertEquals(expected, targets);
+ }
+
+ @Test
+ public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+ testForRuntime(parameters)
+ .addInnerClasses(SimpleInterfaceInvokeTest.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(SimpleInterfaceInvokeTest.class)
+ .enableInliningAnnotations()
+ .enableMergeAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @NeverMerge
+ public interface I {
+ void foo();
+ }
+
+ @NeverClassInline
+ public static class A implements I {
+
+ @Override
+ @NeverInline
+ public void foo() {
+ System.out.println("A.foo");
+ }
+ }
+
+ @NeverClassInline
+ public static class B implements I {
+
+ @Override
+ @NeverInline
+ public void foo() {
+ System.out.println("B.foo");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ I a = args.length == 0 ? new A() : new B();
+ I b = args.length != 0 ? new A() : new B();
+ invokeI(a);
+ invokeI(b);
+ }
+
+ private static void invokeI(I i) {
+ i.foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java
new file mode 100644
index 0000000..4986840
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java
@@ -0,0 +1,138 @@
+// Copyright (c) 2020, 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.resolution.interfacetargets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+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.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+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 SubInterfaceOverridesTest extends TestBase {
+
+ private static final String[] EXPECTED = new String[] {"A.foo"};
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public SubInterfaceOverridesTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testResolution() throws Exception {
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
+ AppInfoWithLiveness appInfo =
+ computeAppViewWithLiveness(
+ buildClasses(I.class, J.class, A.class, B.class, C.class, Main.class).build(),
+ Main.class)
+ .appInfo();
+ DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
+ ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
+ Set<String> targets =
+ resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+ .map(DexEncodedMethod::qualifiedName)
+ .collect(Collectors.toSet());
+ ImmutableSet<String> expected =
+ ImmutableSet.of(
+ J.class.getTypeName() + ".foo",
+ A.class.getTypeName() + ".foo",
+ C.class.getTypeName() + ".foo");
+ assertEquals(expected, targets);
+ }
+
+ @Test
+ public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+ testForRuntime(parameters)
+ .addInnerClasses(SubInterfaceOverridesTest.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(SubInterfaceOverridesTest.class)
+ .enableMergeAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @NeverMerge
+ public interface I {
+
+ void foo();
+ }
+
+ @NeverMerge
+ public interface J extends I {
+
+ @Override
+ default void foo() {
+ System.out.println("J.foo");
+ }
+ }
+
+ @NeverClassInline
+ public static class A implements I {
+
+ @Override
+ public void foo() {
+ System.out.println("A.foo");
+ ;
+ }
+ }
+
+ @NeverClassInline
+ public static class B implements J {}
+
+ @NeverClassInline
+ public static class C implements J {
+
+ @Override
+ public void foo() {
+ System.out.println("C.foo");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ I i;
+ if (args.length == 0) {
+ i = new A();
+ } else {
+ i = new B();
+ }
+ i.foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java
new file mode 100644
index 0000000..3ccb04b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java
@@ -0,0 +1,124 @@
+// Copyright (c) 2020, 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.resolution.interfacetargets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+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.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+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 SubTypeMissingOverridesTest extends TestBase {
+
+ private static final String[] EXPECTED = new String[] {"B.foo"};
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public SubTypeMissingOverridesTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testResolution() throws Exception {
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
+ AppInfoWithLiveness appInfo =
+ computeAppViewWithLiveness(
+ buildClasses(I.class, A.class, B.class, C.class, Main.class).build(), Main.class)
+ .appInfo();
+ DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
+ ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
+ Set<String> targets =
+ resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+ .map(DexEncodedMethod::qualifiedName)
+ .collect(Collectors.toSet());
+ ImmutableSet<String> expected =
+ ImmutableSet.of(B.class.getTypeName() + ".foo", C.class.getTypeName() + ".foo");
+ assertEquals(expected, targets);
+ }
+
+ @Test
+ public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+ testForRuntime(parameters)
+ .addInnerClasses(SubTypeMissingOverridesTest.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(SubTypeMissingOverridesTest.class)
+ .enableMergeAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @NeverMerge
+ public interface I {
+
+ void foo();
+ }
+
+ @NeverClassInline
+ public abstract static class A implements I {}
+
+ @NeverClassInline
+ public static class B extends A {
+
+ @Override
+ public void foo() {
+ System.out.println("B.foo");
+ }
+ }
+
+ @NeverClassInline
+ public static class C extends A {
+
+ @Override
+ public void foo() {
+ System.out.println("C.foo");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ I i;
+ if (args.length == 0) {
+ i = new B();
+ } else {
+ i = new C();
+ }
+ i.foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java
new file mode 100644
index 0000000..4e4a494
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java
@@ -0,0 +1,122 @@
+// Copyright (c) 2020, 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.resolution.interfacetargets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+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.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+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 SubTypeOverridesTest extends TestBase {
+
+ private static final String[] EXPECTED = new String[] {"A.foo"};
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public SubTypeOverridesTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testResolution() throws Exception {
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
+ AppInfoWithLiveness appInfo =
+ computeAppViewWithLiveness(
+ buildClasses(I.class, A.class, B.class, Main.class).build(), Main.class)
+ .appInfo();
+ DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
+ ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
+ Set<String> targets =
+ resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+ .map(DexEncodedMethod::qualifiedName)
+ .collect(Collectors.toSet());
+ ImmutableSet<String> expected =
+ ImmutableSet.of(A.class.getTypeName() + ".foo", B.class.getTypeName() + ".foo");
+ assertEquals(expected, targets);
+ }
+
+ @Test
+ public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+ testForRuntime(parameters)
+ .addInnerClasses(SubTypeOverridesTest.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(SubTypeOverridesTest.class)
+ .enableMergeAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @NeverMerge
+ public interface I {
+
+ void foo();
+ }
+
+ @NeverClassInline
+ public static class A implements I {
+
+ @Override
+ public void foo() {
+ System.out.println("A.foo");
+ ;
+ }
+ }
+
+ @NeverClassInline
+ public static class B extends A {
+
+ @Override
+ public void foo() {
+ System.out.println("B.foo");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ I i;
+ if (args.length == 0) {
+ i = new A();
+ } else {
+ i = new B();
+ }
+ i.foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index e29c928..cfc4fba 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -8,6 +8,7 @@
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestDiagnosticMessagesImpl;
@@ -18,6 +19,7 @@
import com.android.tools.r8.retrace.stacktraces.ActualRetraceBotStackTrace;
import com.android.tools.r8.retrace.stacktraces.AmbiguousMissingLineStackTrace;
import com.android.tools.r8.retrace.stacktraces.AmbiguousStackTrace;
+import com.android.tools.r8.retrace.stacktraces.CircularReferenceStackTrace;
import com.android.tools.r8.retrace.stacktraces.FileNameExtensionStackTrace;
import com.android.tools.r8.retrace.stacktraces.InlineFileNameStackTrace;
import com.android.tools.r8.retrace.stacktraces.InlineNoLineNumberStackTrace;
@@ -25,6 +27,7 @@
import com.android.tools.r8.retrace.stacktraces.InvalidStackTrace;
import com.android.tools.r8.retrace.stacktraces.NullStackTrace;
import com.android.tools.r8.retrace.stacktraces.ObfucatedExceptionClassStackTrace;
+import com.android.tools.r8.retrace.stacktraces.ObfuscatedRangeToSingleLineStackTrace;
import com.android.tools.r8.retrace.stacktraces.RetraceAssertionErrorStackTrace;
import com.android.tools.r8.retrace.stacktraces.StackTraceForTest;
import com.android.tools.r8.retrace.stacktraces.SuppressedStackTrace;
@@ -149,6 +152,19 @@
runRetraceTest(new InlineNoLineNumberStackTrace());
}
+ @Test
+ public void testCircularReferenceStackTrace() {
+ // Proguard retrace (and therefore the default regular expression) will not retrace circular
+ // reference exceptions.
+ assumeFalse(useRegExpParsing);
+ runRetraceTest(new CircularReferenceStackTrace());
+ }
+
+ @Test
+ public void testObfuscatedRangeToSingleLine() {
+ runRetraceTest(new ObfuscatedRangeToSingleLineStackTrace());
+ }
+
private TestDiagnosticMessagesImpl runRetraceTest(StackTraceForTest stackTraceForTest) {
TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
RetraceCommand retraceCommand =
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/CircularReferenceStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/CircularReferenceStackTrace.java
new file mode 100644
index 0000000..5df37f8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/CircularReferenceStackTrace.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2020, 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.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class CircularReferenceStackTrace implements StackTraceForTest {
+
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return Arrays.asList(
+ " [CIRCULAR REFERENCE:A.A]",
+ " [CIRCULAR REFERENCE:A.B]",
+ " [CIRCULAR REFERENCE:None.existing.class]",
+ " [CIRCULAR REFERENCE:A.A] ",
+ // Invalid Circular Reference lines.
+ " [CIRCU:AA]",
+ " [CIRCULAR REFERENCE:A.A",
+ " [CIRCULAR REFERENCE:]",
+ " [CIRCULAR REFERENCE:None existing class]");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines("foo.bar.Baz -> A.A:", "foo.bar.Qux -> A.B:");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return Arrays.asList(
+ " [CIRCULAR REFERENCE:foo.bar.Baz]",
+ " [CIRCULAR REFERENCE:foo.bar.Qux]",
+ " [CIRCULAR REFERENCE:None.existing.class]",
+ " [CIRCULAR REFERENCE:foo.bar.Baz] ",
+ " [CIRCU:AA]",
+ " [CIRCULAR REFERENCE:A.A",
+ " [CIRCULAR REFERENCE:]",
+ " [CIRCULAR REFERENCE:None existing class]");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 5;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/ObfuscatedRangeToSingleLineStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/ObfuscatedRangeToSingleLineStackTrace.java
new file mode 100644
index 0000000..1579756
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/ObfuscatedRangeToSingleLineStackTrace.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2020, 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.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class ObfuscatedRangeToSingleLineStackTrace implements StackTraceForTest {
+
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return Arrays.asList(
+ "UnknownException: This is just a fake exception",
+ " at a.a(:8)",
+ " at a.a(:13)",
+ " at a.b(:1399)");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines(
+ "foo.bar.Baz -> a:",
+ " 1:10:void qux():27:27 -> a",
+ " 11:15:void qux():42 -> a",
+ " 1337:1400:void foo.bar.Baz.quux():113:113 -> b",
+ " 1337:1400:void quuz():72 -> b");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return Arrays.asList(
+ "UnknownException: This is just a fake exception",
+ " at foo.bar.Baz.qux(Baz.java:27)",
+ " at foo.bar.Baz.qux(Baz.java:42)",
+ " at foo.bar.Baz.quux(Baz.java:113)",
+ " at foo.bar.Baz.quuz(Baz.java:72)");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
index 06ccf5a..2d85c6b 100644
--- a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
+++ b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
@@ -76,9 +76,7 @@
options.programConsumer = DexIndexedConsumer.emptyConsumer();
DexApplication application =
- new ApplicationReader(
- originalApplication, options, new Timing("CatchSuccessorFallthroughTest"))
- .read();
+ new ApplicationReader(originalApplication, options, Timing.empty()).read();
DexEncodedMethod method = getMethod(originalApplication, methodSig);
// Get the IR pre-optimization.
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index c528eb0..8ecb463 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -63,7 +63,7 @@
protected DexApplication buildApplication(AndroidApp input, InternalOptions options) {
try {
options.itemFactory.resetSortedIndices();
- return new ApplicationReader(input, options, new Timing("SmaliTest")).read();
+ return new ApplicationReader(input, options, Timing.empty()).read();
} catch (IOException | ExecutionException e) {
throw new RuntimeException(e);
}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 598f6f1..0c2358a 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AccessFlags;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.references.ClassReference;
@@ -284,6 +285,10 @@
});
}
+ public ClassFileTransformer setSynthetic(Method method) {
+ return setAccessFlags(method, AccessFlags::setSynthetic);
+ }
+
public ClassFileTransformer setAccessFlags(Method method, Consumer<MethodAccessFlags> setter) {
return addClassTransformer(
new ClassTransformer() {
diff --git a/src/test/java/com/android/tools/r8/utils/Smali.java b/src/test/java/com/android/tools/r8/utils/Smali.java
index 0092be2..02aa682 100644
--- a/src/test/java/com/android/tools/r8/utils/Smali.java
+++ b/src/test/java/com/android/tools/r8/utils/Smali.java
@@ -109,8 +109,7 @@
options.programConsumer = consumer;
ExecutorService executor = ThreadUtils.getExecutorService(1);
try {
- DexApplication dexApp = new ApplicationReader(
- app, options, new Timing("smali")).read(executor);
+ DexApplication dexApp = new ApplicationReader(app, options, Timing.empty()).read(executor);
ApplicationWriter writer =
new ApplicationWriter(
dexApp,
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
index 6a2f504..1a640ed 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
@@ -97,4 +97,14 @@
public List<ClassSubject> getSuperTypes() {
return null;
}
+
+ @Override
+ public List<String> getSealedSubclassDescriptors() {
+ return null;
+ }
+
+ @Override
+ public List<ClassSubject> getSealedSubclasses() {
+ return null;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 1a4dcc16..5e1e668 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -27,6 +27,7 @@
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.DexValue.DexValueArray;
import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.ClassNamingForNameMapper;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -102,7 +103,7 @@
originalToObfuscatedMapping = null;
obfuscatedToOriginalMapping = null;
}
- Timing timing = new Timing("CodeInspector");
+ Timing timing = Timing.empty();
InternalOptions options = runOptionsConsumer(optionsConsumer);
dexItemFactory = options.itemFactory;
AndroidApp input = AndroidApp.builder().addProgramFiles(files).build();
@@ -111,14 +112,14 @@
public CodeInspector(AndroidApp app) throws IOException, ExecutionException {
this(
- new ApplicationReader(app, runOptionsConsumer(null), new Timing("CodeInspector"))
+ new ApplicationReader(app, runOptionsConsumer(null), Timing.empty())
.read(app.getProguardMapOutputData()));
}
public CodeInspector(AndroidApp app, Consumer<InternalOptions> optionsConsumer)
throws IOException, ExecutionException {
this(
- new ApplicationReader(app, runOptionsConsumer(optionsConsumer), new Timing("CodeInspector"))
+ new ApplicationReader(app, runOptionsConsumer(optionsConsumer), Timing.empty())
.read(app.getProguardMapOutputData()));
}
@@ -133,14 +134,14 @@
public CodeInspector(AndroidApp app, Path proguardMapFile)
throws IOException, ExecutionException {
this(
- new ApplicationReader(app, runOptionsConsumer(null), new Timing("CodeInspector"))
+ new ApplicationReader(app, runOptionsConsumer(null), Timing.empty())
.read(StringResource.fromFile(proguardMapFile)));
}
public CodeInspector(AndroidApp app, String proguardMapContent)
throws IOException, ExecutionException {
this(
- new ApplicationReader(app, runOptionsConsumer(null), new Timing("CodeInspector"))
+ new ApplicationReader(app, runOptionsConsumer(null), Timing.empty())
.read(StringResource.fromString(proguardMapContent, Origin.unknown())));
}
@@ -251,7 +252,7 @@
return rewriter.getSignature();
}
- public ClassSubject clazz(Class clazz) {
+ public ClassSubject clazz(Class<?> clazz) {
return clazz(Reference.classFromClass(clazz));
}
@@ -284,6 +285,12 @@
return new FoundClassSubject(this, clazz, naming);
}
+ public ClassSubject companionClassFor(Class<?> clazz) {
+ return clazz(
+ Reference.classFromTypeName(
+ clazz.getTypeName() + InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX));
+ }
+
public void forAllClasses(Consumer<FoundClassSubject> inspection) {
forAll(
application.classes(),
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
index e60f9f3..6c65c2d 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.utils.codeinspector;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.utils.DescriptorUtils;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@@ -69,4 +70,19 @@
.filter(ClassSubject::isPresent)
.collect(Collectors.toList());
}
+
+ @Override
+ public List<String> getSealedSubclassDescriptors() {
+ return kmClass.getSealedSubclasses().stream()
+ .map(DescriptorUtils::getDescriptorFromKotlinClassifier)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List<ClassSubject> getSealedSubclasses() {
+ return kmClass.getSealedSubclasses().stream()
+ .map(DescriptorUtils::getDescriptorFromKotlinClassifier)
+ .map(this::getClassSubjectFromDescriptor)
+ .collect(Collectors.toList());
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
index 34bc771..9fece50 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
@@ -140,12 +140,16 @@
.collect(Collectors.toList());
}
+ default ClassSubject getClassSubjectFromDescriptor(String descriptor) {
+ return codeInspector().clazz(Reference.classFromDescriptor(descriptor));
+ }
+
default ClassSubject getClassSubjectFromKmType(KmType kmType) {
String descriptor = getDescriptorFromKmType(kmType);
if (descriptor == null) {
return new AbsentClassSubject();
}
- return codeInspector().clazz(Reference.classFromDescriptor(descriptor));
+ return getClassSubjectFromDescriptor(descriptor);
}
@Override
@@ -167,7 +171,7 @@
}
// TODO(b/145824437): This is a dup of KotlinMetadataJvmExtensionUtils$KmPropertyProcessor
- static class KmPropertyProcessor {
+ class KmPropertyProcessor {
JvmFieldSignature fieldSignature = null;
// Custom getter via @get:JvmName("..."). Otherwise, null.
JvmMethodSignature getterSignature = null;
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
index d36f233..97b233d 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
@@ -12,4 +12,8 @@
public abstract List<String> getSuperTypeDescriptors();
public abstract List<ClassSubject> getSuperTypes();
+
+ public abstract List<String> getSealedSubclassDescriptors();
+
+ public abstract List<ClassSubject> getSealedSubclasses();
}
diff --git a/tools/internal_test.py b/tools/internal_test.py
index 182ccf8..81ccd98 100755
--- a/tools/internal_test.py
+++ b/tools/internal_test.py
@@ -334,8 +334,14 @@
log('Running with hash: %s' % git_hash)
exitcode = run_once(archive=True)
log('Running finished with exit code %s' % exitcode)
- put_magic_file(TESTING_COMPLETE, git_hash)
- delete_magic_file(TESTING)
+ # If the bot timed out or something else triggered the bot to fail, don't
+ # put up the result (it will not be displayed anywhere, and we can't
+ # remove the magic file if the bot cleaned up).
+ if get_magic_file_exists(TESTING):
+ put_magic_file(TESTING_COMPLETE, git_hash)
+ # There is still a potential race here (we check, bot deletes, we try to
+ # delete) - this is unlikely and we ignore it (restart if it happens).
+ delete_magic_file(TESTING)
time.sleep(PULL_DELAY)
def handle_output(archive, stderr, stdout, exitcode, timed_out, cmd):