Reland "Prevent merging of virtual methods outside of main-dex"
This reverts commit 8920f58a77461485b7611017a16d6cd594592210.
Change-Id: Ic388dd69940c1019c6850702aeb72b59ce3a0018
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index b3e100a..2ebde33 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -84,7 +84,7 @@
}
Enqueuer enqueuer =
- EnqueuerFactory.createForFinalMainDexTracing(
+ EnqueuerFactory.createForGenerateMainDexList(
appView, executor, subtypingInfo, graphConsumer);
MainDexInfo mainDexInfo = enqueuer.traceMainDex(executor, timing);
R8.processWhyAreYouKeepingAndCheckDiscarded(
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index 25f4d3b..80b5ec7 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -364,7 +364,7 @@
AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(
application,
ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap(),
- MainDexInfo.createEmptyMainDexClasses());
+ MainDexInfo.none());
}
private void analyze() {
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index eef9f3d..283e461 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -535,6 +535,9 @@
}
}
+ // Clear traced methods roots to not hold on to the main dex live method set.
+ appView.appInfo().getMainDexInfo().clearTracedMethodRoots();
+
// None of the optimizations above should lead to the creation of type lattice elements.
assert appView.dexItemFactory().verifyNoCachedTypeElements();
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 23db0d9..21de6ac 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -199,7 +199,7 @@
}
public MainDexInfo readMainDexClasses(DexApplication app) {
- MainDexInfo.Builder builder = MainDexInfo.builder();
+ MainDexInfo.Builder builder = MainDexInfo.none().builder();
if (inputApp.hasMainDexList()) {
for (StringResource resource : inputApp.getMainDexListResources()) {
addToMainDexClasses(app, builder, MainDexListParser.parseList(resource, itemFactory));
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 d8785d1..de3d976 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -28,7 +28,7 @@
private final BooleanBox obsolete;
public static AppInfo createInitialAppInfo(DexApplication application) {
- return createInitialAppInfo(application, MainDexInfo.createEmptyMainDexClasses());
+ return createInitialAppInfo(application, MainDexInfo.none());
}
public static AppInfo createInitialAppInfo(DexApplication application, MainDexInfo mainDexInfo) {
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 954580b..b56d682 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -156,7 +156,7 @@
}
public static AppView<AppInfoWithClassHierarchy> createForR8(DexApplication application) {
- return createForR8(application, MainDexInfo.createEmptyMainDexClasses());
+ return createForR8(application, MainDexInfo.none());
}
public static AppView<AppInfoWithClassHierarchy> createForR8(
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
index fcafba0..4a789ba 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
@@ -20,6 +20,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
/**
* Policy that enforces that methods are only merged if they have the same visibility and library
@@ -32,8 +33,10 @@
private final MethodAccessFlags accessFlags;
private final boolean isAssumeNoSideEffectsMethod;
private final OptionalBool isLibraryMethodOverride;
+ private final boolean isMainDexRoot;
- private MethodCharacteristics(boolean isAssumeNoSideEffectsMethod, DexEncodedMethod method) {
+ private MethodCharacteristics(
+ DexEncodedMethod method, boolean isAssumeNoSideEffectsMethod, boolean isMainDexRoot) {
this.accessFlags =
MethodAccessFlags.builder()
.setPrivate(method.getAccessFlags().isPrivate())
@@ -44,17 +47,24 @@
.build();
this.isAssumeNoSideEffectsMethod = isAssumeNoSideEffectsMethod;
this.isLibraryMethodOverride = method.isLibraryMethodOverride();
+ this.isMainDexRoot = isMainDexRoot;
}
static MethodCharacteristics create(
AppView<AppInfoWithLiveness> appView, DexEncodedMethod method) {
return new MethodCharacteristics(
- appView.appInfo().isAssumeNoSideEffectsMethod(method.getReference()), method);
+ method,
+ appView.appInfo().isAssumeNoSideEffectsMethod(method.getReference()),
+ appView.appInfo().getMainDexInfo().isTracedMethodRoot(method.getReference()));
}
@Override
public int hashCode() {
- return (accessFlags.hashCode() << 2) | isLibraryMethodOverride.ordinal();
+ return Objects.hash(
+ accessFlags,
+ isAssumeNoSideEffectsMethod,
+ isLibraryMethodOverride.ordinal(),
+ isMainDexRoot);
}
@Override
@@ -68,7 +78,8 @@
MethodCharacteristics characteristics = (MethodCharacteristics) obj;
return accessFlags.equals(characteristics.accessFlags)
&& isAssumeNoSideEffectsMethod == characteristics.isAssumeNoSideEffectsMethod
- && isLibraryMethodOverride == characteristics.isLibraryMethodOverride;
+ && isLibraryMethodOverride == characteristics.isLibraryMethodOverride
+ && isMainDexRoot == characteristics.isMainDexRoot;
}
}
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 9c672b1..9161f4f 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -177,6 +177,7 @@
FINAL_TREE_SHAKING,
INITIAL_MAIN_DEX_TRACING,
FINAL_MAIN_DEX_TRACING,
+ GENERATE_MAIN_DEX_LIST,
WHY_ARE_YOU_KEEPING;
public boolean isInitialTreeShaking() {
@@ -199,8 +200,12 @@
return this == FINAL_MAIN_DEX_TRACING;
}
+ public boolean isGenerateMainDexList() {
+ return this == GENERATE_MAIN_DEX_LIST;
+ }
+
public boolean isMainDexTracing() {
- return isInitialMainDexTracing() || isFinalMainDexTracing();
+ return isInitialMainDexTracing() || isFinalMainDexTracing() || isGenerateMainDexList();
}
public boolean isWhyAreYouKeeping() {
@@ -3030,8 +3035,14 @@
trace(executorService, timing);
options.reporter.failIfPendingErrors();
// Calculate the automatic main dex list according to legacy multidex constraints.
- MainDexInfo.Builder builder = MainDexInfo.builder();
+ MainDexInfo.Builder builder = appView.appInfo().getMainDexInfo().builder();
liveTypes.getItems().forEach(builder::addRoot);
+ if (mode.isInitialMainDexTracing()) {
+ liveMethods.getItems().forEach(method -> builder.addRoot(method.method));
+ } else {
+ assert appView.appInfo().getMainDexInfo().isTracedMethodRootsCleared()
+ || mode.isGenerateMainDexList();
+ }
new MainDexListBuilder(appView, builder.getRoots(), builder).run();
MainDexInfo previousMainDexInfo = appInfo.getMainDexInfo();
return builder.build(previousMainDexInfo);
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
index 7636fe8..cfce371 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
@@ -54,6 +54,15 @@
appView, executorService, subtypingInfo, keptGraphConsumer, Mode.FINAL_MAIN_DEX_TRACING);
}
+ public static Enqueuer createForGenerateMainDexList(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ ExecutorService executorService,
+ SubtypingInfo subtypingInfo,
+ GraphConsumer keptGraphConsumer) {
+ return new Enqueuer(
+ appView, executorService, subtypingInfo, keptGraphConsumer, Mode.GENERATE_MAIN_DEX_LIST);
+ }
+
public static Enqueuer createForWhyAreYouKeeping(
AppView<? extends AppInfoWithClassHierarchy> appView,
ExecutorService executorService,
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java b/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java
index 8d793aa..58315d4 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java
@@ -9,6 +9,7 @@
import static com.android.tools.r8.utils.PredicateUtils.not;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
@@ -19,6 +20,7 @@
import com.android.tools.r8.utils.ConsumerUtils;
import com.android.tools.r8.utils.SetUtils;
import com.google.common.collect.Sets;
+import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -26,7 +28,14 @@
public class MainDexInfo {
- private static final MainDexInfo NONE = new MainDexInfo(Sets.newIdentityHashSet());
+ private static final MainDexInfo NONE =
+ new MainDexInfo(
+ Collections.emptySet(),
+ Collections.emptySet(),
+ Collections.emptySet(),
+ Collections.emptySet(),
+ Collections.emptySet(),
+ false);
public enum MainDexGroup {
MAIN_DEX_LIST,
@@ -40,24 +49,38 @@
private final Map<DexType, DexType> synthesizedClassesMap;
// Traced roots are traced main dex references.
private final Set<DexType> tracedRoots;
+ // Traced method roots are the methods visited from an initial main dex root set. The set is
+ // cleared after the mergers has run.
+ private Set<DexMethod> tracedMethodRoots;
// Traced dependencies are those classes that are directly referenced from traced roots, but will
// not be loaded before loading the remaining dex files.
private final Set<DexType> tracedDependencies;
+ // Bit indicating if the traced methods are cleared.
+ private boolean tracedMethodRootsCleared = false;
private MainDexInfo(Set<DexType> classList) {
this(
- classList, Sets.newIdentityHashSet(), Sets.newIdentityHashSet(), Sets.newIdentityHashSet());
+ classList,
+ Collections.emptySet(),
+ Collections.emptySet(),
+ Collections.emptySet(),
+ /* synthesized classes - cannot be emptyset */ Sets.newIdentityHashSet(),
+ false);
}
private MainDexInfo(
Set<DexType> classList,
Set<DexType> tracedRoots,
+ Set<DexMethod> tracedMethodRoots,
Set<DexType> tracedDependencies,
- Set<DexType> synthesizedClasses) {
+ Set<DexType> synthesizedClasses,
+ boolean tracedMethodRootsCleared) {
this.classList = classList;
this.tracedRoots = tracedRoots;
+ this.tracedMethodRoots = tracedMethodRoots;
this.tracedDependencies = tracedDependencies;
this.synthesizedClassesMap = new ConcurrentHashMap<>();
+ this.tracedMethodRootsCleared = tracedMethodRootsCleared;
synthesizedClasses.forEach(type -> synthesizedClassesMap.put(type, type));
assert tracedDependencies.stream().noneMatch(tracedRoots::contains);
assert tracedRoots.containsAll(synthesizedClasses);
@@ -84,6 +107,11 @@
return isTracedRoot(definition.getContextType());
}
+ public boolean isTracedMethodRoot(DexMethod method) {
+ assert !tracedMethodRootsCleared : "Traced method roots are cleared after mergers has run";
+ return tracedMethodRoots.contains(method);
+ }
+
private boolean isTracedRoot(DexReference reference) {
return tracedRoots.contains(reference.getContextType());
}
@@ -96,6 +124,15 @@
return tracedDependencies.contains(reference.getContextType());
}
+ public boolean isTracedMethodRootsCleared() {
+ return tracedMethodRootsCleared;
+ }
+
+ public void clearTracedMethodRoots() {
+ this.tracedMethodRootsCleared = true;
+ this.tracedMethodRoots = Sets.newIdentityHashSet();
+ }
+
public boolean canRebindReference(ProgramMethod context, DexReference referenceToTarget) {
MainDexGroup holderGroup = getMainDexGroupInternal(context);
if (holderGroup == MainDexGroup.NOT_IN_MAIN_DEX
@@ -105,8 +142,7 @@
}
if (holderGroup == MainDexGroup.MAIN_DEX_LIST) {
// If the holder is in the class list, we are not allowed to make any assumptions on the
- // holder
- // being a root or a dependency. Therefore we cannot merge.
+ // holder being a root or a dependency. Therefore we cannot merge.
return false;
}
assert holderGroup == MAIN_DEX_ROOT;
@@ -244,6 +280,12 @@
.forEach(type -> ifNotRemoved(type, removedClasses, modifiedSynthesized::add));
MainDexInfo.Builder builder = builder();
tracedRoots.forEach(type -> ifNotRemoved(type, removedClasses, builder::addRoot));
+ // TODO(b/169927809): Methods could be pruned without the holder being pruned, however, one has
+ // to have a reference for querying a root.
+ tracedMethodRoots.forEach(
+ method ->
+ ifNotRemoved(
+ method.getHolderType(), removedClasses, ignored -> builder.addRoot(method)));
tracedDependencies.forEach(type -> ifNotRemoved(type, removedClasses, builder::addDependency));
return builder.build(modifiedClassList, modifiedSynthesized);
}
@@ -263,6 +305,7 @@
synthesizedClassesMap.keySet().forEach(type -> modifiedSynthesized.add(lens.lookupType(type)));
MainDexInfo.Builder builder = builder();
tracedRoots.forEach(type -> rewriteAndApplyIfNotPrimitiveType(lens, type, builder::addRoot));
+ tracedMethodRoots.forEach(method -> builder.addRoot(lens.getRenamedMethodSignature(method)));
tracedDependencies.forEach(
type -> {
if (lens.isSyntheticFinalizationGraphLens()) {
@@ -276,25 +319,21 @@
return builder.build(modifiedClassList, modifiedSynthesized);
}
- public static Builder builder() {
- return new Builder();
- }
-
- public static MainDexInfo createEmptyMainDexClasses() {
- return new MainDexInfo(
- Sets.newIdentityHashSet(),
- Sets.newIdentityHashSet(),
- Sets.newIdentityHashSet(),
- Sets.newIdentityHashSet());
+ public Builder builder() {
+ return new Builder(tracedMethodRootsCleared);
}
public static class Builder {
private final Set<DexType> list = Sets.newIdentityHashSet();
private final Set<DexType> roots = Sets.newIdentityHashSet();
+ private final Set<DexMethod> methodRoots = Sets.newIdentityHashSet();
private final Set<DexType> dependencies = Sets.newIdentityHashSet();
+ private final boolean tracedMethodRootsCleared;
- private Builder() {}
+ private Builder(boolean tracedMethodRootsCleared) {
+ this.tracedMethodRootsCleared = tracedMethodRootsCleared;
+ }
public void addList(DexProgramClass clazz) {
addList(clazz.getType());
@@ -313,6 +352,10 @@
roots.add(type);
}
+ public void addRoot(DexMethod method) {
+ methodRoots.add(method);
+ }
+
public void addDependency(DexProgramClass clazz) {
addDependency(clazz.getType());
}
@@ -371,8 +414,10 @@
return new MainDexInfo(
classList,
SetUtils.unionIdentityHashSet(roots, synthesizedClasses),
+ methodRoots,
Sets.difference(dependencies, synthesizedClasses),
- synthesizedClasses);
+ synthesizedClasses,
+ tracedMethodRootsCleared);
}
public MainDexInfo build(MainDexInfo previous) {
diff --git a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
index 2bac333..039f28d 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
@@ -236,7 +236,7 @@
AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(
application,
ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap(),
- MainDexInfo.createEmptyMainDexClasses());
+ MainDexInfo.none());
}
void run(TraceReferencesConsumer consumer) {
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 3df21b1..a80cc32 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -710,7 +710,7 @@
return AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(
readApplicationForDexOutput(app, new InternalOptions()),
ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap(),
- MainDexInfo.createEmptyMainDexClasses());
+ MainDexInfo.none());
}
protected static AppView<AppInfoWithClassHierarchy> computeAppViewWithClassHierachy(
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListMergeInRootTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListMergeInRootTest.java
index 7041c75..9451f9a 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListMergeInRootTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListMergeInRootTest.java
@@ -4,17 +4,13 @@
package com.android.tools.r8.maindexlist;
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
-import static com.android.tools.r8.utils.codeinspector.AssertUtils.assertFailsCompilation;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assume.assumeTrue;
-
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
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.HorizontallyMergedClassesInspector;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -27,7 +23,10 @@
@Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ return getTestParameters()
+ .withDexRuntimes()
+ .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
+ .build();
}
public MainDexListMergeInRootTest(TestParameters parameters) {
@@ -35,35 +34,26 @@
}
@Test
- public void testMainDexTracing() {
- assumeTrue(parameters.getDexRuntimeVersion().isDalvik());
- assertFailsCompilation(
- () ->
- testForR8(parameters.getBackend())
- .addProgramClasses(OutsideMainDex.class, InsideA.class, InsideB.class, Main.class)
- .addKeepClassAndMembersRules(Main.class)
- .setMinApi(parameters.getApiLevel())
- .enableNeverClassInliningAnnotations()
- .enableNoHorizontalClassMergingAnnotations()
- .enableInliningAnnotations()
- .noMinification()
- .addMainDexRules(
- "-keep class "
- + Main.class.getTypeName()
- + " { public static void main(***); }")
- .addOptionsModification(
- options -> {
- options.testing.checkForNotExpandingMainDexTracingResult = true;
- })
- .compileWithExpectedDiagnostics(
- diagnostics -> {
- diagnostics.assertErrorsMatch(
- diagnosticMessage(
- containsString(
- "Class com.android.tools.r8.maindexlist"
- + ".MainDexListMergeInRootTest$OutsideMainDex"
- + " was not a main dex root in the first round")));
- }));
+ public void testMainDexTracing() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(OutsideMainDex.class, InsideA.class, InsideB.class, Main.class)
+ .addKeepClassAndMembersRules(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableNeverClassInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .enableInliningAnnotations()
+ .noMinification()
+ .addMainDexRules(
+ "-keep class " + Main.class.getTypeName() + " { public static void main(***); }")
+ .addOptionsModification(
+ options -> {
+ options.testing.checkForNotExpandingMainDexTracingResult = true;
+ })
+ // TODO(b/178151906): See if we can merge the classes.
+ .addHorizontallyMergedClassesInspector(
+ HorizontallyMergedClassesInspector::assertNoClassesMerged)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("InsideB::live0", "InsideA::live");
}
@NoHorizontalClassMerging
@@ -80,7 +70,7 @@
public static class InsideA {
public void bar() {
- System.out.println("A::live");
+ System.out.println("InsideA::live");
}
/* Not a traced root */