Support for constructor shrinking when compiling to api >= L
Bug: b/173751869
Change-Id: Iefbeec728f551b7b48f7a558ec39f636d113ee9a
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index d4e7f3a..d57d450 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -68,6 +68,7 @@
import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
import com.android.tools.r8.optimize.AccessModifier;
import com.android.tools.r8.optimize.MemberRebindingAnalysis;
+import com.android.tools.r8.optimize.MemberRebindingIdentityLens;
import com.android.tools.r8.optimize.MemberRebindingIdentityLensFactory;
import com.android.tools.r8.optimize.RedundantBridgeRemover;
import com.android.tools.r8.optimize.bridgehoisting.BridgeHoisting;
@@ -460,7 +461,7 @@
// We can now remove redundant bridges. Note that we do not need to update the
// invoke-targets here, as the existing invokes will simply dispatch to the now
// visible super-method. MemberRebinding, if run, will then dispatch it correctly.
- new RedundantBridgeRemover(appView.withLiveness()).run(executorService);
+ new RedundantBridgeRemover(appView.withLiveness()).run(null, executorService);
}
}
@@ -688,10 +689,17 @@
}
}
+ // Insert a member rebinding oracle in the graph to ensure that all subsequent rewritings of
+ // the application has an applied oracle for looking up non-rebound references.
+ MemberRebindingIdentityLens memberRebindingIdentityLens =
+ MemberRebindingIdentityLensFactory.create(appView, executorService);
+ appView.setGraphLens(memberRebindingIdentityLens);
+
// Remove redundant bridges that have been inserted for member rebinding.
// This can only be done if we have AppInfoWithLiveness.
if (appView.appInfo().hasLiveness()) {
- new RedundantBridgeRemover(appView.withLiveness()).run(executorService);
+ new RedundantBridgeRemover(appView.withLiveness())
+ .run(memberRebindingIdentityLens, executorService);
} else {
// If we don't have AppInfoWithLiveness here, it must be because we are not shrinking. When
// we are not shrinking, we can't move visibility bridges. In principle, though, it would be
@@ -700,10 +708,6 @@
assert !options.isShrinking();
}
- // Insert a member rebinding oracle in the graph to ensure that all subsequent rewritings of
- // the application has an applied oracle for looking up non-rebound references.
- appView.setGraphLens(MemberRebindingIdentityLensFactory.create(appView, executorService));
-
if (appView.appInfo().hasLiveness()) {
SyntheticFinalization.finalizeWithLiveness(appView.withLiveness(), executorService, timing);
} else {
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index b891c89..42f5d96 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -688,6 +688,13 @@
return resolveMethodOnLegacy(invokedMethod.getHolderType(), invokedMethod, isInterface);
}
+ public MethodResolutionResult resolveMethodOn(DexClass clazz, DexMethod method) {
+ assert checkIfObsolete();
+ return clazz.isInterface()
+ ? resolveMethodOnInterface(clazz, method)
+ : resolveMethodOnClass(clazz, method);
+ }
+
public MethodResolutionResult resolveMethodOnLegacy(DexClass clazz, DexMethod method) {
assert checkIfObsolete();
return clazz.isInterface()
diff --git a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
index 5569e34..425c5d5 100644
--- a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.dex.code.DexReturnVoid;
import com.android.tools.r8.graph.DexCode.Try;
import com.android.tools.r8.graph.DexCode.TryHandler;
+import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.NumberGenerator;
@@ -178,8 +179,10 @@
IndexedItemCollection indexedItems,
ProgramMethod context,
LensCodeRewriterUtils rewriter) {
- getParentConstructor(context, rewriter.dexItemFactory())
- .collectIndexedItems(appView, indexedItems);
+ DexMethod parentConstructor = getParentConstructor(context, rewriter.dexItemFactory());
+ MethodLookupResult lookupResult =
+ appView.graphLens().lookupInvokeDirect(parentConstructor, context);
+ lookupResult.getReference().collectIndexedItems(appView, indexedItems);
}
@Override
@@ -370,7 +373,9 @@
GraphLens graphLens,
LensCodeRewriterUtils lensCodeRewriter,
ObjectToOffsetMapping mapping) {
- new DexInvokeDirect(1, getParentConstructor(context, mapping.dexItemFactory()), 0, 0, 0, 0, 0)
+ DexMethod parentConstructor = getParentConstructor(context, mapping.dexItemFactory());
+ MethodLookupResult lookupResult = graphLens.lookupInvokeDirect(parentConstructor, context);
+ new DexInvokeDirect(1, lookupResult.getReference(), 0, 0, 0, 0, 0)
.write(shortBuffer, context, graphLens, mapping, lensCodeRewriter);
new DexReturnVoid().write(shortBuffer, context, graphLens, mapping, lensCodeRewriter);
}
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 3069a60..0564b9c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -893,7 +893,7 @@
}
public final boolean classInitializationMayHaveSideEffectsInContext(
- AppView<?> appView, ProgramDefinition context) {
+ AppView<?> appView, Definition context) {
// Types that are a super type of the current context are guaranteed to be initialized already.
return classInitializationMayHaveSideEffects(
appView, type -> appView.isSubtype(context.getContextType(), type).isTrue());
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
index e5b23f5..0cc861f 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
@@ -49,6 +49,11 @@
return new Builder(appView, previousLens);
}
+ public void addNonReboundMethodReference(
+ DexMethod nonReboundMethodReference, DexMethod reboundMethodReference) {
+ nonReboundMethodReferenceToDefinitionMap.put(nonReboundMethodReference, reboundMethodReference);
+ }
+
@Override
public boolean hasCodeRewritings() {
return false;
diff --git a/src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java
index 6c2af55..c592a7d 100644
--- a/src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.utils.ThreadUtils.processItems;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
@@ -13,6 +14,7 @@
import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
import com.android.tools.r8.optimize.InvokeSingleTargetExtractor.InvokeKind;
+import com.android.tools.r8.optimize.redundantbridgeremoval.RedundantBridgeRemovalLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import java.util.Map;
@@ -28,17 +30,17 @@
this.appView = appView;
}
- private boolean isRedundantBridge(ProgramMethod method) {
+ private DexClassAndMethod getTargetForRedundantBridge(ProgramMethod method) {
// Clean-up the predicate check.
if (appView.appInfo().isPinned(method.getReference())) {
- return false;
+ return null;
}
DexEncodedMethod definition = method.getDefinition();
// TODO(b/197490164): Remove if method is abstract.
BridgeInfo bridgeInfo = definition.getOptimizationInfo().getBridgeInfo();
boolean isBridge = definition.isBridge() || bridgeInfo != null;
if (!isBridge || definition.isAbstract()) {
- return false;
+ return null;
}
InvokeSingleTargetExtractor targetExtractor = new InvokeSingleTargetExtractor(appView, method);
method.registerCodeReferences(targetExtractor);
@@ -46,31 +48,28 @@
// javac-generated visibility forward bridge method has same descriptor (name, signature and
// return type).
if (target == null || !target.match(method.getReference())) {
- return false;
+ return null;
}
if (!isTargetingSuperMethod(method, targetExtractor.getKind(), target)) {
- return false;
+ return null;
}
// This is a visibility forward, so check for the direct target.
- ProgramMethod targetMethod =
- appView
- .appInfo()
- .unsafeResolveMethodDueToDexFormatLegacy(target)
- .getResolvedProgramMethod();
+ DexClassAndMethod targetMethod =
+ appView.appInfo().unsafeResolveMethodDueToDexFormatLegacy(target).getResolutionPair();
if (targetMethod == null) {
- return false;
+ return null;
}
if (method.getAccessFlags().isPublic()) {
if (!targetMethod.getAccessFlags().isPublic()) {
- return false;
+ return null;
}
} else {
if (targetMethod.getAccessFlags().isProtected()
&& !targetMethod.getHolderType().isSamePackage(method.getHolderType())) {
- return false;
+ return null;
}
if (targetMethod.getAccessFlags().isPrivate()) {
- return false;
+ return null;
}
}
if (definition.isStatic()
@@ -78,9 +77,9 @@
&& method
.getHolder()
.classInitializationMayHaveSideEffectsInContext(appView, targetMethod)) {
- return false;
+ return null;
}
- return true;
+ return targetMethod;
}
private boolean isTargetingSuperMethod(ProgramMethod method, InvokeKind kind, DexMethod target) {
@@ -105,15 +104,44 @@
return false;
}
- public void run(ExecutorService executorService) throws ExecutionException {
+ public void run(
+ MemberRebindingIdentityLens memberRebindingIdentityLens, ExecutorService executorService)
+ throws ExecutionException {
+ assert memberRebindingIdentityLens == null
+ || memberRebindingIdentityLens == appView.graphLens();
+
// Collect all redundant bridges to remove.
+ RedundantBridgeRemovalLens.Builder lensBuilder = new RedundantBridgeRemovalLens.Builder();
Map<DexProgramClass, ProgramMethodSet> bridgesToRemove =
- computeBridgesToRemove(executorService);
+ computeBridgesToRemove(lensBuilder, executorService);
+ if (bridgesToRemove.isEmpty()) {
+ return;
+ }
+
pruneApp(bridgesToRemove, executorService);
+
+ if (!lensBuilder.isEmpty()) {
+ appView.setGraphLens(lensBuilder.build(appView));
+ }
+
+ if (memberRebindingIdentityLens != null) {
+ for (ProgramMethodSet bridgesToRemoveFromClass : bridgesToRemove.values()) {
+ for (ProgramMethod bridgeToRemove : bridgesToRemoveFromClass) {
+ DexClassAndMethod resolvedMethod =
+ appView
+ .appInfo()
+ .resolveMethodOn(bridgeToRemove.getHolder(), bridgeToRemove.getReference())
+ .getResolutionPair();
+ memberRebindingIdentityLens.addNonReboundMethodReference(
+ bridgeToRemove.getReference(), resolvedMethod.getReference());
+ }
+ }
+ }
}
private Map<DexProgramClass, ProgramMethodSet> computeBridgesToRemove(
- ExecutorService executorService) throws ExecutionException {
+ RedundantBridgeRemovalLens.Builder lensBuilder, ExecutorService executorService)
+ throws ExecutionException {
Map<DexProgramClass, ProgramMethodSet> bridgesToRemove = new ConcurrentHashMap<>();
processItems(
appView.appInfo().classes(),
@@ -121,8 +149,20 @@
ProgramMethodSet bridgesToRemoveForClass = ProgramMethodSet.create();
clazz.forEachProgramMethod(
method -> {
- if (isRedundantBridge(method)) {
+ DexClassAndMethod target = getTargetForRedundantBridge(method);
+ if (target != null) {
+ // Record that the redundant bridge should be removed.
bridgesToRemoveForClass.add(method);
+
+ // Rewrite invokes to the bridge to the target if it is accessible.
+ // TODO(b/173751869): Consider enabling this for constructors as well.
+ // TODO(b/245882297): Refine these visibility checks so that we also rewrite when
+ // the target is not public, but still accessible to call sites.
+ if (!method.getDefinition().isInstanceInitializer()
+ && target.getAccessFlags().isPublic()
+ && target.getHolder().isPublic()) {
+ lensBuilder.map(method, target);
+ }
}
});
if (!bridgesToRemoveForClass.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizerGraphLens.java b/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizerGraphLens.java
index 1ce0703..6f7f61f 100644
--- a/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizerGraphLens.java
@@ -105,7 +105,8 @@
if (methodSignature == newMethodSignature) {
return previous;
}
- assert !previous.hasReboundReference();
+ assert !previous.hasReboundReference()
+ || previous.getReference() == previous.getReboundReference();
return MethodLookupResult.builder(this)
.setPrototypeChanges(
previous
diff --git a/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalLens.java b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalLens.java
new file mode 100644
index 0000000..a680b36
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalLens.java
@@ -0,0 +1,150 @@
+// Copyright (c) 2022, 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.optimize.redundantbridgeremoval;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
+import com.google.common.collect.Sets;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class RedundantBridgeRemovalLens extends NonIdentityGraphLens {
+
+ private final Set<DexType> interfaces;
+ private final Map<DexMethod, DexMethod> methodMap;
+
+ public RedundantBridgeRemovalLens(
+ AppView<?> appView, Set<DexType> interfaces, Map<DexMethod, DexMethod> methodMap) {
+ super(appView);
+ this.interfaces = interfaces;
+ this.methodMap = methodMap;
+ }
+
+ // Fields.
+
+ @Override
+ public DexField getOriginalFieldSignature(DexField field) {
+ return getPrevious().getOriginalFieldSignature(field);
+ }
+
+ @Override
+ public DexField getRenamedFieldSignature(DexField originalField, GraphLens codeLens) {
+ if (this == codeLens) {
+ return originalField;
+ }
+ return getPrevious().getRenamedFieldSignature(originalField, codeLens);
+ }
+
+ @Override
+ protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
+ return previous;
+ }
+
+ // Methods.
+
+ @Override
+ public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) {
+ if (this == applied) {
+ return originalMethod;
+ }
+ DexMethod previousMethodSignature =
+ getPrevious().getRenamedMethodSignature(originalMethod, applied);
+ return methodMap.getOrDefault(previousMethodSignature, previousMethodSignature);
+ }
+
+ @Override
+ public DexMethod getNextMethodSignature(DexMethod method) {
+ return method;
+ }
+
+ @Override
+ public DexMethod getPreviousMethodSignature(DexMethod method) {
+ return method;
+ }
+
+ @Override
+ protected MethodLookupResult internalDescribeLookupMethod(
+ MethodLookupResult previous, DexMethod context) {
+ if (methodMap.containsKey(previous.getReference())) {
+ DexMethod newReference = previous.getReference();
+ do {
+ newReference = methodMap.get(newReference);
+ } while (methodMap.containsKey(newReference));
+ if (previous.getType().isSuper() && interfaces.contains(newReference.getHolderType())) {
+ return previous;
+ }
+ return MethodLookupResult.builder(this)
+ .setReference(newReference)
+ .setReboundReference(newReference)
+ .setPrototypeChanges(previous.getPrototypeChanges())
+ .setType(previous.getType())
+ .build();
+ }
+ return previous;
+ }
+
+ @Override
+ public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(
+ DexMethod method, GraphLens codeLens) {
+ if (this == codeLens) {
+ return RewrittenPrototypeDescription.none();
+ }
+ return getPrevious().lookupPrototypeChangesForMethodDefinition(method, codeLens);
+ }
+
+ // Types.
+
+ @Override
+ public DexType getOriginalType(DexType type) {
+ return getPrevious().getOriginalType(type);
+ }
+
+ @Override
+ public Iterable<DexType> getOriginalTypes(DexType type) {
+ return getPrevious().getOriginalTypes(type);
+ }
+
+ @Override
+ protected DexType internalDescribeLookupClassType(DexType previous) {
+ return previous;
+ }
+
+ // Misc.
+
+ @Override
+ public boolean isContextFreeForMethods() {
+ return getPrevious().isContextFreeForMethods();
+ }
+
+ public static class Builder {
+
+ private final Set<DexType> interfaces = Sets.newIdentityHashSet();
+ private final Map<DexMethod, DexMethod> methodMap = new IdentityHashMap<>();
+
+ public synchronized Builder map(ProgramMethod from, DexClassAndMethod to) {
+ methodMap.put(from.getReference(), to.getReference());
+ if (to.getHolder().isInterface()) {
+ interfaces.add(to.getHolderType());
+ }
+ return this;
+ }
+
+ public boolean isEmpty() {
+ return methodMap.isEmpty();
+ }
+
+ public RedundantBridgeRemovalLens build(AppView<?> appView) {
+ return new RedundantBridgeRemovalLens(appView, interfaces, methodMap);
+ }
+ }
+}