Merge commit 'b3e71c33f5f9f60c893ddcbc62a9c08f428dfccf' into dev-release
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index fda08a6..f66fff8 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -111,8 +111,9 @@
"\n",
Iterables.concat(
Arrays.asList(
- "Usage: d8 [options] <input-files>",
+ "Usage: d8 [options] [@<argfile>] <input-files>",
" where <input-files> are any combination of dex, class, zip, jar, or apk files",
+ " and each <argfile> is a file containing additional arguments (one per line)",
" and options are:",
" --debug # Compile with debugging information (default).",
" --release # Compile without debugging information.",
@@ -258,6 +259,8 @@
builder.error(new StringDiagnostic("Unknown option: " + arg, origin));
continue;
}
+ } else if (arg.startsWith("@")) {
+ builder.error(new StringDiagnostic("Recursive @argfiles are not supported: ", origin));
} else {
builder.addProgramFiles(Paths.get(arg));
}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 7db3d7c..75f862e 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -35,7 +35,6 @@
import com.android.tools.r8.ir.desugar.NestedPrivateMethodLense;
import com.android.tools.r8.ir.desugar.R8NestBasedAccessDesugaring;
import com.android.tools.r8.ir.optimize.AssertionsRewriter;
-import com.android.tools.r8.ir.optimize.EnumInfoMapCollector;
import com.android.tools.r8.ir.optimize.MethodPoolCollection;
import com.android.tools.r8.ir.optimize.NestReducer;
import com.android.tools.r8.ir.optimize.SwitchMapCollector;
@@ -43,6 +42,7 @@
import com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization.UninstantiatedTypeOptimizationGraphLense;
import com.android.tools.r8.ir.optimize.UnusedArgumentsCollector;
import com.android.tools.r8.ir.optimize.UnusedArgumentsCollector.UnusedArgumentsGraphLense;
+import com.android.tools.r8.ir.optimize.enums.EnumInfoMapCollector;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
import com.android.tools.r8.jar.CfApplicationWriter;
import com.android.tools.r8.kotlin.Kotlin;
@@ -308,7 +308,7 @@
// Compute kotlin info before setting the roots and before
// kotlin metadata annotation is removed.
- computeKotlinInfoForProgramClasses(application, appView);
+ computeKotlinInfoForProgramClasses(application, appView, executorService);
// Add synthesized -assumenosideeffects from min api if relevant.
if (options.isGeneratingDex()) {
@@ -508,6 +508,8 @@
// Collect switch maps and ordinals maps.
if (options.enableEnumValueOptimization) {
appViewWithLiveness.setAppInfo(new SwitchMapCollector(appViewWithLiveness).run());
+ }
+ if (options.enableEnumValueOptimization || options.enableEnumUnboxing) {
appViewWithLiveness.setAppInfo(new EnumInfoMapCollector(appViewWithLiveness).run());
}
@@ -725,7 +727,8 @@
// Rewrite signature annotations for applications that are not minified.
if (appView.appInfo().hasLiveness()) {
// TODO(b/124726014): Rewrite signature annotations in lens rewriting instead of here?
- new GenericSignatureRewriter(appView.withLiveness()).run(appView.appInfo().classes());
+ new GenericSignatureRewriter(appView.withLiveness())
+ .run(appView.appInfo().classes(), executorService);
}
namingLens = NamingLens.getIdentityLens();
}
@@ -884,17 +887,23 @@
throw new CompilationError("Discard checks failed.");
}
- private void computeKotlinInfoForProgramClasses(DexApplication application, AppView<?> appView) {
+ private void computeKotlinInfoForProgramClasses(
+ DexApplication application, AppView<?> appView, ExecutorService executorService)
+ throws ExecutionException{
if (appView.options().kotlinOptimizationOptions().disableKotlinSpecificOptimizations) {
return;
}
Kotlin kotlin = appView.dexItemFactory().kotlin;
Reporter reporter = options.reporter;
- for (DexProgramClass programClass : application.classes()) {
- KotlinInfo kotlinInfo = kotlin.getKotlinInfo(programClass, reporter);
- programClass.setKotlinInfo(kotlinInfo);
- KotlinMemberInfo.markKotlinMemberInfo(programClass, kotlinInfo, reporter);
- }
+ ThreadUtils.processItems(
+ application.classes(),
+ programClass -> {
+ KotlinInfo kotlinInfo = kotlin.getKotlinInfo(programClass, reporter);
+ programClass.setKotlinInfo(kotlinInfo);
+ KotlinMemberInfo.markKotlinMemberInfo(programClass, kotlinInfo, reporter);
+ },
+ executorService
+ );
}
private static boolean verifyNoJarApplicationReaders(List<DexProgramClass> classes) {
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
index f1541bd..22ebb5e 100644
--- a/src/main/java/com/android/tools/r8/R8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -52,8 +52,9 @@
"\n",
Iterables.concat(
Arrays.asList(
- "Usage: r8 [options] <input-files>",
+ "Usage: r8 [options] [@<argfile>] <input-files>",
" where <input-files> are any combination of dex, class, zip, jar, or apk files",
+ " and each <argfile> is a file containing additional arguments (one per line)",
" and options are:",
" --release # Compile without debugging information (default).",
" --debug # Compile with debugging information.",
@@ -223,6 +224,8 @@
builder.error(new StringDiagnostic("Unknown option: " + arg, argsOrigin));
continue;
}
+ } else if (arg.startsWith("@")) {
+ builder.error(new StringDiagnostic("Recursive @argfiles are not supported: ", argsOrigin));
} else {
builder.addProgramFiles(Paths.get(arg));
}
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index c69d8e0..df91f96 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -14,7 +14,6 @@
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.ClassAccessFlags;
import com.android.tools.r8.graph.ClassKind;
-import com.android.tools.r8.graph.Descriptor;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationElement;
import com.android.tools.r8.graph.DexAnnotationSet;
@@ -33,6 +32,7 @@
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMember;
import com.android.tools.r8.graph.DexMemberAnnotation;
import com.android.tools.r8.graph.DexMemberAnnotation.DexFieldAnnotation;
import com.android.tools.r8.graph.DexMemberAnnotation.DexMethodAnnotation;
@@ -570,21 +570,21 @@
return new DexDebugInfo(start, parameters, events.toArray(DexDebugEvent.EMPTY_ARRAY));
}
- private static class MemberAnnotationIterator<S extends Descriptor<?, S>, T extends DexItem> {
+ private static class MemberAnnotationIterator<R extends DexMember<?, R>, T extends DexItem> {
private int index = 0;
- private final DexMemberAnnotation<S, T>[] annotations;
+ private final DexMemberAnnotation<R, T>[] annotations;
private final Supplier<T> emptyValue;
- private MemberAnnotationIterator(DexMemberAnnotation<S, T>[] annotations,
- Supplier<T> emptyValue) {
+ private MemberAnnotationIterator(
+ DexMemberAnnotation<R, T>[] annotations, Supplier<T> emptyValue) {
this.annotations = annotations;
this.emptyValue = emptyValue;
}
// Get the annotation set for an item. This method assumes that it is always called with
// an item that is higher in the sorting order than the last item.
- T getNextFor(S item) {
+ T getNextFor(R item) {
// TODO(ager): We could use the indices from the file to make this search faster using
// compareTo instead of slowCompareTo. That would require us to assign indices during
// reading. Those indices should be cleared after reading to make sure that we resort
@@ -1189,7 +1189,7 @@
MethodHandleType type = MethodHandleType.getKind(dexReader.getUshort());
dexReader.getUshort(); // unused
int indexFieldOrMethod = dexReader.getUshort();
- Descriptor<? extends DexItem, ? extends Descriptor<?, ?>> fieldOrMethod;
+ DexMember<? extends DexItem, ? extends DexMember<?, ?>> fieldOrMethod;
switch (type) {
case INSTANCE_GET:
case INSTANCE_PUT:
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 53f65b6..8b0cd00 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -9,7 +9,6 @@
import com.android.tools.r8.ByteBufferProvider;
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.graph.Descriptor;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationDirectory;
import com.android.tools.r8.graph.DexAnnotationElement;
@@ -29,6 +28,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItem;
+import com.android.tools.r8.graph.DexMember;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexMethodHandle;
import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
@@ -577,10 +577,10 @@
}
}
- private <S extends Descriptor<T, S>, T extends DexEncodedMember<S>> void writeMemberAnnotations(
- List<T> items, ToIntFunction<T> getter) {
- for (T item : items) {
- dest.putInt(item.getKey().getOffset(mapping));
+ private <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> void writeMemberAnnotations(
+ List<D> items, ToIntFunction<D> getter) {
+ for (D item : items) {
+ dest.putInt(item.toReference().getOffset(mapping));
dest.putInt(getter.applyAsInt(item));
}
}
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 c709a31..a6a9471 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -150,6 +150,10 @@
return visibilityOrdinal() == 1 || visibilityOrdinal() == 2;
}
+ public boolean isPackagePrivate() {
+ return !isPublic() && !isPrivate() && !isProtected();
+ }
+
public boolean isPublic() {
return isSet(Constants.ACC_PUBLIC);
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 29a5995..141f742 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -29,8 +29,8 @@
private final DexApplication app;
private final DexItemFactory dexItemFactory;
- private final ConcurrentHashMap<DexType, Map<Descriptor<?, ?>, DexEncodedMember<?>>> definitions =
- new ConcurrentHashMap<>();
+ private final ConcurrentHashMap<DexType, Map<DexMember<?, ?>, DexEncodedMember<?, ?>>>
+ definitions = new ConcurrentHashMap<>();
// For some optimizations, e.g. optimizing synthetic classes, we may need to resolve the current
// class being optimized.
final ConcurrentHashMap<DexType, DexProgramClass> synthesizedClasses = new ConcurrentHashMap<>();
@@ -101,12 +101,12 @@
return Collections.unmodifiableCollection(synthesizedClasses.values());
}
- private Map<Descriptor<?, ?>, DexEncodedMember<?>> computeDefinitions(DexType type) {
- Builder<Descriptor<?, ?>, DexEncodedMember<?>> builder = ImmutableMap.builder();
+ private Map<DexMember<?, ?>, DexEncodedMember<?, ?>> computeDefinitions(DexType type) {
+ Builder<DexMember<?, ?>, DexEncodedMember<?, ?>> builder = ImmutableMap.builder();
DexClass clazz = definitionFor(type);
if (clazz != null) {
- clazz.forEachMethod(method -> builder.put(method.getKey(), method));
- clazz.forEachField(field -> builder.put(field.getKey(), field));
+ clazz.forEachMethod(method -> builder.put(method.toReference(), method));
+ clazz.forEachField(field -> builder.put(field.toReference(), field));
}
return builder.build();
}
@@ -186,14 +186,14 @@
return (DexEncodedField) getDefinitions(field.holder).get(field);
}
- private Map<Descriptor<?, ?>, DexEncodedMember<?>> getDefinitions(DexType type) {
- Map<Descriptor<?, ?>, DexEncodedMember<?>> typeDefinitions = definitions.get(type);
+ private Map<DexMember<?, ?>, DexEncodedMember<?, ?>> getDefinitions(DexType type) {
+ Map<DexMember<?, ?>, DexEncodedMember<?, ?>> typeDefinitions = definitions.get(type);
if (typeDefinitions != null) {
return typeDefinitions;
}
typeDefinitions = computeDefinitions(type);
- Map<Descriptor<?, ?>, DexEncodedMember<?>> existing =
+ Map<DexMember<?, ?>, DexEncodedMember<?, ?>> existing =
definitions.putIfAbsent(type, typeDefinitions);
return existing != null ? existing : typeDefinitions;
}
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 495c0b3..813ea84 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -7,6 +7,8 @@
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.WorkList;
+import com.android.tools.r8.utils.WorkList.EqualityTest;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -20,11 +22,13 @@
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
-import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.function.Consumer;
import java.util.function.Function;
-public class AppInfoWithSubtyping extends AppInfoWithClassHierarchy implements LiveSubTypeInfo {
+public class AppInfoWithSubtyping extends AppInfoWithClassHierarchy
+ implements InstantiatedSubTypeInfo {
private static final int ROOT_LEVEL = 0;
private static final int UNKNOWN_LEVEL = -1;
@@ -34,16 +38,23 @@
private static final Set<DexType> NO_DIRECT_SUBTYPE = ImmutableSet.of();
@Override
- public LiveSubTypeResult getLiveSubTypes(DexType type) {
- // TODO(b/139464956): Remove this when we start to have live type information in the enqueuer.
- Set<DexProgramClass> programClasses = new HashSet<>();
- for (DexType subtype : subtypes(type)) {
- DexProgramClass subClass = definitionForProgramType(subtype);
- if (subClass != null) {
- programClasses.add(subClass);
+ public void forEachInstantiatedSubType(
+ DexType type,
+ Consumer<DexProgramClass> subTypeConsumer,
+ Consumer<DexCallSite> callSiteConsumer) {
+ WorkList<DexType> workList = new WorkList<>(EqualityTest.IDENTITY);
+ workList.addIfNotSeen(type);
+ workList.addIfNotSeen(allImmediateSubtypes(type));
+ while (workList.hasNext()) {
+ DexType subType = workList.next();
+ DexProgramClass clazz = definitionForProgramType(subType);
+ if (clazz != null) {
+ subTypeConsumer.accept(clazz);
}
+ workList.addIfNotSeen(allImmediateSubtypes(subType));
}
- return new LiveSubTypeResult(programClasses, null);
+ // TODO(b/148769279): Change this when we have information about callsites.
+ callSiteConsumer.accept(null);
}
private static class TypeInfo {
@@ -71,7 +82,7 @@
private void ensureDirectSubTypeSet() {
if (directSubtypes == NO_DIRECT_SUBTYPE) {
- directSubtypes = new TreeSet<>(DexType::slowCompareTo);
+ directSubtypes = new ConcurrentSkipListSet<>(DexType::slowCompareTo);
}
}
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 9f7b4cf..1f24d35 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
import com.android.tools.r8.ir.optimize.CallSiteOptimizationInfoPropagator;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory;
import com.android.tools.r8.ir.optimize.library.LibraryMethodOptimizer;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
@@ -45,6 +46,8 @@
private final InternalOptions options;
private RootSet rootSet;
private final AbstractValueFactory abstractValueFactory = new AbstractValueFactory();
+ private final InstanceFieldInitializationInfoFactory instanceFieldInitializationInfoFactory =
+ new InstanceFieldInitializationInfoFactory();
// Desugared library prefix rewriter.
public final PrefixRewritingMapper rewritePrefix;
@@ -125,6 +128,10 @@
return abstractValueFactory;
}
+ public InstanceFieldInitializationInfoFactory instanceFieldInitializationInfoFactory() {
+ return instanceFieldInitializationInfoFactory;
+ }
+
public T appInfo() {
return appInfo;
}
@@ -149,6 +156,10 @@
allCodeProcessed = true;
}
+ public GraphLense clearCodeRewritings() {
+ return graphLense = graphLense.withCodeRewritingsApplied();
+ }
+
public AppServices appServices() {
return appServices;
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
index 18f8fe6..a05883a 100644
--- a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
@@ -8,7 +8,6 @@
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import java.util.IdentityHashMap;
-import java.util.List;
import java.util.Map;
/**
@@ -29,7 +28,7 @@
new IdentityHashMap<>();
public AppliedGraphLens(
- AppView<? extends AppInfoWithSubtyping> appView, List<DexProgramClass> classes) {
+ AppView<? extends AppInfoWithSubtyping> appView, Iterable<DexProgramClass> classes) {
this.appView = appView;
for (DexProgramClass clazz : classes) {
@@ -135,4 +134,9 @@
public boolean isContextFreeForMethods() {
return true;
}
+
+ @Override
+ public boolean hasCodeRewritings() {
+ return false;
+ }
}
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 cbc8303..5c8de46 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -469,7 +469,7 @@
} else {
continue;
}
- if (index >= 0) {
+ if (index >= 0 && indexToNumber.containsKey(index)) {
registry.register(indexToNumber.get(index));
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/Descriptor.java b/src/main/java/com/android/tools/r8/graph/Descriptor.java
deleted file mode 100644
index f0ba1b6..0000000
--- a/src/main/java/com/android/tools/r8/graph/Descriptor.java
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) 2016, 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.graph;
-
-public abstract class Descriptor<T extends DexItem, S extends Descriptor<T,S>>
- extends DexReference implements PresortedComparable<S> {
-
- public abstract boolean match(S entry);
-
- public abstract boolean match(T entry);
-
- @Override
- public boolean isDescriptor() {
- return true;
- }
-
- @Override
- public Descriptor asDescriptor() {
- return this;
- }
-}
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
index e616e9e..2d0667b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
@@ -107,9 +107,9 @@
throw new Unreachable();
}
- private static <T extends PresortedComparable<T>> boolean isSorted(
- List<? extends DexEncodedMember<T>> items) {
- return isSorted(items, DexEncodedMember::getKey);
+ private static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> boolean isSorted(
+ List<D> items) {
+ return isSorted(items, DexEncodedMember::toReference);
}
private static <S, T extends Comparable<T>> boolean isSorted(
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 3e1a144..3601754 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -129,7 +129,7 @@
Iterables.filter(Arrays.asList(staticFields), predicate::test));
}
- public Iterable<DexEncodedMember<?>> members() {
+ public Iterable<DexEncodedMember<?, ?>> members() {
return Iterables.concat(fields(), methods());
}
@@ -611,8 +611,9 @@
&& method.method.proto.parameters.values[0] != factory.objectArrayType;
}
- private <T extends DexItem, S extends Descriptor<T, S>> T lookupTarget(T[] items, S descriptor) {
- for (T entry : items) {
+ private <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> D lookupTarget(
+ D[] items, R descriptor) {
+ for (D entry : items) {
if (descriptor.match(entry)) {
return entry;
}
@@ -773,11 +774,15 @@
}
public boolean classInitializationMayHaveSideEffects(AppView<?> appView) {
- return classInitializationMayHaveSideEffects(appView, Predicates.alwaysFalse());
+ return classInitializationMayHaveSideEffects(
+ appView, Predicates.alwaysFalse(), Sets.newIdentityHashSet());
}
public boolean classInitializationMayHaveSideEffects(
- AppView<?> appView, Predicate<DexType> ignore) {
+ AppView<?> appView, Predicate<DexType> ignore, Set<DexType> seen) {
+ if (!seen.add(type)) {
+ return false;
+ }
if (ignore.test(type)) {
return false;
}
@@ -795,7 +800,7 @@
if (defaultValuesForStaticFieldsMayTriggerAllocation()) {
return true;
}
- return initializationOfParentTypesMayHaveSideEffects(appView, ignore);
+ return initializationOfParentTypesMayHaveSideEffects(appView, ignore, seen);
}
private boolean hasClassInitializerThatCannotBePostponed() {
@@ -820,17 +825,19 @@
}
public boolean initializationOfParentTypesMayHaveSideEffects(AppView<?> appView) {
- return initializationOfParentTypesMayHaveSideEffects(appView, Predicates.alwaysFalse());
+ return initializationOfParentTypesMayHaveSideEffects(
+ appView, Predicates.alwaysFalse(), Sets.newIdentityHashSet());
}
public boolean initializationOfParentTypesMayHaveSideEffects(
- AppView<?> appView, Predicate<DexType> ignore) {
+ AppView<?> appView, Predicate<DexType> ignore, Set<DexType> seen) {
for (DexType iface : interfaces.values) {
- if (iface.classInitializationMayHaveSideEffects(appView, ignore)) {
+ if (iface.classInitializationMayHaveSideEffects(appView, ignore, seen)) {
return true;
}
}
- if (superType != null && superType.classInitializationMayHaveSideEffects(appView, ignore)) {
+ if (superType != null
+ && superType.classInitializationMayHaveSideEffects(appView, ignore, seen)) {
return true;
}
return false;
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
new file mode 100644
index 0000000..281b58a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.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.graph;
+
+import com.android.tools.r8.errors.Unreachable;
+
+public class DexClassAndMethod {
+
+ private final DexClass holder;
+ private final DexEncodedMethod method;
+
+ protected DexClassAndMethod(DexClass holder, DexEncodedMethod method) {
+ assert holder.type == method.method.holder;
+ this.holder = holder;
+ this.method = method;
+ }
+
+ public static DexClassAndMethod create(DexClass holder, DexEncodedMethod method) {
+ if (holder.isProgramClass()) {
+ return new ProgramMethod(holder.asProgramClass(), method);
+ } else {
+ return new DexClassAndMethod(holder, method);
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ throw new Unreachable("Unsupported attempt at comparing Class and DexClassAndMethod");
+ }
+
+ @Override
+ public int hashCode() {
+ throw new Unreachable("Unsupported attempt at computing the hashcode of DexClassAndMethod");
+ }
+
+ public DexClass getHolder() {
+ return holder;
+ }
+
+ public DexEncodedMethod getMethod() {
+ return method;
+ }
+
+ public boolean isProgramMethod() {
+ return false;
+ }
+
+ public ProgramMethod asProgramMethod() {
+ return null;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/DexDefinition.java b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
index dbf18c3..7f6796d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
@@ -51,7 +51,7 @@
return false;
}
- public DexEncodedMember<?> asDexEncodedMember() {
+ public DexEncodedMember<?, ?> asDexEncodedMember() {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 4f332f9..ce89a41 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -18,8 +18,9 @@
import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
import com.android.tools.r8.kotlin.KotlinMemberInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Sets;
-public class DexEncodedField extends DexEncodedMember<DexField> {
+public class DexEncodedField extends DexEncodedMember<DexEncodedField, DexField> {
public static final DexEncodedField[] EMPTY_ARRAY = {};
public final DexField field;
@@ -110,12 +111,7 @@
}
@Override
- public DexField getKey() {
- return field;
- }
-
- @Override
- public DexReference toReference() {
+ public DexField toReference() {
return field;
}
@@ -222,7 +218,8 @@
appView,
// Types that are a super type of the current context are guaranteed to be initialized
// already.
- type -> appView.isSubtype(context, type).isTrue())) {
+ type -> appView.isSubtype(context, type).isTrue(),
+ Sets.newIdentityHashSet())) {
// Ignore class initialization side-effects for dead proto extension fields to ensure that
// we force replace these field reads by null.
boolean ignore =
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
index 6987227..0e436d6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
@@ -3,13 +3,15 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
-public abstract class DexEncodedMember<T extends PresortedComparable<T>> extends DexDefinition {
+public abstract class DexEncodedMember<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+ extends DexDefinition {
public DexEncodedMember(DexAnnotationSet annotations) {
super(annotations);
}
- public abstract T getKey();
+ @Override
+ public abstract R toReference();
@Override
public boolean isDexEncodedMember() {
@@ -17,7 +19,7 @@
}
@Override
- public DexEncodedMember<?> asDexEncodedMember() {
+ public DexEncodedMember<D, R> asDexEncodedMember() {
return this;
}
@@ -27,11 +29,11 @@
return true;
}
return other.getClass() == getClass()
- && ((DexEncodedMember<?>) other).getKey().equals(getKey());
+ && ((DexEncodedMember<?, ?>) other).toReference().equals(toReference());
}
@Override
public final int hashCode() {
- return getKey().hashCode();
+ return toReference().hashCode();
}
}
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 184e15e..b755c49 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -76,7 +76,7 @@
import java.util.function.IntPredicate;
import org.objectweb.asm.Opcodes;
-public class DexEncodedMethod extends DexEncodedMember<DexMethod> {
+public class DexEncodedMethod extends DexEncodedMember<DexEncodedMethod, DexMethod> {
public static final String CONFIGURATION_DEBUGGING_PREFIX = "Shaking error: Missing method in ";
@@ -1163,14 +1163,7 @@
}
@Override
- public DexMethod getKey() {
- // Here, we can't check if the current instance of DexEncodedMethod is obsolete
- // because itself can be used as a key while making mappings to avoid using obsolete instances.
- return method;
- }
-
- @Override
- public DexReference toReference() {
+ public DexMethod toReference() {
checkIfObsolete();
return method;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexField.java b/src/main/java/com/android/tools/r8/graph/DexField.java
index dfd3203..02cb985 100644
--- a/src/main/java/com/android/tools/r8/graph/DexField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -7,15 +7,13 @@
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.naming.NamingLens;
-public class DexField extends Descriptor<DexEncodedField, DexField> implements
- PresortedComparable<DexField> {
+public class DexField extends DexMember<DexEncodedField, DexField> {
- public final DexType holder;
public final DexType type;
public final DexString name;
DexField(DexType holder, DexType type, DexString name, boolean skipNameValidationForTesting) {
- this.holder = holder;
+ super(holder);
this.type = type;
this.name = name;
if (!skipNameValidationForTesting && !name.isValidFieldName()) {
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 df2402f..8cb71bb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -1539,7 +1539,7 @@
public DexMethodHandle createMethodHandle(
MethodHandleType type,
- Descriptor<? extends DexItem, ? extends Descriptor<?, ?>> fieldOrMethod,
+ DexMember<? extends DexItem, ? extends DexMember<?, ?>> fieldOrMethod,
boolean isInterface) {
assert !sorted;
DexMethodHandle methodHandle = new DexMethodHandle(type, fieldOrMethod, isInterface);
diff --git a/src/main/java/com/android/tools/r8/graph/DexMember.java b/src/main/java/com/android/tools/r8/graph/DexMember.java
new file mode 100644
index 0000000..96afd57
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/DexMember.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2016, 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.graph;
+
+public abstract class DexMember<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+ extends DexReference implements PresortedComparable<R> {
+
+ public final DexType holder;
+
+ public DexMember(DexType holder) {
+ this.holder = holder;
+ }
+
+ public abstract boolean match(R entry);
+
+ public abstract boolean match(D entry);
+
+ @Override
+ public boolean isDexMember() {
+ return true;
+ }
+
+ @Override
+ public DexMember<D, R> asDexMember() {
+ return this;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/DexMemberAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexMemberAnnotation.java
index 6cc2fb4..d9868a5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMemberAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMemberAnnotation.java
@@ -6,12 +6,12 @@
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.dex.MixedSectionCollection;
-public class DexMemberAnnotation<T extends Descriptor<?,?>, S extends DexItem> extends DexItem {
+public class DexMemberAnnotation<R extends DexMember<?, R>, S extends DexItem> extends DexItem {
- public final T item;
+ public final R item;
public final S annotations;
- public DexMemberAnnotation(T item, S annotations) {
+ public DexMemberAnnotation(R item, S annotations) {
this.item = item;
this.annotations = annotations;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index a1e98f7..a29eb31 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -9,10 +9,8 @@
import com.google.common.collect.Maps;
import java.util.Map;
-public class DexMethod extends Descriptor<DexEncodedMethod, DexMethod>
- implements PresortedComparable<DexMethod> {
+public class DexMethod extends DexMember<DexEncodedMethod, DexMethod> {
- public final DexType holder;
public final DexProto proto;
public final DexString name;
@@ -20,7 +18,7 @@
private Map<DexType, DexEncodedMethod> singleTargetCache;
DexMethod(DexType holder, DexProto proto, DexString name, boolean skipNameValidationForTesting) {
- this.holder = holder;
+ super(holder);
this.proto = proto;
this.name = name;
if (!skipNameValidationForTesting && !name.isValidMethodName()) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
index 336842f..fbe8a9f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
@@ -187,7 +187,7 @@
public final MethodHandleType type;
// Field or method that the method handle is targeting.
- public final Descriptor<? extends DexItem, ? extends Descriptor<?,?>> fieldOrMethod;
+ public final DexMember<? extends DexItem, ? extends DexMember<?, ?>> fieldOrMethod;
public final boolean isInterface;
@@ -204,7 +204,7 @@
public DexMethodHandle(
MethodHandleType type,
- Descriptor<? extends DexItem, ? extends Descriptor<?, ?>> fieldOrMethod,
+ DexMember<? extends DexItem, ? extends DexMember<?, ?>> fieldOrMethod,
boolean isInterface) {
this.type = type;
this.fieldOrMethod = fieldOrMethod;
@@ -214,7 +214,7 @@
public DexMethodHandle(
MethodHandleType type,
- Descriptor<? extends DexItem, ? extends Descriptor<?, ?>> fieldOrMethod,
+ DexMember<? extends DexItem, ? extends DexMember<?, ?>> fieldOrMethod,
boolean isInterface,
DexMethod rewrittenTarget) {
this.type = type;
@@ -226,7 +226,7 @@
public static DexMethodHandle fromAsmHandle(
Handle handle, JarApplicationReader application, DexType clazz) {
MethodHandleType methodHandleType = MethodHandleType.fromAsmHandle(handle, application, clazz);
- Descriptor<? extends DexItem, ? extends Descriptor<?, ?>> descriptor =
+ DexMember<? extends DexItem, ? extends DexMember<?, ?>> descriptor =
methodHandleType.isFieldType()
? application.getField(handle.getOwner(), handle.getName(), handle.getDesc())
: application.getMethod(handle.getOwner(), handle.getName(), handle.getDesc());
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 1ac7c58..78a59c2 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -352,11 +352,11 @@
&& isSorted(instanceFields);
}
- private static <T extends DexEncodedMember<S>, S extends PresortedComparable<S>> boolean isSorted(
- T[] items) {
+ private static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> boolean isSorted(
+ D[] items) {
synchronized (items) {
- T[] sorted = items.clone();
- Arrays.sort(sorted, Comparator.comparing(DexEncodedMember::getKey));
+ D[] sorted = items.clone();
+ Arrays.sort(sorted, Comparator.comparing(DexEncodedMember::toReference));
return Arrays.equals(items, sorted);
}
}
@@ -441,7 +441,7 @@
* entire scope.
*/
public boolean hasReachabilitySensitiveAnnotation(DexItemFactory factory) {
- for (DexEncodedMember<?> member : members()) {
+ for (DexEncodedMember<?, ?> member : members()) {
for (DexAnnotation annotation : member.annotations().annotations) {
if (annotation.annotation.type == factory.annotationReachabilitySensitive) {
return true;
diff --git a/src/main/java/com/android/tools/r8/graph/DexReference.java b/src/main/java/com/android/tools/r8/graph/DexReference.java
index d51810e..42286db 100644
--- a/src/main/java/com/android/tools/r8/graph/DexReference.java
+++ b/src/main/java/com/android/tools/r8/graph/DexReference.java
@@ -21,11 +21,11 @@
return null;
}
- public boolean isDescriptor() {
+ public boolean isDexMember() {
return false;
}
- public Descriptor asDescriptor() {
+ public DexMember<?, ?> asDexMember() {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 9a8a73c..08d4d61 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -25,9 +25,11 @@
import com.android.tools.r8.utils.Pair;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.function.Predicate;
public class DexType extends DexReference implements PresortedComparable<DexType> {
@@ -60,23 +62,26 @@
}
public boolean classInitializationMayHaveSideEffects(AppView<?> appView) {
- return classInitializationMayHaveSideEffects(appView, Predicates.alwaysFalse());
+ return classInitializationMayHaveSideEffects(
+ appView, Predicates.alwaysFalse(), Sets.newIdentityHashSet());
}
public boolean classInitializationMayHaveSideEffects(
- AppView<?> appView, Predicate<DexType> ignore) {
+ AppView<?> appView, Predicate<DexType> ignore, Set<DexType> seen) {
DexClass clazz = appView.definitionFor(this);
- return clazz == null || clazz.classInitializationMayHaveSideEffects(appView, ignore);
+ return clazz == null || clazz.classInitializationMayHaveSideEffects(appView, ignore, seen);
}
public boolean initializationOfParentTypesMayHaveSideEffects(AppView<?> appView) {
- return initializationOfParentTypesMayHaveSideEffects(appView, Predicates.alwaysFalse());
+ return initializationOfParentTypesMayHaveSideEffects(
+ appView, Predicates.alwaysFalse(), Sets.newIdentityHashSet());
}
public boolean initializationOfParentTypesMayHaveSideEffects(
- AppView<?> appView, Predicate<DexType> ignore) {
+ AppView<?> appView, Predicate<DexType> ignore, Set<DexType> seen) {
DexClass clazz = appView.definitionFor(this);
- return clazz == null || clazz.initializationOfParentTypesMayHaveSideEffects(appView, ignore);
+ return clazz == null
+ || clazz.initializationOfParentTypesMayHaveSideEffects(appView, ignore, seen);
}
public boolean isAlwaysNull(AppView<AppInfoWithLiveness> appView) {
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index f1d1a34..e01f0ee 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -223,10 +223,21 @@
return IdentityGraphLense.getInstance();
}
+ public boolean hasCodeRewritings() {
+ return true;
+ }
+
public final boolean isIdentityLense() {
return this == getIdentityLense();
}
+ public GraphLense withCodeRewritingsApplied() {
+ if (hasCodeRewritings()) {
+ return new ClearCodeRewritingGraphLens(this);
+ }
+ return this;
+ }
+
public <T extends DexDefinition> boolean assertDefinitionsNotModified(Iterable<T> definitions) {
for (DexDefinition definition : definitions) {
DexReference reference = definition.toReference();
@@ -455,6 +466,51 @@
public boolean isContextFreeForMethods() {
return true;
}
+
+ @Override
+ public boolean hasCodeRewritings() {
+ return false;
+ }
+ }
+
+ // This lens clears all code rewriting (lookup methods mimics identity lens behavior) but still
+ // relies on the previous lens for names (getRenamed/Original methods).
+ public static class ClearCodeRewritingGraphLens extends IdentityGraphLense {
+ private final GraphLense previous;
+
+ public ClearCodeRewritingGraphLens(GraphLense previous) {
+ this.previous = previous;
+ }
+
+ @Override
+ public DexType getOriginalType(DexType type) {
+ return previous.getOriginalType(type);
+ }
+
+ @Override
+ public DexField getOriginalFieldSignature(DexField field) {
+ return previous.getOriginalFieldSignature(field);
+ }
+
+ @Override
+ public DexMethod getOriginalMethodSignature(DexMethod method) {
+ return previous.getOriginalMethodSignature(method);
+ }
+
+ @Override
+ public DexField getRenamedFieldSignature(DexField originalField) {
+ return previous.getRenamedFieldSignature(originalField);
+ }
+
+ @Override
+ public DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
+ return previous.getRenamedMethodSignature(originalMethod);
+ }
+
+ @Override
+ public DexType lookupType(DexType type) {
+ return previous.lookupType(type);
+ }
}
/**
diff --git a/src/main/java/com/android/tools/r8/graph/InstantiatedSubTypeInfo.java b/src/main/java/com/android/tools/r8/graph/InstantiatedSubTypeInfo.java
new file mode 100644
index 0000000..e4942bb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/InstantiatedSubTypeInfo.java
@@ -0,0 +1,16 @@
+// 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.graph;
+
+import java.util.function.Consumer;
+
+@FunctionalInterface
+public interface InstantiatedSubTypeInfo {
+
+ void forEachInstantiatedSubType(
+ DexType type,
+ Consumer<DexProgramClass> subTypeConsumer,
+ Consumer<DexCallSite> callSiteConsumer);
+}
diff --git a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
index 27f4c9c..8b5977f 100644
--- a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
@@ -104,7 +104,7 @@
public DexMethodHandle getMethodHandle(
MethodHandleType type,
- Descriptor<? extends DexItem, ? extends Descriptor<?, ?>> fieldOrMethod,
+ DexMember<? extends DexItem, ? extends DexMember<?, ?>> fieldOrMethod,
boolean isInterface) {
return options.itemFactory.createMethodHandle(type, fieldOrMethod, isInterface);
}
diff --git a/src/main/java/com/android/tools/r8/graph/LiveSubTypeInfo.java b/src/main/java/com/android/tools/r8/graph/LiveSubTypeInfo.java
deleted file mode 100644
index f812d7e..0000000
--- a/src/main/java/com/android/tools/r8/graph/LiveSubTypeInfo.java
+++ /dev/null
@@ -1,33 +0,0 @@
-// 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.graph;
-
-import java.util.Set;
-
-@FunctionalInterface
-public interface LiveSubTypeInfo {
-
- LiveSubTypeResult getLiveSubTypes(DexType type);
-
- class LiveSubTypeResult {
-
- private final Set<DexProgramClass> programClasses;
- private final Set<DexCallSite> callSites;
-
- public LiveSubTypeResult(Set<DexProgramClass> programClasses, Set<DexCallSite> callSites) {
- // TODO(b/149006127): Enable when we no longer rely on callSites == null.
- this.programClasses = programClasses;
- this.callSites = callSites;
- }
-
- public Set<DexProgramClass> getProgramClasses() {
- return programClasses;
- }
-
- public Set<DexCallSite> getCallSites() {
- return callSites;
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
index fa13c74..af2bdd4 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -33,6 +33,11 @@
return new Builder(trackAllocationSites, reporter);
}
+ public void markNoLongerInstantiated(DexProgramClass clazz) {
+ classesWithAllocationSiteTracking.remove(clazz);
+ classesWithoutAllocationSiteTracking.remove(clazz);
+ }
+
@Override
public void forEachClassWithKnownAllocationSites(
BiConsumer<DexProgramClass, Set<DexEncodedMethod>> consumer) {
diff --git a/src/main/java/com/android/tools/r8/graph/PresortedComparable.java b/src/main/java/com/android/tools/r8/graph/PresortedComparable.java
index 81e6d56..075c187 100644
--- a/src/main/java/com/android/tools/r8/graph/PresortedComparable.java
+++ b/src/main/java/com/android/tools/r8/graph/PresortedComparable.java
@@ -10,9 +10,9 @@
public interface PresortedComparable<T> extends Presorted, Comparable<T> {
- static <T extends PresortedComparable<T>> boolean isSorted(
- List<? extends DexEncodedMember<T>> items) {
- return isSorted(items, DexEncodedMember::getKey);
+ static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> boolean isSorted(
+ List<? extends DexEncodedMember<D, R>> items) {
+ return isSorted(items, DexEncodedMember::toReference);
}
static <S, T extends Comparable<T>> boolean isSorted(S[] items, Function<S, T> getter) {
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
index 1e33e66..4924d0c 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -3,26 +3,27 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
-import com.android.tools.r8.errors.Unreachable;
-
/** Type representing a method definition in the programs compilation unit and its holder. */
-public class ProgramMethod {
- public final DexProgramClass holder;
- public final DexEncodedMethod method;
+public final class ProgramMethod extends DexClassAndMethod {
- public ProgramMethod(DexProgramClass holder, DexEncodedMethod method) {
- assert holder.type == method.method.holder;
- this.holder = holder;
- this.method = method;
+ public ProgramMethod(DexProgramClass holder, DexEncodedMethod method) {
+ super(holder, method);
}
@Override
- public boolean equals(Object obj) {
- throw new Unreachable("Unsupported attempt at comparing ProgramMethod");
+ public boolean isProgramMethod() {
+ return true;
}
@Override
- public int hashCode() {
- throw new Unreachable("Unsupported attempt at computing the hashcode of ProgramMethod");
+ public ProgramMethod asProgramMethod() {
+ return this;
+ }
+
+ @Override
+ public DexProgramClass getHolder() {
+ DexClass holder = super.getHolder();
+ assert holder.isProgramClass();
+ return holder.asProgramClass();
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index c2bd3a4..feffac7 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -4,10 +4,8 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.graph.LiveSubTypeInfo.LiveSubTypeResult;
import com.android.tools.r8.graph.LookupResult.LookupResultSuccess.LookupResultCollectionState;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.SetUtils;
import com.google.common.collect.Sets;
import java.util.Collection;
import java.util.Collections;
@@ -79,12 +77,18 @@
DexProgramClass context, AppInfoWithClassHierarchy appInfo);
public abstract LookupResult lookupVirtualDispatchTargets(
- AppView<?> appView, LiveSubTypeInfo liveSubTypes);
+ DexProgramClass context,
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ InstantiatedSubTypeInfo instantiatedInfo);
- public final LookupResult lookupVirtualDispatchTargets(AppView<AppInfoWithLiveness> appView) {
- return lookupVirtualDispatchTargets(appView, appView.appInfo());
+ public final LookupResult lookupVirtualDispatchTargets(
+ DexProgramClass context, AppView<AppInfoWithLiveness> appView) {
+ return lookupVirtualDispatchTargets(context, appView, appView.appInfo());
}
+ public abstract DexClassAndMethod lookupVirtualDispatchTarget(
+ DexProgramClass dynamicInstance, AppView<? extends AppInfoWithClassHierarchy> appView);
+
/** Result for a resolution that succeeds with a known declaration/definition. */
public static class SingleResolutionResult extends ResolutionResult {
private final DexClass initialResolutionHolder;
@@ -102,6 +106,8 @@
this.resolvedHolder = resolvedHolder;
this.resolvedMethod = resolvedMethod;
this.initialResolutionHolder = initialResolutionHolder;
+ assert !resolvedMethod.isPrivateMethod()
+ || initialResolutionHolder.type == resolvedMethod.method.holder;
}
public DexClass getResolvedHolder() {
@@ -331,132 +337,186 @@
@Override
public LookupResult lookupVirtualDispatchTargets(
- AppView<?> appView, LiveSubTypeInfo liveSubTypeInfo) {
- return initialResolutionHolder.isInterface()
- ? lookupInterfaceTargets(appView, liveSubTypeInfo)
- : lookupVirtualTargets(appView, liveSubTypeInfo);
- }
-
- // TODO(b/140204899): Leverage refined receiver type if available.
- private LookupResult lookupVirtualTargets(AppView<?> appView, LiveSubTypeInfo liveSubTypeInfo) {
- assert !initialResolutionHolder.isInterface();
+ DexProgramClass context,
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ InstantiatedSubTypeInfo instantiatedInfo) {
+ // Check that the initial resolution holder is accessible from the context.
+ if (context != null && !isAccessibleFrom(context, appView.appInfo())) {
+ return LookupResult.createFailedResult();
+ }
if (resolvedMethod.isPrivateMethod()) {
// If the resolved reference is private there is no dispatch.
// This is assuming that the method is accessible, which implies self/nest access.
- return LookupResult.createResult(
- Collections.singleton(resolvedMethod), LookupResultCollectionState.Complete);
- }
- assert resolvedMethod.isNonPrivateVirtualMethod();
- // First add the target for receiver type method.type.
- Set<DexEncodedMethod> result = SetUtils.newIdentityHashSet(resolvedMethod);
- // Add all matching targets from the subclass hierarchy.
- DexMethod method = resolvedMethod.method;
- // TODO(b/140204899): Instead of subtypes of holder, we could iterate subtypes of refined
- // receiver type if available.
- for (DexProgramClass programClass :
- liveSubTypeInfo.getLiveSubTypes(method.holder).getProgramClasses()) {
- if (!programClass.isInterface()) {
- ResolutionResult methods = appView.appInfo().resolveMethodOnClass(programClass, method);
- DexEncodedMethod target = methods.getSingleTarget();
- if (target != null && target.isNonPrivateVirtualMethod()) {
- result.add(target);
- }
- }
- }
- return LookupResult.createResult(result, LookupResultCollectionState.Complete);
- }
-
- // TODO(b/140204899): Leverage refined receiver type if available.
- private LookupResult lookupInterfaceTargets(
- AppView<?> appView, LiveSubTypeInfo liveSubTypeInfo) {
- assert initialResolutionHolder.isInterface();
- if (resolvedMethod.isPrivateMethod()) {
- // If the resolved reference is private there is no dispatch.
- // This is assuming that the method is accessible, which implies self/nest access.
- assert resolvedMethod.hasCode();
+ // Only include if the target has code or is native.
return LookupResult.createResult(
Collections.singleton(resolvedMethod), LookupResultCollectionState.Complete);
}
assert resolvedMethod.isNonPrivateVirtualMethod();
Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
- // Add default interface methods to the list of targets.
- //
- // This helps to make sure we take into account synthesized lambda classes
- // that we are not aware of. Like in the following example, we know that all
- // classes, XX in this case, override B::bar(), but there are also synthesized
- // classes for lambda which don't, so we still need default method to be live.
- //
- // public static void main(String[] args) {
- // X x = () -> {};
- // x.bar();
- // }
- //
- // interface X {
- // void foo();
- // default void bar() { }
- // }
- //
- // class XX implements X {
- // public void foo() { }
- // public void bar() { }
- // }
- //
- addIfDefaultMethodWithLambdaInstantiations(liveSubTypeInfo, resolvedMethod, result);
-
- DexMethod method = resolvedMethod.method;
- Consumer<DexEncodedMethod> addIfNotAbstract =
- m -> {
- if (!m.accessFlags.isAbstract()) {
- result.add(m);
+ instantiatedInfo.forEachInstantiatedSubType(
+ resolvedHolder.type,
+ subClass -> {
+ DexClassAndMethod dexClassAndMethod = lookupVirtualDispatchTarget(subClass, appView);
+ if (dexClassAndMethod != null) {
+ addVirtualDispatchTarget(
+ dexClassAndMethod.getMethod(), resolvedHolder.isInterface(), result);
}
- };
- // Default methods are looked up when looking at a specific subtype that does not override
- // them. Otherwise, we would look up default methods that are actually never used. However, we
- // have to add bridge methods, otherwise we can remove a bridge that will be used.
- Consumer<DexEncodedMethod> addIfNotAbstractAndBridge =
- m -> {
- if (!m.accessFlags.isAbstract() && m.accessFlags.isBridge()) {
- result.add(m);
- }
- };
-
- // TODO(b/140204899): Instead of subtypes of holder, we could iterate subtypes of refined
- // receiver type if available.
- for (DexProgramClass clazz :
- liveSubTypeInfo.getLiveSubTypes(method.holder).getProgramClasses()) {
- if (clazz.isInterface()) {
- ResolutionResult targetMethods =
- appView.appInfo().resolveMethodOnInterface(clazz, method);
- if (targetMethods.isSingleResolution()) {
- // Sub-interfaces can have default implementations that override the resolved method.
- // Therefore we have to add default methods in sub interfaces.
- DexEncodedMethod singleTarget = targetMethods.getSingleTarget();
- addIfDefaultMethodWithLambdaInstantiations(liveSubTypeInfo, singleTarget, result);
- addIfNotAbstractAndBridge.accept(singleTarget);
- }
- } else {
- ResolutionResult targetMethods = appView.appInfo().resolveMethodOnClass(clazz, method);
- if (targetMethods.isSingleResolution()) {
- addIfNotAbstract.accept(targetMethods.getSingleTarget());
- }
- }
- }
+ },
+ dexCallSite -> {
+ // TODO(b/148769279): We need to look at the call site to see if it overrides
+ // the resolved method or not.
+ });
return LookupResult.createResult(result, LookupResultCollectionState.Complete);
}
- private void addIfDefaultMethodWithLambdaInstantiations(
- LiveSubTypeInfo liveSubTypesInfo, DexEncodedMethod method, Set<DexEncodedMethod> result) {
- if (method == null) {
- return;
+ private static void addVirtualDispatchTarget(
+ DexEncodedMethod target, boolean holderIsInterface, Set<DexEncodedMethod> result) {
+ assert !target.isPrivateMethod();
+ if (holderIsInterface) {
+ // Add default interface methods to the list of targets.
+ //
+ // This helps to make sure we take into account synthesized lambda classes
+ // that we are not aware of. Like in the following example, we know that all
+ // classes, XX in this case, override B::bar(), but there are also synthesized
+ // classes for lambda which don't, so we still need default method to be live.
+ //
+ // public static void main(String[] args) {
+ // X x = () -> {};
+ // x.bar();
+ // }
+ //
+ // interface X {
+ // void foo();
+ // default void bar() { }
+ // }
+ //
+ // class XX implements X {
+ // public void foo() { }
+ // public void bar() { }
+ // }
+ //
+ if (target.isDefaultMethod()) {
+ result.add(target);
+ }
+ // Default methods are looked up when looking at a specific subtype that does not override
+ // them. Otherwise, we would look up default methods that are actually never used.
+ // However, we have to add bridge methods, otherwise we can remove a bridge that will be
+ // used.
+ if (!target.accessFlags.isAbstract() && target.accessFlags.isBridge()) {
+ result.add(target);
+ }
+ } else {
+ result.add(target);
}
- if (method.hasCode()) {
- LiveSubTypeResult liveSubTypes = liveSubTypesInfo.getLiveSubTypes(method.method.holder);
- // TODO(b/148769279): The below is basically if (true), but should be changed when we
- // have live sub type information.
- if (liveSubTypes.getCallSites() == null || liveSubTypes.getCallSites() != null) {
- result.add(method);
+ }
+
+ /**
+ * This implements the logic for the actual method selection for a virtual target, according to
+ * https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokevirtual where
+ * we have an object ref on the stack.
+ */
+ @Override
+ public DexClassAndMethod lookupVirtualDispatchTarget(
+ DexProgramClass dynamicInstance, AppView<? extends AppInfoWithClassHierarchy> appView) {
+ // TODO(b/148591377): Enable this assertion.
+ // The dynamic type cannot be an interface.
+ // assert !dynamicInstance.isInterface();
+ boolean allowPackageBlocked = resolvedMethod.accessFlags.isPackagePrivate();
+ DexClass current = dynamicInstance;
+ DexEncodedMethod overrideTarget = resolvedMethod;
+ while (current != null) {
+ DexEncodedMethod candidate = lookupOverrideCandidate(overrideTarget, current);
+ if (candidate == DexEncodedMethod.SENTINEL && allowPackageBlocked) {
+ overrideTarget = findWideningOverride(resolvedMethod, current, appView);
+ allowPackageBlocked = false;
+ continue;
+ }
+ if (candidate == null || candidate == DexEncodedMethod.SENTINEL) {
+ // We cannot find a target above the resolved method.
+ if (current.type == overrideTarget.method.holder) {
+ return null;
+ }
+ current = current.superType == null ? null : appView.definitionFor(current.superType);
+ continue;
+ }
+ return DexClassAndMethod.create(current, candidate);
+ }
+ // TODO(b/149557233): Enable assertion.
+ // assert resolvedHolder.isInterface();
+ DexEncodedMethod maximalSpecific =
+ lookupMaximallySpecificDispatchTarget(dynamicInstance, appView);
+ return maximalSpecific == null
+ ? null
+ : DexClassAndMethod.create(
+ appView.definitionFor(maximalSpecific.method.holder), maximalSpecific);
+ }
+
+ private DexEncodedMethod lookupMaximallySpecificDispatchTarget(
+ DexProgramClass dynamicInstance, AppView<? extends AppInfoWithClassHierarchy> appView) {
+ ResolutionResult maximallySpecificResult =
+ appView.appInfo().resolveMaximallySpecificMethods(dynamicInstance, resolvedMethod.method);
+ if (maximallySpecificResult.isSingleResolution()) {
+ return maximallySpecificResult.asSingleResolution().resolvedMethod;
+ }
+ return null;
+ }
+
+ /**
+ * C contains a declaration for an instance method m that overrides (§5.4.5) the resolved
+ * method, then m is the method to be invoked. If the candidate is not a valid override, we
+ * return sentinel to indicate that we have to search for a method that is widening access
+ * inside the package.
+ */
+ private static DexEncodedMethod lookupOverrideCandidate(
+ DexEncodedMethod method, DexClass clazz) {
+ DexEncodedMethod candidate = clazz.lookupVirtualMethod(method.method);
+ assert candidate == null || !candidate.isPrivateMethod();
+ if (candidate != null) {
+ return isOverriding(method, candidate) ? candidate : DexEncodedMethod.SENTINEL;
+ }
+ return null;
+ }
+
+ private static DexEncodedMethod findWideningOverride(
+ DexEncodedMethod resolvedMethod,
+ DexClass clazz,
+ AppView<? extends AppInfoWithClassHierarchy> appView) {
+ // Otherwise, lookup to first override that is distinct from resolvedMethod.
+ assert resolvedMethod.accessFlags.isPackagePrivate();
+ while (clazz.superType != null) {
+ clazz = appView.definitionFor(clazz.superType);
+ if (clazz == null) {
+ return resolvedMethod;
+ }
+ DexEncodedMethod otherOverride = clazz.lookupVirtualMethod(resolvedMethod.method);
+ if (otherOverride != null
+ && isOverriding(resolvedMethod, otherOverride)
+ && (otherOverride.accessFlags.isPublic() || otherOverride.accessFlags.isProtected())) {
+ assert resolvedMethod != otherOverride;
+ return otherOverride;
}
}
+ return resolvedMethod;
+ }
+
+ /**
+ * Implementation of method overriding according to the jvm specification
+ * https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.5
+ *
+ * <p>The implementation assumes that the holder of the candidate is a subtype of the holder of
+ * the resolved method. It also assumes that resolvedMethod is the actual method to find a
+ * lookup for (that is, it is either mA or m').
+ */
+ private static boolean isOverriding(
+ DexEncodedMethod resolvedMethod, DexEncodedMethod candidate) {
+ assert resolvedMethod.method.match(candidate.method);
+ assert !candidate.isPrivateMethod();
+ if (resolvedMethod.accessFlags.isPublic() || resolvedMethod.accessFlags.isProtected()) {
+ return true;
+ }
+ // For package private methods, a valid override has to be inside the package.
+ assert resolvedMethod.accessFlags.isPackagePrivate();
+ return resolvedMethod.method.holder.isSamePackage(candidate.method.holder);
}
}
@@ -488,9 +548,17 @@
@Override
public LookupResult lookupVirtualDispatchTargets(
- AppView<?> appView, LiveSubTypeInfo liveSubTypeInfo) {
+ DexProgramClass context,
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ InstantiatedSubTypeInfo instantiatedInfo) {
return LookupResult.getIncompleteEmptyResult();
}
+
+ @Override
+ public DexClassAndMethod lookupVirtualDispatchTarget(
+ DexProgramClass dynamicInstance, AppView<? extends AppInfoWithClassHierarchy> appView) {
+ return null;
+ }
}
/** Singleton result for the special case resolving the array clone() method. */
diff --git a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
index 5c22878..3beb9ba 100644
--- a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
+++ b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
@@ -8,11 +8,9 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.IteratorUtils;
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.ListIterator;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
+import it.unimi.dsi.fastutil.ints.IntBidirectionalIterator;
import java.util.function.Consumer;
public class RewrittenPrototypeDescription {
@@ -21,15 +19,9 @@
public static class Builder {
- private int argumentIndex = -1;
private boolean isAlwaysNull = false;
private DexType type = null;
- public Builder setArgumentIndex(int argumentIndex) {
- this.argumentIndex = argumentIndex;
- return this;
- }
-
public Builder setIsAlwaysNull() {
this.isAlwaysNull = true;
return this;
@@ -41,18 +33,15 @@
}
public RemovedArgumentInfo build() {
- assert argumentIndex >= 0;
assert type != null;
- return new RemovedArgumentInfo(argumentIndex, isAlwaysNull, type);
+ return new RemovedArgumentInfo(isAlwaysNull, type);
}
}
- private final int argumentIndex;
private final boolean isAlwaysNull;
private final DexType type;
- private RemovedArgumentInfo(int argumentIndex, boolean isAlwaysNull, DexType type) {
- this.argumentIndex = argumentIndex;
+ private RemovedArgumentInfo(boolean isAlwaysNull, DexType type) {
this.isAlwaysNull = isAlwaysNull;
this.type = type;
}
@@ -61,10 +50,6 @@
return new Builder();
}
- public int getArgumentIndex() {
- return argumentIndex;
- }
-
public DexType getType() {
return type;
}
@@ -76,61 +61,40 @@
public boolean isNeverUsed() {
return !isAlwaysNull;
}
-
- RemovedArgumentInfo withArgumentIndex(int argumentIndex) {
- return this.argumentIndex != argumentIndex
- ? new RemovedArgumentInfo(argumentIndex, isAlwaysNull, type)
- : this;
- }
}
- public static class RemovedArgumentsInfo {
+ public static class RemovedArgumentInfoCollection {
- private static final RemovedArgumentsInfo empty = new RemovedArgumentsInfo(null);
+ private static final RemovedArgumentInfoCollection EMPTY = new RemovedArgumentInfoCollection();
- private final List<RemovedArgumentInfo> removedArguments;
+ private final Int2ReferenceSortedMap<RemovedArgumentInfo> removedArguments;
- public RemovedArgumentsInfo(List<RemovedArgumentInfo> removedArguments) {
- assert verifyRemovedArguments(removedArguments);
+ // Specific constructor for empty.
+ private RemovedArgumentInfoCollection() {
+ this.removedArguments = new Int2ReferenceLinkedOpenHashMap<>();
+ }
+
+ private RemovedArgumentInfoCollection(
+ Int2ReferenceSortedMap<RemovedArgumentInfo> removedArguments) {
+ assert removedArguments != null : "should use empty.";
+ assert !removedArguments.isEmpty() : "should use empty.";
this.removedArguments = removedArguments;
}
- private static boolean verifyRemovedArguments(List<RemovedArgumentInfo> removedArguments) {
- if (removedArguments != null && !removedArguments.isEmpty()) {
- // Check that list is sorted by argument indices.
- int lastArgumentIndex = removedArguments.get(0).getArgumentIndex();
- for (int i = 1; i < removedArguments.size(); ++i) {
- int currentArgumentIndex = removedArguments.get(i).getArgumentIndex();
- assert lastArgumentIndex < currentArgumentIndex;
- lastArgumentIndex = currentArgumentIndex;
- }
- }
- return true;
+ public static RemovedArgumentInfoCollection empty() {
+ return EMPTY;
}
- public static RemovedArgumentsInfo empty() {
- return empty;
- }
-
- public ListIterator<RemovedArgumentInfo> iterator() {
- return removedArguments == null
- ? Collections.emptyListIterator()
- : removedArguments.listIterator();
+ public RemovedArgumentInfo getArgumentInfo(int argIndex) {
+ return removedArguments.get(argIndex);
}
public boolean hasRemovedArguments() {
- return removedArguments != null && !removedArguments.isEmpty();
+ return !removedArguments.isEmpty();
}
public boolean isArgumentRemoved(int argumentIndex) {
- if (removedArguments != null) {
- for (RemovedArgumentInfo info : removedArguments) {
- if (info.getArgumentIndex() == argumentIndex) {
- return true;
- }
- }
- }
- return false;
+ return removedArguments.containsKey(argumentIndex);
}
public DexType[] rewriteParameters(DexEncodedMethod encodedMethod) {
@@ -156,8 +120,7 @@
return removedArguments != null ? removedArguments.size() : 0;
}
- public RemovedArgumentsInfo combine(RemovedArgumentsInfo info) {
- assert info != null;
+ public RemovedArgumentInfoCollection combine(RemovedArgumentInfoCollection info) {
if (hasRemovedArguments()) {
if (!info.hasRemovedArguments()) {
return this;
@@ -166,19 +129,32 @@
return info;
}
- List<RemovedArgumentInfo> newRemovedArguments = new LinkedList<>(removedArguments);
- ListIterator<RemovedArgumentInfo> iterator = newRemovedArguments.listIterator();
+ Int2ReferenceSortedMap<RemovedArgumentInfo> newRemovedArguments =
+ new Int2ReferenceLinkedOpenHashMap<>();
+ newRemovedArguments.putAll(removedArguments);
+ IntBidirectionalIterator iterator = removedArguments.keySet().iterator();
int offset = 0;
- for (RemovedArgumentInfo pending : info.removedArguments) {
- RemovedArgumentInfo next = IteratorUtils.peekNext(iterator);
- while (next != null && next.getArgumentIndex() <= pending.getArgumentIndex() + offset) {
- iterator.next();
- next = IteratorUtils.peekNext(iterator);
+ for (int pendingArgIndex : info.removedArguments.keySet()) {
+ int nextArgindex = peekNextOrMax(iterator);
+ while (nextArgindex <= pendingArgIndex + offset) {
+ iterator.nextInt();
+ nextArgindex = peekNextOrMax(iterator);
offset++;
}
- iterator.add(pending.withArgumentIndex(pending.getArgumentIndex() + offset));
+ assert !newRemovedArguments.containsKey(pendingArgIndex + offset);
+ newRemovedArguments.put(
+ pendingArgIndex + offset, info.removedArguments.get(pendingArgIndex));
}
- return new RemovedArgumentsInfo(newRemovedArguments);
+ return new RemovedArgumentInfoCollection(newRemovedArguments);
+ }
+
+ static int peekNextOrMax(IntBidirectionalIterator iterator) {
+ if (iterator.hasNext()) {
+ int i = iterator.nextInt();
+ iterator.previousInt();
+ return i;
+ }
+ return Integer.MAX_VALUE;
}
public Consumer<DexEncodedMethod.Builder> createParameterAnnotationsRemover(
@@ -192,22 +168,46 @@
}
return null;
}
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private Int2ReferenceSortedMap<RemovedArgumentInfo> removedArguments;
+
+ public Builder addRemovedArgument(int argIndex, RemovedArgumentInfo argInfo) {
+ if (removedArguments == null) {
+ removedArguments = new Int2ReferenceLinkedOpenHashMap<>();
+ }
+ assert !removedArguments.containsKey(argIndex);
+ removedArguments.put(argIndex, argInfo);
+ return this;
+ }
+
+ public RemovedArgumentInfoCollection build() {
+ if (removedArguments == null || removedArguments.isEmpty()) {
+ return EMPTY;
+ }
+ return new RemovedArgumentInfoCollection(removedArguments);
+ }
+ }
}
private static final RewrittenPrototypeDescription none = new RewrittenPrototypeDescription();
private final boolean hasBeenChangedToReturnVoid;
private final boolean extraNullParameter;
- private final RemovedArgumentsInfo removedArgumentsInfo;
+ private final RemovedArgumentInfoCollection removedArgumentsInfo;
private RewrittenPrototypeDescription() {
- this(false, false, RemovedArgumentsInfo.empty());
+ this(false, false, RemovedArgumentInfoCollection.empty());
}
private RewrittenPrototypeDescription(
boolean hasBeenChangedToReturnVoid,
boolean extraNullParameter,
- RemovedArgumentsInfo removedArgumentsInfo) {
+ RemovedArgumentInfoCollection removedArgumentsInfo) {
assert removedArgumentsInfo != null;
this.extraNullParameter = extraNullParameter;
this.hasBeenChangedToReturnVoid = hasBeenChangedToReturnVoid;
@@ -215,7 +215,7 @@
}
public static RewrittenPrototypeDescription createForUninstantiatedTypes(
- boolean hasBeenChangedToReturnVoid, RemovedArgumentsInfo removedArgumentsInfo) {
+ boolean hasBeenChangedToReturnVoid, RemovedArgumentInfoCollection removedArgumentsInfo) {
return new RewrittenPrototypeDescription(
hasBeenChangedToReturnVoid, false, removedArgumentsInfo);
}
@@ -227,7 +227,7 @@
public boolean isEmpty() {
return !extraNullParameter
&& !hasBeenChangedToReturnVoid
- && !getRemovedArgumentsInfo().hasRemovedArguments();
+ && !getRemovedArgumentInfoCollection().hasRemovedArguments();
}
public boolean hasExtraNullParameter() {
@@ -238,7 +238,7 @@
return hasBeenChangedToReturnVoid;
}
- public RemovedArgumentsInfo getRemovedArgumentsInfo() {
+ public RemovedArgumentInfoCollection getRemovedArgumentInfoCollection() {
return removedArgumentsInfo;
}
@@ -276,7 +276,7 @@
: this;
}
- public RewrittenPrototypeDescription withRemovedArguments(RemovedArgumentsInfo other) {
+ public RewrittenPrototypeDescription withRemovedArguments(RemovedArgumentInfoCollection other) {
return new RewrittenPrototypeDescription(
hasBeenChangedToReturnVoid, extraNullParameter, removedArgumentsInfo.combine(other));
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
index b463c75..1e8bb52 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
@@ -4,11 +4,15 @@
package com.android.tools.r8.ir.analysis.fieldaccess;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexProgramClass;
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.NewInstance;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
@@ -56,20 +60,33 @@
public void recordFieldAccesses(
IRCode code, OptimizationFeedback feedback, MethodProcessor methodProcessor) {
- if (!code.metadata().mayHaveFieldInstruction() || !methodProcessor.isPrimary()) {
+ if (!methodProcessor.isPrimary()) {
return;
}
- Iterable<FieldInstruction> fieldInstructions =
- code.instructions(Instruction::isFieldInstruction);
- for (FieldInstruction fieldInstruction : fieldInstructions) {
- DexEncodedField encodedField = appView.appInfo().resolveField(fieldInstruction.getField());
- if (encodedField != null && encodedField.isProgramField(appView)) {
- if (fieldAssignmentTracker != null) {
- fieldAssignmentTracker.recordFieldAccess(fieldInstruction, encodedField, code.method);
+ if (!code.metadata().mayHaveFieldInstruction() && !code.metadata().mayHaveNewInstance()) {
+ return;
+ }
+
+ for (Instruction instruction : code.instructions()) {
+ if (instruction.isFieldInstruction()) {
+ FieldInstruction fieldInstruction = instruction.asFieldInstruction();
+ DexEncodedField encodedField = appView.appInfo().resolveField(fieldInstruction.getField());
+ if (encodedField != null && encodedField.isProgramField(appView)) {
+ if (fieldAssignmentTracker != null) {
+ fieldAssignmentTracker.recordFieldAccess(fieldInstruction, encodedField, code.method);
+ }
+ if (fieldBitAccessAnalysis != null) {
+ fieldBitAccessAnalysis.recordFieldAccess(fieldInstruction, encodedField, feedback);
+ }
}
- if (fieldBitAccessAnalysis != null) {
- fieldBitAccessAnalysis.recordFieldAccess(fieldInstruction, encodedField, feedback);
+ } else if (instruction.isNewInstance()) {
+ NewInstance newInstance = instruction.asNewInstance();
+ DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(newInstance.clazz));
+ if (clazz != null) {
+ if (fieldAssignmentTracker != null) {
+ fieldAssignmentTracker.recordAllocationSite(newInstance, clazz, code.method);
+ }
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
index 0b0b542..0bef6b1 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
@@ -7,13 +7,22 @@
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.DexProgramClass;
import com.android.tools.r8.graph.FieldAccessInfoCollection;
+import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.BottomValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.ir.code.FieldInstruction;
+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.FieldOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldArgumentInitializationInfo;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
@@ -21,9 +30,11 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.IdentityHashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
public class FieldAssignmentTracker {
@@ -35,12 +46,52 @@
// processed when a field no longer has any incoming edges.
private final FieldAccessGraph fieldAccessGraph;
+ // An object allocation graph with edges from methods to the classes they instantiate. Edges are
+ // removed from the graph as we process methods, such that we can conclude that all allocation
+ // sites have been seen when a class no longer has any incoming edges.
+ private final ObjectAllocationGraph objectAllocationGraph;
+
// The set of fields that may store a non-zero value.
private final Set<DexEncodedField> nonZeroFields = Sets.newConcurrentHashSet();
+ private final Map<DexProgramClass, Map<DexEncodedField, AbstractValue>>
+ abstractInstanceFieldValues = new ConcurrentHashMap<>();
+
FieldAssignmentTracker(AppView<AppInfoWithLiveness> appView) {
this.appView = appView;
this.fieldAccessGraph = new FieldAccessGraph(appView);
+ this.objectAllocationGraph = new ObjectAllocationGraph(appView);
+ initializeAbstractInstanceFieldValues();
+ }
+
+ /**
+ * For each class with known allocation sites, adds a mapping from clazz -> instance field ->
+ * bottom.
+ *
+ * <p>If an entry (clazz, instance field) is missing in {@link #abstractInstanceFieldValues}, it
+ * is interpreted as if we known nothing about the value of the field.
+ */
+ private void initializeAbstractInstanceFieldValues() {
+ ObjectAllocationInfoCollection objectAllocationInfos =
+ appView.appInfo().getObjectAllocationInfoCollection();
+ objectAllocationInfos.forEachClassWithKnownAllocationSites(
+ (clazz, allocationSites) -> {
+ if (appView.appInfo().isInstantiatedIndirectly(clazz)) {
+ // TODO(b/147652121): Handle classes that are instantiated indirectly.
+ return;
+ }
+ List<DexEncodedField> instanceFields = clazz.instanceFields();
+ if (instanceFields.isEmpty()) {
+ // No instance fields to track.
+ return;
+ }
+ Map<DexEncodedField, AbstractValue> abstractInstanceFieldValuesForClass =
+ new IdentityHashMap<>();
+ for (DexEncodedField field : clazz.instanceFields()) {
+ abstractInstanceFieldValuesForClass.put(field, BottomValue.getInstance());
+ }
+ abstractInstanceFieldValues.put(clazz, abstractInstanceFieldValuesForClass);
+ });
}
private boolean isAlwaysZero(DexEncodedField field) {
@@ -72,16 +123,98 @@
}
}
- private void recordAllFieldPutsProcessed(DexEncodedField field, OptimizationFeedback feedback) {
+ void recordAllocationSite(
+ NewInstance instruction, DexProgramClass clazz, DexEncodedMethod context) {
+ Map<DexEncodedField, AbstractValue> abstractInstanceFieldValuesForClass =
+ abstractInstanceFieldValues.get(clazz);
+ if (abstractInstanceFieldValuesForClass == null) {
+ // We are not tracking the value of any of clazz' instance fields.
+ return;
+ }
+
+ InvokeDirect invoke = instruction.getUniqueConstructorInvoke(appView.dexItemFactory());
+ if (invoke == null) {
+ // We just lost track.
+ abstractInstanceFieldValues.remove(clazz);
+ return;
+ }
+
+ DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, context.method.holder);
+ if (singleTarget == null) {
+ // We just lost track.
+ abstractInstanceFieldValues.remove(clazz);
+ return;
+ }
+
+ InstanceFieldInitializationInfoCollection initializationInfoCollection =
+ singleTarget.getOptimizationInfo().getInstanceInitializerInfo().fieldInitializationInfos();
+
+ // Synchronize on the lattice element (abstractInstanceFieldValuesForClass) in case we process
+ // another allocation site of `clazz` concurrently.
+ synchronized (abstractInstanceFieldValuesForClass) {
+ Iterator<Map.Entry<DexEncodedField, AbstractValue>> iterator =
+ abstractInstanceFieldValuesForClass.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Map.Entry<DexEncodedField, AbstractValue> entry = iterator.next();
+ DexEncodedField field = entry.getKey();
+ InstanceFieldInitializationInfo initializationInfo =
+ initializationInfoCollection.get(field);
+ if (initializationInfo.isArgumentInitializationInfo()) {
+ InstanceFieldArgumentInitializationInfo argumentInitializationInfo =
+ initializationInfo.asArgumentInitializationInfo();
+ Value argument = invoke.arguments().get(argumentInitializationInfo.getArgumentIndex());
+ AbstractValue abstractValue =
+ argument.getAbstractValue(appView, context.method.holder).join(entry.getValue());
+ assert !abstractValue.isBottom();
+ if (!abstractValue.isUnknown()) {
+ entry.setValue(abstractValue);
+ continue;
+ }
+ } else {
+ assert initializationInfo.isUnknown();
+ }
+
+ // We just lost track for this field.
+ iterator.remove();
+ }
+ }
+ }
+
+ private void recordAllFieldPutsProcessed(
+ DexEncodedField field, OptimizationFeedbackDelayed feedback) {
if (isAlwaysZero(field)) {
feedback.recordFieldHasAbstractValue(
field, appView, appView.abstractValueFactory().createSingleNumberValue(0));
}
}
- public void waveDone(Collection<DexEncodedMethod> wave, OptimizationFeedback feedback) {
+ private void recordAllAllocationsSitesProcessed(
+ DexProgramClass clazz, OptimizationFeedbackDelayed feedback) {
+ Map<DexEncodedField, AbstractValue> abstractInstanceFieldValuesForClass =
+ abstractInstanceFieldValues.get(clazz);
+ if (abstractInstanceFieldValuesForClass == null) {
+ return;
+ }
+
+ for (DexEncodedField field : clazz.instanceFields()) {
+ AbstractValue abstractValue =
+ abstractInstanceFieldValuesForClass.getOrDefault(field, UnknownValue.getInstance());
+ if (abstractValue.isBottom()) {
+ feedback.modifyAppInfoWithLiveness(modifier -> modifier.removeInstantiatedType(clazz));
+ break;
+ }
+ if (abstractValue.isUnknown()) {
+ continue;
+ }
+ feedback.recordFieldHasAbstractValue(field, appView, abstractValue);
+ }
+ }
+
+ public void waveDone(Collection<DexEncodedMethod> wave, OptimizationFeedbackDelayed feedback) {
for (DexEncodedMethod method : wave) {
fieldAccessGraph.markProcessed(method, field -> recordAllFieldPutsProcessed(field, feedback));
+ objectAllocationGraph.markProcessed(
+ method, clazz -> recordAllAllocationsSitesProcessed(clazz, feedback));
}
}
@@ -139,4 +272,42 @@
}
}
}
+
+ static class ObjectAllocationGraph {
+
+ // The classes instantiated by each method.
+ private final Map<DexEncodedMethod, List<DexProgramClass>> objectAllocations =
+ new IdentityHashMap<>();
+
+ // The number of allocation sites that have not yet been processed per class.
+ private final Reference2IntMap<DexProgramClass> pendingObjectAllocations =
+ new Reference2IntOpenHashMap<>();
+
+ ObjectAllocationGraph(AppView<AppInfoWithLiveness> appView) {
+ ObjectAllocationInfoCollection objectAllocationInfos =
+ appView.appInfo().getObjectAllocationInfoCollection();
+ objectAllocationInfos.forEachClassWithKnownAllocationSites(
+ (clazz, contexts) -> {
+ for (DexEncodedMethod context : contexts) {
+ objectAllocations.computeIfAbsent(context, ignore -> new ArrayList<>()).add(clazz);
+ }
+ pendingObjectAllocations.put(clazz, contexts.size());
+ });
+ }
+
+ void markProcessed(
+ DexEncodedMethod method, Consumer<DexProgramClass> allAllocationsSitesSeenConsumer) {
+ List<DexProgramClass> allocationSitesInMethod = objectAllocations.get(method);
+ if (allocationSitesInMethod != null) {
+ for (DexProgramClass type : allocationSitesInMethod) {
+ int numberOfPendingAllocationSites = pendingObjectAllocations.removeInt(type) - 1;
+ if (numberOfPendingAllocationSites > 0) {
+ pendingObjectAllocations.put(type, numberOfPendingAllocationSites);
+ } else {
+ allAllocationsSitesSeenConsumer.accept(type);
+ }
+ }
+ }
+ }
+ }
}
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 29dc41b..2289a28 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
@@ -275,7 +275,7 @@
return true;
}
- private void updateFieldOptimizationInfo(DexEncodedField field, Value value) {
+ void updateFieldOptimizationInfo(DexEncodedField field, Value value) {
// Abstract value.
Value root = value.getAliasedValue();
AbstractValue abstractValue = computeAbstractValue(root);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
index d636715..ecc93b7 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
@@ -5,15 +5,29 @@
package com.android.tools.r8.ir.analysis.fieldvalueanalysis;
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.DexProgramClass;
+import com.android.tools.r8.ir.code.Argument;
import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+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.ir.optimize.info.field.EmptyInstanceFieldInitializationInfoCollection;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
public class InstanceFieldValueAnalysis extends FieldValueAnalysis {
+ // Information about how this instance constructor initializes the fields on the newly created
+ // instance.
+ private final InstanceFieldInitializationInfoCollection.Builder builder =
+ InstanceFieldInitializationInfoCollection.builder();
+
+ private final InstanceFieldInitializationInfoFactory factory;
+
private InstanceFieldValueAnalysis(
AppView<AppInfoWithLiveness> appView,
IRCode code,
@@ -21,9 +35,14 @@
DexProgramClass clazz,
DexEncodedMethod method) {
super(appView, code, feedback, clazz, method);
+ factory = appView.instanceFieldInitializationInfoFactory();
}
- public static void run(
+ /**
+ * Returns information about how this instance constructor initializes the fields on the newly
+ * created instance.
+ */
+ public static InstanceFieldInitializationInfoCollection run(
AppView<?> appView,
IRCode code,
ClassInitializerDefaultsResult classInitializerDefaultsResult,
@@ -34,16 +53,32 @@
assert method.isInstanceInitializer();
DexProgramClass clazz = appView.definitionFor(method.method.holder).asProgramClass();
if (!appView.options().enableValuePropagationForInstanceFields) {
- return;
+ return EmptyInstanceFieldInitializationInfoCollection.getInstance();
}
DexEncodedMethod otherInstanceInitializer =
clazz.lookupDirectMethod(other -> other.isInstanceInitializer() && other != method);
if (otherInstanceInitializer != null) {
// Conservatively bail out.
// TODO(b/125282093): Handle multiple instance initializers on the same class.
- return;
+ return EmptyInstanceFieldInitializationInfoCollection.getInstance();
}
- new InstanceFieldValueAnalysis(appView.withLiveness(), code, feedback, clazz, method)
- .computeFieldOptimizationInfo(classInitializerDefaultsResult);
+ InstanceFieldValueAnalysis analysis =
+ new InstanceFieldValueAnalysis(appView.withLiveness(), code, feedback, clazz, method);
+ analysis.computeFieldOptimizationInfo(classInitializerDefaultsResult);
+ return analysis.builder.build();
+ }
+
+ @Override
+ void updateFieldOptimizationInfo(DexEncodedField field, Value value) {
+ super.updateFieldOptimizationInfo(field, value);
+
+ // If this instance field is initialized with an argument, then record this in the instance
+ // field initialization info.
+ Value root = value.getAliasedValue();
+ if (root.isDefinedByInstructionSatisfying(Instruction::isArgument)) {
+ Argument argument = root.definition.asArgument();
+ builder.recordInitializationInfo(
+ field, factory.createArgumentInitializationInfo(argument.getIndex()));
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
index 37a8dc6..717df30 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -21,9 +21,9 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.CallGraph.Node;
import com.android.tools.r8.ir.conversion.MethodProcessor;
-import com.android.tools.r8.ir.optimize.CodeRewriter;
import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.inliner.FixedInliningReasonStrategy;
import com.android.tools.r8.utils.PredicateSet;
@@ -91,7 +91,7 @@
public void inlineCallsToDynamicMethod(
DexEncodedMethod method,
IRCode code,
- CodeRewriter codeRewriter,
+ EnumValueOptimizer enumValueOptimizer,
OptimizationFeedback feedback,
MethodProcessor methodProcessor,
Inliner inliner) {
@@ -103,8 +103,8 @@
// Run the enum optimization to optimize all Enum.ordinal() invocations. This is required to
// get rid of the enum switch in dynamicMethod().
- if (appView.options().enableEnumValueOptimization) {
- codeRewriter.rewriteConstantEnumMethodCalls(code);
+ if (enumValueOptimizer != null) {
+ enumValueOptimizer.rewriteConstantEnumMethodCalls(code);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
index 007fd9f..5371255 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
@@ -8,6 +8,10 @@
public abstract boolean isNonTrivial();
+ public boolean isBottom() {
+ return false;
+ }
+
public boolean isZero() {
return false;
}
@@ -69,7 +73,13 @@
}
public AbstractValue join(AbstractValue other) {
- if (this.equals(other)) {
+ if (isBottom() || other.isUnknown()) {
+ return other;
+ }
+ if (isUnknown() || other.isBottom()) {
+ return this;
+ }
+ if (equals(other)) {
return this;
}
return UnknownValue.getInstance();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/BottomValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/BottomValue.java
new file mode 100644
index 0000000..ca376e7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/BottomValue.java
@@ -0,0 +1,41 @@
+// 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.analysis.value;
+
+public class BottomValue extends AbstractValue {
+
+ private static final BottomValue INSTANCE = new BottomValue();
+
+ private BottomValue() {}
+
+ public static BottomValue getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public boolean isBottom() {
+ return true;
+ }
+
+ @Override
+ public boolean isNonTrivial() {
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return this == o;
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(this);
+ }
+
+ @Override
+ public String toString() {
+ return "BottomValue";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Argument.java b/src/main/java/com/android/tools/r8/ir/code/Argument.java
index 93c39ec..1db2ba6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Argument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Argument.java
@@ -21,12 +21,33 @@
*/
public class Argument extends Instruction {
+ private final int index;
private final boolean knownToBeBoolean;
- public Argument(Value outValue, boolean knownToBeBoolean) {
+ public Argument(Value outValue, int index, boolean knownToBeBoolean) {
super(outValue);
+ this.index = index;
this.knownToBeBoolean = knownToBeBoolean;
- outValue.markAsArgument();
+ }
+
+ public int getIndex() {
+ assert verifyIndex();
+ return index;
+ }
+
+ private boolean verifyIndex() {
+ int index = 0;
+ InstructionIterator instructionIterator = getBlock().iterator();
+ while (instructionIterator.hasNext()) {
+ Instruction instruction = instructionIterator.next();
+ assert instruction.isArgument();
+ if (instruction == this) {
+ assert index == this.index;
+ return true;
+ }
+ index++;
+ }
+ return false;
}
@Override
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 f4bb790..584ea2c 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
@@ -621,6 +621,10 @@
return instructions.isEmpty();
}
+ public int size() {
+ return instructions.size();
+ }
+
public boolean isReturnBlock() {
return exit().isReturn();
}
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 39b6fb8..c8d6abc 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
@@ -21,6 +21,7 @@
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Sets;
import java.util.Collections;
import java.util.List;
@@ -128,7 +129,8 @@
if (field.holder.classInitializationMayHaveSideEffects(
appView,
// Types that are a super type of `context` are guaranteed to be initialized already.
- type -> appView.isSubtype(context, type).isTrue())) {
+ type -> appView.isSubtype(context, type).isTrue(),
+ Sets.newIdentityHashSet())) {
return AbstractError.top();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/FixedRegisterValue.java b/src/main/java/com/android/tools/r8/ir/code/FixedRegisterValue.java
index f7411d9..b074e44 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FixedRegisterValue.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FixedRegisterValue.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import java.util.function.Predicate;
// Value that has a fixed register allocated. These are used for inserting spill, restore, and phi
// moves in the spilling register allocator.
@@ -50,6 +51,11 @@
}
@Override
+ public boolean isDefinedByInstructionSatisfying(Predicate<Instruction> predicate) {
+ return false;
+ }
+
+ @Override
public boolean isFixedRegisterValue() {
return true;
}
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 39c3d39..e620cb1 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
@@ -826,6 +826,9 @@
} else if (instruction.isMul()) {
assert metadata.mayHaveMul() && metadata.mayHaveArithmeticOrLogicalBinop()
: "IR metadata should indicate that code has a mul";
+ } else if (instruction.isNewInstance()) {
+ assert metadata.mayHaveNewInstance()
+ : "IR metadata should indicate that code has a new-instance";
} else if (instruction.isRem()) {
assert metadata.mayHaveRem() && metadata.mayHaveArithmeticOrLogicalBinop()
: "IR metadata should indicate that code has a rem";
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
index c4d55d8..8db09e9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
@@ -206,6 +206,10 @@
return get(Opcodes.MUL);
}
+ public boolean mayHaveNewInstance() {
+ return get(Opcodes.NEW_INSTANCE);
+ }
+
public boolean mayHaveOr() {
return get(Opcodes.OR);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index bdba162..df0c10d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -88,7 +88,8 @@
appView
.appInfo()
.resolveMethod(method.holder, method)
- .lookupVirtualDispatchTargets(appView.withLiveness())
+ .lookupVirtualDispatchTargets(
+ appView.definitionForProgramType(invocationContext), appView.withLiveness())
.asLookupResultSuccess();
if (lookupResult == null) {
return null;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index 6ba4a3a..3a7969a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -24,6 +24,7 @@
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Sets;
import java.util.List;
import java.util.function.Predicate;
@@ -197,7 +198,8 @@
appView,
// Types that are a super type of `context` are guaranteed to be initialized
// already.
- type -> appView.isSubtype(context, type).isTrue());
+ type -> appView.isSubtype(context, type).isTrue(),
+ Sets.newIdentityHashSet());
}
return targetMayHaveSideEffects;
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 aed4b9e..cd88da6 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
@@ -24,6 +24,7 @@
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.google.common.collect.Sets;
public class NewInstance extends Instruction {
@@ -188,7 +189,8 @@
if (definition.classInitializationMayHaveSideEffects(
appView,
// Types that are a super type of `context` are guaranteed to be initialized already.
- type -> appView.isSubtype(context, type).isTrue())) {
+ type -> appView.isSubtype(context, type).isTrue(),
+ Sets.newIdentityHashSet())) {
return true;
}
@@ -228,7 +230,8 @@
return clazz.classInitializationMayHaveSideEffects(
appView,
// Types that are a super type of `context` are guaranteed to be initialized already.
- type -> appView.isSubtype(context, type).isTrue());
+ type -> appView.isSubtype(context, type).isTrue(),
+ Sets.newIdentityHashSet());
} else {
// In D8, this instruction may trigger class initialization if the holder of the field is
// different from the current context.
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 eecf8c3..95c2608 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
@@ -30,6 +30,7 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.function.Predicate;
public class Phi extends Value implements InstructionOrPhi {
@@ -62,6 +63,11 @@
}
@Override
+ public boolean isDefinedByInstructionSatisfying(Predicate<Instruction> predicate) {
+ return false;
+ }
+
+ @Override
public boolean isPhi() {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index f59eb4b..08285c5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -27,6 +27,7 @@
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.google.common.collect.Sets;
import java.util.Set;
public class StaticGet extends FieldInstruction {
@@ -236,7 +237,8 @@
return holder.classInitializationMayHaveSideEffects(
appView,
// Types that are a super type of `context` are guaranteed to be initialized already.
- type -> appView.isSubtype(context, type).isTrue());
+ type -> appView.isSubtype(context, type).isTrue(),
+ Sets.newIdentityHashSet());
} else {
// In D8, this instruction may trigger class initialization if the holder of the field is
// different from the current context.
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index 63e5c10..c9bab8b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -28,6 +28,7 @@
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.ProguardMemberRule;
+import com.google.common.collect.Sets;
public class StaticPut extends FieldInstruction {
@@ -235,7 +236,8 @@
return holder.classInitializationMayHaveSideEffects(
appView,
// Types that are a super type of `context` are guaranteed to be initialized already.
- type -> appView.isSubtype(context, type).isTrue());
+ type -> appView.isSubtype(context, type).isTrue(),
+ Sets.newIdentityHashSet());
} else {
// In D8, this instruction may trigger class initialization if the holder of the field is
// different from the current context.
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 8e75d62..5566552 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
@@ -226,7 +226,6 @@
private LiveIntervals liveIntervals;
private int needsRegister = -1;
private boolean isThis = false;
- private boolean isArgument = false;
private LongInterval valueRange;
private DebugData debugData;
protected TypeLatticeElement typeLattice;
@@ -893,6 +892,10 @@
return root.definition.getAbstractValue(appView, context);
}
+ public boolean isDefinedByInstructionSatisfying(Predicate<Instruction> predicate) {
+ return predicate.test(definition);
+ }
+
public boolean isPhi() {
return false;
}
@@ -910,14 +913,8 @@
|| typeLattice.nullability().isDefinitelyNotNull();
}
- public void markAsArgument() {
- assert !isArgument;
- assert !isThis;
- isArgument = true;
- }
-
public boolean isArgument() {
- return isArgument;
+ return isDefinedByInstructionSatisfying(Instruction::isArgument);
}
public boolean onlyDependsOnArgument() {
@@ -941,21 +938,6 @@
}
}
- public int computeArgumentPosition(IRCode code) {
- assert isArgument;
- int position = 0;
- InstructionIterator instructionIterator = code.entryBlock().iterator();
- while (instructionIterator.hasNext()) {
- Instruction instruction = instructionIterator.next();
- assert instruction.isArgument();
- if (instruction.outValue() == this) {
- return position;
- }
- position++;
- }
- throw new Unreachable();
- }
-
public boolean knownToBeBoolean() {
return knownToBeBoolean(null);
}
@@ -987,7 +969,7 @@
}
public void markAsThis() {
- assert isArgument;
+ assert isArgument();
assert !isThis;
isThis = true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
index 56462db..2bb3869 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
@@ -169,7 +169,7 @@
ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method.holder, method);
DexEncodedMethod target = resolutionResult.getSingleTarget();
if (target != null) {
- processInvokeWithDynamicDispatch(type, target);
+ processInvokeWithDynamicDispatch(type, target, context.holder);
}
} else {
DexEncodedMethod singleTarget =
@@ -190,7 +190,7 @@
}
private void processInvokeWithDynamicDispatch(
- Invoke.Type type, DexEncodedMethod encodedTarget) {
+ Invoke.Type type, DexEncodedMethod encodedTarget, DexType context) {
DexMethod target = encodedTarget.method;
DexClass clazz = appView.definitionFor(target.holder);
if (clazz == null) {
@@ -214,7 +214,8 @@
appView.appInfo().resolveMethod(method.holder, method, isInterface);
if (resolution.isVirtualTarget()) {
LookupResult lookupResult =
- resolution.lookupVirtualDispatchTargets(appView, appView.appInfo());
+ resolution.lookupVirtualDispatchTargets(
+ appView.definitionForProgramType(context), appView);
if (lookupResult.isLookupResultSuccess()) {
return lookupResult.asLookupResultSuccess().getMethodTargets();
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index a7c90fe..8f6ea1e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -385,7 +385,7 @@
inPrelude = true;
state.buildPrelude(canonicalPositions.getPreamblePosition());
setLocalVariableLists();
- DexSourceCode.buildArgumentsWithUnusedArgumentStubs(builder, 0, method, state::write);
+ builder.buildArgumentsWithUnusedArgumentStubs(0, method, state::write);
// Add debug information for all locals at the initial label.
Int2ReferenceMap<DebugLocalInfo> locals = getLocalVariables(0).locals;
if (!locals.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index 01dcf5d..2436ed1 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -40,10 +40,6 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
-import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentsInfo;
-import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.CanonicalPositions;
import com.android.tools.r8.ir.code.CatchHandlers;
import com.android.tools.r8.ir.code.Position;
@@ -51,7 +47,6 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
@@ -142,8 +137,7 @@
if (code.incomingRegisterSize == 0) {
return;
}
- buildArgumentsWithUnusedArgumentStubs(
- builder,
+ builder.buildArgumentsWithUnusedArgumentStubs(
code.registerSize - code.incomingRegisterSize,
method,
DexSourceCode::doNothingWriteConsumer);
@@ -153,62 +147,6 @@
// Intentionally empty.
}
- public static void buildArgumentsWithUnusedArgumentStubs(
- IRBuilder builder,
- int register,
- DexEncodedMethod method,
- BiConsumer<Integer, DexType> writeCallback) {
- RemovedArgumentsInfo removedArgumentsInfo =
- builder.getPrototypeChanges().getRemovedArgumentsInfo();
- ListIterator<RemovedArgumentInfo> removedArgumentIterator = removedArgumentsInfo.iterator();
- RemovedArgumentInfo nextRemovedArgument =
- removedArgumentIterator.hasNext() ? removedArgumentIterator.next() : null;
-
- // Fill in the Argument instructions (incomingRegisterSize last registers) in the argument
- // block.
- int argumentIndex = 0;
-
- if (!method.isStatic()) {
- writeCallback.accept(register, method.method.holder);
- builder.addThisArgument(register);
- ++argumentIndex;
- ++register;
- }
-
- int numberOfArguments =
- method.method.proto.parameters.values.length
- + removedArgumentsInfo.numberOfRemovedArguments()
- + (method.isStatic() ? 0 : 1);
-
- for (int usedArgumentIndex = 0; argumentIndex < numberOfArguments; ++argumentIndex) {
- TypeLatticeElement type;
- if (nextRemovedArgument != null && nextRemovedArgument.getArgumentIndex() == argumentIndex) {
- writeCallback.accept(register, nextRemovedArgument.getType());
- type =
- TypeLatticeElement.fromDexType(
- nextRemovedArgument.getType(), Nullability.maybeNull(), builder.appView);
- builder.addConstantOrUnusedArgument(register, nextRemovedArgument);
- nextRemovedArgument =
- removedArgumentIterator.hasNext() ? removedArgumentIterator.next() : null;
- } else {
- DexType dexType = method.method.proto.parameters.values[usedArgumentIndex++];
- writeCallback.accept(register, dexType);
- type =
- TypeLatticeElement.fromDexType(
- dexType,
- Nullability.maybeNull(),
- builder.appView);
- if (dexType.isBooleanType()) {
- builder.addBooleanNonThisArgument(register);
- } else {
- builder.addNonThisArgument(register, type);
- }
- }
- register += type.requiredRegisters();
- }
- builder.flushArgumentInstructions();
- }
-
@Override
public void buildPostlude(IRBuilder builder) {
// Intentionally left empty. (Needed in the Java bytecode frontend for synchronization support.)
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 a39a66b..78b1405 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
@@ -36,6 +36,7 @@
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.RewrittenPrototypeDescription;
import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfoCollection;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
@@ -135,6 +136,7 @@
import java.util.Map;
import java.util.Queue;
import java.util.Set;
+import java.util.function.BiConsumer;
/**
* Builder object for constructing high-level IR from dex bytecode.
@@ -448,11 +450,12 @@
} else {
this.prototypeChanges = appView.graphLense().lookupPrototypeChanges(method.method);
- if (Log.ENABLED && prototypeChanges.getRemovedArgumentsInfo().hasRemovedArguments()) {
+ if (Log.ENABLED
+ && prototypeChanges.getRemovedArgumentInfoCollection().hasRemovedArguments()) {
Log.info(
getClass(),
"Removed "
- + prototypeChanges.getRemovedArgumentsInfo().numberOfRemovedArguments()
+ + prototypeChanges.getRemovedArgumentInfoCollection().numberOfRemovedArguments()
+ " arguments from "
+ method.toSourceString());
}
@@ -503,6 +506,53 @@
currentBlock = block;
}
+ public void buildArgumentsWithUnusedArgumentStubs(
+ int register, DexEncodedMethod method, BiConsumer<Integer, DexType> writeCallback) {
+ RemovedArgumentInfoCollection removedArgumentsInfo =
+ prototypeChanges.getRemovedArgumentInfoCollection();
+
+ // Fill in the Argument instructions (incomingRegisterSize last registers) in the argument
+ // block.
+ int argumentIndex = 0;
+
+ if (!method.isStatic()) {
+ writeCallback.accept(register, method.method.holder);
+ addThisArgument(register);
+ argumentIndex++;
+ register++;
+ }
+
+ int numberOfArguments =
+ method.method.proto.parameters.values.length
+ + removedArgumentsInfo.numberOfRemovedArguments()
+ + (method.isStatic() ? 0 : 1);
+
+ int usedArgumentIndex = 0;
+ while (argumentIndex < numberOfArguments) {
+ TypeLatticeElement type;
+ if (removedArgumentsInfo.isArgumentRemoved(argumentIndex)) {
+ RemovedArgumentInfo argumentInfo = removedArgumentsInfo.getArgumentInfo(argumentIndex);
+ writeCallback.accept(register, argumentInfo.getType());
+ type =
+ TypeLatticeElement.fromDexType(
+ argumentInfo.getType(), Nullability.maybeNull(), appView);
+ addConstantOrUnusedArgument(register, argumentInfo);
+ } else {
+ DexType dexType = method.method.proto.parameters.values[usedArgumentIndex++];
+ writeCallback.accept(register, dexType);
+ type = TypeLatticeElement.fromDexType(dexType, Nullability.maybeNull(), appView);
+ if (dexType.isBooleanType()) {
+ addBooleanNonThisArgument(register);
+ } else {
+ addNonThisArgument(register, type);
+ }
+ }
+ register += type.requiredRegisters();
+ argumentIndex++;
+ }
+ flushArgumentInstructions();
+ }
+
/**
* Build the high-level IR in SSA form.
*
@@ -864,7 +914,7 @@
public void addThisArgument(int register, TypeLatticeElement receiverType) {
DebugLocalInfo local = getOutgoingLocal(register);
Value value = writeRegister(register, receiverType, ThrowingInfo.NO_THROW, local);
- addInstruction(new Argument(value, false));
+ addInstruction(new Argument(value, currentBlock.size(), false));
receiverValue = value;
value.markAsThis();
}
@@ -872,13 +922,13 @@
public void addNonThisArgument(int register, TypeLatticeElement typeLattice) {
DebugLocalInfo local = getOutgoingLocal(register);
Value value = writeRegister(register, typeLattice, ThrowingInfo.NO_THROW, local);
- addNonThisArgument(new Argument(value, false));
+ addNonThisArgument(new Argument(value, currentBlock.size(), false));
}
public void addBooleanNonThisArgument(int register) {
DebugLocalInfo local = getOutgoingLocal(register);
Value value = writeRegister(register, getInt(), ThrowingInfo.NO_THROW, local);
- addNonThisArgument(new Argument(value, true));
+ addNonThisArgument(new Argument(value, currentBlock.size(), true));
}
private void addNonThisArgument(Argument argument) {
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 94df8fb..ce65222 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
@@ -59,7 +59,6 @@
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;
@@ -72,11 +71,14 @@
import com.android.tools.r8.ir.optimize.ServiceLoaderRewriter;
import com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization;
import com.android.tools.r8.ir.optimize.classinliner.ClassInliner;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxer;
+import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfoCollector;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
import com.android.tools.r8.ir.optimize.lambda.LambdaMerger;
import com.android.tools.r8.ir.optimize.staticizer.ClassStaticizer;
import com.android.tools.r8.ir.optimize.string.StringBuilderOptimizer;
@@ -157,6 +159,7 @@
private final TypeChecker typeChecker;
private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
private final ServiceLoaderRewriter serviceLoaderRewriter;
+ private final EnumValueOptimizer enumValueOptimizer;
private final EnumUnboxer enumUnboxer;
// Assumers that will insert Assume instructions.
@@ -246,6 +249,7 @@
this.stringSwitchRemover = null;
this.serviceLoaderRewriter = null;
this.methodOptimizationInfoCollector = null;
+ this.enumValueOptimizer = null;
this.enumUnboxer = null;
return;
}
@@ -325,6 +329,8 @@
? new DesugaredLibraryAPIConverter(
appView, Mode.ASSERT_CALLBACKS_AND_WRAPPERS_GENERATED)
: null;
+ this.enumValueOptimizer =
+ options.enableEnumValueOptimization ? new EnumValueOptimizer(appViewWithLiveness) : null;
this.enumUnboxer = options.enableEnumUnboxing ? new EnumUnboxer(appViewWithLiveness) : null;
} else {
this.classInliner = null;
@@ -349,6 +355,7 @@
: null;
this.serviceLoaderRewriter = null;
this.methodOptimizationInfoCollector = null;
+ this.enumValueOptimizer = null;
this.enumUnboxer = null;
}
this.stringSwitchRemover =
@@ -664,6 +671,10 @@
// Assure that no more optimization feedback left after primary processing.
assert feedback.noUpdatesLeft();
appView.setAllCodeProcessed();
+ // All the code has been processed so the rewriting required by the lenses is done everywhere,
+ // we clear lens code rewriting so that the lens rewriter can be re-executed in phase 2 if new
+ // lenses with code rewriting are added.
+ graphLenseForIR = appView.clearCodeRewritings();
if (libraryMethodOverrideAnalysis != null) {
libraryMethodOverrideAnalysis.finish();
@@ -694,6 +705,11 @@
}
timing.end();
+ // All the code that should be impacted by the lenses inserted between phase 1 and phase 2
+ // have now been processed and rewritten, we clear code lens rewriting so that the class
+ // staticizer and phase 3 does not perform again the rewriting.
+ appView.clearCodeRewritings();
+
// TODO(b/112831361): Implement support for staticizeClasses in CF backend.
if (!options.isGeneratingClassFiles()) {
printPhase("Class staticizer post processing");
@@ -702,6 +718,10 @@
feedback.updateVisibleOptimizationInfo();
}
+ // The class staticizer lens shall not be applied through lens code rewriting or it breaks
+ // the lambda merger.
+ appView.clearCodeRewritings();
+
// Build a new application with jumbo string info.
Builder<?> builder = application.builder();
builder.setHighestSortingString(highestSortingString);
@@ -814,6 +834,7 @@
if (options.enableFieldAssignmentTracker) {
fieldAccessAnalysis.fieldAssignmentTracker().waveDone(wave, delayedOptimizationFeedback);
}
+ delayedOptimizationFeedback.refineAppInfoWithLiveness(appView.appInfo().withLiveness());
delayedOptimizationFeedback.updateVisibleOptimizationInfo();
onWaveDoneActions.forEach(com.android.tools.r8.utils.Action::execute);
onWaveDoneActions = null;
@@ -1105,18 +1126,17 @@
codeRewriter.simplifyDebugLocals(code);
}
+ if (appView.graphLense().hasCodeRewritings()) {
+ assert lensCodeRewriter != null;
+ timing.begin("Lens rewrite");
+ lensCodeRewriter.rewrite(code, method);
+ timing.end();
+ }
+
if (method.isProcessed()) {
assert !appView.enableWholeProgramOptimizations()
|| !appView.appInfo().withLiveness().neverReprocess.contains(method.method);
} else {
- 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);
@@ -1173,10 +1193,10 @@
timing.end();
}
- if (options.enableEnumValueOptimization) {
+ if (enumValueOptimizer != null) {
assert appView.enableWholeProgramOptimizations();
timing.begin("Remove switch maps");
- codeRewriter.removeSwitchMaps(code);
+ enumValueOptimizer.removeSwitchMaps(code);
timing.end();
}
@@ -1233,12 +1253,17 @@
assert code.isConsistentSSA();
}
+ assert code.verifyTypes(appView);
+
if (devirtualizer != null) {
assert code.verifyTypes(appView);
timing.begin("Devirtualize invoke interface");
devirtualizer.devirtualizeInvokeInterface(code, method.method.holder);
timing.end();
}
+
+ assert code.verifyTypes(appView);
+
if (uninstantiatedTypeOptimization != null) {
timing.begin("Rewrite uninstantiated types");
uninstantiatedTypeOptimization.rewrite(code);
@@ -1246,14 +1271,15 @@
}
assert code.verifyTypes(appView);
+
timing.begin("Remove trivial type checks/casts");
codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(code);
timing.end();
- if (options.enableEnumValueOptimization) {
+ if (enumValueOptimizer != null) {
assert appView.enableWholeProgramOptimizations();
timing.begin("Rewrite constant enum methods");
- codeRewriter.rewriteConstantEnumMethodCalls(code);
+ enumValueOptimizer.rewriteConstantEnumMethodCalls(code);
timing.end();
}
@@ -1323,7 +1349,7 @@
timing.begin("Optimize class initializers");
ClassInitializerDefaultsResult classInitializerDefaultsResult =
- classInitializerDefaultsOptimization.optimize(method, code);
+ classInitializerDefaultsOptimization.optimize(method, code, feedback);
timing.end();
if (Log.ENABLED) {
@@ -1364,6 +1390,7 @@
appView.withLiveness(),
codeRewriter,
stringOptimizer,
+ enumValueOptimizer,
method,
code,
feedback,
@@ -1563,18 +1590,19 @@
return;
}
- methodOptimizationInfoCollector
- .collectMethodOptimizationInfo(code.method, code, feedback, dynamicTypeOptimization);
-
+ InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos = null;
if (method.isInitializer()) {
if (method.isClassInitializer()) {
StaticFieldValueAnalysis.run(
appView, code, classInitializerDefaultsResult, feedback, code.method);
} else {
- InstanceFieldValueAnalysis.run(
- appView, code, classInitializerDefaultsResult, feedback, code.method);
+ instanceFieldInitializationInfos =
+ InstanceFieldValueAnalysis.run(
+ appView, code, classInitializerDefaultsResult, feedback, code.method);
}
}
+ methodOptimizationInfoCollector.collectMethodOptimizationInfo(
+ code.method, code, feedback, dynamicTypeOptimization, instanceFieldInitializationInfos);
}
public void removeDeadCodeAndFinalizeIR(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 246d865..fc50632 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -27,9 +27,8 @@
import com.android.tools.r8.graph.DexValue.DexValueType;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.graph.RewrittenPrototypeDescription;
-import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentsInfo;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfoCollection;
import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater;
@@ -47,7 +46,6 @@
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.Invoke;
-import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.code.InvokeCustom;
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
@@ -169,15 +167,11 @@
graphLense.lookupMethod(invokedMethod, method.method, invoke.getType());
DexMethod actualTarget = lenseLookup.getMethod();
Invoke.Type actualInvokeType = lenseLookup.getType();
- if (actualInvokeType == Type.VIRTUAL) {
- actualTarget =
- rebindVirtualInvokeToMostSpecific(
- actualTarget, invoke.inValues().get(0), method.method.holder);
- }
if (actualTarget != invokedMethod || invoke.getType() != actualInvokeType) {
RewrittenPrototypeDescription prototypeChanges =
graphLense.lookupPrototypeChanges(actualTarget);
- RemovedArgumentsInfo removedArgumentsInfo = prototypeChanges.getRemovedArgumentsInfo();
+ RemovedArgumentInfoCollection removedArgumentsInfo =
+ prototypeChanges.getRemovedArgumentInfoCollection();
ConstInstruction constantReturnMaterializingInstruction = null;
if (prototypeChanges.hasBeenChangedToReturnVoid() && invoke.outValue() != null) {
@@ -597,80 +591,6 @@
return newProto != oldProto ? new DexValueMethodType(newProto) : type;
}
- /**
- * This rebinds invoke-virtual instructions to their most specific target.
- *
- * <p>As a simple example, consider the instruction "invoke-virtual A.foo(v0)", and assume that v0
- * is defined by an instruction "new-instance v0, B". If B is a subtype of A, and B overrides the
- * method foo(), then we rewrite the invocation into "invoke-virtual B.foo(v0)".
- *
- * <p>If A.foo() ends up being unused, this helps to ensure that we can get rid of A.foo()
- * entirely. Without this rewriting, we would have to keep A.foo() because the method is targeted.
- */
- private DexMethod rebindVirtualInvokeToMostSpecific(
- DexMethod target, Value receiver, DexType context) {
- if (!receiver.getTypeLattice().isClassType()) {
- return target;
- }
- DexEncodedMethod encodedTarget = appView.definitionFor(target);
- if (encodedTarget == null
- || !canInvokeTargetWithInvokeVirtual(encodedTarget)
- || !hasAccessToInvokeTargetFromContext(encodedTarget, context)) {
- // Don't rewrite this instruction as it could remove an error from the program.
- return target;
- }
- DexType receiverType =
- appView
- .graphLense()
- .lookupType(receiver.getTypeLattice().asClassTypeLatticeElement().getClassType());
- if (receiverType == target.holder) {
- // Virtual invoke is already as specific as it can get.
- return target;
- }
- ResolutionResult resolutionResult = appView.appInfo().resolveMethod(receiverType, target);
- DexEncodedMethod newTarget =
- resolutionResult.isVirtualTarget() ? resolutionResult.getSingleTarget() : null;
- if (newTarget == null || newTarget.method == target) {
- // Most likely due to a missing class, or invoke is already as specific as it gets.
- return target;
- }
- DexClass newTargetClass = appView.definitionFor(newTarget.method.holder);
- if (newTargetClass == null
- || newTargetClass.isLibraryClass()
- || !canInvokeTargetWithInvokeVirtual(newTarget)
- || !hasAccessToInvokeTargetFromContext(newTarget, context)) {
- // Not safe to invoke `newTarget` with virtual invoke from the current context.
- return target;
- }
- return newTarget.method;
- }
-
- private boolean canInvokeTargetWithInvokeVirtual(DexEncodedMethod target) {
- return target.isNonPrivateVirtualMethod()
- && appView.isInterface(target.method.holder).isFalse();
- }
-
- private boolean hasAccessToInvokeTargetFromContext(DexEncodedMethod target, DexType context) {
- assert !target.accessFlags.isPrivate();
- DexType holder = target.method.holder;
- if (holder == context) {
- // It is always safe to invoke a method from the same enclosing class.
- return true;
- }
- DexClass clazz = appView.definitionFor(holder);
- if (clazz == null) {
- // Conservatively report an illegal access.
- return false;
- }
- if (holder.isSamePackage(context)) {
- // The class must be accessible (note that we have already established that the method is not
- // private).
- return !clazz.accessFlags.isPrivate();
- }
- // If the method is in another package, then the method and its holder must be public.
- return clazz.accessFlags.isPublic() && target.accessFlags.isPublic();
- }
-
class InstructionReplacer {
private final IRCode code;
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 fae9325..0831e6d 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
@@ -592,9 +592,9 @@
@Override
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()) {
- target.method.accessFlags.setPublic();
+ target.getMethod().accessFlags.unsetPrivate();
+ if (target.getHolder().isInterface()) {
+ target.getMethod().accessFlags.setPublic();
}
return null;
}
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 e35087a..1e02c2b 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
@@ -45,6 +45,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.info.OptimizationFeedback;
import com.android.tools.r8.naming.dexitembasedstring.ClassNameComputationInfo;
import com.android.tools.r8.naming.dexitembasedstring.ClassNameComputationInfo.ClassNameMapping;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -91,32 +92,20 @@
}
}
- private class WaveDoneAction implements Action {
+ private static class WaveDoneAction implements Action {
private final Map<DexEncodedField, DexValue> fieldsWithStaticValues = new IdentityHashMap<>();
- private final Set<DexField> noLongerWrittenFields = Sets.newIdentityHashSet();
- public WaveDoneAction(
- Map<DexEncodedField, DexValue> fieldsWithStaticValues,
- Set<DexField> noLongerWrittenFields) {
+ WaveDoneAction(Map<DexEncodedField, DexValue> fieldsWithStaticValues) {
this.fieldsWithStaticValues.putAll(fieldsWithStaticValues);
- this.noLongerWrittenFields.addAll(noLongerWrittenFields);
}
- public synchronized void join(
- Map<DexEncodedField, DexValue> fieldsWithStaticValues,
- Set<DexField> noLongerWrittenFields) {
+ public synchronized void join(Map<DexEncodedField, DexValue> fieldsWithStaticValues) {
this.fieldsWithStaticValues.putAll(fieldsWithStaticValues);
- this.noLongerWrittenFields.addAll(noLongerWrittenFields);
}
@Override
public void execute() {
- // Update AppInfo.
- AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
- appViewWithLiveness.setAppInfo(
- appViewWithLiveness.appInfo().withoutStaticFieldsWrites(noLongerWrittenFields));
-
// Update static field values of classes.
fieldsWithStaticValues.forEach(DexEncodedField::setStaticValue);
}
@@ -134,7 +123,8 @@
this.dexItemFactory = appView.dexItemFactory();
}
- public ClassInitializerDefaultsResult optimize(DexEncodedMethod method, IRCode code) {
+ public ClassInitializerDefaultsResult optimize(
+ DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
if (appView.options().debug || method.getOptimizationInfo().isReachabilitySensitive()) {
return ClassInitializerDefaultsResult.empty();
}
@@ -269,17 +259,21 @@
}
}
- // Finally, remove these fields from the set of assigned static fields.
+ // Remove these fields from the set of assigned static fields.
+ feedback.modifyAppInfoWithLiveness(
+ modifier -> candidates.forEach(modifier::removeWrittenField));
+
+ // Update the static value of the fields when the wave ends.
synchronized (this) {
if (waveDoneAction == null) {
- waveDoneAction = new WaveDoneAction(fieldsWithStaticValues, candidates);
+ waveDoneAction = new WaveDoneAction(fieldsWithStaticValues);
converter.addWaveDoneAction(
() -> {
waveDoneAction.execute();
waveDoneAction = null;
});
} else {
- waveDoneAction.join(fieldsWithStaticValues, candidates);
+ waveDoneAction.join(fieldsWithStaticValues);
}
}
} else {
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 5b695a7..7f2cee9 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
@@ -16,7 +16,6 @@
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexClass;
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.DexItemFactory.ThrowableMethods;
import com.android.tools.r8.graph.DexMethod;
@@ -57,11 +56,9 @@
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.InvokeNewArray;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeVirtual;
-import com.android.tools.r8.ir.code.JumpInstruction;
import com.android.tools.r8.ir.code.Move;
import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.NewArrayFilledData;
@@ -76,9 +73,7 @@
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.code.Xor;
import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.optimize.SwitchUtils.EnumSwitchInfo;
import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
-import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOutputMode;
import com.android.tools.r8.utils.LongInterval;
@@ -93,7 +88,6 @@
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
-import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
@@ -1043,69 +1037,6 @@
}
/**
- * Inline the indirection of switch maps into the switch statement.
- * <p>
- * To ensure binary compatibility, javac generated code does not use ordinal values of enums
- * directly in switch statements but instead generates a companion class that computes a mapping
- * from switch branches to ordinals at runtime. As we have whole-program knowledge, we can
- * analyze these maps and inline the indirection into the switch map again.
- * <p>
- * In particular, we look for code of the form
- *
- * <blockquote><pre>
- * switch(CompanionClass.$switchmap$field[enumValue.ordinal()]) {
- * ...
- * }
- * </pre></blockquote>
- */
- public void removeSwitchMaps(IRCode code) {
- for (BasicBlock block : code.blocks) {
- JumpInstruction exit = block.exit();
- // Pattern match a switch on a switch map as input.
- if (exit.isIntSwitch()) {
- IntSwitch switchInsn = exit.asIntSwitch();
- EnumSwitchInfo info = SwitchUtils.analyzeSwitchOverEnum(switchInsn, appView.withLiveness());
- if (info != null) {
- Int2IntMap targetMap = new Int2IntArrayMap();
- for (int i = 0; i < switchInsn.numberOfKeys(); i++) {
- assert switchInsn.targetBlockIndices()[i] != switchInsn.getFallthroughBlockIndex();
- EnumValueInfo valueInfo =
- info.valueInfoMap.get(info.indexMap.get(switchInsn.getKey(i)));
- targetMap.put(valueInfo.ordinal, switchInsn.targetBlockIndices()[i]);
- }
- int[] keys = targetMap.keySet().toIntArray();
- Arrays.sort(keys);
- int[] targets = new int[keys.length];
- for (int i = 0; i < keys.length; i++) {
- targets[i] = targetMap.get(keys[i]);
- }
-
- IntSwitch newSwitch =
- new IntSwitch(
- info.ordinalInvoke.outValue(),
- keys,
- targets,
- switchInsn.getFallthroughBlockIndex());
- // Replace the switch itself.
- exit.replace(newSwitch, code);
- // If the original input to the switch is now unused, remove it too. It is not dead
- // as it might have side-effects but we ignore these here.
- Instruction arrayGet = info.arrayGet;
- if (!arrayGet.outValue().hasUsers()) {
- arrayGet.inValues().forEach(v -> v.removeUser(arrayGet));
- arrayGet.getBlock().removeInstruction(arrayGet);
- }
- Instruction staticGet = info.staticGet;
- if (!staticGet.outValue().hasUsers()) {
- assert staticGet.inValues().isEmpty();
- staticGet.getBlock().removeInstruction(staticGet);
- }
- }
- }
- }
- }
-
- /**
* Rewrite all branch targets to the destination of trivial goto chains when possible. Does not
* rewrite fallthrough targets as that would require block reordering and the transformation only
* makes sense after SSA destruction where there are no phis.
@@ -2966,80 +2897,6 @@
return true;
}
- public void rewriteConstantEnumMethodCalls(IRCode code) {
- if (!code.metadata().mayHaveInvokeMethodWithReceiver()) {
- return;
- }
-
- InstructionListIterator iterator = code.instructionListIterator();
- while (iterator.hasNext()) {
- Instruction current = iterator.next();
-
- if (!current.isInvokeMethodWithReceiver()) {
- continue;
- }
- InvokeMethodWithReceiver methodWithReceiver = current.asInvokeMethodWithReceiver();
- DexMethod invokedMethod = methodWithReceiver.getInvokedMethod();
- boolean isOrdinalInvoke = invokedMethod == dexItemFactory.enumMethods.ordinal;
- boolean isNameInvoke = invokedMethod == dexItemFactory.enumMethods.name;
- boolean isToStringInvoke = invokedMethod == dexItemFactory.enumMethods.toString;
- if (!isOrdinalInvoke && !isNameInvoke && !isToStringInvoke) {
- continue;
- }
-
- Value receiver = methodWithReceiver.getReceiver().getAliasedValue();
- if (receiver.isPhi()) {
- continue;
- }
- Instruction definition = receiver.getDefinition();
- if (!definition.isStaticGet()) {
- continue;
- }
- DexField enumField = definition.asStaticGet().getField();
-
- Map<DexField, EnumValueInfo> valueInfoMap =
- appView.appInfo().withLiveness().getEnumValueInfoMapFor(enumField.type);
- if (valueInfoMap == null) {
- continue;
- }
-
- // The receiver value is identified as being from a constant enum field lookup by the fact
- // that it is a static-get to a field whose type is the same as the enclosing class (which
- // is known to be an enum type). An enum may still define a static field using the enum type
- // so ensure the field is present in the ordinal map for final validation.
- EnumValueInfo valueInfo = valueInfoMap.get(enumField);
- if (valueInfo == null) {
- continue;
- }
-
- Value outValue = methodWithReceiver.outValue();
- if (isOrdinalInvoke) {
- iterator.replaceCurrentInstruction(new ConstNumber(outValue, valueInfo.ordinal));
- } else if (isNameInvoke) {
- iterator.replaceCurrentInstruction(
- new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW));
- } else {
- assert isToStringInvoke;
- DexClass enumClazz = appView.appInfo().definitionFor(enumField.type);
- if (!enumClazz.accessFlags.isFinal()) {
- continue;
- }
- if (appView
- .appInfo()
- .resolveMethodOnClass(valueInfo.type, dexItemFactory.objectMethods.toString)
- .getSingleTarget()
- .method
- != dexItemFactory.enumMethods.toString) {
- continue;
- }
- iterator.replaceCurrentInstruction(
- new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW));
- }
- }
-
- assert code.isConsistentSSA();
- }
-
public void rewriteKnownArrayLengthCalls(IRCode code) {
InstructionListIterator iterator = code.instructionListIterator();
while (iterator.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 8370b69..f1c5dc9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -607,11 +607,11 @@
for (Monitor monitor : inlinee.code.<Monitor>instructions(Instruction::isMonitorEnter)) {
Value monitorEnterValue = monitor.object().getAliasedValue();
- if (monitorEnterValue.isArgument()) {
+ if (monitorEnterValue.isDefinedByInstructionSatisfying(Instruction::isArgument)) {
monitorEnterValue =
invoke
.arguments()
- .get(monitorEnterValue.computeArgumentPosition(inlinee.code))
+ .get(monitorEnterValue.definition.asArgument().getIndex())
.getAliasedValue();
}
addMonitorEnterValue(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index bebbbf4..0c53a4c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -6,7 +6,9 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.Assume;
@@ -17,6 +19,7 @@
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.Invoke;
import com.android.tools.r8.ir.code.InvokeInterface;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.Value;
@@ -31,8 +34,14 @@
import java.util.Set;
/**
- * Rewrites all invoke-interface instructions that have a unique target on a class into
+ * Tries to rewrite virtual invokes to their most specific target by:
+ *
+ * <pre>
+ * 1) Rewriting all invoke-interface instructions that have a unique target on a class into
* invoke-virtual with the corresponding unique target.
+ * 2) Rewriting all invoke-virtual instructions that have a more specific target to an
+ * invoke-virtual with the corresponding target.
+ * </pre>
*/
public class Devirtualizer {
@@ -105,6 +114,20 @@
}
}
+ if (current.isInvokeVirtual()) {
+ InvokeVirtual invoke = current.asInvokeVirtual();
+ DexMethod invokedMethod = invoke.getInvokedMethod();
+ DexMethod reboundTarget =
+ rebindVirtualInvokeToMostSpecific(
+ invokedMethod, invoke.getReceiver(), invocationContext);
+ if (reboundTarget != invokedMethod) {
+ Invoke newInvoke =
+ Invoke.create(
+ Invoke.Type.VIRTUAL, reboundTarget, null, invoke.outValue(), invoke.inValues());
+ it.replaceCurrentInstruction(newInvoke);
+ }
+ }
+
if (!current.isInvokeInterface()) {
continue;
}
@@ -226,4 +249,78 @@
}
assert code.isConsistentSSA();
}
+
+ /**
+ * This rebinds invoke-virtual instructions to their most specific target.
+ *
+ * <p>As a simple example, consider the instruction "invoke-virtual A.foo(v0)", and assume that v0
+ * is defined by an instruction "new-instance v0, B". If B is a subtype of A, and B overrides the
+ * method foo(), then we rewrite the invocation into "invoke-virtual B.foo(v0)".
+ *
+ * <p>If A.foo() ends up being unused, this helps to ensure that we can get rid of A.foo()
+ * entirely. Without this rewriting, we would have to keep A.foo() because the method is targeted.
+ */
+ private DexMethod rebindVirtualInvokeToMostSpecific(
+ DexMethod target, Value receiver, DexType context) {
+ if (!receiver.getTypeLattice().isClassType()) {
+ return target;
+ }
+ DexEncodedMethod encodedTarget = appView.definitionFor(target);
+ if (encodedTarget == null
+ || !canInvokeTargetWithInvokeVirtual(encodedTarget)
+ || !hasAccessToInvokeTargetFromContext(encodedTarget, context)) {
+ // Don't rewrite this instruction as it could remove an error from the program.
+ return target;
+ }
+ DexType receiverType =
+ appView
+ .graphLense()
+ .lookupType(receiver.getTypeLattice().asClassTypeLatticeElement().getClassType());
+ if (receiverType == target.holder) {
+ // Virtual invoke is already as specific as it can get.
+ return target;
+ }
+ ResolutionResult resolutionResult = appView.appInfo().resolveMethod(receiverType, target);
+ DexEncodedMethod newTarget =
+ resolutionResult.isVirtualTarget() ? resolutionResult.getSingleTarget() : null;
+ if (newTarget == null || newTarget.method == target) {
+ // Most likely due to a missing class, or invoke is already as specific as it gets.
+ return target;
+ }
+ DexClass newTargetClass = appView.definitionFor(newTarget.method.holder);
+ if (newTargetClass == null
+ || newTargetClass.isLibraryClass()
+ || !canInvokeTargetWithInvokeVirtual(newTarget)
+ || !hasAccessToInvokeTargetFromContext(newTarget, context)) {
+ // Not safe to invoke `newTarget` with virtual invoke from the current context.
+ return target;
+ }
+ return newTarget.method;
+ }
+
+ private boolean canInvokeTargetWithInvokeVirtual(DexEncodedMethod target) {
+ return target.isNonPrivateVirtualMethod()
+ && appView.isInterface(target.method.holder).isFalse();
+ }
+
+ private boolean hasAccessToInvokeTargetFromContext(DexEncodedMethod target, DexType context) {
+ assert !target.accessFlags.isPrivate();
+ DexType holder = target.method.holder;
+ if (holder == context) {
+ // It is always safe to invoke a method from the same enclosing class.
+ return true;
+ }
+ DexClass clazz = appView.definitionFor(holder);
+ if (clazz == null) {
+ // Conservatively report an illegal access.
+ return false;
+ }
+ if (holder.isSamePackage(context)) {
+ // The class must be accessible (note that we have already established that the method is not
+ // private).
+ return !clazz.accessFlags.isPrivate();
+ }
+ // If the method is in another package, then the method and its holder must be public.
+ return clazz.accessFlags.isPublic() && target.accessFlags.isPublic();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index 96819d0..70a9795 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -442,7 +442,9 @@
// For each of the actual potential targets, derive constraints based on the accessibility
// of the method itself.
- LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+ LookupResult lookupResult =
+ resolutionResult.lookupVirtualDispatchTargets(
+ appView.definitionForProgramType(invocationContext), appView);
if (lookupResult.isLookupResultFailure()) {
return ConstraintWithTarget.NEVER;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java
index aeedc02..6e53d40 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java
@@ -4,8 +4,8 @@
package com.android.tools.r8.ir.optimize;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.Descriptor;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexMember;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -28,13 +28,13 @@
import java.util.function.Predicate;
// Per-class collection of member signatures.
-public abstract class MemberPoolCollection<T extends Descriptor> {
+public abstract class MemberPoolCollection<R extends DexMember<?, R>> {
- final Equivalence<T> equivalence;
+ final Equivalence<R> equivalence;
final AppView<AppInfoWithLiveness> appView;
- final Map<DexClass, MemberPool<T>> memberPools = new ConcurrentHashMap<>();
+ final Map<DexClass, MemberPool<R>> memberPools = new ConcurrentHashMap<>();
- MemberPoolCollection(AppView<AppInfoWithLiveness> appView, Equivalence<T> equivalence) {
+ MemberPoolCollection(AppView<AppInfoWithLiveness> appView, Equivalence<R> equivalence) {
this.appView = appView;
this.equivalence = equivalence;
}
@@ -56,7 +56,7 @@
}
}
- public MemberPool<T> buildForHierarchy(
+ public MemberPool<R> buildForHierarchy(
DexClass clazz, ExecutorService executorService, Timing timing) throws ExecutionException {
timing.begin("Building member pool collection");
try {
@@ -75,14 +75,14 @@
return memberPools.containsKey(clazz);
}
- public MemberPool<T> get(DexClass clazz) {
+ public MemberPool<R> get(DexClass clazz) {
assert hasPool(clazz);
return memberPools.get(clazz);
}
- public boolean markIfNotSeen(DexClass clazz, T reference) {
- MemberPool<T> memberPool = get(clazz);
- Wrapper<T> key = equivalence.wrap(reference);
+ public boolean markIfNotSeen(DexClass clazz, R reference) {
+ MemberPool<R> memberPool = get(clazz);
+ Wrapper<R> key = equivalence.wrap(reference);
if (memberPool.hasSeen(key)) {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
index 5577a8c..f362ef7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -186,7 +186,8 @@
if (newInstance.clazz.classInitializationMayHaveSideEffects(
appView,
// Types that are a super type of `context` are guaranteed to be initialized already.
- type -> appView.isSubtype(context, type).isTrue())) {
+ type -> appView.isSubtype(context, type).isTrue(),
+ Sets.newIdentityHashSet())) {
killAllActiveFields();
}
} else {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SwitchUtils.java b/src/main/java/com/android/tools/r8/ir/optimize/SwitchUtils.java
deleted file mode 100644
index 0dd0bcf..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/SwitchUtils.java
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.optimize;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.ArrayGet;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InvokeVirtual;
-import com.android.tools.r8.ir.code.StaticGet;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
-import java.util.Map;
-
-public class SwitchUtils {
-
- public static final class EnumSwitchInfo {
- public final DexType enumClass;
- public final Instruction ordinalInvoke;
- public final Instruction arrayGet;
- public final Instruction staticGet;
- public final Int2ReferenceMap<DexField> indexMap;
- public final Map<DexField, EnumValueInfo> valueInfoMap;
-
- private EnumSwitchInfo(DexType enumClass,
- Instruction ordinalInvoke,
- Instruction arrayGet, Instruction staticGet,
- Int2ReferenceMap<DexField> indexMap,
- Map<DexField, EnumValueInfo> valueInfoMap) {
- this.enumClass = enumClass;
- this.ordinalInvoke = ordinalInvoke;
- this.arrayGet = arrayGet;
- this.staticGet = staticGet;
- this.indexMap = indexMap;
- this.valueInfoMap = valueInfoMap;
- }
- }
-
- /**
- * Looks for a switch statement over the enum companion class of the form
- *
- * <blockquote>
- *
- * <pre>
- * switch(CompanionClass.$switchmap$field[enumValue.ordinal()]) {
- * ...
- * }
- * </pre>
- *
- * </blockquote>
- *
- * and extracts the components and the index and ordinal maps. See {@link EnumInfoMapCollector}
- * and {@link SwitchMapCollector} for details.
- */
- public static EnumSwitchInfo analyzeSwitchOverEnum(
- Instruction switchInsn, AppView<AppInfoWithLiveness> appView) {
- AppInfoWithLiveness appInfo = appView.appInfo();
- Instruction input = switchInsn.inValues().get(0).definition;
- if (input == null || !input.isArrayGet()) {
- return null;
- }
- ArrayGet arrayGet = input.asArrayGet();
- Instruction index = arrayGet.index().definition;
- if (index == null || !index.isInvokeVirtual()) {
- return null;
- }
- InvokeVirtual ordinalInvoke = index.asInvokeVirtual();
- DexMethod ordinalMethod = ordinalInvoke.getInvokedMethod();
- DexClass enumClass = appInfo.definitionFor(ordinalMethod.holder);
- DexItemFactory dexItemFactory = appInfo.dexItemFactory();
- // After member rebinding, enumClass will be the actual java.lang.Enum class.
- if (enumClass == null
- || (!enumClass.accessFlags.isEnum() && enumClass.type != dexItemFactory.enumType)
- || ordinalMethod.name != dexItemFactory.ordinalMethodName
- || ordinalMethod.proto.returnType != dexItemFactory.intType
- || !ordinalMethod.proto.parameters.isEmpty()) {
- return null;
- }
- Instruction array = arrayGet.array().definition;
- if (array == null || !array.isStaticGet()) {
- return null;
- }
- StaticGet staticGet = array.asStaticGet();
- Int2ReferenceMap<DexField> indexMap = appInfo.getSwitchMapFor(staticGet.getField());
- if (indexMap == null || indexMap.isEmpty()) {
- return null;
- }
- // Due to member rebinding, only the fields are certain to provide the actual enums
- // class.
- DexType enumType = indexMap.values().iterator().next().holder;
- Map<DexField, EnumValueInfo> valueInfoMap = appInfo.getEnumValueInfoMapFor(enumType);
- if (valueInfoMap == null) {
- return null;
- }
- return new EnumSwitchInfo(enumType, ordinalInvoke, arrayGet, staticGet, indexMap,
- valueInfoMap);
- }
-
-
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index c234bc3..a04e7c5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -20,7 +20,7 @@
import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
import com.android.tools.r8.graph.RewrittenPrototypeDescription;
import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
-import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentsInfo;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfoCollection;
import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
import com.android.tools.r8.ir.analysis.AbstractError;
import com.android.tools.r8.ir.analysis.TypeChecker;
@@ -43,7 +43,6 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
-import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashMap;
import java.util.HashSet;
@@ -63,11 +62,11 @@
public static class UninstantiatedTypeOptimizationGraphLense extends NestedGraphLense {
- private final Map<DexMethod, RemovedArgumentsInfo> removedArgumentsInfoPerMethod;
+ private final Map<DexMethod, RemovedArgumentInfoCollection> removedArgumentsInfoPerMethod;
UninstantiatedTypeOptimizationGraphLense(
BiMap<DexMethod, DexMethod> methodMap,
- Map<DexMethod, RemovedArgumentsInfo> removedArgumentsInfoPerMethod,
+ Map<DexMethod, RemovedArgumentInfoCollection> removedArgumentsInfoPerMethod,
AppView<?> appView) {
super(
ImmutableMap.of(),
@@ -88,7 +87,8 @@
if (method.proto.returnType.isVoidType() && !originalMethod.proto.returnType.isVoidType()) {
result = result.withConstantReturn();
}
- RemovedArgumentsInfo removedArgumentsInfo = removedArgumentsInfoPerMethod.get(method);
+ RemovedArgumentInfoCollection removedArgumentsInfo =
+ removedArgumentsInfoPerMethod.get(method);
if (removedArgumentsInfo != null) {
result = result.withRemovedArguments(removedArgumentsInfo);
}
@@ -125,7 +125,8 @@
Map<Wrapper<DexMethod>, Set<DexType>> changedVirtualMethods = new HashMap<>();
BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create();
- Map<DexMethod, RemovedArgumentsInfo> removedArgumentsInfoPerMethod = new IdentityHashMap<>();
+ Map<DexMethod, RemovedArgumentInfoCollection> removedArgumentsInfoPerMethod =
+ new IdentityHashMap<>();
TopDownClassHierarchyTraversal.forProgramClasses(appView)
.visit(
@@ -150,7 +151,7 @@
Map<Wrapper<DexMethod>, Set<DexType>> changedVirtualMethods,
BiMap<DexMethod, DexMethod> methodMapping,
MethodPoolCollection methodPoolCollection,
- Map<DexMethod, RemovedArgumentsInfo> removedArgumentsInfoPerMethod) {
+ Map<DexMethod, RemovedArgumentInfoCollection> removedArgumentsInfoPerMethod) {
MemberPool<DexMethod> methodPool = methodPoolCollection.get(clazz);
if (clazz.isInterface()) {
@@ -198,7 +199,8 @@
RewrittenPrototypeDescription prototypeChanges =
prototypeChangesPerMethod.getOrDefault(
encodedMethod, RewrittenPrototypeDescription.none());
- RemovedArgumentsInfo removedArgumentsInfo = prototypeChanges.getRemovedArgumentsInfo();
+ RemovedArgumentInfoCollection removedArgumentsInfo =
+ prototypeChanges.getRemovedArgumentInfoCollection();
DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges);
if (newMethod != method) {
Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod);
@@ -231,7 +233,8 @@
DexMethod method = encodedMethod.method;
RewrittenPrototypeDescription prototypeChanges =
getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL);
- RemovedArgumentsInfo removedArgumentsInfo = prototypeChanges.getRemovedArgumentsInfo();
+ RemovedArgumentInfoCollection removedArgumentsInfo =
+ prototypeChanges.getRemovedArgumentInfoCollection();
DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges);
if (newMethod != method) {
Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod);
@@ -259,7 +262,8 @@
DexMethod method = encodedMethod.method;
RewrittenPrototypeDescription prototypeChanges =
getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL);
- RemovedArgumentsInfo removedArgumentsInfo = prototypeChanges.getRemovedArgumentsInfo();
+ RemovedArgumentInfoCollection removedArgumentsInfo =
+ prototypeChanges.getRemovedArgumentInfoCollection();
DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges);
if (newMethod != method) {
Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod);
@@ -298,32 +302,24 @@
getRemovedArgumentsInfo(encodedMethod, strategy));
}
- private RemovedArgumentsInfo getRemovedArgumentsInfo(
+ private RemovedArgumentInfoCollection getRemovedArgumentsInfo(
DexEncodedMethod encodedMethod, Strategy strategy) {
if (strategy == DISALLOW_ARGUMENT_REMOVAL) {
- return RemovedArgumentsInfo.empty();
+ return RemovedArgumentInfoCollection.empty();
}
- List<RemovedArgumentInfo> removedArgumentsInfo = null;
+ RemovedArgumentInfoCollection.Builder argInfosBuilder = RemovedArgumentInfoCollection.builder();
DexProto proto = encodedMethod.method.proto;
int offset = encodedMethod.isStatic() ? 0 : 1;
for (int i = 0; i < proto.parameters.size(); ++i) {
DexType type = proto.parameters.values[i];
if (type.isAlwaysNull(appView)) {
- if (removedArgumentsInfo == null) {
- removedArgumentsInfo = new ArrayList<>();
- }
- removedArgumentsInfo.add(
- RemovedArgumentInfo.builder()
- .setArgumentIndex(i + offset)
- .setIsAlwaysNull()
- .setType(type)
- .build());
+ RemovedArgumentInfo removedArg =
+ RemovedArgumentInfo.builder().setIsAlwaysNull().setType(type).build();
+ argInfosBuilder.addRemovedArgument(i + offset, removedArg);
}
}
- return removedArgumentsInfo != null
- ? new RemovedArgumentsInfo(removedArgumentsInfo)
- : RemovedArgumentsInfo.empty();
+ return argInfosBuilder.build();
}
private DexMethod getNewMethodSignature(
@@ -336,6 +332,7 @@
}
public void rewrite(IRCode code) {
+ AssumeDynamicTypeRemover assumeDynamicTypeRemover = new AssumeDynamicTypeRemover(appView, code);
Set<BasicBlock> blocksToBeRemoved = Sets.newIdentityHashSet();
ListIterator<BasicBlock> blockIterator = code.listIterator();
Set<Value> valuesToNarrow = Sets.newIdentityHashSet();
@@ -375,6 +372,7 @@
blockIterator,
instructionIterator,
code,
+ assumeDynamicTypeRemover,
valuesToNarrow);
} else if (instruction.isInvokeMethod()) {
rewriteInvoke(
@@ -382,11 +380,13 @@
blockIterator,
instructionIterator,
code,
+ assumeDynamicTypeRemover,
blocksToBeRemoved,
valuesToNarrow);
}
}
}
+ assumeDynamicTypeRemover.removeMarkedInstructions(blocksToBeRemoved).finish();
code.removeBlocks(blocksToBeRemoved);
code.removeAllTrivialPhis(valuesToNarrow);
code.removeUnreachableBlocks();
@@ -444,6 +444,7 @@
ListIterator<BasicBlock> blockIterator,
InstructionListIterator instructionIterator,
IRCode code,
+ AssumeDynamicTypeRemover assumeDynamicTypeRemover,
Set<Value> affectedValues) {
DexType context = code.method.method.holder;
DexField field = instruction.getField();
@@ -473,10 +474,12 @@
} else {
if (instructionCanBeRemoved) {
// Replace the field read by the constant null.
+ assumeDynamicTypeRemover.markUsersForRemoval(instruction.outValue());
affectedValues.addAll(instruction.outValue().affectedValues());
instructionIterator.replaceCurrentInstruction(code.createConstNull());
} else {
- replaceOutValueByNull(instruction, instructionIterator, code, affectedValues);
+ replaceOutValueByNull(
+ instruction, instructionIterator, code, assumeDynamicTypeRemover, affectedValues);
}
}
@@ -496,6 +499,7 @@
ListIterator<BasicBlock> blockIterator,
InstructionListIterator instructionIterator,
IRCode code,
+ AssumeDynamicTypeRemover assumeDynamicTypeRemover,
Set<BasicBlock> blocksToBeRemoved,
Set<Value> affectedValues) {
DexEncodedMethod target = invoke.lookupSingleTarget(appView, code.method.method.holder);
@@ -518,7 +522,8 @@
DexType returnType = target.method.proto.returnType;
if (returnType.isAlwaysNull(appView)) {
- replaceOutValueByNull(invoke, instructionIterator, code, affectedValues);
+ replaceOutValueByNull(
+ invoke, instructionIterator, code, assumeDynamicTypeRemover, affectedValues);
}
}
@@ -526,11 +531,13 @@
Instruction instruction,
InstructionListIterator instructionIterator,
IRCode code,
+ AssumeDynamicTypeRemover assumeDynamicTypeRemover,
Set<Value> affectedValues) {
assert instructionIterator.peekPrevious() == instruction;
if (instruction.hasOutValue()) {
Value outValue = instruction.outValue();
if (outValue.numberOfAllUsers() > 0) {
+ assumeDynamicTypeRemover.markUsersForRemoval(outValue);
instructionIterator.previous();
affectedValues.addAll(outValue.affectedValues());
outValue.replaceUsers(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index d8568d5..e99ba9a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -18,7 +18,7 @@
import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
import com.android.tools.r8.graph.RewrittenPrototypeDescription;
import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
-import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentsInfo;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfoCollection;
import com.android.tools.r8.ir.optimize.MemberPoolCollection.MemberPool;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
@@ -32,7 +32,6 @@
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Streams;
-import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashSet;
import java.util.IdentityHashMap;
@@ -51,11 +50,12 @@
private final MethodPoolCollection methodPoolCollection;
private final BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create();
- private final Map<DexMethod, RemovedArgumentsInfo> removedArguments = new IdentityHashMap<>();
+ private final Map<DexMethod, RemovedArgumentInfoCollection> removedArguments =
+ new IdentityHashMap<>();
public static class UnusedArgumentsGraphLense extends NestedGraphLense {
- private final Map<DexMethod, RemovedArgumentsInfo> removedArguments;
+ private final Map<DexMethod, RemovedArgumentInfoCollection> removedArguments;
UnusedArgumentsGraphLense(
Map<DexType, DexType> typeMap,
@@ -65,7 +65,7 @@
BiMap<DexMethod, DexMethod> originalMethodSignatures,
GraphLense previousLense,
DexItemFactory dexItemFactory,
- Map<DexMethod, RemovedArgumentsInfo> removedArguments) {
+ Map<DexMethod, RemovedArgumentInfoCollection> removedArguments) {
super(
typeMap,
methodMap,
@@ -84,7 +84,7 @@
? originalMethodSignatures.getOrDefault(method, method)
: method;
RewrittenPrototypeDescription result = previousLense.lookupPrototypeChanges(originalMethod);
- RemovedArgumentsInfo removedArguments = this.removedArguments.get(method);
+ RemovedArgumentInfoCollection removedArguments = this.removedArguments.get(method);
return removedArguments != null ? result.withRemovedArguments(removedArguments) : result;
}
}
@@ -167,7 +167,7 @@
}
DexEncodedMethod removeArguments(
- DexEncodedMethod method, DexMethod newSignature, RemovedArgumentsInfo unused) {
+ DexEncodedMethod method, DexMethod newSignature, RemovedArgumentInfoCollection unused) {
boolean removed = usedSignatures.remove(equivalence.wrap(method.method));
assert removed;
@@ -208,7 +208,7 @@
}
DexEncodedMethod removeArguments(
- DexEncodedMethod method, DexMethod newSignature, RemovedArgumentsInfo unused) {
+ DexEncodedMethod method, DexMethod newSignature, RemovedArgumentInfoCollection unused) {
methodPool.seen(equivalence.wrap(newSignature));
return method.toTypeSubstitutedMethod(
newSignature, unused.createParameterAnnotationsRemover(method));
@@ -234,7 +234,7 @@
continue;
}
- RemovedArgumentsInfo unused = collectUnusedArguments(method);
+ RemovedArgumentInfoCollection unused = collectUnusedArguments(method);
if (unused != null && unused.hasRemovedArguments()) {
DexProto newProto = createProtoWithRemovedArguments(method, unused);
DexMethod newSignature = signatures.getNewSignature(method, newProto);
@@ -259,7 +259,7 @@
List<DexEncodedMethod> virtualMethods = clazz.virtualMethods();
for (int i = 0; i < virtualMethods.size(); i++) {
DexEncodedMethod method = virtualMethods.get(i);
- RemovedArgumentsInfo unused = collectUnusedArguments(method, methodPool);
+ RemovedArgumentInfoCollection unused = collectUnusedArguments(method, methodPool);
if (unused != null && unused.hasRemovedArguments()) {
DexProto newProto = createProtoWithRemovedArguments(method, unused);
DexMethod newSignature = signatures.getNewSignature(method, newProto);
@@ -279,11 +279,11 @@
}
}
- private RemovedArgumentsInfo collectUnusedArguments(DexEncodedMethod method) {
+ private RemovedArgumentInfoCollection collectUnusedArguments(DexEncodedMethod method) {
return collectUnusedArguments(method, null);
}
- private RemovedArgumentsInfo collectUnusedArguments(
+ private RemovedArgumentInfoCollection collectUnusedArguments(
DexEncodedMethod method, MemberPool<DexMethod> methodPool) {
if (ArgumentRemovalUtils.isPinned(method, appView)
|| appView.appInfo().keepUnusedArguments.contains(method.method)) {
@@ -314,23 +314,24 @@
method.getCode().registerArgumentReferences(method, collector);
BitSet used = collector.getUsedArguments();
if (used.cardinality() < argumentCount) {
- List<RemovedArgumentInfo> unused = new ArrayList<>();
+ RemovedArgumentInfoCollection.Builder argInfosBuilder =
+ RemovedArgumentInfoCollection.builder();
for (int argumentIndex = 0; argumentIndex < argumentCount; argumentIndex++) {
if (!used.get(argumentIndex)) {
- unused.add(
+ RemovedArgumentInfo removedArg =
RemovedArgumentInfo.builder()
- .setArgumentIndex(argumentIndex)
.setType(method.method.proto.parameters.values[argumentIndex - offset])
- .build());
+ .build();
+ argInfosBuilder.addRemovedArgument(argumentIndex, removedArg);
}
}
- return new RemovedArgumentsInfo(unused);
+ return argInfosBuilder.build();
}
return null;
}
private DexProto createProtoWithRemovedArguments(
- DexEncodedMethod encodedMethod, RemovedArgumentsInfo unused) {
+ DexEncodedMethod encodedMethod, RemovedArgumentInfoCollection unused) {
DexType[] parameters = unused.rewriteParameters(encodedMethod);
return appView.dexItemFactory().createProto(encodedMethod.method.proto.returnType, parameters);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index c4a761b..b20eba1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.ir.optimize.InliningOracle;
import com.android.tools.r8.ir.optimize.classinliner.InlineCandidateProcessor.IllegalClassInlinerStateException;
+import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
import com.android.tools.r8.ir.optimize.string.StringOptimizer;
@@ -164,6 +165,7 @@
AppView<AppInfoWithLiveness> appView,
CodeRewriter codeRewriter,
StringOptimizer stringOptimizer,
+ EnumValueOptimizer enumValueOptimizer,
DexEncodedMethod method,
IRCode code,
OptimizationFeedback feedback,
@@ -282,7 +284,7 @@
appView.withGeneratedMessageLiteBuilderShrinker(
shrinker ->
shrinker.inlineCallsToDynamicMethod(
- method, code, codeRewriter, feedback, methodProcessor, inliner));
+ method, code, enumValueOptimizer, feedback, methodProcessor, inliner));
}
if (anyInlinedMethods) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 9231d05..f076b41 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -160,7 +160,8 @@
if (eligibleClass.classInitializationMayHaveSideEffects(
appView,
// Types that are a super type of the current context are guaranteed to be initialized.
- type -> appView.isSubtype(method.method.holder, type).isTrue())) {
+ type -> appView.isSubtype(method.method.holder, type).isTrue(),
+ Sets.newIdentityHashSet())) {
return EligibilityStatus.HAS_CLINIT;
}
return EligibilityStatus.ELIGIBLE;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumInfoMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInfoMapCollector.java
similarity index 96%
rename from src/main/java/com/android/tools/r8/ir/optimize/EnumInfoMapCollector.java
rename to src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInfoMapCollector.java
index 2472589..e0991e0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/EnumInfoMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInfoMapCollector.java
@@ -1,7 +1,7 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// 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;
+package com.android.tools.r8.ir.optimize.enums;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
similarity index 96%
rename from src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java
rename to src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index c38b7b0..b3a25d0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -2,7 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.optimize;
+package com.android.tools.r8.ir.optimize.enums;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
@@ -143,7 +143,13 @@
assert enumClass != null;
DexEncodedMethod initializer = enumClass.lookupDirectMethod(factory.enumMethods.constructor);
- assert initializer != null;
+ if (initializer == null) {
+ // This case typically happens when a programmer uses EnumSet/EnumMap without using the
+ // enum keep rules. The code is incorrect in this case (EnumSet/EnumMap won't work).
+ // We bail out.
+ markEnumAsUnboxable(Reason.NO_INIT, enumClass);
+ continue;
+ }
if (initializer.getOptimizationInfo().mayHaveSideEffects()) {
markEnumAsUnboxable(Reason.INVALID_INIT, enumClass);
continue;
@@ -325,6 +331,7 @@
VIRTUAL_METHOD,
UNEXPECTED_DIRECT_METHOD,
INVALID_PHI,
+ NO_INIT,
INVALID_INIT,
INVALID_CLINIT,
INVALID_INVOKE,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
similarity index 97%
rename from src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxingCandidateAnalysis.java
rename to src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
index 3fb84e1..7b207c6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxingCandidateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
@@ -2,7 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.optimize;
+package com.android.tools.r8.ir.optimize.enums;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
@@ -10,7 +10,7 @@
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.android.tools.r8.ir.optimize.enums.EnumUnboxer.Reason;
import com.google.common.collect.Sets;
import java.util.Set;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
new file mode 100644
index 0000000..c8122470
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
@@ -0,0 +1,268 @@
+// 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.enums;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.ArrayGet;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.ConstString;
+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.IntSwitch;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.JumpInstruction;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.SwitchMapCollector;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo;
+import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2IntMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import java.util.Arrays;
+import java.util.Map;
+
+public class EnumValueOptimizer {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final DexItemFactory factory;
+
+ public EnumValueOptimizer(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ this.factory = appView.dexItemFactory();
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ public void rewriteConstantEnumMethodCalls(IRCode code) {
+ if (!code.metadata().mayHaveInvokeMethodWithReceiver()) {
+ return;
+ }
+
+ InstructionListIterator iterator = code.instructionListIterator();
+ while (iterator.hasNext()) {
+ Instruction current = iterator.next();
+
+ if (!current.isInvokeMethodWithReceiver()) {
+ continue;
+ }
+ InvokeMethodWithReceiver methodWithReceiver = current.asInvokeMethodWithReceiver();
+ DexMethod invokedMethod = methodWithReceiver.getInvokedMethod();
+ boolean isOrdinalInvoke = invokedMethod == factory.enumMethods.ordinal;
+ boolean isNameInvoke = invokedMethod == factory.enumMethods.name;
+ boolean isToStringInvoke = invokedMethod == factory.enumMethods.toString;
+ if (!isOrdinalInvoke && !isNameInvoke && !isToStringInvoke) {
+ continue;
+ }
+
+ Value receiver = methodWithReceiver.getReceiver().getAliasedValue();
+ if (receiver.isPhi()) {
+ continue;
+ }
+ Instruction definition = receiver.getDefinition();
+ if (!definition.isStaticGet()) {
+ continue;
+ }
+ DexField enumField = definition.asStaticGet().getField();
+
+ Map<DexField, EnumValueInfo> valueInfoMap =
+ appView.appInfo().withLiveness().getEnumValueInfoMapFor(enumField.type);
+ if (valueInfoMap == null) {
+ continue;
+ }
+
+ // The receiver value is identified as being from a constant enum field lookup by the fact
+ // that it is a static-get to a field whose type is the same as the enclosing class (which
+ // is known to be an enum type). An enum may still define a static field using the enum type
+ // so ensure the field is present in the ordinal map for final validation.
+ EnumValueInfo valueInfo = valueInfoMap.get(enumField);
+ if (valueInfo == null) {
+ continue;
+ }
+
+ Value outValue = methodWithReceiver.outValue();
+ if (isOrdinalInvoke) {
+ iterator.replaceCurrentInstruction(new ConstNumber(outValue, valueInfo.ordinal));
+ } else if (isNameInvoke) {
+ iterator.replaceCurrentInstruction(
+ new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW));
+ } else {
+ assert isToStringInvoke;
+ DexClass enumClazz = appView.appInfo().definitionFor(enumField.type);
+ if (!enumClazz.accessFlags.isFinal()) {
+ continue;
+ }
+ DexEncodedMethod singleTarget =
+ appView
+ .appInfo()
+ .resolveMethodOnClass(valueInfo.type, factory.objectMethods.toString)
+ .getSingleTarget();
+ if (singleTarget != null && singleTarget.method != factory.enumMethods.toString) {
+ continue;
+ }
+ iterator.replaceCurrentInstruction(
+ new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW));
+ }
+ }
+ assert code.isConsistentSSA();
+ }
+
+ /**
+ * Inline the indirection of switch maps into the switch statement.
+ *
+ * <p>To ensure binary compatibility, javac generated code does not use ordinal values of enums
+ * directly in switch statements but instead generates a companion class that computes a mapping
+ * from switch branches to ordinals at runtime. As we have whole-program knowledge, we can analyze
+ * these maps and inline the indirection into the switch map again.
+ *
+ * <p>In particular, we look for code of the form
+ *
+ * <blockquote>
+ *
+ * <pre>
+ * switch(CompanionClass.$switchmap$field[enumValue.ordinal()]) {
+ * ...
+ * }
+ * </pre>
+ *
+ * </blockquote>
+ */
+ public void removeSwitchMaps(IRCode code) {
+ for (BasicBlock block : code.blocks) {
+ JumpInstruction exit = block.exit();
+ // Pattern match a switch on a switch map as input.
+ if (!exit.isIntSwitch()) {
+ continue;
+ }
+ IntSwitch switchInsn = exit.asIntSwitch();
+ EnumSwitchInfo info = analyzeSwitchOverEnum(switchInsn);
+ if (info == null) {
+ continue;
+ }
+ Int2IntMap targetMap = new Int2IntArrayMap();
+ for (int i = 0; i < switchInsn.numberOfKeys(); i++) {
+ assert switchInsn.targetBlockIndices()[i] != switchInsn.getFallthroughBlockIndex();
+ EnumValueInfo valueInfo = info.valueInfoMap.get(info.indexMap.get(switchInsn.getKey(i)));
+ targetMap.put(valueInfo.ordinal, switchInsn.targetBlockIndices()[i]);
+ }
+ int[] keys = targetMap.keySet().toIntArray();
+ Arrays.sort(keys);
+ int[] targets = new int[keys.length];
+ for (int i = 0; i < keys.length; i++) {
+ targets[i] = targetMap.get(keys[i]);
+ }
+
+ IntSwitch newSwitch =
+ new IntSwitch(
+ info.ordinalInvoke.outValue(), keys, targets, switchInsn.getFallthroughBlockIndex());
+ // Replace the switch itself.
+ exit.replace(newSwitch, code);
+ // If the original input to the switch is now unused, remove it too. It is not dead
+ // as it might have side-effects but we ignore these here.
+ Instruction arrayGet = info.arrayGet;
+ if (!arrayGet.outValue().hasUsers()) {
+ arrayGet.inValues().forEach(v -> v.removeUser(arrayGet));
+ arrayGet.getBlock().removeInstruction(arrayGet);
+ }
+ Instruction staticGet = info.staticGet;
+ if (!staticGet.outValue().hasUsers()) {
+ assert staticGet.inValues().isEmpty();
+ staticGet.getBlock().removeInstruction(staticGet);
+ }
+ }
+ }
+
+ private static final class EnumSwitchInfo {
+
+ final DexType enumClass;
+ final Instruction ordinalInvoke;
+ final Instruction arrayGet;
+ public final Instruction staticGet;
+ final Int2ReferenceMap<DexField> indexMap;
+ final Map<DexField, EnumValueInfo> valueInfoMap;
+
+ private EnumSwitchInfo(
+ DexType enumClass,
+ Instruction ordinalInvoke,
+ Instruction arrayGet,
+ Instruction staticGet,
+ Int2ReferenceMap<DexField> indexMap,
+ Map<DexField, EnumValueInfo> valueInfoMap) {
+ this.enumClass = enumClass;
+ this.ordinalInvoke = ordinalInvoke;
+ this.arrayGet = arrayGet;
+ this.staticGet = staticGet;
+ this.indexMap = indexMap;
+ this.valueInfoMap = valueInfoMap;
+ }
+ }
+
+ /**
+ * Looks for a switch statement over the enum companion class of the form
+ *
+ * <blockquote>
+ *
+ * <pre>
+ * switch(CompanionClass.$switchmap$field[enumValue.ordinal()]) {
+ * ...
+ * }
+ * </pre>
+ *
+ * </blockquote>
+ *
+ * and extracts the components and the index and ordinal maps. See {@link EnumInfoMapCollector}
+ * and {@link SwitchMapCollector} for details.
+ */
+ private EnumSwitchInfo analyzeSwitchOverEnum(Instruction switchInsn) {
+ AppInfoWithLiveness appInfo = appView.appInfo();
+ Instruction input = switchInsn.inValues().get(0).definition;
+ if (input == null || !input.isArrayGet()) {
+ return null;
+ }
+ ArrayGet arrayGet = input.asArrayGet();
+ Instruction index = arrayGet.index().definition;
+ if (index == null || !index.isInvokeVirtual()) {
+ return null;
+ }
+ InvokeVirtual ordinalInvoke = index.asInvokeVirtual();
+ DexMethod ordinalMethod = ordinalInvoke.getInvokedMethod();
+ DexClass enumClass = appInfo.definitionFor(ordinalMethod.holder);
+ DexItemFactory dexItemFactory = appInfo.dexItemFactory();
+ // After member rebinding, enumClass will be the actual java.lang.Enum class.
+ if (enumClass == null
+ || (!enumClass.accessFlags.isEnum() && enumClass.type != dexItemFactory.enumType)
+ || ordinalMethod.name != dexItemFactory.ordinalMethodName
+ || ordinalMethod.proto.returnType != dexItemFactory.intType
+ || !ordinalMethod.proto.parameters.isEmpty()) {
+ return null;
+ }
+ Instruction array = arrayGet.array().definition;
+ if (array == null || !array.isStaticGet()) {
+ return null;
+ }
+ StaticGet staticGet = array.asStaticGet();
+ Int2ReferenceMap<DexField> indexMap = appInfo.getSwitchMapFor(staticGet.getField());
+ if (indexMap == null || indexMap.isEmpty()) {
+ return null;
+ }
+ // Due to member rebinding, only the fields are certain to provide the actual enums
+ // class.
+ DexType enumType = indexMap.values().iterator().next().holder;
+ Map<DexField, EnumValueInfo> valueInfoMap = appInfo.getEnumValueInfoMapFor(enumType);
+ if (valueInfoMap == null) {
+ return null;
+ }
+ return new EnumSwitchInfo(enumType, ordinalInvoke, arrayGet, staticGet, indexMap, valueInfoMap);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index d1a6c3a..b5215f6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -84,6 +84,7 @@
import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerReceiverAnalysis;
import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsageBuilder;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
import com.android.tools.r8.ir.optimize.info.initializer.NonTrivialInstanceInitializerInfo;
@@ -120,7 +121,8 @@
DexEncodedMethod method,
IRCode code,
OptimizationFeedback feedback,
- DynamicTypeOptimization dynamicTypeOptimization) {
+ DynamicTypeOptimization dynamicTypeOptimization,
+ InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos) {
identifyClassInlinerEligibility(method, code, feedback);
identifyParameterUsages(method, code, feedback);
identifyReturnsArgument(method, code, feedback);
@@ -129,7 +131,7 @@
}
computeDynamicReturnType(dynamicTypeOptimization, feedback, method, code);
computeInitializedClassesOnNormalExit(feedback, method, code);
- computeInstanceInitializerInfo(method, code, feedback);
+ computeInstanceInitializerInfo(method, code, feedback, instanceFieldInitializationInfos);
computeMayHaveSideEffects(feedback, method, code);
computeReturnValueOnlyDependsOnArguments(feedback, method, code);
computeNonNullParamOrThrow(feedback, method, code);
@@ -336,10 +338,7 @@
if (!aliasedValue.isPhi()) {
Instruction definition = aliasedValue.definition;
if (definition.isArgument()) {
- // Find the argument number.
- int index = aliasedValue.computeArgumentPosition(code);
- assert index >= 0;
- feedback.methodReturnsArgument(method, index);
+ feedback.methodReturnsArgument(method, definition.asArgument().getIndex());
}
DexType context = method.method.holder;
AbstractValue abstractReturnValue = definition.getAbstractValue(appView, context);
@@ -354,13 +353,18 @@
}
private void computeInstanceInitializerInfo(
- DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+ DexEncodedMethod method,
+ IRCode code,
+ OptimizationFeedback feedback,
+ InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos) {
assert !appView.appInfo().isPinned(method.method);
if (!method.isInstanceInitializer()) {
return;
}
+ assert instanceFieldInitializationInfos != null;
+
if (method.accessFlags.isNative()) {
return;
}
@@ -375,7 +379,10 @@
return;
}
- InstanceInitializerInfo instanceInitializerInfo = analyzeInstanceInitializer(code, clazz);
+ NonTrivialInstanceInitializerInfo.Builder builder =
+ NonTrivialInstanceInitializerInfo.builder(instanceFieldInitializationInfos);
+ InstanceInitializerInfo instanceInitializerInfo =
+ analyzeInstanceInitializer(code, clazz, builder);
feedback.setInstanceInitializerInfo(
method,
instanceInitializerInfo != null
@@ -398,13 +405,13 @@
// ** Assigns arguments or non-throwing constants to fields of this class.
//
// (Note that this initializer does not have to have zero arguments.)
- private InstanceInitializerInfo analyzeInstanceInitializer(IRCode code, DexClass clazz) {
+ private InstanceInitializerInfo analyzeInstanceInitializer(
+ IRCode code, DexClass clazz, NonTrivialInstanceInitializerInfo.Builder builder) {
if (clazz.definesFinalizer(options.itemFactory)) {
// Defining a finalize method can observe the side-effect of Object.<init> GC registration.
return null;
}
- NonTrivialInstanceInitializerInfo.Builder builder = NonTrivialInstanceInitializerInfo.builder();
Value receiver = code.getThis();
boolean hasCatchHandler = false;
for (BasicBlock block : code.blocks) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
index b65a579..46cff7b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
@@ -9,9 +9,11 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.ir.conversion.FieldOptimizationFeedback;
import com.android.tools.r8.ir.conversion.MethodOptimizationFeedback;
+import com.android.tools.r8.shaking.AppInfoWithLivenessModifier;
import com.android.tools.r8.utils.ThreadUtils;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
public abstract class OptimizationFeedback
implements FieldOptimizationFeedback, MethodOptimizationFeedback {
@@ -34,4 +36,8 @@
},
executorService);
}
+
+ public void modifyAppInfoWithLiveness(Consumer<AppInfoWithLivenessModifier> consumer) {
+ // Intentionally empty.
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index 0fec1f7..c241c0a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.AppInfoWithLivenessModifier;
import com.android.tools.r8.utils.IteratorUtils;
import com.android.tools.r8.utils.StringUtils;
import java.util.BitSet;
@@ -23,10 +24,13 @@
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
public class OptimizationFeedbackDelayed extends OptimizationFeedback {
// Caching of updated optimization info and processed status.
+ private final AppInfoWithLivenessModifier appInfoWithLivenessModifier =
+ AppInfoWithLiveness.modifier();
private final Map<DexEncodedField, MutableFieldOptimizationInfo> fieldOptimizationInfos =
new IdentityHashMap<>();
private final Map<DexEncodedMethod, UpdatableMethodOptimizationInfo> methodOptimizationInfos =
@@ -63,6 +67,15 @@
super.fixupOptimizationInfos(appView, executorService, fixer);
}
+ @Override
+ public void modifyAppInfoWithLiveness(Consumer<AppInfoWithLivenessModifier> consumer) {
+ consumer.accept(appInfoWithLivenessModifier);
+ }
+
+ public void refineAppInfoWithLiveness(AppInfoWithLiveness appInfo) {
+ appInfoWithLivenessModifier.modify(appInfo);
+ }
+
public void updateVisibleOptimizationInfo() {
// Remove methods that have become obsolete. A method may become obsolete, for example, as a
// result of the class staticizer, which aims to transform virtual methods on companion classes
@@ -85,6 +98,7 @@
}
public boolean noUpdatesLeft() {
+ assert appInfoWithLivenessModifier.isEmpty();
assert fieldOptimizationInfos.isEmpty()
: StringUtils.join(fieldOptimizationInfos.keySet(), ", ");
assert methodOptimizationInfos.isEmpty()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/EmptyInstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/EmptyInstanceFieldInitializationInfoCollection.java
new file mode 100644
index 0000000..54a50fe
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/EmptyInstanceFieldInitializationInfoCollection.java
@@ -0,0 +1,34 @@
+// 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.info.field;
+
+import com.android.tools.r8.graph.DexEncodedField;
+
+/**
+ * Represents that no information is known about the way a constructor initializes the instance
+ * fields of the newly created instance.
+ */
+public class EmptyInstanceFieldInitializationInfoCollection
+ extends InstanceFieldInitializationInfoCollection {
+
+ private static final EmptyInstanceFieldInitializationInfoCollection INSTANCE =
+ new EmptyInstanceFieldInitializationInfoCollection();
+
+ private EmptyInstanceFieldInitializationInfoCollection() {}
+
+ public static EmptyInstanceFieldInitializationInfoCollection getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public InstanceFieldInitializationInfo get(DexEncodedField field) {
+ return UnknownInstanceFieldInitializationInfo.getInstance();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java
new file mode 100644
index 0000000..c56c562
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java
@@ -0,0 +1,33 @@
+// 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.info.field;
+
+/**
+ * Used to represent that a constructor initializes an instance field on the newly created instance
+ * with argument number {@link #argumentIndex} from the constructor's argument list.
+ */
+public class InstanceFieldArgumentInitializationInfo extends InstanceFieldInitializationInfo {
+
+ private final int argumentIndex;
+
+ /** Intentionally package private, use {@link InstanceFieldInitializationInfoFactory} instead. */
+ InstanceFieldArgumentInitializationInfo(int argumentIndex) {
+ this.argumentIndex = argumentIndex;
+ }
+
+ public int getArgumentIndex() {
+ return argumentIndex;
+ }
+
+ @Override
+ public boolean isArgumentInitializationInfo() {
+ return true;
+ }
+
+ @Override
+ public InstanceFieldArgumentInitializationInfo asArgumentInitializationInfo() {
+ return this;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfo.java
new file mode 100644
index 0000000..312d7a0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfo.java
@@ -0,0 +1,27 @@
+// 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.info.field;
+
+/**
+ * Information about the way a constructor initializes an instance field on the newly created
+ * instance.
+ *
+ * <p>For example, this can be used to represent that a constructor always initializes a particular
+ * instance field with a constant, or with an argument from the constructor's argument list.
+ */
+public abstract class InstanceFieldInitializationInfo {
+
+ public boolean isArgumentInitializationInfo() {
+ return false;
+ }
+
+ public InstanceFieldArgumentInitializationInfo asArgumentInitializationInfo() {
+ return null;
+ }
+
+ public boolean isUnknown() {
+ return false;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
new file mode 100644
index 0000000..4912d46
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.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.ir.optimize.info.field;
+
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+/**
+ * A mapping from instance fields of a class to information about how a particular constructor
+ * initializes these instance fields.
+ *
+ * <p>Returns {@link UnknownInstanceFieldInitializationInfo} if no information is known about the
+ * initialization of a given instance field.
+ */
+public abstract class InstanceFieldInitializationInfoCollection {
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public abstract InstanceFieldInitializationInfo get(DexEncodedField field);
+
+ public abstract boolean isEmpty();
+
+ public static class Builder {
+
+ Map<DexField, InstanceFieldInitializationInfo> infos = new IdentityHashMap<>();
+
+ public void recordInitializationInfo(
+ DexEncodedField field, InstanceFieldInitializationInfo info) {
+ assert !infos.containsKey(field.field);
+ infos.put(field.field, info);
+ }
+
+ public InstanceFieldInitializationInfoCollection build() {
+ if (infos.isEmpty()) {
+ return EmptyInstanceFieldInitializationInfoCollection.getInstance();
+ }
+ return new NonTrivialInstanceFieldInitializationInfoCollection(infos);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoFactory.java
new file mode 100644
index 0000000..42209b4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoFactory.java
@@ -0,0 +1,19 @@
+// 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.info.field;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+public class InstanceFieldInitializationInfoFactory {
+
+ private ConcurrentHashMap<Integer, InstanceFieldArgumentInitializationInfo>
+ argumentInitializationInfos = new ConcurrentHashMap<>();
+
+ public InstanceFieldArgumentInitializationInfo createArgumentInitializationInfo(
+ int argumentIndex) {
+ return argumentInitializationInfos.computeIfAbsent(
+ argumentIndex, InstanceFieldArgumentInitializationInfo::new);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
new file mode 100644
index 0000000..bdaaf78
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
@@ -0,0 +1,33 @@
+// 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.info.field;
+
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
+import java.util.Map;
+
+/** See {@link InstanceFieldArgumentInitializationInfo}. */
+public class NonTrivialInstanceFieldInitializationInfoCollection
+ extends InstanceFieldInitializationInfoCollection {
+
+ private final Map<DexField, InstanceFieldInitializationInfo> infos;
+
+ NonTrivialInstanceFieldInitializationInfoCollection(
+ Map<DexField, InstanceFieldInitializationInfo> infos) {
+ assert !infos.isEmpty();
+ assert infos.values().stream().noneMatch(InstanceFieldInitializationInfo::isUnknown);
+ this.infos = infos;
+ }
+
+ @Override
+ public InstanceFieldInitializationInfo get(DexEncodedField field) {
+ return infos.getOrDefault(field.field, UnknownInstanceFieldInitializationInfo.getInstance());
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java
new file mode 100644
index 0000000..66e882e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java
@@ -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.ir.optimize.info.field;
+
+/**
+ * Represents that no information is known about the way a particular constructor initializes an
+ * instance field of the newly created instance.
+ */
+public class UnknownInstanceFieldInitializationInfo extends InstanceFieldInitializationInfo {
+
+ private static final UnknownInstanceFieldInitializationInfo INSTANCE =
+ new UnknownInstanceFieldInitializationInfo();
+
+ private UnknownInstanceFieldInitializationInfo() {}
+
+ public static UnknownInstanceFieldInitializationInfo getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public boolean isUnknown() {
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java
index ef3ca08..20aff53 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java
@@ -7,6 +7,8 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet;
+import com.android.tools.r8.ir.optimize.info.field.EmptyInstanceFieldInitializationInfoCollection;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
public class DefaultInstanceInitializerInfo extends InstanceInitializerInfo {
@@ -30,6 +32,11 @@
}
@Override
+ public InstanceFieldInitializationInfoCollection fieldInitializationInfos() {
+ return EmptyInstanceFieldInitializationInfoCollection.getInstance();
+ }
+
+ @Override
public AbstractFieldSet readSet() {
return UnknownFieldSet.getInstance();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java
index 5452bce..b27888e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java
@@ -6,11 +6,14 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
public abstract class InstanceInitializerInfo {
public abstract DexMethod getParent();
+ public abstract InstanceFieldInitializationInfoCollection fieldInitializationInfos();
+
/**
* Returns an abstraction of the set of fields that may be as a result of executing this
* initializer.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
index 70017cc..4e19cef 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.ConcreteMutableFieldSet;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
public final class NonTrivialInstanceInitializerInfo extends InstanceInitializerInfo {
@@ -18,12 +19,18 @@
private static final int RECEIVER_NEVER_ESCAPE_OUTSIDE_CONSTRUCTOR_CHAIN = 1 << 2;
private final int data;
+ private final InstanceFieldInitializationInfoCollection fieldInitializationInfos;
private final AbstractFieldSet readSet;
private final DexMethod parent;
- private NonTrivialInstanceInitializerInfo(int data, AbstractFieldSet readSet, DexMethod parent) {
+ private NonTrivialInstanceInitializerInfo(
+ int data,
+ InstanceFieldInitializationInfoCollection fieldInitializationInfos,
+ AbstractFieldSet readSet,
+ DexMethod parent) {
assert verifyNoUnknownBits(data);
this.data = data;
+ this.fieldInitializationInfos = fieldInitializationInfos;
this.readSet = readSet;
this.parent = parent;
}
@@ -37,8 +44,9 @@
return true;
}
- public static Builder builder() {
- return new Builder();
+ public static Builder builder(
+ InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos) {
+ return new Builder(instanceFieldInitializationInfos);
}
@Override
@@ -47,6 +55,11 @@
}
@Override
+ public InstanceFieldInitializationInfoCollection fieldInitializationInfos() {
+ return fieldInitializationInfos;
+ }
+
+ @Override
public AbstractFieldSet readSet() {
return readSet;
}
@@ -68,6 +81,8 @@
public static class Builder {
+ private final InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos;
+
private int data =
INSTANCE_FIELD_INITIALIZATION_INDEPENDENT_OF_ENVIRONMENT
| NO_OTHER_SIDE_EFFECTS_THAN_INSTANCE_FIELD_ASSIGNMENTS
@@ -75,8 +90,15 @@
private AbstractFieldSet readSet = EmptyFieldSet.getInstance();
private DexMethod parent;
+ public Builder(InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos) {
+ this.instanceFieldInitializationInfos = instanceFieldInitializationInfos;
+ }
+
private boolean isTrivial() {
- return data == 0 && readSet.isTop() && parent == null;
+ return instanceFieldInitializationInfos.isEmpty()
+ && data == 0
+ && readSet.isTop()
+ && parent == null;
}
public Builder markFieldAsRead(DexEncodedField field) {
@@ -158,7 +180,8 @@
public InstanceInitializerInfo build() {
return isTrivial()
? DefaultInstanceInitializerInfo.getInstance()
- : new NonTrivialInstanceInitializerInfo(data, readSet, parent);
+ : new NonTrivialInstanceInitializerInfo(
+ data, instanceFieldInitializationInfos, readSet, parent);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
index 98e83c4..ed47e54 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
@@ -156,11 +156,8 @@
@Override
public final void buildPrelude(IRBuilder builder) {
- DexSourceCode.buildArgumentsWithUnusedArgumentStubs(
- builder,
- 0,
- builder.getMethod(),
- DexSourceCode::doNothingWriteConsumer);
+ builder.buildArgumentsWithUnusedArgumentStubs(
+ 0, builder.getMethod(), DexSourceCode::doNothingWriteConsumer);
}
@Override
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 48f29c2..6085350 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -42,9 +42,7 @@
}
@Override
- void processMetadata() {
- assert !isProcessed;
- isProcessed = true;
+ void processMetadata(KotlinClassMetadata.Class metadata) {
kmClass = metadata.toKmClass();
}
@@ -142,4 +140,8 @@
return this;
}
+ @Override
+ public String toString() {
+ return clazz.toString() + ": " + kmClass.toString();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
index 07ec533..4433017 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
@@ -4,15 +4,26 @@
package com.android.tools.r8.kotlin;
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedBinaryName;
+
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.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
import kotlinx.metadata.jvm.KotlinClassHeader;
import kotlinx.metadata.jvm.KotlinClassMetadata;
public final class KotlinClassFacade extends KotlinInfo<KotlinClassMetadata.MultiFileClassFacade> {
+ // TODO(b/70169921): is it better to maintain List<DexType>?
+ List<String> partClassNames;
+
static KotlinClassFacade fromKotlinClassMetadata(
KotlinClassMetadata kotlinClassMetadata, DexClass clazz) {
assert kotlinClassMetadata instanceof KotlinClassMetadata.MultiFileClassFacade;
@@ -26,24 +37,32 @@
}
@Override
- void processMetadata() {
- assert !isProcessed;
- isProcessed = true;
- // No API to explore metadata details, hence nothing to do further.
+ void processMetadata(KotlinClassMetadata.MultiFileClassFacade metadata) {
+ // Part Class names are stored in `d1`, which is immutable. Make a copy instead.
+ partClassNames = new ArrayList<>(metadata.getPartClassNames());
+ // No API to explore metadata details, hence nothing further to do.
}
@Override
void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
- // TODO(b/70169921): no idea yet!
- assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type
- || appView.options().enableKotlinMetadataRewritingForRenamedClasses
- : toString();
+ ListIterator<String> partClassIterator = partClassNames.listIterator();
+ while (partClassIterator.hasNext()) {
+ String partClassName = partClassIterator.next();
+ partClassIterator.remove();
+ DexType partClassType = appView.dexItemFactory().createType(
+ DescriptorUtils.getDescriptorFromClassBinaryName(partClassName));
+ String renamedPartClassName = toRenamedBinaryName(partClassType, appView, lens);
+ if (renamedPartClassName != null) {
+ partClassIterator.add(renamedPartClassName);
+ }
+ }
}
@Override
KotlinClassHeader createHeader() {
- // TODO(b/70169921): may need to update if `rewrite` is implemented.
- return metadata.getHeader();
+ KotlinClassMetadata.MultiFileClassFacade.Writer writer =
+ new KotlinClassMetadata.MultiFileClassFacade.Writer();
+ return writer.write(partClassNames).getHeader();
}
@Override
@@ -61,4 +80,9 @@
return this;
}
+ @Override
+ public String toString() {
+ return clazz.toString()
+ + ": MultiFileClassFacade(" + StringUtils.join(partClassNames, ", ") + ")";
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
index 58c5aff..f108252 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
@@ -4,10 +4,14 @@
package com.android.tools.r8.kotlin;
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedBinaryName;
+
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.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DescriptorUtils;
import kotlinx.metadata.KmPackage;
import kotlinx.metadata.jvm.KotlinClassHeader;
import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -15,6 +19,8 @@
public final class KotlinClassPart extends KotlinInfo<KotlinClassMetadata.MultiFileClassPart> {
KmPackage kmPackage;
+ // TODO(b/70169921): is it better to maintain DexType?
+ String facadeClassName;
static KotlinClassPart fromKotlinClassMetadata(
KotlinClassMetadata kotlinClassMetadata, DexClass clazz) {
@@ -29,14 +35,16 @@
}
@Override
- void processMetadata() {
- assert !isProcessed;
- isProcessed = true;
+ void processMetadata(KotlinClassMetadata.MultiFileClassPart metadata) {
kmPackage = metadata.toKmPackage();
+ facadeClassName = metadata.getFacadeClassName();
}
@Override
void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+ DexType facadeClassType = appView.dexItemFactory().createType(
+ DescriptorUtils.getDescriptorFromClassBinaryName(facadeClassName));
+ facadeClassName = toRenamedBinaryName(facadeClassType, appView, lens);
if (!appView.options().enableKotlinMetadataRewritingForMembers) {
return;
}
@@ -45,10 +53,17 @@
@Override
KotlinClassHeader createHeader() {
- KotlinClassMetadata.MultiFileClassPart.Writer writer =
- new KotlinClassMetadata.MultiFileClassPart.Writer();
- kmPackage.accept(writer);
- return writer.write(metadata.getFacadeClassName()).getHeader();
+ if (facadeClassName != null) {
+ KotlinClassMetadata.MultiFileClassPart.Writer writer =
+ new KotlinClassMetadata.MultiFileClassPart.Writer();
+ kmPackage.accept(writer);
+ return writer.write(facadeClassName).getHeader();
+ } else {
+ // It's no longer part of multi-file class.
+ KotlinClassMetadata.FileFacade.Writer writer = new KotlinClassMetadata.FileFacade.Writer();
+ kmPackage.accept(writer);
+ return writer.write().getHeader();
+ }
}
@Override
@@ -66,4 +81,9 @@
return this;
}
+ @Override
+ public String toString() {
+ return clazz.toString() + ": " + kmPackage.toString()
+ + ": MultiFileClassPart(" + facadeClassName + ")";
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
index 5eac335..bb4dc47 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
@@ -29,9 +29,7 @@
}
@Override
- void processMetadata() {
- assert !isProcessed;
- isProcessed = true;
+ void processMetadata(KotlinClassMetadata.FileFacade metadata) {
kmPackage = metadata.toKmPackage();
}
@@ -65,4 +63,8 @@
return this;
}
+ @Override
+ public String toString() {
+ return clazz.toString() + ": " + kmPackage.toString();
+ }
}
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 56d9359..e9c1eb6 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
@@ -25,19 +25,16 @@
// Provides access to package/class-level Kotlin information.
public abstract class KotlinInfo<MetadataKind extends KotlinClassMetadata> {
- final MetadataKind metadata;
final DexClass clazz;
- boolean isProcessed;
KotlinInfo(MetadataKind metadata, DexClass clazz) {
assert clazz != null;
- this.metadata = metadata;
this.clazz = clazz;
- processMetadata();
+ processMetadata(metadata);
}
// Subtypes will define how to process the given metadata.
- abstract void processMetadata();
+ abstract void processMetadata(MetadataKind metadata);
// Subtypes will define how to rewrite metadata after shrinking and minification.
// Subtypes that represent subtypes of {@link KmDeclarationContainer} can use
@@ -96,12 +93,6 @@
return isClass() || isFile() || isClassPart();
}
- @Override
- public String toString() {
- return (clazz != null ? clazz.toSourceString() : "<null class?!>")
- + ": " + metadata.toString();
- }
-
// {@link KmClass} and {@link KmPackage} are inherited from {@link KmDeclarationContainer} that
// abstract functions and properties. Rewriting of those portions can be unified here.
void rewriteDeclarationContainer(
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 6e959b6..b16d90a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -5,6 +5,7 @@
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.getBinaryNameFromDescriptor;
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;
@@ -44,6 +45,30 @@
return kmType;
}
+ static DexType toRenamedType(
+ DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+ // For library or classpath class, synthesize @Metadata always.
+ // For a program class, make sure it is live.
+ if (!appView.appInfo().isNonProgramTypeOrLiveProgramType(type)) {
+ return null;
+ }
+ DexType renamedType = lens.lookupType(type, appView.dexItemFactory());
+ // For library or classpath class, we should not have renamed it.
+ DexClass clazz = appView.definitionFor(type);
+ assert clazz == null || clazz.isProgramClass() || renamedType == type
+ : type.toSourceString() + " -> " + renamedType.toSourceString();
+ return renamedType;
+ }
+
+ static String toRenamedBinaryName(
+ DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+ DexType renamedType = toRenamedType(type, appView, lens);
+ if (renamedType == null) {
+ return null;
+ }
+ return getBinaryNameFromDescriptor(renamedType.toDescriptorString());
+ }
+
static String toRenamedClassifier(
DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
// E.g., V -> kotlin/Unit, J -> kotlin/Long, [J -> kotlin/LongArray
@@ -56,16 +81,10 @@
if (type.isArrayType()) {
return NAME + "/Array";
}
- // For library or classpath class, synthesize @Metadata always.
- // For a program class, make sure it is live.
- if (!appView.appInfo().isNonProgramTypeOrLiveProgramType(type)) {
+ DexType renamedType = toRenamedType(type, appView, lens);
+ if (renamedType == null) {
return null;
}
- DexType renamedType = lens.lookupType(type, appView.dexItemFactory());
- // For library or classpath class, we should not have renamed it.
- DexClass clazz = appView.definitionFor(type);
- assert clazz == null || clazz.isProgramClass() || renamedType == type
- : type.toSourceString() + " -> " + renamedType.toSourceString();
return descriptorToKotlinClassifier(renamedType.toDescriptorString());
}
@@ -125,7 +144,7 @@
? method.getKotlinMemberInfo().flag
: method.accessFlags.getAsKotlinFlags();
KmFunction kmFunction = new KmFunction(flag, renamedMethod.name.toString());
- JvmExtensionsKt.setSignature(kmFunction, toJvmMethodSignature(method.method));
+ JvmExtensionsKt.setSignature(kmFunction, toJvmMethodSignature(renamedMethod));
KmType kmReturnType = toRenamedKmType(method.method.proto.returnType, appView, lens);
if (kmReturnType == null) {
return null;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
index 28a6eb1..5cc7d2e 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
@@ -12,6 +12,9 @@
import kotlinx.metadata.jvm.KotlinClassMetadata;
public final class KotlinSyntheticClass extends KotlinInfo<KotlinClassMetadata.SyntheticClass> {
+ // TODO(b/70169921): Once converted to internal data structure, this can be gone.
+ private KotlinClassMetadata.SyntheticClass metadata;
+
public enum Flavour {
KotlinStyleLambda,
JavaStyleLambda,
@@ -41,9 +44,8 @@
}
@Override
- void processMetadata() {
- assert !isProcessed;
- isProcessed = true;
+ void processMetadata(KotlinClassMetadata.SyntheticClass metadata) {
+ this.metadata = metadata;
if (metadata.isLambda()) {
// TODO(b/70169921): Use #toKmLambda to store a mutable model if needed.
}
@@ -116,4 +118,8 @@
&& clazz.interfaces.size() == 1;
}
+ @Override
+ public String toString() {
+ return clazz.toString() + ": " + metadata.toString();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index 1c75720..96cd284 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -13,7 +13,6 @@
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.DexProgramClass;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
@@ -32,10 +31,11 @@
import com.google.common.collect.Sets;
import java.util.Collections;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
import java.util.function.Predicate;
class ClassNameMinifier {
@@ -91,11 +91,14 @@
}
}
- ClassRenaming computeRenaming(Timing timing) {
- return computeRenaming(timing, Collections.emptyMap());
+ ClassRenaming computeRenaming(Timing timing, ExecutorService executorService)
+ throws ExecutionException {
+ return computeRenaming(timing, executorService, Collections.emptyMap());
}
- ClassRenaming computeRenaming(Timing timing, Map<DexType, DexString> syntheticClasses) {
+ ClassRenaming computeRenaming(
+ Timing timing, ExecutorService executorService, Map<DexType, DexString> syntheticClasses)
+ throws ExecutionException {
// Externally defined synthetic classes populate an initial renaming.
renaming.putAll(syntheticClasses);
@@ -139,13 +142,8 @@
timing.begin("rename-generic");
new GenericSignatureRewriter(appView, renaming)
.run(
- new Iterable<DexProgramClass>() {
- @Override
- public Iterator<DexProgramClass> iterator() {
- return IteratorUtils.<DexClass, DexProgramClass>filter(
- classes.iterator(), DexClass::isProgramClass);
- }
- });
+ () -> IteratorUtils.filter(classes.iterator(), DexClass::isProgramClass),
+ executorService);
timing.end();
timing.begin("rename-arrays");
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 359de49..f2d7c1a 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -57,7 +57,7 @@
new MinificationPackageNamingStrategy(appView),
// Use deterministic class order to make sure renaming is deterministic.
appView.appInfo().classesWithDeterministicOrder());
- ClassRenaming classRenaming = classNameMinifier.computeRenaming(timing);
+ ClassRenaming classRenaming = classNameMinifier.computeRenaming(timing, executorService);
timing.end();
assert new MinifiedRenaming(
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 7b6d191..471d66f 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -137,7 +137,7 @@
new MinificationPackageNamingStrategy(appView),
mappedClasses);
ClassRenaming classRenaming =
- classNameMinifier.computeRenaming(timing, syntheticCompanionClasses);
+ classNameMinifier.computeRenaming(timing, executorService, syntheticCompanionClasses);
timing.end();
ApplyMappingMemberNamingStrategy nameStrategy =
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
index e891f95..b4ddb8e 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
@@ -19,9 +19,12 @@
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.ThreadUtils;
import com.google.common.collect.Maps;
import java.lang.reflect.GenericSignatureFormatError;
import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -44,39 +47,44 @@
this.reporter = appView.options().reporter;
}
- public void run(Iterable<? extends DexProgramClass> classes) {
- final GenericSignatureCollector genericSignatureCollector = new GenericSignatureCollector();
- final GenericSignatureParser<DexType> genericSignatureParser =
- new GenericSignatureParser<>(genericSignatureCollector);
+ public void run(Iterable<? extends DexProgramClass> classes, ExecutorService executorService)
+ throws ExecutionException {
// Classes may not be the same as appInfo().classes() if applymapping is used on classpath
// arguments. If that is the case, the ProguardMapMinifier will pass in all classes that is
// either ProgramClass or has a mapping. This is then transitively called inside the
// ClassNameMinifier.
- for (DexProgramClass clazz : classes) {
- genericSignatureCollector.setCurrentClassContext(clazz);
- clazz.setAnnotations(
- rewriteGenericSignatures(
- clazz.annotations(),
- genericSignatureParser::parseClassSignature,
- genericSignatureCollector::getRenamedSignature,
- (signature, e) -> parseError(clazz, clazz.getOrigin(), signature, e)));
- clazz.forEachField(
- field ->
- field.setAnnotations(
- rewriteGenericSignatures(
- field.annotations(),
- genericSignatureParser::parseFieldSignature,
- genericSignatureCollector::getRenamedSignature,
- (signature, e) -> parseError(field, clazz.getOrigin(), signature, e))));
- clazz.forEachMethod(
- method ->
- method.setAnnotations(
- rewriteGenericSignatures(
- method.annotations(),
- genericSignatureParser::parseMethodSignature,
- genericSignatureCollector::getRenamedSignature,
- (signature, e) -> parseError(method, clazz.getOrigin(), signature, e))));
- }
+ ThreadUtils.processItems(
+ classes,
+ clazz -> {
+ GenericSignatureCollector genericSignatureCollector =
+ new GenericSignatureCollector(clazz);
+ GenericSignatureParser<DexType> genericSignatureParser =
+ new GenericSignatureParser<>(genericSignatureCollector);
+ clazz.setAnnotations(
+ rewriteGenericSignatures(
+ clazz.annotations(),
+ genericSignatureParser::parseClassSignature,
+ genericSignatureCollector::getRenamedSignature,
+ (signature, e) -> parseError(clazz, clazz.getOrigin(), signature, e)));
+ clazz.forEachField(
+ field ->
+ field.setAnnotations(
+ rewriteGenericSignatures(
+ field.annotations(),
+ genericSignatureParser::parseFieldSignature,
+ genericSignatureCollector::getRenamedSignature,
+ (signature, e) -> parseError(field, clazz.getOrigin(), signature, e))));
+ clazz.forEachMethod(
+ method ->
+ method.setAnnotations(
+ rewriteGenericSignatures(
+ method.annotations(),
+ genericSignatureParser::parseMethodSignature,
+ genericSignatureCollector::getRenamedSignature,
+ (signature, e) -> parseError(method, clazz.getOrigin(), signature, e))));
+ },
+ executorService
+ );
}
private DexAnnotationSet rewriteGenericSignatures(
@@ -154,17 +162,17 @@
private class GenericSignatureCollector implements GenericSignatureAction<DexType> {
private StringBuilder renamedSignature;
- private DexProgramClass currentClassContext;
+ private final DexProgramClass currentClassContext;
private DexType lastWrittenType = null;
+ GenericSignatureCollector(DexProgramClass clazz) {
+ this.currentClassContext = clazz;
+ }
+
String getRenamedSignature() {
return renamedSignature.toString();
}
- void setCurrentClassContext(DexProgramClass clazz) {
- currentClassContext = clazz;
- }
-
@Override
public void parsedSymbol(char symbol) {
if (symbol == ';' && lastWrittenType == null) {
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 69ed3b0..509a832 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -504,6 +504,10 @@
private boolean dontAssertDefinitionFor = true;
+ public static AppInfoWithLivenessModifier modifier() {
+ return new AppInfoWithLivenessModifier();
+ }
+
@Override
public void enableDefinitionForAssert() {
dontAssertDefinitionFor = false;
@@ -715,11 +719,19 @@
return fieldAccessInfoCollection;
}
+ FieldAccessInfoCollectionImpl getMutableFieldAccessInfoCollection() {
+ return fieldAccessInfoCollection;
+ }
+
/** This method provides immutable access to `objectAllocationInfoCollection`. */
public ObjectAllocationInfoCollection getObjectAllocationInfoCollection() {
return objectAllocationInfoCollection;
}
+ ObjectAllocationInfoCollectionImpl getMutableObjectAllocationInfoCollection() {
+ return objectAllocationInfoCollection;
+ }
+
private boolean assertNoItemRemoved(Collection<DexReference> items, Collection<DexType> types) {
Set<DexType> typeSet = ImmutableSet.copyOf(types);
for (DexReference item : items) {
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java
new file mode 100644
index 0000000..75c27fd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java
@@ -0,0 +1,58 @@
+// 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.shaking;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
+import com.android.tools.r8.graph.FieldAccessInfoImpl;
+import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+/** Used to mutate AppInfoWithLiveness between waves. */
+public class AppInfoWithLivenessModifier {
+
+ private final Set<DexProgramClass> noLongerInstantiatedClasses = Sets.newConcurrentHashSet();
+ private final Set<DexField> noLongerWrittenFields = Sets.newConcurrentHashSet();
+
+ AppInfoWithLivenessModifier() {}
+
+ public boolean isEmpty() {
+ return noLongerInstantiatedClasses.isEmpty();
+ }
+
+ public void removeInstantiatedType(DexProgramClass clazz) {
+ noLongerInstantiatedClasses.add(clazz);
+ }
+
+ public void removeWrittenField(DexField field) {
+ noLongerWrittenFields.add(field);
+ }
+
+ public void modify(AppInfoWithLiveness appInfo) {
+ // Instantiated classes.
+ ObjectAllocationInfoCollectionImpl objectAllocationInfoCollection =
+ appInfo.getMutableObjectAllocationInfoCollection();
+ noLongerInstantiatedClasses.forEach(objectAllocationInfoCollection::markNoLongerInstantiated);
+
+ // Written fields.
+ FieldAccessInfoCollectionImpl fieldAccessInfoCollection =
+ appInfo.getMutableFieldAccessInfoCollection();
+ noLongerWrittenFields.forEach(
+ field -> {
+ FieldAccessInfoImpl fieldAccessInfo = fieldAccessInfoCollection.get(field);
+ if (fieldAccessInfo != null) {
+ fieldAccessInfo.clearWrites();
+ }
+ });
+
+ clear();
+ }
+
+ private void clear() {
+ noLongerInstantiatedClasses.clear();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
index f05d068..dfcee69 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -32,11 +32,11 @@
}
public DexProgramClass getContextHolder() {
- return context.holder;
+ return context.getHolder();
}
public DexEncodedMethod getContextMethod() {
- return context.method;
+ return context.getMethod();
}
@Override
@@ -66,12 +66,12 @@
@Override
public boolean registerInstanceFieldWrite(DexField field) {
- return enqueuer.traceInstanceFieldWrite(field, context.method);
+ return enqueuer.traceInstanceFieldWrite(field, context.getMethod());
}
@Override
public boolean registerInstanceFieldRead(DexField field) {
- return enqueuer.traceInstanceFieldRead(field, context.method);
+ return enqueuer.traceInstanceFieldRead(field, context.getMethod());
}
@Override
@@ -81,33 +81,33 @@
@Override
public boolean registerStaticFieldRead(DexField field) {
- return enqueuer.traceStaticFieldRead(field, context.method);
+ return enqueuer.traceStaticFieldRead(field, context.getMethod());
}
@Override
public boolean registerStaticFieldWrite(DexField field) {
- return enqueuer.traceStaticFieldWrite(field, context.method);
+ return enqueuer.traceStaticFieldWrite(field, context.getMethod());
}
@Override
public boolean registerConstClass(DexType type) {
- return enqueuer.traceConstClass(type, context.method);
+ return enqueuer.traceConstClass(type, context.getMethod());
}
@Override
public boolean registerCheckCast(DexType type) {
- return enqueuer.traceCheckCast(type, context.method);
+ return enqueuer.traceCheckCast(type, context.getMethod());
}
@Override
public boolean registerTypeReference(DexType type) {
- return enqueuer.traceTypeReference(type, context.method);
+ return enqueuer.traceTypeReference(type, context.getMethod());
}
@Override
public void registerMethodHandle(DexMethodHandle methodHandle, MethodHandleUse use) {
super.registerMethodHandle(methodHandle, use);
- enqueuer.traceMethodHandle(methodHandle, use, context.method);
+ enqueuer.traceMethodHandle(methodHandle, use, context.getMethod());
}
@Override
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 d0e971e..a7bb64b 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -27,7 +27,6 @@
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.DexAnnotationSet;
import com.android.tools.r8.graph.DexCallSite;
@@ -41,6 +40,7 @@
import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexLibraryClass;
+import com.android.tools.r8.graph.DexMember;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexMethodHandle;
import com.android.tools.r8.graph.DexProgramClass;
@@ -106,6 +106,7 @@
import java.lang.reflect.InvocationHandler;
import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
@@ -171,6 +172,9 @@
private ProguardClassFilter dontWarnPatterns;
private final EnqueuerUseRegistryFactory useRegistryFactory;
+ private final Map<DexProgramClass, Set<DexProgramClass>> immediateSubtypesOfLiveTypes =
+ new IdentityHashMap<>();
+
private final Map<DexMethod, Set<DexEncodedMethod>> virtualInvokes = new IdentityHashMap<>();
private final Map<DexMethod, Set<DexEncodedMethod>> interfaceInvokes = new IdentityHashMap<>();
private final Map<DexMethod, Set<DexEncodedMethod>> superInvokes = new IdentityHashMap<>();
@@ -365,9 +369,8 @@
instantiatedInterfaceTypes = Sets.newIdentityHashSet();
lambdaRewriter = options.desugarState == DesugarState.ON ? new LambdaRewriter(appView) : null;
- // TODO(b/147799448): Enable allocation site tracking during the initial round of tree shaking.
objectAllocationInfoCollection =
- ObjectAllocationInfoCollectionImpl.builder(false, graphReporter);
+ ObjectAllocationInfoCollectionImpl.builder(mode.isInitialTreeShaking(), graphReporter);
if (appView.rewritePrefix.isRewriting() && mode.isInitialTreeShaking()) {
desugaredLibraryWrapperAnalysis = new DesugaredLibraryConversionWrapperAnalysis(appView);
@@ -636,13 +639,14 @@
bootstrapMethods.add(callSite.bootstrapMethod.asMethod());
}
- LambdaDescriptor descriptor = LambdaDescriptor.tryInfer(callSite, appInfo, context.holder);
+ DexProgramClass contextHolder = context.getHolder();
+ LambdaDescriptor descriptor = LambdaDescriptor.tryInfer(callSite, appInfo, contextHolder);
if (descriptor == null) {
if (!appInfo.isStringConcat(callSite.bootstrapMethod)) {
if (options.reporter != null) {
Diagnostic message =
new StringDiagnostic(
- "Unknown bootstrap method " + callSite.bootstrapMethod, context.holder.origin);
+ "Unknown bootstrap method " + callSite.bootstrapMethod, contextHolder.origin);
options.reporter.warning(message);
}
}
@@ -651,22 +655,23 @@
return;
}
+ DexEncodedMethod contextMethod = context.getMethod();
for (DexType lambdaInstantiatedInterface : descriptor.interfaces) {
- markLambdaInstantiated(lambdaInstantiatedInterface, context.method);
+ markLambdaInstantiated(lambdaInstantiatedInterface, contextMethod);
}
if (lambdaRewriter != null) {
- assert context.method.getCode().isCfCode() : "Unexpected input type with lambdas";
- CfCode code = context.method.getCode().asCfCode();
+ assert contextMethod.getCode().isCfCode() : "Unexpected input type with lambdas";
+ CfCode code = contextMethod.getCode().asCfCode();
if (code != null) {
LambdaClass lambdaClass =
- lambdaRewriter.getOrCreateLambdaClass(descriptor, context.method.method.holder);
+ lambdaRewriter.getOrCreateLambdaClass(descriptor, contextMethod.method.holder);
lambdaClasses.put(lambdaClass.type, lambdaClass);
lambdaCallSites
- .computeIfAbsent(context.method, k -> new IdentityHashMap<>())
+ .computeIfAbsent(contextMethod, k -> new IdentityHashMap<>())
.put(callSite, lambdaClass);
if (lambdaClass.descriptor.interfaces.contains(appView.dexItemFactory().serializableType)) {
- classesWithSerializableLambdas.add(context.holder);
+ classesWithSerializableLambdas.add(contextHolder);
}
}
if (descriptor.delegatesToLambdaImplMethod()) {
@@ -794,8 +799,8 @@
}
boolean traceInvokeDirect(DexMethod invokedMethod, ProgramMethod context) {
- DexProgramClass currentHolder = context.holder;
- DexEncodedMethod currentMethod = context.method;
+ DexProgramClass currentHolder = context.getHolder();
+ DexEncodedMethod currentMethod = context.getMethod();
boolean skipTracing =
registerDeferredActionForDeadProtoBuilder(
invokedMethod.holder,
@@ -827,12 +832,12 @@
boolean traceInvokeDirectFromLambda(DexMethod invokedMethod, ProgramMethod context) {
return traceInvokeDirect(
- invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.method));
+ invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.getMethod()));
}
private boolean traceInvokeDirect(
DexMethod invokedMethod, ProgramMethod context, KeepReason reason) {
- DexEncodedMethod currentMethod = context.method;
+ DexEncodedMethod currentMethod = context.getMethod();
if (!registerMethodWithTargetAndContext(directInvokes, invokedMethod, currentMethod)) {
return false;
}
@@ -845,18 +850,17 @@
}
boolean traceInvokeInterface(DexMethod invokedMethod, ProgramMethod context) {
- return traceInvokeInterface(
- invokedMethod, context, KeepReason.invokedFrom(context.holder, context.method));
+ return traceInvokeInterface(invokedMethod, context, KeepReason.invokedFrom(context));
}
boolean traceInvokeInterfaceFromLambda(DexMethod invokedMethod, ProgramMethod context) {
return traceInvokeInterface(
- invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.method));
+ invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.getMethod()));
}
private boolean traceInvokeInterface(
DexMethod method, ProgramMethod context, KeepReason keepReason) {
- DexEncodedMethod currentMethod = context.method;
+ DexEncodedMethod currentMethod = context.getMethod();
if (!registerMethodWithTargetAndContext(interfaceInvokes, method, currentMethod)) {
return false;
}
@@ -869,18 +873,17 @@
}
boolean traceInvokeStatic(DexMethod invokedMethod, ProgramMethod context) {
- return traceInvokeStatic(
- invokedMethod, context, KeepReason.invokedFrom(context.holder, context.method));
+ return traceInvokeStatic(invokedMethod, context, KeepReason.invokedFrom(context));
}
boolean traceInvokeStaticFromLambda(DexMethod invokedMethod, ProgramMethod context) {
return traceInvokeStatic(
- invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.method));
+ invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.getMethod()));
}
private boolean traceInvokeStatic(
DexMethod invokedMethod, ProgramMethod context, KeepReason reason) {
- DexEncodedMethod currentMethod = context.method;
+ DexEncodedMethod currentMethod = context.getMethod();
DexItemFactory dexItemFactory = appView.dexItemFactory();
if (dexItemFactory.classMethods.isReflectiveClassLookup(invokedMethod)
|| dexItemFactory.atomicFieldUpdaterMethods.isFieldUpdater(invokedMethod)) {
@@ -912,8 +915,7 @@
}
boolean traceInvokeSuper(DexMethod invokedMethod, ProgramMethod context) {
- DexProgramClass currentHolder = context.holder;
- DexEncodedMethod currentMethod = context.method;
+ DexEncodedMethod currentMethod = context.getMethod();
// We have to revisit super invokes based on the context they are found in. The same
// method descriptor will hit different targets, depending on the context it is used in.
DexMethod actualTarget = getInvokeSuperTarget(invokedMethod, currentMethod);
@@ -929,27 +931,26 @@
}
boolean traceInvokeVirtual(DexMethod invokedMethod, ProgramMethod context) {
- return traceInvokeVirtual(
- invokedMethod, context, KeepReason.invokedFrom(context.holder, context.method));
+ return traceInvokeVirtual(invokedMethod, context, KeepReason.invokedFrom(context));
}
boolean traceInvokeVirtualFromLambda(DexMethod invokedMethod, ProgramMethod context) {
return traceInvokeVirtual(
- invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.method));
+ invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.getMethod()));
}
private boolean traceInvokeVirtual(
DexMethod invokedMethod, ProgramMethod context, KeepReason reason) {
if (invokedMethod == appView.dexItemFactory().classMethods.newInstance
|| invokedMethod == appView.dexItemFactory().constructorMethods.newInstance) {
- pendingReflectiveUses.add(context.method);
+ pendingReflectiveUses.add(context.getMethod());
} else if (appView.dexItemFactory().classMethods.isReflectiveMemberLookup(invokedMethod)) {
// Implicitly add -identifiernamestring rule for the Java reflection in use.
identifierNameStrings.add(invokedMethod);
// Revisit the current method to implicitly add -keep rule for items with reflective access.
- pendingReflectiveUses.add(context.method);
+ pendingReflectiveUses.add(context.getMethod());
}
- if (!registerMethodWithTargetAndContext(virtualInvokes, invokedMethod, context.method)) {
+ if (!registerMethodWithTargetAndContext(virtualInvokes, invokedMethod, context.getMethod())) {
return false;
}
if (Log.ENABLED) {
@@ -961,7 +962,7 @@
}
boolean traceNewInstance(DexType type, ProgramMethod context) {
- DexEncodedMethod currentMethod = context.method;
+ DexEncodedMethod currentMethod = context.getMethod();
boolean skipTracing =
registerDeferredActionForDeadProtoBuilder(
type, currentMethod, () -> workList.enqueueTraceNewInstanceAction(type, context));
@@ -981,7 +982,7 @@
type,
context,
InstantiationReason.LAMBDA,
- KeepReason.invokedFromLambdaCreatedIn(context.method));
+ KeepReason.invokedFromLambdaCreatedIn(context.getMethod()));
}
private boolean traceNewInstance(
@@ -989,7 +990,7 @@
ProgramMethod context,
InstantiationReason instantiationReason,
KeepReason keepReason) {
- DexEncodedMethod currentMethod = context.method;
+ DexEncodedMethod currentMethod = context.getMethod();
DexProgramClass clazz = getProgramClassOrNull(type);
if (clazz != null) {
if (clazz.isAnnotation() || clazz.isInterface()) {
@@ -1248,6 +1249,19 @@
witness);
}
+ private void addImmediateSubtype(DexProgramClass superType, DexProgramClass subType) {
+ assert liveTypes.contains(subType);
+ assert subType.superType == superType.type
+ || Arrays.asList(subType.interfaces.values).contains(superType.type);
+ immediateSubtypesOfLiveTypes
+ .computeIfAbsent(superType, k -> Sets.newIdentityHashSet())
+ .add(subType);
+ }
+
+ private Set<DexProgramClass> getImmediateLiveSubtypes(DexProgramClass clazz) {
+ return immediateSubtypesOfLiveTypes.getOrDefault(clazz, Collections.emptySet());
+ }
+
private void markTypeAsLive(
DexProgramClass holder, ScopedDexMethodSet seen, KeepReasonWitness witness) {
if (!liveTypes.add(holder, witness)) {
@@ -1277,6 +1291,10 @@
holder.superType, ignore -> new ScopedDexMethodSet());
seen.setParent(seenForSuper);
markTypeAsLive(holder.superType, reason);
+ DexProgramClass superClass = getProgramClassOrNull(holder.superType);
+ if (superClass != null) {
+ addImmediateSubtype(superClass, holder);
+ }
}
// If this is an interface that has just become live, then report previously seen but unreported
@@ -1345,6 +1363,8 @@
return;
}
+ addImmediateSubtype(clazz, implementer);
+
if (!appView.options().enableUnusedInterfaceRemoval || mode.isTracingMainDex()) {
markTypeAsLive(clazz, graphReporter.reportClassReferencedFrom(clazz, implementer));
} else {
@@ -1388,10 +1408,7 @@
private void processAnnotation(
DexProgramClass holder, DexDefinition annotatedItem, DexAnnotation annotation) {
assert annotatedItem == holder
- || (annotatedItem.isDexEncodedField()
- && annotatedItem.asDexEncodedField().field.holder == holder.type)
- || (annotatedItem.isDexEncodedMethod()
- && annotatedItem.asDexEncodedMethod().method.holder == holder.type);
+ || annotatedItem.asDexEncodedMember().toReference().holder == holder.type;
assert !holder.isDexClass() || holder.asDexClass().isProgramClass();
DexType type = annotation.annotation.type;
recordTypeReference(type);
@@ -2135,7 +2152,7 @@
if (contextOrNull != null
&& !resolution.isUnresolved()
&& !AccessControl.isMethodAccessible(
- resolution.method, holder, contextOrNull.holder, appInfo)) {
+ resolution.method, holder, contextOrNull.getHolder(), appInfo)) {
// Not accessible from this context, so this call will cause a runtime exception.
// Note that the resolution is not cached, as another call context may be valid.
return;
@@ -2155,10 +2172,11 @@
assert resolution.holder.isProgramClass();
assert interfaceInvoke == holder.isInterface();
+ DexProgramClass context = contextOrNull == null ? null : contextOrNull.getHolder();
LookupResult lookupResult =
// TODO(b/140214802): Call on the resolution once proper resolution and lookup is resolved.
new SingleResolutionResult(holder, resolution.holder, resolution.method)
- .lookupVirtualDispatchTargets(appView, appInfo);
+ .lookupVirtualDispatchTargets(context, appView, appInfo);
if (!lookupResult.isLookupResultSuccess()) {
return;
}
@@ -2412,10 +2430,11 @@
assert fieldAccessInfoCollection.verifyMappingIsOneToOne();
for (ProgramMethod bridge : syntheticInterfaceMethodBridges.values()) {
- appView.appInfo().invalidateTypeCacheFor(bridge.holder.type);
- bridge.holder.appendVirtualMethod(bridge.method);
- targetedMethods.add(bridge.method, graphReporter.fakeReportShouldNotBeUsed());
- liveMethods.add(bridge.holder, bridge.method, graphReporter.fakeReportShouldNotBeUsed());
+ appView.appInfo().invalidateTypeCacheFor(bridge.getHolder().type);
+ bridge.getHolder().appendVirtualMethod(bridge.getMethod());
+ targetedMethods.add(bridge.getMethod(), graphReporter.fakeReportShouldNotBeUsed());
+ liveMethods.add(
+ bridge.getHolder(), bridge.getMethod(), graphReporter.fakeReportShouldNotBeUsed());
}
// Ensure references from various root set collections.
@@ -2693,12 +2712,12 @@
assert replaced == callSites.size();
}
- private static <T extends PresortedComparable<T>> SortedSet<T> toSortedDescriptorSet(
- Set<? extends DexEncodedMember<T>> set) {
- ImmutableSortedSet.Builder<T> builder =
+ private static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+ SortedSet<R> toSortedDescriptorSet(Set<D> set) {
+ ImmutableSortedSet.Builder<R> builder =
new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompareTo);
- for (DexEncodedMember<T> item : set) {
- builder.add(item.getKey());
+ for (D item : set) {
+ builder.add(item.toReference());
}
return builder.build();
}
@@ -2846,25 +2865,27 @@
InterfaceMethodSyntheticBridgeAction action, RootSetBuilder builder) {
ProgramMethod methodToKeep = action.getMethodToKeep();
ProgramMethod singleTarget = action.getSingleTarget();
- if (rootSet.noShrinking.containsKey(singleTarget.method.method)) {
+ DexEncodedMethod singleTargetMethod = singleTarget.getMethod();
+ if (rootSet.noShrinking.containsKey(singleTargetMethod.method)) {
return;
}
if (methodToKeep != singleTarget) {
- assert null == methodToKeep.holder.lookupMethod(methodToKeep.method.method);
+ assert null == methodToKeep.getHolder().lookupMethod(methodToKeep.getMethod().method);
ProgramMethod old =
- syntheticInterfaceMethodBridges.put(methodToKeep.method.method, methodToKeep);
+ syntheticInterfaceMethodBridges.put(methodToKeep.getMethod().method, methodToKeep);
if (old == null) {
- if (singleTarget.method.isLibraryMethodOverride().isTrue()) {
- methodToKeep.method.setLibraryMethodOverride(OptionalBool.TRUE);
+ if (singleTargetMethod.isLibraryMethodOverride().isTrue()) {
+ methodToKeep.getMethod().setLibraryMethodOverride(OptionalBool.TRUE);
}
- assert singleTarget.holder.isInterface();
+ DexProgramClass singleTargetHolder = singleTarget.getHolder();
+ assert singleTargetHolder.isInterface();
markVirtualMethodAsReachable(
- singleTarget.method.method,
- singleTarget.holder.isInterface(),
+ singleTargetMethod.method,
+ singleTargetHolder.isInterface(),
null,
graphReporter.fakeReportShouldNotBeUsed());
enqueueMarkMethodLiveAction(
- singleTarget.holder, singleTarget.method, graphReporter.fakeReportShouldNotBeUsed());
+ singleTargetHolder, singleTargetMethod, graphReporter.fakeReportShouldNotBeUsed());
}
}
action.getAction().accept(builder);
@@ -2938,18 +2959,16 @@
// If there is a subtype of `clazz` that escapes into the library and does not override `method`
// then we need to mark the method as being reachable.
- Deque<DexType> worklist = new ArrayDeque<>(appView.appInfo().allImmediateSubtypes(clazz.type));
-
- Set<DexType> visited = Sets.newIdentityHashSet();
- visited.addAll(worklist);
+ Set<DexProgramClass> immediateSubtypes = getImmediateLiveSubtypes(clazz);
+ if (immediateSubtypes.isEmpty()) {
+ return false;
+ }
+ Deque<DexProgramClass> worklist = new ArrayDeque<>(immediateSubtypes);
+ Set<DexProgramClass> visited = SetUtils.newIdentityHashSet(immediateSubtypes);
while (!worklist.isEmpty()) {
- DexClass current = appView.definitionFor(worklist.removeFirst());
- if (current == null) {
- continue;
- }
-
- assert visited.contains(current.type);
+ DexProgramClass current = worklist.removeFirst();
+ assert visited.contains(current);
if (current.lookupVirtualMethod(method.method) != null) {
continue;
@@ -2959,7 +2978,7 @@
return true;
}
- for (DexType subtype : appView.appInfo().allImmediateSubtypes(current.type)) {
+ for (DexProgramClass subtype : getImmediateLiveSubtypes(current)) {
if (visited.add(subtype)) {
worklist.add(subtype);
}
@@ -3543,17 +3562,17 @@
}
}
- private static final class TargetWithContext<T extends Descriptor<?, T>> {
+ private static final class TargetWithContext<R extends DexMember<?, R>> {
- private final T target;
+ private final R target;
private final DexEncodedMethod context;
- private TargetWithContext(T target, DexEncodedMethod context) {
+ private TargetWithContext(R target, DexEncodedMethod context) {
this.target = target;
this.context = context;
}
- public T getTarget() {
+ public R getTarget() {
return target;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepReason.java b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
index 680c0b5..cc86522 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -12,6 +12,7 @@
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.ProgramMethod;
// TODO(herhut): Canonicalize reason objects.
public abstract class KeepReason {
@@ -40,6 +41,10 @@
return new InvokedFrom(holder, method);
}
+ public static KeepReason invokedFrom(ProgramMethod context) {
+ return invokedFrom(context.getHolder(), context.getMethod());
+ }
+
public static KeepReason invokedFromLambdaCreatedIn(DexEncodedMethod method) {
return new InvokedFromLambdaCreatedIn(method);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 7495c6b..7d93686 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -557,9 +557,10 @@
resolutionResult.getResolvedHolder().asProgramClass(),
resolutionResult.getResolvedMethod());
ProgramMethod methodToKeep =
- canInsertForwardingMethod(originalClazz, resolutionMethod.method)
+ canInsertForwardingMethod(originalClazz, resolutionMethod.getMethod())
? new ProgramMethod(
- originalClazz, resolutionMethod.method.toForwardingMethod(originalClazz, appView))
+ originalClazz,
+ resolutionMethod.getMethod().toForwardingMethod(originalClazz, appView))
: resolutionMethod;
delayedRootSetActionItems.add(
@@ -576,9 +577,9 @@
rule);
}
DexDefinition precondition =
- testAndGetPrecondition(methodToKeep.method, preconditionSupplier);
+ testAndGetPrecondition(methodToKeep.getMethod(), preconditionSupplier);
rootSetBuilder.addItemToSets(
- methodToKeep.method, context, rule, precondition, ifRule);
+ methodToKeep.getMethod(), context, rule, precondition, ifRule);
}));
}
}
@@ -1539,7 +1540,7 @@
// Create a Set of the fields to avoid quadratic behavior.
fields =
Streams.stream(clazz.fields())
- .map(DexEncodedField::getKey)
+ .map(DexEncodedField::toReference)
.collect(Collectors.toSet());
}
assert fields.contains(requiredField)
@@ -1552,7 +1553,7 @@
// Create a Set of the methods to avoid quadratic behavior.
methods =
Streams.stream(clazz.methods())
- .map(DexEncodedMethod::getKey)
+ .map(DexEncodedMethod::toReference)
.collect(Collectors.toSet());
}
assert methods.contains(requiredMethod)
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 3ded35c..05d281b 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMember;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMember;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexReference;
@@ -17,7 +18,6 @@
import com.android.tools.r8.graph.EnclosingMethodAttribute;
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.graph.NestMemberClassAttribute;
-import com.android.tools.r8.graph.PresortedComparable;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalOptions;
@@ -243,8 +243,8 @@
return context == null || !isTypeLive(context);
}
- private <S extends PresortedComparable<S>, T extends DexEncodedMember<S>>
- int firstUnreachableIndex(List<T> items, Predicate<T> live) {
+ private <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> int firstUnreachableIndex(
+ List<D> items, Predicate<D> live) {
for (int i = 0; i < items.size(); i++) {
if (!live.test(items.get(i))) {
return i;
@@ -268,7 +268,7 @@
}
for (int i = firstUnreachable; i < methods.size(); i++) {
DexEncodedMethod method = methods.get(i);
- if (appInfo.liveMethods.contains(method.getKey())) {
+ if (appInfo.liveMethods.contains(method.toReference())) {
reachableMethods.add(method);
} else if (options.configurationDebugging) {
// Keep the method but rewrite its body, if it has one.
@@ -277,7 +277,7 @@
? method
: method.toMethodThatLogsError(appView));
methodsToKeepForConfigurationDebugging.add(method.method);
- } else if (appInfo.targetedMethods.contains(method.getKey())) {
+ } else if (appInfo.targetedMethods.contains(method.toReference())) {
// If the method is already abstract, and doesn't have code, let it be.
if (method.shouldNotHaveCode() && !method.hasCode()) {
reachableMethods.add(method);
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 76db9fe..3ba6b3a 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.graph.DexEncodedMember;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMember;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProto;
@@ -30,7 +31,6 @@
import com.android.tools.r8.graph.LookupResult;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
-import com.android.tools.r8.graph.PresortedComparable;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.graph.RewrittenPrototypeDescription;
import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
@@ -335,7 +335,7 @@
}
assert Streams.stream(Iterables.concat(clazz.fields(), clazz.methods()))
- .map(DexEncodedMember::getKey)
+ .map(DexEncodedMember::toReference)
.noneMatch(appInfo::isPinned);
if (appView.options().featureSplitConfiguration != null &&
@@ -420,7 +420,8 @@
// We basically can't move the clinit, since it is not called when implementing classes have
// their clinit called - except when the interface has a default method.
if ((clazz.hasClassInitializer() && targetClass.hasClassInitializer())
- || targetClass.classInitializationMayHaveSideEffects(appView, type -> type == clazz.type)
+ || targetClass.classInitializationMayHaveSideEffects(
+ appView, type -> type == clazz.type, Sets.newIdentityHashSet())
|| (clazz.isInterface() && clazz.classInitializationMayHaveSideEffects(appView))) {
// TODO(herhut): Handle class initializers.
if (Log.ENABLED) {
@@ -699,7 +700,7 @@
return true;
}
- private boolean methodResolutionMayChange(DexClass source, DexClass target) {
+ private boolean methodResolutionMayChange(DexProgramClass source, DexProgramClass target) {
for (DexEncodedMethod virtualSourceMethod : source.virtualMethods()) {
DexEncodedMethod directTargetMethod = target.lookupDirectMethod(virtualSourceMethod.method);
if (directTargetMethod != null) {
@@ -738,7 +739,7 @@
LookupResult lookupResult =
appInfo
.resolveMethodOnInterface(method.method.holder, method.method)
- .lookupVirtualDispatchTargets(appView, appInfo);
+ .lookupVirtualDispatchTargets(target, appView);
assert lookupResult.isLookupResultSuccess();
if (lookupResult.isLookupResultFailure()) {
return true;
@@ -917,10 +918,7 @@
add(directMethods, resultingDirectMethod, MethodSignatureEquivalence.get());
deferredRenamings.map(directMethod.method, resultingDirectMethod.method);
deferredRenamings.recordMove(directMethod.method, resultingDirectMethod.method);
-
- if (!directMethod.isStatic()) {
- blockRedirectionOfSuperCalls(resultingDirectMethod.method);
- }
+ blockRedirectionOfSuperCalls(resultingDirectMethod.method);
}
}
@@ -1269,15 +1267,15 @@
return null;
}
- private <T extends DexEncodedMember<S>, S extends PresortedComparable<S>> void add(
- Map<Wrapper<S>, T> map, T item, Equivalence<S> equivalence) {
- map.put(equivalence.wrap(item.getKey()), item);
+ private <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> void add(
+ Map<Wrapper<R>, D> map, D item, Equivalence<R> equivalence) {
+ map.put(equivalence.wrap(item.toReference()), item);
}
- private <T extends DexEncodedMember<S>, S extends PresortedComparable<S>> void addAll(
- Collection<Wrapper<S>> collection, Iterable<T> items, Equivalence<S> equivalence) {
- for (T item : items) {
- collection.add(equivalence.wrap(item.getKey()));
+ private <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> void addAll(
+ Collection<Wrapper<R>> collection, Iterable<D> items, Equivalence<R> equivalence) {
+ for (D item : items) {
+ collection.add(equivalence.wrap(item.toReference()));
}
}
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 78edccb..68f1749 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -308,7 +308,8 @@
// methods, we either did not rename them, we renamed them according to a supplied map or
// they may be bridges for interface methods with covariant return types.
sortMethods(methods);
- assert verifyMethodsAreKeptDirectlyOrIndirectly(appView, methods);
+ // TODO(b/149360203): Reenable assert.
+ // assert verifyMethodsAreKeptDirectlyOrIndirectly(appView, methods);
}
boolean identityMapping =
diff --git a/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java b/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java
index 40fa54f..2458a64 100644
--- a/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java
+++ b/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java
@@ -5,25 +5,25 @@
import com.android.tools.r8.errors.InternalCompilerError;
import com.android.tools.r8.graph.DexEncodedMember;
-import com.android.tools.r8.graph.PresortedComparable;
+import com.android.tools.r8.graph.DexMember;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
-public class OrderedMergingIterator<T extends DexEncodedMember<S>, S extends PresortedComparable<S>>
- implements Iterator<T> {
+public class OrderedMergingIterator<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+ implements Iterator<D> {
- private final List<T> one;
- private final List<T> other;
+ private final List<D> one;
+ private final List<D> other;
private int oneIndex = 0;
private int otherIndex = 0;
- public OrderedMergingIterator(List<T> one, List<T> other) {
+ public OrderedMergingIterator(List<D> one, List<D> other) {
this.one = one;
this.other = other;
}
- private static <T> T getNextChecked(List<T> list, int position) {
+ private D getNextChecked(List<D> list, int position) {
if (position >= list.size()) {
throw new NoSuchElementException();
}
@@ -36,14 +36,14 @@
}
@Override
- public T next() {
+ public D next() {
if (oneIndex >= one.size()) {
return getNextChecked(other, otherIndex++);
}
if (otherIndex >= other.size()) {
return getNextChecked(one, oneIndex++);
}
- int comparison = one.get(oneIndex).getKey().compareTo(other.get(otherIndex).getKey());
+ int comparison = one.get(oneIndex).toReference().compareTo(other.get(otherIndex).toReference());
if (comparison < 0) {
return one.get(oneIndex++);
}
diff --git a/src/main/java/com/android/tools/r8/utils/WorkList.java b/src/main/java/com/android/tools/r8/utils/WorkList.java
new file mode 100644
index 0000000..b631262
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/WorkList.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.utils;
+
+import com.google.common.collect.Sets;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.Set;
+
+public class WorkList<T> {
+
+ private final Deque<T> workingList = new ArrayDeque<>();
+ private final Set<T> seen;
+
+ public WorkList(EqualityTest equalityTest) {
+ if (equalityTest == EqualityTest.HASH) {
+ seen = new HashSet<>();
+ } else {
+ seen = Sets.newIdentityHashSet();
+ }
+ }
+
+ public void addIfNotSeen(Iterable<T> items) {
+ items.forEach(this::addIfNotSeen);
+ }
+
+ public void addIfNotSeen(T item) {
+ if (seen.add(item)) {
+ workingList.addLast(item);
+ }
+ }
+
+ public boolean hasNext() {
+ return !workingList.isEmpty();
+ }
+
+ public T next() {
+ assert hasNext();
+ return workingList.removeFirst();
+ }
+
+ public enum EqualityTest {
+ HASH,
+ IDENTITY
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index fbacef6..14d460e 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -142,6 +142,24 @@
handler).build()));
}
+ @Test(expected = CompilationFailedException.class)
+ public void recursiveFlagsFile() throws Throwable {
+ Path working = temp.getRoot().toPath();
+ Path flagsFile = working.resolve("flags.txt");
+ Path recursiveFlagsFile = working.resolve("recursive_flags.txt");
+ Path input = Paths.get(EXAMPLES_BUILD_DIR + "/arithmetic.jar").toAbsolutePath();
+ FileUtils.writeTextFile(recursiveFlagsFile, "--output", "output.zip");
+ FileUtils.writeTextFile(
+ flagsFile, "--min-api", "24", input.toString(), "@" + recursiveFlagsFile);
+ DiagnosticsChecker.checkErrorsContains(
+ "Recursive @argfiles are not supported",
+ handler ->
+ D8.run(
+ D8Command.parse(
+ new String[] {"@" + flagsFile.toString()}, EmbeddedOrigin.INSTANCE, handler)
+ .build()));
+ }
+
@Test
public void printsHelpOnNoInput() throws Throwable {
ProcessResult result = ToolHelper.forkD8(temp.getRoot().toPath());
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index cc92bf3..4a703d7 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -143,7 +143,6 @@
assertEquals(Tool.R8, marker.getTool());
}
-
@Test(expected=CompilationFailedException.class)
public void nonExistingFlagsFile() throws Throwable {
Path working = temp.getRoot().toPath();
@@ -157,6 +156,24 @@
handler).build()));
}
+ @Test(expected = CompilationFailedException.class)
+ public void recursiveFlagsFile() throws Throwable {
+ Path working = temp.getRoot().toPath();
+ Path flagsFile = working.resolve("flags.txt");
+ Path recursiveFlagsFile = working.resolve("recursive_flags.txt");
+ Path input = Paths.get(EXAMPLES_BUILD_DIR + "/arithmetic.jar").toAbsolutePath();
+ FileUtils.writeTextFile(recursiveFlagsFile, "--output", "output.zip");
+ FileUtils.writeTextFile(
+ flagsFile, "--min-api", "24", input.toString(), "@" + recursiveFlagsFile);
+ DiagnosticsChecker.checkErrorsContains(
+ "Recursive @argfiles are not supported",
+ handler ->
+ R8.run(
+ R8Command.parse(
+ new String[] {"@" + flagsFile.toString()}, EmbeddedOrigin.INSTANCE, handler)
+ .build()));
+ }
+
@Test
public void printsHelpOnNoInput() throws Throwable {
ProcessResult result = ToolHelper.forkR8(temp.getRoot().toPath());
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSuperCallInStaticTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSuperCallInStaticTest.java
new file mode 100644
index 0000000..8dcad81
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSuperCallInStaticTest.java
@@ -0,0 +1,111 @@
+// 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.classmerging;
+
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class VerticalClassMergerSuperCallInStaticTest extends TestBase {
+
+ private static final String[] EXPECTED = new String[] {"A.collect()", "Base.collect()"};
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public VerticalClassMergerSuperCallInStaticTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+ testForRuntime(parameters)
+ .addProgramClasses(Base.class, B.class, Main.class)
+ .addProgramClassFileData(getAWithRewrittenInvokeSpecialToBase())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Base.class, B.class, Main.class)
+ .addProgramClassFileData(getAWithRewrittenInvokeSpecialToBase())
+ .addKeepMainRule(Main.class)
+ .addKeepClassRules(Base.class, B.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ private byte[] getAWithRewrittenInvokeSpecialToBase() throws IOException {
+ return transformer(A.class)
+ .transformMethodInsnInMethod(
+ "callSuper",
+ (opcode, owner, name, descriptor, isInterface, continuation) -> {
+ continuation.apply(
+ INVOKESPECIAL,
+ DescriptorUtils.getBinaryNameFromJavaType(Base.class.getTypeName()),
+ name,
+ descriptor,
+ false);
+ })
+ .transform();
+ }
+
+ public static class Base {
+
+ public void collect() {
+ System.out.println("Base.collect()");
+ }
+ }
+
+ public static class A extends Base {
+
+ @Override
+ @NeverInline
+ public void collect() {
+ System.out.println("A.collect()");
+ }
+
+ @NeverInline
+ public static void callSuper(A a) {
+ a.collect(); // Will be rewritten from invoke-virtual to invoke-special Base.collect();
+ }
+ }
+
+ public static class B extends A {
+
+ public void bar() {
+ collect();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ B b = new B();
+ b.bar();
+ A.callSuper(b);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/compatproguard/reflection/ReflectionTest.java b/src/test/java/com/android/tools/r8/compatproguard/reflection/ReflectionTest.java
index 90993ee..6a1e059 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/reflection/ReflectionTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/reflection/ReflectionTest.java
@@ -48,6 +48,8 @@
Method m;
m = A.class.getMethod("method0");
m.invoke(a);
+ // The call with in-exact argument to getMethod is intended and should stay.
+ // The warning cannot be suppressed according to b/117198454.
m = A.class.getMethod("method0", null);
m.invoke(a);
m = A.class.getMethod("method0", (Class<?>[]) null);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java
index af4088e..21074c9 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java
@@ -6,7 +6,6 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
-import com.android.tools.r8.desugar.desugaredlibrary.conversiontests.MoreFunctionConversionTest.CustomLibClass;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingAnalysisTest.java
index ddcb725..a15dc08 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingAnalysisTest.java
@@ -10,7 +10,7 @@
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 java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -19,16 +19,22 @@
@RunWith(Parameterized.class)
public class ComparisonEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
- private final TestParameters parameters;
- private final Class<?>[] INPUTS = new Class<?>[] {NullCheck.class, EnumComparison.class};
+ private static final Class<?>[] INPUTS = new Class<?>[] {NullCheck.class, EnumComparison.class};
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
+ private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final boolean enumKeepRules;
+
+ @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
return enumUnboxingTestParameters();
}
- public ComparisonEnumUnboxingAnalysisTest(TestParameters parameters) {
+ public ComparisonEnumUnboxingAnalysisTest(
+ TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
}
@Test
@@ -37,9 +43,9 @@
testForR8(parameters.getBackend())
.addInnerClasses(ComparisonEnumUnboxingAnalysisTest.class)
.addKeepMainRules(INPUTS)
- .addKeepRules(KEEP_ENUM)
.enableInliningAnnotations()
- .addOptionsModification(this::enableEnumOptions)
+ .addKeepRules(enumKeepRules ? KEEP_ENUM : "")
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.allowDiagnosticInfoMessages()
.setMinApi(parameters.getApiLevel())
.compile()
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
index 332ad2e..b7ebc83 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
@@ -10,9 +10,9 @@
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.BooleanUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringUtils;
import java.util.List;
@@ -34,8 +34,9 @@
}
}
- void enableEnumOptions(InternalOptions options) {
+ void enableEnumOptions(InternalOptions options, boolean enumValueOptimization) {
options.enableEnumUnboxing = true;
+ options.enableEnumValueOptimization = enumValueOptimization;
options.testing.enableEnumUnboxingDebugLogs = true;
}
@@ -55,12 +56,15 @@
diagnostic.getDiagnosticMessage().contains(enumClass.getSimpleName()));
}
- static TestParametersCollection enumUnboxingTestParameters() {
- return getTestParameters()
- .withCfRuntime(CfVm.JDK9)
- .withDexRuntime(DexVm.Version.first())
- .withDexRuntime(DexVm.Version.last())
- .withAllApiLevels()
- .build();
+ static List<Object[]> enumUnboxingTestParameters() {
+ return buildParameters(
+ getTestParameters()
+ .withCfRuntime(CfVm.JDK9)
+ .withDexRuntime(DexVm.Version.first())
+ .withDexRuntime(DexVm.Version.last())
+ .withAllApiLevels()
+ .build(),
+ BooleanUtils.values(),
+ BooleanUtils.values());
}
}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java
index 9c4872f..c0eeaf4 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java
@@ -12,13 +12,13 @@
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 java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -36,14 +36,19 @@
};
private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final boolean enumKeepRules;
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
+ @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
return enumUnboxingTestParameters();
}
- public FailingEnumUnboxingAnalysisTest(TestParameters parameters) {
+ public FailingEnumUnboxingAnalysisTest(
+ TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
}
@Test
@@ -57,8 +62,8 @@
r8FullTestBuilder
.noTreeShaking() // Disabled to avoid merging Itf into EnumInterface.
.enableInliningAnnotations()
- .addKeepRules(KEEP_ENUM)
- .addOptionsModification(this::enableEnumOptions)
+ .addKeepRules(enumKeepRules ? KEEP_ENUM : "")
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.allowDiagnosticInfoMessages()
.setMinApi(parameters.getApiLevel())
.compile()
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java
index c7ad117..944c9c3 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java
@@ -11,9 +11,9 @@
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 java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -31,14 +31,19 @@
};
private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final boolean enumKeepRules;
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
+ @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
return enumUnboxingTestParameters();
}
- public FailingMethodEnumUnboxingAnalysisTest(TestParameters parameters) {
+ public FailingMethodEnumUnboxingAnalysisTest(
+ TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
}
@Test
@@ -47,13 +52,10 @@
testForR8(parameters.getBackend())
.addInnerClasses(FailingMethodEnumUnboxingAnalysisTest.class)
.addKeepMainRules(FAILURES)
- .addKeepRules(KEEP_ENUM)
- .addOptionsModification(this::enableEnumOptions)
+ .addKeepRules(enumKeepRules ? KEEP_ENUM : "")
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.allowDiagnosticInfoMessages()
.enableInliningAnnotations()
- .addOptionsModification(
- // Disabled to avoid toString() being removed.
- opt -> opt.enableEnumValueOptimization = false)
.setMinApi(parameters.getApiLevel())
.compile()
.inspect(this::assertEnumsAsExpected);
@@ -64,9 +66,14 @@
m ->
assertEnumIsBoxed(
failure.getDeclaredClasses()[0], failure.getSimpleName(), m))
- .run(parameters.getRuntime(), failure)
- .assertSuccess();
- assertLines2By2Correct(run.getStdOut());
+ .run(parameters.getRuntime(), failure);
+ if (failure == EnumSetTest.class && !enumKeepRules) {
+ // EnumSet and EnumMap cannot be used without the enumKeepRules.
+ run.assertFailure();
+ } else {
+ run.assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java
index 8811ee5..daaf504 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java
@@ -10,7 +10,7 @@
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 java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -19,17 +19,23 @@
@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() {
+ private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final boolean enumKeepRules;
+
+ @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
return enumUnboxingTestParameters();
}
- public FieldPutEnumUnboxingAnalysisTest(TestParameters parameters) {
+ public FieldPutEnumUnboxingAnalysisTest(
+ TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
}
@Test
@@ -38,8 +44,8 @@
testForR8(parameters.getBackend())
.addInnerClasses(FieldPutEnumUnboxingAnalysisTest.class)
.addKeepMainRules(INPUTS)
- .addKeepRules(KEEP_ENUM)
- .addOptionsModification(this::enableEnumOptions)
+ .addKeepRules(enumKeepRules ? KEEP_ENUM : "")
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.allowDiagnosticInfoMessages()
.enableInliningAnnotations()
.setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java
index 2570a12..afd2e6a 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java
@@ -6,7 +6,7 @@
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -15,19 +15,24 @@
@RunWith(Parameterized.class)
public class OrdinalEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
- private final TestParameters parameters;
+ private static final Class<?> ENUM_CLASS = MyEnum.class;
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
+ private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final boolean enumKeepRules;
+
+ @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
return enumUnboxingTestParameters();
}
- public OrdinalEnumUnboxingAnalysisTest(TestParameters parameters) {
+ public OrdinalEnumUnboxingAnalysisTest(
+ TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
}
- private static final Class<?> ENUM_CLASS = MyEnum.class;
-
@Test
public void testEnumUnboxing() throws Exception {
Class<Ordinal> classToTest = Ordinal.class;
@@ -35,8 +40,8 @@
testForR8(parameters.getBackend())
.addProgramClasses(classToTest, ENUM_CLASS)
.addKeepMainRule(classToTest)
- .addKeepRules(KEEP_ENUM)
- .addOptionsModification(this::enableEnumOptions)
+ .addKeepRules(enumKeepRules ? KEEP_ENUM : "")
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.allowDiagnosticInfoMessages()
.setMinApi(parameters.getApiLevel())
.compile()
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java
index 2abd358..b231d32 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java
@@ -7,7 +7,7 @@
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 java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -16,19 +16,24 @@
@RunWith(Parameterized.class)
public class PhiEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
- private final TestParameters parameters;
+ private static final Class<?> ENUM_CLASS = MyEnum.class;
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
+ private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final boolean enumKeepRules;
+
+ @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
return enumUnboxingTestParameters();
}
- public PhiEnumUnboxingAnalysisTest(TestParameters parameters) {
+ public PhiEnumUnboxingAnalysisTest(
+ TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
}
- private static final Class<?> ENUM_CLASS = MyEnum.class;
-
@Test
public void testEnumUnboxing() throws Exception {
Class<?> classToTest = Phi.class;
@@ -36,9 +41,9 @@
testForR8(parameters.getBackend())
.addProgramClasses(classToTest, ENUM_CLASS)
.addKeepMainRule(classToTest)
- .addKeepRules(KEEP_ENUM)
+ .addKeepRules(enumKeepRules ? KEEP_ENUM : "")
.enableInliningAnnotations()
- .addOptionsModification(this::enableEnumOptions)
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.allowDiagnosticInfoMessages()
.setMinApi(parameters.getApiLevel())
.compile()
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java
index 53cddc6..a40dec0 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java
@@ -7,7 +7,7 @@
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 java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -16,19 +16,24 @@
@RunWith(Parameterized.class)
public class SwitchEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
- private final TestParameters parameters;
+ private static final Class<?> ENUM_CLASS = MyEnum.class;
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
+ private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final boolean enumKeepRules;
+
+ @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
return enumUnboxingTestParameters();
}
- public SwitchEnumUnboxingAnalysisTest(TestParameters parameters) {
+ public SwitchEnumUnboxingAnalysisTest(
+ TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
}
- private static final Class<?> ENUM_CLASS = MyEnum.class;
-
@Test
public void testEnumUnboxing() throws Exception {
Class<Switch> classToTest = Switch.class;
@@ -36,9 +41,9 @@
testForR8(parameters.getBackend())
.addInnerClasses(SwitchEnumUnboxingAnalysisTest.class)
.addKeepMainRule(classToTest)
- .addKeepRules(KEEP_ENUM)
+ .addKeepRules(enumKeepRules ? KEEP_ENUM : "")
.enableInliningAnnotations()
- .addOptionsModification(this::enableEnumOptions)
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.allowDiagnosticInfoMessages()
.setMinApi(parameters.getApiLevel())
.compile()
diff --git a/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java
index fc6e7d3..fc8f3bf 100644
--- a/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.internal;
import static com.android.tools.r8.ToolHelper.isLocalDevelopment;
+import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
@@ -42,8 +43,13 @@
.addKeepRuleFiles(
Paths.get(base).resolve(BASE_PG_CONF),
Paths.get(ToolHelper.PROGUARD_SETTINGS_FOR_INTERNAL_APPS, PG_CONF))
+ .allowDiagnosticInfoMessages()
.allowUnusedProguardConfigurationRules()
- .compile();
+ .compile()
+ .assertAllInfoMessagesMatch(
+ anyOf(
+ equalTo("Ignoring option: -optimizations"),
+ containsString("Proguard configuration rule does not match anything")));
int appSize = compileResult.app.applicationSize();
assertTrue("Expected max size of " + MAX_SIZE+ ", got " + appSize, appSize < MAX_SIZE);
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 4484311..4c3085e 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -36,7 +37,7 @@
private AndroidApp app;
private DirectMappedDexApplication program;
private AppInfoWithSubtyping appInfo;
- private AppView<?> appView;
+ private AppView<? extends AppInfoWithClassHierarchy> appView;
@Before
public void readGMSCore() throws Exception {
@@ -64,7 +65,8 @@
// Check lookup targets with include method.
ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(clazz, method.method);
- LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView, appInfo);
+ LookupResult lookupResult =
+ resolutionResult.lookupVirtualDispatchTargets(clazz, appView, appInfo);
assertTrue(lookupResult.isLookupResultSuccess());
Set<DexEncodedMethod> targets = lookupResult.asLookupResultSuccess().getMethodTargets();
assertTrue(targets.contains(method));
@@ -74,7 +76,7 @@
LookupResult lookupResult =
appInfo
.resolveMethodOnInterface(clazz, method.method)
- .lookupVirtualDispatchTargets(appView, appInfo);
+ .lookupVirtualDispatchTargets(clazz, appView, appInfo);
assertTrue(lookupResult.isLookupResultSuccess());
Set<DexEncodedMethod> targets = lookupResult.asLookupResultSuccess().getMethodTargets();
if (appInfo.subtypes(method.method.holder).stream()
@@ -103,6 +105,6 @@
@Test
public void testLookup() {
- program.classes().forEach(this::testLookup);
+ program.classesWithDeterministicOrder().forEach(this::testLookup);
}
}
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 5d48fa6..6104883 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
@@ -137,7 +137,7 @@
TypeLatticeElement.fromDexType(
app.dexItemFactory.throwableType, Nullability.definitelyNotNull(), appView),
null);
- instruction = new Argument(value, false);
+ instruction = new Argument(value, 0, false);
instruction.setPosition(position);
block0.add(instruction, metadata);
instruction = new If(Type.EQ, value);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
index 9ccdb7f..9ebb411 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
@@ -70,12 +70,7 @@
// inlined into main(), which makes A.foo() eligible for inlining into main().
ClassSubject aClassSubject = inspector.clazz(A.class);
assertThat(aClassSubject, isPresent());
- if (maxInliningDepth == 0) {
- assertThat(aClassSubject.uniqueMethodWithName("foo"), isPresent());
- } else {
- assert maxInliningDepth == 1;
- assertThat(aClassSubject.uniqueMethodWithName("foo"), not(isPresent()));
- }
+ assertThat(aClassSubject.uniqueMethodWithName("foo"), not(isPresent()));
// A.bar() should always be inlined because it is marked as @AlwaysInline.
assertThat(aClassSubject.uniqueMethodWithName("bar"), not(isPresent()));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentTest.java
index beecf65..ca34efd 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentTest.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.optimize.membervaluepropagation.fields;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.NeverClassInline;
@@ -50,8 +51,7 @@
ClassSubject testClassSubject = inspector.clazz(TestClass.class);
assertThat(testClassSubject, isPresent());
assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent());
- // TODO(b/147652121): Should be absent.
- assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+ assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByDifferentConstantsInMultipleConstructorsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByDifferentConstantsInMultipleConstructorsTest.java
new file mode 100644
index 0000000..cf7650f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByDifferentConstantsInMultipleConstructorsTest.java
@@ -0,0 +1,99 @@
+// 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.fields;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class FieldInitializedByDifferentConstantsInMultipleConstructorsTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public FieldInitializedByDifferentConstantsInMultipleConstructorsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(FieldInitializedByDifferentConstantsInMultipleConstructorsTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Live!", "Live!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+ assertThat(testClassSubject, isPresent());
+ assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent());
+ // TODO(b/147652121): Should be absent.
+ assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ A obj1 = new A();
+ if (obj1.x == 42) {
+ live();
+ } else {
+ dead();
+ }
+
+ A obj2 = new A(null);
+ if (obj2.x == 43) {
+ live();
+ } else {
+ dead();
+ }
+ }
+
+ @NeverInline
+ static void live() {
+ System.out.println("Live!");
+ }
+
+ @NeverInline
+ static void dead() {
+ System.out.println("Dead!");
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ int x;
+
+ A() {
+ this.x = 42;
+ }
+
+ A(Object unused) {
+ this.x = 43;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedBySameConstantInMultipleConstructorsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedBySameConstantInMultipleConstructorsTest.java
new file mode 100644
index 0000000..2f0cf08
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedBySameConstantInMultipleConstructorsTest.java
@@ -0,0 +1,99 @@
+// 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.fields;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class FieldInitializedBySameConstantInMultipleConstructorsTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public FieldInitializedBySameConstantInMultipleConstructorsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(FieldInitializedBySameConstantInMultipleConstructorsTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Live!", "Live!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+ assertThat(testClassSubject, isPresent());
+ assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent());
+ // TODO(b/147652121): Should be absent.
+ assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ A obj1 = new A();
+ if (obj1.x == 42) {
+ live();
+ } else {
+ dead();
+ }
+
+ A obj2 = new A(null);
+ if (obj2.x == 42) {
+ live();
+ } else {
+ dead();
+ }
+ }
+
+ @NeverInline
+ static void live() {
+ System.out.println("Live!");
+ }
+
+ @NeverInline
+ static void dead() {
+ System.out.println("Dead!");
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ int x;
+
+ A() {
+ this.x = 42;
+ }
+
+ A(Object unused) {
+ this.x = 42;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAndUninstantiatedTypesTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAndUninstantiatedTypesTest.java
new file mode 100644
index 0000000..af6296b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAndUninstantiatedTypesTest.java
@@ -0,0 +1,172 @@
+// 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.unusedarguments;
+
+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.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+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 UnusedAndUninstantiatedTypesTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public UnusedAndUninstantiatedTypesTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testUnusedAndUninstantiatedTypes() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(UnusedAndUninstantiatedTypesTest.class)
+ .addKeepMainRule(Main.class)
+ .noMinification()
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::assertMethodsAreThere)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(
+ "nothing",
+ "uninstantiatedThenUnused null",
+ "unusedThenUninstantiated null",
+ "doubleUninstantiatedThenUnused null and null",
+ "doubleUnusedThenUninstantiated null and null",
+ "tripleUninstantiatedThenUnused null and null and null",
+ "tripleUnusedThenUninstantiated null and null and null",
+ "withWideParameters null and null and null");
+ }
+
+ private void assertMethodsAreThere(CodeInspector i) {
+ List<FoundMethodSubject> methods = i.clazz(Main.class).allMethods();
+ assertEquals(9, methods.size());
+ for (FoundMethodSubject method : methods) {
+ if (!method.getMethod().method.name.toString().equals("main")) {
+ assertEquals(0, method.getMethod().method.getArity());
+ }
+ }
+ }
+
+ @SuppressWarnings("SameParameterValue")
+ static class Main {
+
+ public static void main(String[] args) {
+ uninstantiatedAndUnused(null);
+ uninstantiatedThenUnused(null, new Unused());
+ unusedThenUninstantiated(new Unused(), null);
+ doubleUninstantiatedThenUnused(null, new Unused(), null, new Unused());
+ doubleUnusedThenUninstantiated(new Unused(), null, new Unused(), null);
+ tripleUninstantiatedThenUnused(null, new Unused(), new Unused(), null, null, new Unused());
+ tripleUnusedThenUninstantiated(new Unused(), null, null, new Unused(), new Unused(), null);
+ withWideParameters(0L, 1L, null, null, 2L, 3L, null);
+ }
+
+ @NeverInline
+ static void uninstantiatedAndUnused(UnInstantiated uninstantiated) {
+ System.out.println("nothing");
+ }
+
+ @NeverInline
+ static void uninstantiatedThenUnused(UnInstantiated uninstantiated, Unused unused) {
+ System.out.println("uninstantiatedThenUnused " + uninstantiated);
+ }
+
+ @NeverInline
+ static void unusedThenUninstantiated(Unused unused, UnInstantiated uninstantiated) {
+ System.out.println("unusedThenUninstantiated " + uninstantiated);
+ }
+
+ @NeverInline
+ static void doubleUninstantiatedThenUnused(
+ UnInstantiated uninstantiated1,
+ Unused unused1,
+ UnInstantiated uninstantiated2,
+ Unused unused2) {
+ System.out.println(
+ "doubleUninstantiatedThenUnused " + uninstantiated1 + " and " + uninstantiated2);
+ }
+
+ @NeverInline
+ static void doubleUnusedThenUninstantiated(
+ Unused unused1,
+ UnInstantiated uninstantiated1,
+ Unused unused2,
+ UnInstantiated uninstantiated2) {
+ System.out.println(
+ "doubleUnusedThenUninstantiated " + uninstantiated1 + " and " + uninstantiated2);
+ }
+
+ @NeverInline
+ static void tripleUninstantiatedThenUnused(
+ UnInstantiated uninstantiated1,
+ Unused unused0,
+ Unused unused1,
+ UnInstantiated uninstantiated0,
+ UnInstantiated uninstantiated2,
+ Unused unused2) {
+ System.out.println(
+ "tripleUninstantiatedThenUnused "
+ + uninstantiated1
+ + " and "
+ + uninstantiated2
+ + " and "
+ + uninstantiated0);
+ }
+
+ @NeverInline
+ static void tripleUnusedThenUninstantiated(
+ Unused unused1,
+ UnInstantiated uninstantiated0,
+ UnInstantiated uninstantiated1,
+ Unused unused0,
+ Unused unused2,
+ UnInstantiated uninstantiated2) {
+ System.out.println(
+ "tripleUnusedThenUninstantiated "
+ + uninstantiated1
+ + " and "
+ + uninstantiated2
+ + " and "
+ + uninstantiated0);
+ }
+
+ @NeverInline
+ static void withWideParameters(
+ long longUnused0,
+ long longUnused1,
+ UnInstantiated uninstantiated0,
+ UnInstantiated uninstantiated1,
+ long longUnused2,
+ long longUnused3,
+ UnInstantiated uninstantiated2) {
+ System.out.println(
+ "withWideParameters "
+ + uninstantiated1
+ + " and "
+ + uninstantiated2
+ + " and "
+ + uninstantiated0);
+ }
+ }
+
+ private static class UnInstantiated {}
+
+ private static class Unused {}
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
index f6649b7..eb98862 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
@@ -9,6 +9,7 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.TestParameters;
@@ -20,6 +21,7 @@
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.MethodSubject;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashMap;
@@ -100,6 +102,7 @@
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());
@@ -110,6 +113,7 @@
ClassSubject bKt = inspector.clazz(bKtClassName);
assertThat(bKt, isPresent());
assertThat(bKt, not(isRenamed()));
+
// API entry is kept, hence the presence of Metadata.
KmPackageSubject kmPackage = bKt.getKmPackage();
assertThat(kmPackage, isPresent());
@@ -128,7 +132,7 @@
.addKeepRules("-keep class **.B")
.addKeepRules("-keep class **.I { <methods>; }")
// Keep Super, but allow minification.
- .addKeepRules("-keep,allowobfuscation class **.Super")
+ .addKeepRules("-keep,allowobfuscation class **.Super { <methods>; }")
// Keep the BKt method, which will be called from other kotlin code.
.addKeepRules("-keep class **.BKt { <methods>; }")
.addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
@@ -158,11 +162,24 @@
ClassSubject sup = inspector.clazz(superClassName);
assertThat(sup, isRenamed());
+ MethodSubject foo = sup.uniqueMethodWithName("foo");
+ assertThat(foo, isRenamed());
+
+ KmClassSubject kmClass = sup.getKmClass();
+ assertThat(kmClass, isPresent());
+
+ // TODO(b/70169921): would be better to look up function with the original name, "foo".
+ KmFunctionSubject kmFunction = kmClass.kmFunctionWithUniqueName(foo.getFinalName());
+ assertThat(kmFunction, isPresent());
+ assertThat(kmFunction, not(isExtensionFunction()));
+ assertEquals(foo.getJvmMethodSignatureAsString(), kmFunction.signature().asString());
+
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();
+ kmClass = impl.getKmClass();
assertThat(kmClass, isPresent());
List<ClassSubject> superTypes = kmClass.getSuperTypes();
assertTrue(superTypes.stream().noneMatch(
@@ -177,7 +194,7 @@
KmPackageSubject kmPackage = bKt.getKmPackage();
assertThat(kmPackage, isPresent());
- KmFunctionSubject kmFunction = kmPackage.kmFunctionWithUniqueName("fun");
+ kmFunction = kmPackage.kmFunctionWithUniqueName("fun");
assertThat(kmFunction, isPresent());
assertThat(kmFunction, not(isExtensionFunction()));
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java
index 2dc57bf..5b9dfed 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java
@@ -4,25 +4,34 @@
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.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.assertNotNull;
+import static org.junit.Assert.assertTrue;
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.DescriptorUtils;
import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.KmFunctionSubject;
+import com.android.tools.r8.utils.codeinspector.KmPackageSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -88,7 +97,6 @@
private void inspectMerged(CodeInspector inspector) {
String utilClassName = PKG + ".multifileclass_lib.UtilKt";
- String signedClassName = PKG + ".multifileclass_lib.UtilKt__SignedKt";
ClassSubject util = inspector.clazz(utilClassName);
assertThat(util, isPresent());
@@ -98,22 +106,10 @@
assertThat(commaJoinOfInt, not(isRenamed()));
MethodSubject joinOfInt = util.uniqueMethodWithName("joinOfInt");
assertThat(joinOfInt, not(isPresent()));
- // API entry is kept, hence the presence of Metadata.
- AnnotationSubject annotationSubject = util.annotation(METADATA_TYPE);
- assertThat(annotationSubject, isPresent());
- // TODO(b/70169921): need further inspection.
- ClassSubject signed = inspector.clazz(signedClassName);
- assertThat(signed, isRenamed());
- commaJoinOfInt = signed.uniqueMethodWithName("commaSeparatedJoinOfInt");
- assertThat(commaJoinOfInt, isPresent());
- assertThat(commaJoinOfInt, not(isRenamed()));
- joinOfInt = signed.uniqueMethodWithName("joinOfInt");
- assertThat(joinOfInt, isRenamed());
- // API entry is kept, hence the presence of Metadata.
- annotationSubject = util.annotation(METADATA_TYPE);
- assertThat(annotationSubject, isPresent());
- // TODO(b/70169921): need further inspection.
+ inspectMetadataForFacade(inspector, util);
+
+ inspectSignedKt(inspector);
}
@Test
@@ -146,7 +142,6 @@
private void inspectRenamed(CodeInspector inspector) {
String utilClassName = PKG + ".multifileclass_lib.UtilKt";
- String signedClassName = PKG + ".multifileclass_lib.UtilKt__SignedKt";
ClassSubject util = inspector.clazz(utilClassName);
assertThat(util, isPresent());
@@ -157,21 +152,48 @@
MethodSubject joinOfInt = util.uniqueMethodWithName("joinOfInt");
assertThat(joinOfInt, isPresent());
assertThat(joinOfInt, isRenamed());
+
+ inspectMetadataForFacade(inspector, util);
+
+ inspectSignedKt(inspector);
+ }
+
+ private void inspectMetadataForFacade(CodeInspector inspector, ClassSubject util) {
// API entry is kept, hence the presence of Metadata.
AnnotationSubject annotationSubject = util.annotation(METADATA_TYPE);
assertThat(annotationSubject, isPresent());
- // TODO(b/70169921): need further inspection.
+ KotlinClassMetadata metadata = util.getKotlinClassMetadata();
+ assertNotNull(metadata);
+ assertTrue(metadata instanceof KotlinClassMetadata.MultiFileClassFacade);
+ KotlinClassMetadata.MultiFileClassFacade facade =
+ (KotlinClassMetadata.MultiFileClassFacade) metadata;
+ List<String> partClassNames = facade.getPartClassNames();
+ assertEquals(2, partClassNames.size());
+ for (String partClassName : partClassNames) {
+ ClassSubject partClass =
+ inspector.clazz(DescriptorUtils.getJavaTypeFromBinaryName(partClassName));
+ assertThat(partClass, isRenamed());
+ }
+ }
+ private void inspectSignedKt(CodeInspector inspector) {
+ String signedClassName = PKG + ".multifileclass_lib.UtilKt__SignedKt";
ClassSubject signed = inspector.clazz(signedClassName);
assertThat(signed, isRenamed());
- commaJoinOfInt = signed.uniqueMethodWithName("commaSeparatedJoinOfInt");
+ MethodSubject commaJoinOfInt = signed.uniqueMethodWithName("commaSeparatedJoinOfInt");
assertThat(commaJoinOfInt, isPresent());
assertThat(commaJoinOfInt, not(isRenamed()));
- joinOfInt = signed.uniqueMethodWithName("joinOfInt");
+ MethodSubject joinOfInt = signed.uniqueMethodWithName("joinOfInt");
assertThat(joinOfInt, isRenamed());
+
// API entry is kept, hence the presence of Metadata.
- annotationSubject = util.annotation(METADATA_TYPE);
- assertThat(annotationSubject, isPresent());
- // TODO(b/70169921): need further inspection.
+ KmPackageSubject kmPackage = signed.getKmPackage();
+ assertThat(kmPackage, isPresent());
+ KmFunctionSubject kmFunction =
+ kmPackage.kmFunctionExtensionWithUniqueName("commaSeparatedJoinOfInt");
+ assertThat(kmFunction, isPresent());
+ assertThat(kmFunction, isExtensionFunction());
+ // TODO(b/70169921): Inspect that parameter type has a correct type argument, Int.
+ // TODO(b/70169921): Inspect that the name in KmFunction is still 'join' so that apps can refer.
}
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/function_lib/B.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/function_lib/B.kt
index d327fda..41d286c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/function_lib/B.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/function_lib/B.kt
@@ -11,6 +11,10 @@
override fun doStuff() {
println("do stuff")
}
+
+ fun foo() {
+ println("Super::foo")
+ }
}
class B : Super()
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 ba27c33..72ede27 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -223,7 +223,10 @@
appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).getSingleTarget());
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
if (resolutionResult.isVirtualTarget()) {
- LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView, appInfo);
+ LookupResult lookupResult =
+ resolutionResult.lookupVirtualDispatchTargets(
+ appView.definitionForProgramType(buildType(Main.class, appView.dexItemFactory())),
+ appView);
assertTrue(lookupResult.isLookupResultSuccess());
Set<DexEncodedMethod> targets = lookupResult.asLookupResultSuccess().getMethodTargets();
Set<DexType> targetHolders =
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
index 3fcc33d..27da1c4 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.LookupResult;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -57,7 +58,9 @@
AppInfoWithLiveness appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(I.class, "bar", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
- LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+ DexProgramClass context =
+ appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+ LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
assertTrue(lookupResult.isLookupResultSuccess());
Set<String> targets =
lookupResult.asLookupResultSuccess().getMethodTargets().stream()
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
index bdd5dd3..c8df559 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.LookupResult;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -55,7 +56,9 @@
AppInfoWithLiveness appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(I.class, "bar", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
- LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+ DexProgramClass context =
+ appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+ LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
assertTrue(lookupResult.isLookupResultSuccess());
Set<String> targets =
lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java
index 266cb48..9136188 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.LookupResult;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -60,7 +61,9 @@
AppInfoWithLiveness appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
- LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+ DexProgramClass context =
+ appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+ LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
assertTrue(lookupResult.isLookupResultSuccess());
Set<String> targets =
lookupResult.asLookupResultSuccess().getMethodTargets().stream()
@@ -102,7 +105,9 @@
AppInfoWithLiveness appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
- LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+ DexProgramClass context =
+ appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+ LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
assertTrue(lookupResult.isLookupResultSuccess());
Set<String> targets =
lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DuplicateImportsTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DuplicateImportsTest.java
new file mode 100644
index 0000000..da61d6a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DuplicateImportsTest.java
@@ -0,0 +1,118 @@
+// 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.Assert.assertTrue;
+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.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.LookupResult;
+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 test is testing visiting direct subtypes and finding the right targets. */
+@RunWith(Parameterized.class)
+public class DuplicateImportsTest extends TestBase {
+
+ private static final String[] EXPECTED = new String[] {"J.foo"};
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public DuplicateImportsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testResolution() throws Exception {
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(
+ buildClasses(I.class, J.class, A.class, Main.class).build(), Main.class);
+ AppInfoWithLiveness appInfo = appView.appInfo();
+ DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
+ ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
+ DexProgramClass context =
+ appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+ LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+ assertTrue(lookupResult.isLookupResultSuccess());
+ Set<String> targets =
+ lookupResult.asLookupResultSuccess().getMethodTargets().stream()
+ .map(DexEncodedMethod::qualifiedName)
+ .collect(Collectors.toSet());
+ ImmutableSet<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo");
+ assertEquals(expected, targets);
+ }
+
+ @Test
+ public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+ testForRuntime(parameters)
+ .addInnerClasses(DuplicateImportsTest.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(DuplicateImportsTest.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 extends I {
+
+ @Override
+ @NeverInline
+ default void foo() {
+ System.out.println("J.foo");
+ }
+ }
+
+ @NeverClassInline
+ public static class A implements I, J {}
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ ((I) new A()).foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java
index 2614319..016347b 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.transformers.ClassTransformer;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -58,9 +59,14 @@
Main.class);
AppInfoWithLiveness appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(I.class, "<clinit>", appInfo.dexItemFactory());
+ DexProgramClass context =
+ appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
Assert.assertThrows(
AssertionError.class,
- () -> appInfo.resolveMethod(method.holder, method).lookupVirtualDispatchTargets(appView));
+ () ->
+ appInfo
+ .resolveMethod(method.holder, method)
+ .lookupVirtualDispatchTargets(context, appView));
}
private Matcher<String> getExpected() {
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java
index 1ca966b..0ba0226 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.DescriptorUtils;
@@ -49,9 +50,14 @@
Main.class);
AppInfoWithLiveness appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(I.class, "bar", appInfo.dexItemFactory());
+ DexProgramClass context =
+ appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
Assert.assertThrows(
AssertionError.class,
- () -> appInfo.resolveMethod(method.holder, method).lookupVirtualDispatchTargets(appView));
+ () ->
+ appInfo
+ .resolveMethod(method.holder, method)
+ .lookupVirtualDispatchTargets(context, appView));
}
@Test
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
index 251fa37..eeca9fe 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.LookupResult;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -56,7 +57,9 @@
AppInfoWithLiveness appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(J.class, "bar", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
- LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+ DexProgramClass context =
+ appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+ LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
assertTrue(lookupResult.isLookupResultSuccess());
Set<String> targets =
lookupResult.asLookupResultSuccess().getMethodTargets().stream()
@@ -84,7 +87,6 @@
.enableNeverClassInliningAnnotations()
.addKeepMainRule(Main.class)
.setMinApi(parameters.getApiLevel())
- .compile()
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines(EXPECTED);
}
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
index eff0088..64c81fa 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.LookupResult;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -56,7 +57,9 @@
AppInfoWithLiveness appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
- LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+ DexProgramClass context =
+ appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+ LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
assertTrue(lookupResult.isLookupResultSuccess());
Set<String> targets =
lookupResult.asLookupResultSuccess().getMethodTargets().stream()
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
index 9552212..a21d3e1 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.LookupResult;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -57,7 +58,9 @@
AppInfoWithLiveness appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
- LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+ DexProgramClass context =
+ appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+ LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
assertTrue(lookupResult.isLookupResultSuccess());
Set<String> targets =
lookupResult.asLookupResultSuccess().getMethodTargets().stream()
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
index 41d4879..80caa62 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.LookupResult;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -56,7 +57,9 @@
AppInfoWithLiveness appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
- LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+ DexProgramClass context =
+ appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+ LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
assertTrue(lookupResult.isLookupResultSuccess());
Set<String> targets =
lookupResult.asLookupResultSuccess().getMethodTargets().stream()
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
index 84236e5..713d80e 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.LookupResult;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -55,7 +56,9 @@
AppInfoWithLiveness appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
- LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+ DexProgramClass context =
+ appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+ LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
assertTrue(lookupResult.isLookupResultSuccess());
Set<String> targets =
lookupResult.asLookupResultSuccess().getMethodTargets().stream()
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
index a952bb3..ba468e0 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.LookupResult;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -55,7 +56,9 @@
AppInfoWithLiveness appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
- LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+ DexProgramClass context =
+ appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+ LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
assertTrue(lookupResult.isLookupResultSuccess());
Set<String> targets =
lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/NonAbstractWidening.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/NonAbstractWidening.java
new file mode 100644
index 0000000..f9d75e4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/NonAbstractWidening.java
@@ -0,0 +1,16 @@
+// 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.packageprivate;
+
+import com.android.tools.r8.resolution.packageprivate.a.AbstractWidening;
+import com.android.tools.r8.resolution.packageprivate.a.I;
+
+public class NonAbstractWidening extends AbstractWidening implements I {
+
+ @Override
+ public void foo() {
+ System.out.println("Method declaration will be removed");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateClasspathWidenTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateClasspathWidenTest.java
new file mode 100644
index 0000000..a714a03
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateClasspathWidenTest.java
@@ -0,0 +1,121 @@
+// 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.packageprivate;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.LookupResult;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.resolution.packageprivate.a.Abstract;
+import com.android.tools.r8.resolution.packageprivate.a.I;
+import com.android.tools.r8.resolution.packageprivate.a.NonAbstract;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class PackagePrivateClasspathWidenTest extends TestBase {
+
+ private static final String[] EXPECTED = new String[] {"C.foo", "C.foo"};
+ private static final Class[] CLASSPATH_CLASSES =
+ new Class[] {Abstract.class, NonAbstract.class, I.class};
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public PackagePrivateClasspathWidenTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private static Path classPathJar = null;
+
+ @BeforeClass
+ public static void createClassPathJar() throws IOException {
+ classPathJar = getStaticTemp().newFile("classpath.jar").toPath();
+ writeClassesToJar(classPathJar, CLASSPATH_CLASSES);
+ }
+
+ @Test
+ public void testResolution() throws Exception {
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(
+ buildClasses(C.class, Main.class).addClasspathFiles(classPathJar).build(), Main.class);
+ AppInfoWithLiveness appInfo = appView.appInfo();
+ DexMethod method = buildNullaryVoidMethod(Abstract.class, "foo", appInfo.dexItemFactory());
+ ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
+ DexProgramClass context =
+ appView.definitionForProgramType(buildType(Abstract.class, appInfo.dexItemFactory()));
+ LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+ assertTrue(lookupResult.isLookupResultSuccess());
+ Set<String> targets =
+ lookupResult.asLookupResultSuccess().getMethodTargets().stream()
+ .map(DexEncodedMethod::qualifiedName)
+ .collect(Collectors.toSet());
+ ImmutableSet<String> expected = ImmutableSet.of(C.class.getTypeName() + ".foo");
+ assertEquals(expected, targets);
+ }
+
+ @Test
+ public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+ testForRuntime(parameters)
+ .addProgramClasses(C.class, Main.class)
+ .addRunClasspathFiles(buildOnDexRuntime(parameters, classPathJar))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(C.class, Main.class)
+ .addClasspathClasses(CLASSPATH_CLASSES)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .addRunClasspathFiles(buildOnDexRuntime(parameters, classPathJar))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ public static class C extends NonAbstract {
+
+ @Override
+ public void foo() {
+ System.out.println("C.foo");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ C c = new C();
+ Abstract.run(c);
+ c.foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryTest.java
index f465727..eb08197 100644
--- a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryTest.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.LookupResult;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.resolution.packageprivate.a.A;
@@ -56,18 +57,18 @@
AppInfoWithLiveness appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(A.class, "bar", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
- LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+ DexProgramClass context =
+ appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
+ LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
assertTrue(lookupResult.isLookupResultSuccess());
Set<String> targets =
lookupResult.asLookupResultSuccess().getMethodTargets().stream()
.map(DexEncodedMethod::qualifiedName)
.collect(Collectors.toSet());
- // TODO(b/149363086): Fix expection, should not include C.bar().
ImmutableSet<String> expected =
ImmutableSet.of(
A.class.getTypeName() + ".bar",
B.class.getTypeName() + ".bar",
- C.class.getTypeName() + ".bar",
D.class.getTypeName() + ".bar");
assertEquals(expected, targets);
}
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryWithNarrowingTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryWithNarrowingTest.java
index 35791e7..619ee45 100644
--- a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryWithNarrowingTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryWithNarrowingTest.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.LookupResult;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.resolution.packageprivate.PackagePrivateReentryTest.C;
@@ -60,18 +61,18 @@
AppInfoWithLiveness appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(A.class, "bar", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
- LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+ DexProgramClass context =
+ appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
+ LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
assertTrue(lookupResult.isLookupResultSuccess());
Set<String> targets =
lookupResult.asLookupResultSuccess().getMethodTargets().stream()
.map(DexEncodedMethod::qualifiedName)
.collect(Collectors.toSet());
- // TODO(b/149363086): Fix expection, should not include C.bar().
ImmutableSet<String> expected =
ImmutableSet.of(
A.class.getTypeName() + ".bar",
B.class.getTypeName() + ".bar",
- C.class.getTypeName() + ".bar",
D.class.getTypeName() + ".bar");
assertEquals(expected, targets);
}
@@ -107,7 +108,7 @@
private byte[] getDWithPackagePrivateFoo() throws NoSuchMethodException, IOException {
return transformer(D.class)
- .setAccessFlags(D.class.getDeclaredMethod("bar", null), m -> m.unsetPublic())
+ .setAccessFlags(D.class.getDeclaredMethod("bar"), m -> m.unsetPublic())
.transform();
}
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethod2Test.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethod2Test.java
new file mode 100644
index 0000000..cc85b0d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethod2Test.java
@@ -0,0 +1,166 @@
+// 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.packageprivate;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.LookupResult;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.resolution.packageprivate.a.Abstract;
+import com.android.tools.r8.resolution.packageprivate.a.AbstractWidening;
+import com.android.tools.r8.resolution.packageprivate.a.I;
+import com.android.tools.r8.resolution.packageprivate.a.J;
+import com.android.tools.r8.resolution.packageprivate.a.NonAbstractWideningExtendingA;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.transformers.ClassTransformer;
+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;
+import org.objectweb.asm.MethodVisitor;
+
+@RunWith(Parameterized.class)
+public class PackagePrivateWithDefaultMethod2Test extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public PackagePrivateWithDefaultMethod2Test(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testResolution() throws Exception {
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(
+ buildClasses(
+ Abstract.class,
+ AbstractWidening.class,
+ I.class,
+ A.class,
+ NonAbstractWideningExtendingA.class,
+ J.class,
+ Main.class)
+ .addClassProgramData(getNonAbstractWithoutDeclaredMethods())
+ .build(),
+ Main.class);
+ AppInfoWithLiveness appInfo = appView.appInfo();
+ DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
+ ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
+ DexProgramClass context =
+ appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
+ LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+ assertTrue(lookupResult.isLookupResultSuccess());
+ Set<String> targets =
+ lookupResult.asLookupResultSuccess().getMethodTargets().stream()
+ .map(DexEncodedMethod::qualifiedName)
+ .collect(Collectors.toSet());
+ // TODO(b/148591377): The set should be empty.
+ ImmutableSet<String> expected = ImmutableSet.of(AbstractWidening.class.getTypeName() + ".foo");
+ assertEquals(expected, targets);
+ }
+
+ @Test
+ public void testRuntime() throws ExecutionException, CompilationFailedException, IOException {
+ TestRunResult<?> runResult =
+ testForRuntime(parameters)
+ .addProgramClasses(
+ Abstract.class,
+ AbstractWidening.class,
+ I.class,
+ A.class,
+ NonAbstractWideningExtendingA.class,
+ J.class,
+ Main.class)
+ .addProgramClassFileData(getNonAbstractWithoutDeclaredMethods())
+ .run(parameters.getRuntime(), Main.class);
+ if (parameters.isDexRuntime()
+ && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
+ runResult.assertFailure();
+ } else {
+ runResult.assertFailureWithErrorThatMatches(containsString("AbstractMethodError"));
+ }
+ }
+
+ @Test
+ public void testR8() throws ExecutionException, CompilationFailedException, IOException {
+ R8TestRunResult runResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(
+ Abstract.class,
+ AbstractWidening.class,
+ I.class,
+ A.class,
+ NonAbstractWideningExtendingA.class,
+ J.class,
+ Main.class)
+ .addProgramClassFileData(getNonAbstractWithoutDeclaredMethods())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .disassemble();
+ if (parameters.isDexRuntime()
+ && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
+ runResult.assertFailure();
+ } else {
+ runResult.assertFailureWithErrorThatMatches(containsString("AbstractMethodError"));
+ }
+ }
+
+ private byte[] getNonAbstractWithoutDeclaredMethods() throws IOException {
+ return transformer(NonAbstractWidening.class)
+ .addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public MethodVisitor visitMethod(
+ int access,
+ String name,
+ String descriptor,
+ String signature,
+ String[] exceptions) {
+ if (!name.equals("foo")) {
+ return super.visitMethod(access, name, descriptor, signature, exceptions);
+ }
+ return null;
+ }
+ })
+ .transform();
+ }
+
+ public static class A extends NonAbstractWidening {}
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ NonAbstractWideningExtendingA d = new NonAbstractWideningExtendingA();
+ Abstract.run(d);
+ d.foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java
index c2d041c..42efe5c 100644
--- a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.LookupResult;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.resolution.packageprivate.a.Abstract;
@@ -71,7 +72,9 @@
AppInfoWithLiveness appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
- LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+ DexProgramClass context =
+ appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
+ LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
assertTrue(lookupResult.isLookupResultSuccess());
Set<String> targets =
lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/WidenAccessOutsidePackageTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/WidenAccessOutsidePackageTest.java
index 28c8c2f..b761626 100644
--- a/src/test/java/com/android/tools/r8/resolution/packageprivate/WidenAccessOutsidePackageTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/WidenAccessOutsidePackageTest.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.resolution.packageprivate;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationFailedException;
@@ -17,6 +16,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.LookupResult;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.resolution.packageprivate.a.A;
@@ -57,18 +57,15 @@
AppInfoWithLiveness appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(A.class, "bar", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
- LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
- assertTrue(lookupResult.isLookupResultSuccess());
+ DexProgramClass context =
+ appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
+ LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
Set<String> targets =
lookupResult.asLookupResultSuccess().getMethodTargets().stream()
.map(DexEncodedMethod::qualifiedName)
.collect(Collectors.toSet());
- // TODO(b/149363086): Fix expectation.
ImmutableSet<String> expected =
- ImmutableSet.of(
- A.class.getTypeName() + ".bar",
- B.class.getTypeName() + ".bar",
- C.class.getTypeName() + ".bar");
+ ImmutableSet.of(A.class.getTypeName() + ".bar", B.class.getTypeName() + ".bar");
assertEquals(expected, targets);
}
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/a/AbstractWidening.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/a/AbstractWidening.java
new file mode 100644
index 0000000..472a97b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/a/AbstractWidening.java
@@ -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.resolution.packageprivate.a;
+
+public abstract class AbstractWidening extends Abstract {
+
+ @Override
+ public abstract void foo();
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/a/NonAbstractWideningExtendingA.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/a/NonAbstractWideningExtendingA.java
new file mode 100644
index 0000000..ec0bd6a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/a/NonAbstractWideningExtendingA.java
@@ -0,0 +1,10 @@
+// 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.packageprivate.a;
+
+import com.android.tools.r8.resolution.packageprivate.PackagePrivateWithDefaultMethod2Test;
+
+public class NonAbstractWideningExtendingA extends PackagePrivateWithDefaultMethod2Test.A
+ implements J {}
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java
index 9538dec..88c7b9e 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.LookupResult;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -56,7 +57,9 @@
AppInfoWithLiveness appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
- LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+ DexProgramClass context =
+ appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+ LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
assertTrue(lookupResult.isLookupResultSuccess());
Set<String> targets =
lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java
index 4133fbe..00ef1db 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.LookupResult;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -56,15 +57,15 @@
AppInfoWithLiveness appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
- LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+ DexProgramClass context =
+ appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+ LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
assertTrue(lookupResult.isLookupResultSuccess());
Set<String> targets =
lookupResult.asLookupResultSuccess().getMethodTargets().stream()
.map(DexEncodedMethod::qualifiedName)
.collect(Collectors.toSet());
- // TODO(b/148591377): I.foo() should ideally not be included in the set.
- ImmutableSet<String> expected =
- ImmutableSet.of(I.class.getTypeName() + ".foo", J.class.getTypeName() + ".foo");
+ ImmutableSet<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo");
assertEquals(expected, targets);
}
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java
index 264ddb0..3c30267 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.LookupResult;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -56,7 +57,9 @@
AppInfoWithLiveness appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
- LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+ DexProgramClass context =
+ appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+ LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
assertTrue(lookupResult.isLookupResultSuccess());
Set<String> targets =
lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java
index ff58047..dccf199 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.LookupResult;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -59,7 +60,9 @@
AppInfoWithLiveness appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
- LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+ DexProgramClass context =
+ appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+ LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
assertTrue(lookupResult.isLookupResultSuccess());
Set<String> targets =
lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvalidResolutionToThisTarget.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvalidResolutionToThisTarget.java
new file mode 100644
index 0000000..da24bcb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvalidResolutionToThisTarget.java
@@ -0,0 +1,119 @@
+// 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.virtualtargets;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+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.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InvalidResolutionToThisTarget extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InvalidResolutionToThisTarget(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testResolution() throws Exception {
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(
+ buildClasses(A.class).addClassProgramData(getMainWithModifiedReceiverCall()).build(),
+ Main.class);
+ AppInfoWithLiveness appInfo = appView.appInfo();
+ DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
+ DexType mainType = buildType(Main.class, appInfo.dexItemFactory());
+ ResolutionResult resolutionResult = appInfo.resolveMethod(mainType, method);
+ // TODO(b/149516194): This should be failed resolution.
+ assertFalse(resolutionResult.isFailedResolution());
+ }
+
+ @Test
+ public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+ testForRuntime(parameters)
+ .addProgramClasses(A.class)
+ .addProgramClassFileData(getMainWithModifiedReceiverCall())
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatMatches(containsString("java.lang.VerifyError"));
+ }
+
+ @Test
+ public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+ CompilationFailedException compilationFailedException =
+ assertThrows(
+ CompilationFailedException.class,
+ () -> {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(A.class)
+ .addProgramClassFileData(getMainWithModifiedReceiverCall())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .compile();
+ });
+ }
+
+ private byte[] getMainWithModifiedReceiverCall() throws IOException {
+ return transformer(Main.class)
+ .transformMethodInsnInMethod(
+ "main",
+ (opcode, owner, name, descriptor, isInterface, continuation) -> {
+ if (name.equals("foo")) {
+ continuation.apply(
+ opcode,
+ DescriptorUtils.getBinaryNameFromJavaType(A.class.getTypeName()),
+ name,
+ descriptor,
+ isInterface);
+ } else {
+ continuation.apply(opcode, owner, name, descriptor, isInterface);
+ }
+ })
+ .transform();
+ }
+
+ public static class A {
+
+ public void foo() {
+ System.out.println("A.foo");
+ }
+ }
+
+ public static class Main {
+
+ public void foo() {
+ System.out.println("Main.foo");
+ new A().foo();
+ }
+
+ public static void main(String[] args) {
+ new Main().foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java
index 055c113..7d32527 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.LookupResult;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -55,7 +56,9 @@
AppInfoWithLiveness appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
- LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+ DexProgramClass context =
+ appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+ LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
assertTrue(lookupResult.isLookupResultSuccess());
Set<String> targets =
lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateChainTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateChainTest.java
index 45c208a..37c071a 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateChainTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateChainTest.java
@@ -5,6 +5,9 @@
package com.android.tools.r8.resolution.virtualtargets;
import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.TestBase;
@@ -12,11 +15,21 @@
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.LookupResult;
+import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.resolution.virtualtargets.package_a.Middle;
import com.android.tools.r8.resolution.virtualtargets.package_a.Top;
import com.android.tools.r8.resolution.virtualtargets.package_a.TopRunner;
+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;
@@ -39,6 +52,30 @@
}
@Test
+ public void testResolution() throws Exception {
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(
+ buildClasses(Top.class, Middle.class, Bottom.class, TopRunner.class, Main.class)
+ .build(),
+ Main.class);
+ AppInfoWithLiveness appInfo = appView.appInfo();
+ DexMethod method = buildNullaryVoidMethod(Top.class, "clear", appInfo.dexItemFactory());
+ ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
+ DexProgramClass context =
+ appView.definitionForProgramType(buildType(TopRunner.class, appInfo.dexItemFactory()));
+ LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+ assertTrue(lookupResult.isLookupResultSuccess());
+ Set<String> targets =
+ lookupResult.asLookupResultSuccess().getMethodTargets().stream()
+ .map(DexEncodedMethod::qualifiedName)
+ .collect(Collectors.toSet());
+ ImmutableSet<String> expected =
+ ImmutableSet.of(Top.class.getTypeName() + ".clear", Middle.class.getTypeName() + ".clear");
+ assertEquals(expected, targets);
+ }
+
+ @Test
public void testRuntime() throws ExecutionException, CompilationFailedException, IOException {
TestRunResult<?> runResult =
testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
@@ -60,7 +97,7 @@
.addKeepMainRule(Main.class)
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
- .assertSuccessWithOutputLines("Bottom.clear()", "Bottom.clear()");
+ .assertFailureWithErrorThatMatches(containsString("AbstractMethodError"));
}
public static class Bottom extends Middle {
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideTest.java
index e786da6..dfc9d43 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideTest.java
@@ -5,22 +5,34 @@
package com.android.tools.r8.resolution.virtualtargets;
import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+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.R8TestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.LookupResult;
+import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.resolution.virtualtargets.package_a.ViewModel;
import com.android.tools.r8.resolution.virtualtargets.package_a.ViewModelRunner;
import com.android.tools.r8.resolution.virtualtargets.package_a.ViewModelRunnerWithCast;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.DescriptorUtils;
+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;
@@ -32,6 +44,9 @@
private static final String[] EXPECTED =
new String[] {"ViewModel.clear()", "MyViewModel.clear()", "ViewModel.clear()"};
+ private static final String[] R8_OUTPUT =
+ new String[] {"MyViewModel.clear()", "MyViewModel.clear()", "MyViewModel.clear()"};
+
private final TestParameters parameters;
@Parameters(name = "{0}")
@@ -44,6 +59,30 @@
}
@Test
+ public void testResolution() throws Exception {
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(
+ buildClasses(MyViewModel.class, ViewModel.class, Main.class, ViewModelRunner.class)
+ .build(),
+ Main.class);
+ AppInfoWithLiveness appInfo = appView.appInfo();
+ DexMethod method = buildNullaryVoidMethod(ViewModel.class, "clear", appInfo.dexItemFactory());
+ ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
+ DexProgramClass context =
+ appView.definitionForProgramType(
+ buildType(ViewModelRunner.class, appInfo.dexItemFactory()));
+ LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+ assertTrue(lookupResult.isLookupResultSuccess());
+ Set<String> targets =
+ lookupResult.asLookupResultSuccess().getMethodTargets().stream()
+ .map(DexEncodedMethod::qualifiedName)
+ .collect(Collectors.toSet());
+ ImmutableSet<String> expected = ImmutableSet.of(ViewModel.class.getTypeName() + ".clear");
+ assertEquals(expected, targets);
+ }
+
+ @Test
public void testRuntime() throws ExecutionException, CompilationFailedException, IOException {
TestRunResult<?> runResult =
testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
@@ -60,19 +99,30 @@
@Test
public void testR8() throws ExecutionException, CompilationFailedException, IOException {
- R8TestRunResult runResult =
- testForR8(parameters.getBackend())
- .addProgramClasses(
- MyViewModel.class, Main.class, ViewModel.class, ViewModelRunner.class)
- .addKeepMainRule(Main.class)
- .setMinApi(parameters.getApiLevel())
- .run(parameters.getRuntime(), Main.class);
- if (parameters.isDexRuntime()
- && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
- runResult.assertFailureWithErrorThatMatches(containsString("overrides final"));
- } else {
- runResult.assertFailureWithErrorThatMatches(containsString("java.lang.NullPointerException"));
- }
+ // TODO(b/148429150): Fix R8 to output expected.
+ testForR8(parameters.getBackend())
+ .addProgramClasses(MyViewModel.class, Main.class, ViewModel.class, ViewModelRunner.class)
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(R8_OUTPUT);
+ }
+
+ @Test
+ public void testResolutionWithInvalidInvoke() throws Exception {
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(
+ buildClasses(MyViewModel.class, ViewModel.class, Main.class, ViewModelRunner.class)
+ .build(),
+ Main.class);
+ AppInfoWithLiveness appInfo = appView.appInfo();
+ DexMethod method = buildNullaryVoidMethod(ViewModel.class, "clear", appInfo.dexItemFactory());
+ ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
+ DexProgramClass context =
+ appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+ LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+ assertTrue(lookupResult.isLookupResultFailure());
}
@Test
@@ -94,19 +144,39 @@
@Test
public void testR8WithInvalidInvoke()
throws ExecutionException, CompilationFailedException, IOException {
- R8TestRunResult runResult =
- testForR8(parameters.getBackend())
- .addProgramClasses(MyViewModel.class, ViewModel.class, ViewModelRunner.class)
- .addProgramClassFileData(getModifiedMainWithIllegalInvokeToViewModelClear())
- .addKeepMainRule(Main.class)
- .setMinApi(parameters.getApiLevel())
- .run(parameters.getRuntime(), Main.class);
- if (parameters.isDexRuntime()
- && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
- runResult.assertFailureWithErrorThatMatches(containsString("overrides final"));
- } else {
- runResult.assertFailureWithErrorThatMatches(containsString("java.lang.NullPointerException"));
- }
+ // TODO(b/148429150): Fix R8 to output expected.
+ testForR8(parameters.getBackend())
+ .addProgramClasses(MyViewModel.class, ViewModel.class, ViewModelRunner.class)
+ .addProgramClassFileData(getModifiedMainWithIllegalInvokeToViewModelClear())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatMatches(containsString("java.lang.NullPointerException"));
+ }
+
+ @Test
+ public void testResolutionWithAmbiguousInvoke() throws Exception {
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(
+ buildClasses(
+ MyViewModel.class, ViewModel.class, Main.class, ViewModelRunnerWithCast.class)
+ .build(),
+ Main.class);
+ AppInfoWithLiveness appInfo = appView.appInfo();
+ DexMethod method = buildNullaryVoidMethod(ViewModel.class, "clear", appInfo.dexItemFactory());
+ ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
+ DexProgramClass context =
+ appView.definitionForProgramType(
+ buildType(ViewModelRunnerWithCast.class, appInfo.dexItemFactory()));
+ LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+ assertTrue(lookupResult.isLookupResultSuccess());
+ Set<String> targets =
+ lookupResult.asLookupResultSuccess().getMethodTargets().stream()
+ .map(DexEncodedMethod::qualifiedName)
+ .collect(Collectors.toSet());
+ ImmutableSet<String> expected = ImmutableSet.of(ViewModel.class.getTypeName() + ".clear");
+ assertEquals(expected, targets);
}
@Test
@@ -129,19 +199,14 @@
@Test
public void testR8WithAmbiguousInvoke()
throws ExecutionException, CompilationFailedException, IOException {
- R8TestRunResult runResult =
- testForR8(parameters.getBackend())
- .addProgramClasses(MyViewModel.class, ViewModel.class, Main.class)
- .addProgramClassFileData(getModifiedViewModelRunnerWithDirectMyViewModelTarget())
- .addKeepMainRule(Main.class)
- .setMinApi(parameters.getApiLevel())
- .run(parameters.getRuntime(), Main.class);
- if (parameters.isDexRuntime()
- && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
- runResult.assertFailureWithErrorThatMatches(containsString("overrides final"));
- } else {
- runResult.assertFailureWithErrorThatMatches(containsString("java.lang.NullPointerException"));
- }
+ // TODO(b/148429150): Fix R8 to output expected.
+ testForR8(parameters.getBackend())
+ .addProgramClasses(MyViewModel.class, ViewModel.class, Main.class)
+ .addProgramClassFileData(getModifiedViewModelRunnerWithDirectMyViewModelTarget())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(R8_OUTPUT);
}
private byte[] getModifiedMainWithIllegalInvokeToViewModelClear() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PrivateOverrideOfVirtualTargetTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PrivateOverrideOfVirtualTargetTest.java
new file mode 100644
index 0000000..915cd21
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PrivateOverrideOfVirtualTargetTest.java
@@ -0,0 +1,149 @@
+// 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.virtualtargets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.LookupResult;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.resolution.virtualtargets.package_a.A;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.DescriptorUtils;
+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;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class PrivateOverrideOfVirtualTargetTest extends TestBase {
+
+ private final TestParameters parameters;
+ private static final String[] EXPECTED =
+ new String[] {"A.foo", "A.bar", "B.foo", "A.bar", "B.bar"};
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public PrivateOverrideOfVirtualTargetTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testResolution() throws Exception {
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(
+ buildClasses(A.class, Main.class)
+ .addClassProgramData(getBWithModifiedInvokes())
+ .build(),
+ DefaultWithoutTopTest.Main.class);
+ AppInfoWithLiveness appInfo = appView.appInfo();
+ DexMethod method = buildNullaryVoidMethod(A.class, "bar", appInfo.dexItemFactory());
+ ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
+ DexProgramClass context =
+ appView.definitionForProgramType(buildType(B.class, appInfo.dexItemFactory()));
+ LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+ assertTrue(lookupResult.isLookupResultSuccess());
+ Set<String> targets =
+ lookupResult.asLookupResultSuccess().getMethodTargets().stream()
+ .map(DexEncodedMethod::qualifiedName)
+ .collect(Collectors.toSet());
+ ImmutableSet<String> expected = ImmutableSet.of(A.class.getTypeName() + ".bar");
+ assertEquals(expected, targets);
+ }
+
+ @Test
+ public void testRuntime()
+ throws NoSuchMethodException, IOException, CompilationFailedException, ExecutionException {
+ testForRuntime(parameters)
+ .addProgramClasses(A.class, Main.class)
+ .addProgramClassFileData(getBWithModifiedInvokes())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8()
+ throws NoSuchMethodException, IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(A.class, Main.class)
+ .addProgramClassFileData(getBWithModifiedInvokes())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ private byte[] getBWithModifiedInvokes() throws NoSuchMethodException, IOException {
+ Box<Boolean> modifyOwner = new Box<>(true);
+ return transformer(B.class)
+ .setPrivate(B.class.getDeclaredMethod("bar"))
+ .transformMethodInsnInMethod(
+ "callOnB",
+ (opcode, owner, name, descriptor, isInterface, continuation) -> {
+ if (name.equals("foo")) {
+ continuation.apply(opcode, owner, name, descriptor, isInterface);
+ return;
+ }
+ if (modifyOwner.get()) {
+ continuation.apply(
+ Opcodes.INVOKEVIRTUAL,
+ DescriptorUtils.getBinaryNameFromJavaType(A.class.getTypeName()),
+ name,
+ descriptor,
+ isInterface);
+ modifyOwner.set(false);
+ } else {
+ continuation.apply(Opcodes.INVOKEVIRTUAL, owner, name, descriptor, isInterface);
+ }
+ })
+ .transform();
+ }
+
+ public static class B extends A {
+ private void foo() {
+ System.out.println("B.foo");
+ }
+
+ @Override
+ protected /* private */ void bar() {
+ System.out.println("B.bar");
+ }
+
+ void callOnB() {
+ foo();
+ bar(); /* will become A.bar() */
+ bar(); /* will become B.bar() */
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ B b = new B();
+ A.run(b);
+ b.callOnB();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
index 570527c..3afeb68 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.LookupResult;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -56,7 +57,9 @@
AppInfoWithLiveness appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
- LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+ DexProgramClass context =
+ appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+ LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
assertTrue(lookupResult.isLookupResultSuccess());
Set<String> targets =
lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/A.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/A.java
new file mode 100644
index 0000000..1b311d5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/A.java
@@ -0,0 +1,21 @@
+// 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.virtualtargets.package_a;
+
+public class A {
+
+ void foo() {
+ System.out.println("A.foo");
+ }
+
+ protected void bar() {
+ System.out.println("A.bar");
+ }
+
+ public static void run(A a) {
+ a.foo();
+ a.bar();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java b/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java
index af204ec..5f6a618 100644
--- a/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java
@@ -31,7 +31,7 @@
public static void main(String[] args) throws Exception {
C obj = new C();
for (Method m : obj.getClass().getDeclaredMethods()) {
- m.invoke(obj, null);
+ m.invoke(obj);
}
}
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index a471b18..7331a22 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.DexClass;
import java.util.List;
import java.util.function.Consumer;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
public class AbsentClassSubject extends ClassSubject {
@@ -147,4 +148,9 @@
public KmPackageSubject getKmPackage() {
return null;
}
+
+ @Override
+ public KotlinClassMetadata getKotlinClassMetadata() {
+ return null;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index d72d1a8..b693ad9 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -18,6 +18,7 @@
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
public abstract class ClassSubject extends Subject {
@@ -174,4 +175,6 @@
public abstract KmClassSubject getKmClass();
public abstract KmPackageSubject getKmPackage();
+
+ public abstract KotlinClassMetadata getKotlinClassMetadata();
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index c0dba89..dc65b71 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -355,8 +355,25 @@
KotlinClassMetadata metadata =
KotlinClassMetadataReader.toKotlinClassMetadata(
codeInspector.getFactory().kotlin, annotationSubject.getAnnotation());
- assertTrue(metadata instanceof KotlinClassMetadata.FileFacade);
- KotlinClassMetadata.FileFacade kFile = (KotlinClassMetadata.FileFacade) metadata;
- return new FoundKmPackageSubject(codeInspector, getDexClass(), kFile.toKmPackage());
+ assertTrue(metadata instanceof KotlinClassMetadata.FileFacade
+ || metadata instanceof KotlinClassMetadata.MultiFileClassPart);
+ if (metadata instanceof KotlinClassMetadata.FileFacade) {
+ KotlinClassMetadata.FileFacade kFile = (KotlinClassMetadata.FileFacade) metadata;
+ return new FoundKmPackageSubject(codeInspector, getDexClass(), kFile.toKmPackage());
+ } else {
+ KotlinClassMetadata.MultiFileClassPart kPart =
+ (KotlinClassMetadata.MultiFileClassPart) metadata;
+ return new FoundKmPackageSubject(codeInspector, getDexClass(), kPart.toKmPackage());
+ }
+ }
+
+ @Override
+ public KotlinClassMetadata getKotlinClassMetadata() {
+ AnnotationSubject annotationSubject = annotation(METADATA_TYPE);
+ if (!annotationSubject.isPresent()) {
+ return null;
+ }
+ return KotlinClassMetadataReader.toKotlinClassMetadata(
+ codeInspector.getFactory().kotlin, annotationSubject.getAnnotation());
}
}
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 b9fa99a..5b3338b 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
@@ -80,6 +80,7 @@
}
}
+ // TODO(b/70169921): Search both original and renamed names.
default KmFunctionSubject kmFunctionOrExtensionWithUniqueName(String name, boolean isExtension) {
KmFunction foundFunction = null;
for (KmFunction kmFunction : getKmDeclarationContainer().getFunctions()) {
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index cf26883..abd0f71 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -1224,6 +1224,7 @@
assert shrinker in SHRINKERS
else:
options.shrinker = [shrinker for shrinker in SHRINKERS]
+
if options.hash or options.version:
# No need to build R8 if a specific version should be used.
options.no_build = True
@@ -1266,7 +1267,7 @@
options.no_logging = False
# TODO(b/141081520): Remove logging filter once fixed.
options.app_logging_filter = ['sqldelight']
- options.shrinker = [shrinker for shrinker in SHRINKERS if shrinker != 'pg']
+ options.shrinker = ['r8', 'r8-full']
print(options.shrinker)
if options.golem: