Remove unneeded visibility bridge methods
Bug: 123832949
Change-Id: I661efcf1a271829ec89b35130531688d5b81ded6
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 5fa0f23..53b47bc 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
@@ -74,6 +75,7 @@
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.VersionProperties;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.io.ByteStreams;
import com.google.common.io.Closer;
@@ -666,6 +668,24 @@
return;
}
+ // Remove unneeeded visibility bridges that have been inserted for member rebinding.
+ // This can only be done if we have AppInfoWithLiveness.
+ if (appView.appInfo().hasLiveness()) {
+ ImmutableSet.Builder<DexMethod> unneededVisibilityBridgeMethods = ImmutableSet.builder();
+ new VisibilityBridgeRemover(
+ appView.withLiveness(),
+ unneededVisibilityBridgeMethod ->
+ unneededVisibilityBridgeMethods.add(unneededVisibilityBridgeMethod.method))
+ .run();
+ appView.setUnneededVisibilityBridgeMethods(unneededVisibilityBridgeMethods.build());
+ } 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
+ // possible to remove visibility bridges that have been synthesized by R8, but we currently
+ // do not have this information.
+ assert !options.isShrinking();
+ }
+
// Validity checks.
assert application.classes().stream().allMatch(DexClass::isValid);
assert appView.rootSet().verifyKeptItemsAreKept(application, appView.appInfo());
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 95e8ab3..a3c686e 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -9,6 +9,8 @@
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.shaking.VerticalClassMerger.VerticallyMergedClasses;
import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableSet;
+import java.util.Set;
public class AppView<T extends AppInfo> implements DexDefinitionSupplier {
@@ -24,6 +26,7 @@
private GraphLense graphLense;
private final InternalOptions options;
private RootSet rootSet;
+ private Set<DexMethod> unneededVisibilityBridgeMethods = ImmutableSet.of();
private VerticallyMergedClasses verticallyMergedClasses;
private AppView(
@@ -137,6 +140,14 @@
this.rootSet = rootSet;
}
+ public Set<DexMethod> unneededVisibilityBridgeMethods() {
+ return unneededVisibilityBridgeMethods;
+ }
+
+ public void setUnneededVisibilityBridgeMethods(Set<DexMethod> unneededVisibilityBridgeMethods) {
+ this.unneededVisibilityBridgeMethods = unneededVisibilityBridgeMethods;
+ }
+
// Get the result of vertical class merging. Returns null if vertical class merging has not been
// run.
public VerticallyMergedClasses verticallyMergedClasses() {
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
index ebfd5c7..c996aee 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
@@ -156,7 +156,8 @@
|| (staticTarget != null && staticTarget.method == item)
|| (directTargetHolder != null && directTargetHolder.isNotProgramClass())
|| (virtualTargetHolder != null && virtualTargetHolder.isNotProgramClass())
- || (staticTargetHolder != null && staticTargetHolder.isNotProgramClass());
+ || (staticTargetHolder != null && staticTargetHolder.isNotProgramClass())
+ || appView.unneededVisibilityBridgeMethods().contains(item);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
index 80316e2..88260d4 100644
--- a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
@@ -14,13 +14,22 @@
import com.google.common.collect.Sets;
import java.util.List;
import java.util.Set;
+import java.util.function.Consumer;
public class VisibilityBridgeRemover {
private final AppView<AppInfoWithLiveness> appView;
+ private final Consumer<DexEncodedMethod> unneededVisibilityBridgeConsumer;
public VisibilityBridgeRemover(AppView<AppInfoWithLiveness> appView) {
+ this(appView, null);
+ }
+
+ public VisibilityBridgeRemover(
+ AppView<AppInfoWithLiveness> appView,
+ Consumer<DexEncodedMethod> unneededVisibilityBridgeConsumer) {
this.appView = appView;
+ this.unneededVisibilityBridgeConsumer = unneededVisibilityBridgeConsumer;
}
private void removeUnneededVisibilityBridgesFromClass(DexProgramClass clazz) {
@@ -42,6 +51,9 @@
methodsToBeRemoved = Sets.newIdentityHashSet();
}
methodsToBeRemoved.add(method);
+ if (unneededVisibilityBridgeConsumer != null) {
+ unneededVisibilityBridgeConsumer.accept(method);
+ }
}
}
if (methodsToBeRemoved != null) {
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingBridgeRemovalTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingBridgeRemovalTest.java
new file mode 100644
index 0000000..77a9ea9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingBridgeRemovalTest.java
@@ -0,0 +1,70 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.memberrebinding;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isBridge;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isSynthetic;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.memberrebinding.testclasses.MemberRebindingBridgeRemovalTestClasses;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MemberRebindingBridgeRemovalTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().build();
+ }
+
+ public MemberRebindingBridgeRemovalTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(
+ MemberRebindingBridgeRemovalTest.class, MemberRebindingBridgeRemovalTestClasses.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .enableMergeAnnotations()
+ .setMinApi(parameters.getRuntime())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(StringUtils.lines("Hello world!"));
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject classSubject = inspector.clazz(MemberRebindingBridgeRemovalTestClasses.B.class);
+ assertThat(classSubject, isPresent());
+
+ for (FoundMethodSubject methodSubject : classSubject.allMethods()) {
+ assertThat(methodSubject, not(isBridge()));
+ assertThat(methodSubject, not(isSynthetic()));
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ new MemberRebindingBridgeRemovalTestClasses.B().m();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/testclasses/MemberRebindingBridgeRemovalTestClasses.java b/src/test/java/com/android/tools/r8/memberrebinding/testclasses/MemberRebindingBridgeRemovalTestClasses.java
new file mode 100644
index 0000000..3e15e6e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/testclasses/MemberRebindingBridgeRemovalTestClasses.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.memberrebinding.testclasses;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+
+public class MemberRebindingBridgeRemovalTestClasses {
+
+ @NeverMerge
+ static class A {
+
+ @NeverInline
+ public void m() {
+ System.out.println("Hello world!");
+ }
+ }
+
+ public static class B extends A {}
+}