Fix Staticizer Reprocessing
Bug: 147860220
Change-Id: Ia3c324f85977e36679e2aa574055cf7fcbc29f7a
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 1b78ff7..0fbc644 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
@@ -725,7 +725,8 @@
}
if (enumUnboxer != null) {
enumUnboxer.finishAnalysis();
- enumUnboxer.unboxEnums(postMethodProcessorBuilder, executorService, feedback);
+ enumUnboxer.unboxEnums(
+ postMethodProcessorBuilder, executorService, feedback, classStaticizer);
}
new TrivialFieldAccessReprocessor(appView.withLiveness(), postMethodProcessorBuilder)
.run(executorService, feedback, timing);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index b117c16..0a35a6f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -48,6 +48,7 @@
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
+import com.android.tools.r8.ir.optimize.staticizer.ClassStaticizer;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.Reporter;
@@ -283,7 +284,8 @@
public void unboxEnums(
PostMethodProcessor.Builder postBuilder,
ExecutorService executorService,
- OptimizationFeedbackDelayed feedback)
+ OptimizationFeedbackDelayed feedback,
+ ClassStaticizer classStaticizer)
throws ExecutionException {
// At this point the enumsToUnbox are no longer candidates, they will all be unboxed.
if (enumsUnboxingCandidates.isEmpty()) {
@@ -299,7 +301,7 @@
appView
.appInfo()
.rewrittenWithLens(appView.appInfo().app().asDirect(), enumUnboxingLens));
-
+ classStaticizer.filterCandidates();
// Update optimization info.
feedback.fixupOptimizationInfos(
appView,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
index 41df56a..ed3e72a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
@@ -96,6 +96,15 @@
candidates.remove(candidate.type);
return null;
}
+
+ void filterMethods() {
+ Set<DexEncodedMethod> newReferencedFrom = Sets.newIdentityHashSet();
+ for (DexEncodedMethod dexEncodedMethod : referencedFrom) {
+ newReferencedFrom.add(appView.graphLense().mapDexEncodedMethod(dexEncodedMethod, appView));
+ }
+ referencedFrom.clear();
+ referencedFrom.addAll(newReferencedFrom);
+ }
}
// The map storing all the potential candidates for staticizing.
@@ -651,6 +660,17 @@
return false;
}
+ // Methods may have their signature changed in-between the IR processing rounds, leading to
+ // duplicates where one version is the outdated version. Remove these.
+ // This also ensures no unboxed enum are staticized, if that would be the case, then
+ // the candidate would need to be removed from the candidate list.
+ public void filterCandidates() {
+ for (Map.Entry<DexType, CandidateInfo> entry : candidates.entrySet()) {
+ assert !appView.unboxedEnums().containsEnum(entry.getKey());
+ entry.getValue().filterMethods();
+ }
+ }
+
// Perform staticizing candidates:
//
// 1. After filtering candidates based on usage, finalize the list of candidates by
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index 1d679ecc..c451bd7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -286,12 +286,22 @@
if (getter != null) {
singletonGetters.put(getter.method, candidate);
}
+ assert validMethods(candidate.referencedFrom);
referencingExtraMethods.addAll(candidate.referencedFrom);
}
referencingExtraMethods.removeAll(removedInstanceMethods);
}
+ private boolean validMethods(Set<DexEncodedMethod> referencedFrom) {
+ for (DexEncodedMethod dexEncodedMethod : referencedFrom) {
+ DexClass clazz = appView.definitionForHolder(dexEncodedMethod.method);
+ assert clazz != null;
+ assert clazz.lookupMethod(dexEncodedMethod.method) == dexEncodedMethod;
+ }
+ return true;
+ }
+
private void enqueueMethodsWithCodeOptimizations(
Iterable<DexEncodedMethod> methods,
Consumer<ImmutableList.Builder<BiConsumer<IRCode, MethodProcessor>>> extension) {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
new file mode 100644
index 0000000..6fb5fd1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
@@ -0,0 +1,126 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.enumunboxing;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class EnumUnboxingClassStaticizerTest extends EnumUnboxingTestBase {
+
+ private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final KeepRule enumKeepRules;
+
+ @Parameterized.Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public EnumUnboxingClassStaticizerTest(
+ TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+ this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
+ }
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ R8TestRunResult run =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(EnumUnboxingClassStaticizerTest.class)
+ .addKeepMainRule(TestClass.class)
+ .addKeepRules(enumKeepRules.getKeepRule())
+ .enableNeverClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .noMinification() // For assertions.
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .allowDiagnosticInfoMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::assertClassStaticized)
+ .inspectDiagnosticMessages(
+ m ->
+ assertEnumIsUnboxed(
+ UnboxableEnum.class,
+ EnumUnboxingClassStaticizerTest.class.getSimpleName(),
+ m))
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
+ }
+
+ private void assertClassStaticized(CodeInspector codeInspector) {
+ if (parameters.isCfRuntime()) {
+ // There is no class staticizer in Cf.
+ assertThat(codeInspector.clazz(Companion.class).uniqueMethodWithName("method"), isPresent());
+ return;
+ }
+ MethodSubject method = codeInspector.clazz(CompanionHost.class).uniqueMethodWithName("method");
+ assertThat(method, isPresent());
+ assertEquals("int", method.getMethod().method.proto.parameters.toString());
+ }
+
+ static class TestClass {
+ public static void main(String[] args) {
+ CompanionHost.toKeep();
+ test(UnboxableEnum.A);
+ System.out.println("0");
+ test(UnboxableEnum.B);
+ System.out.println("1");
+ test(UnboxableEnum.C);
+ System.out.println("2");
+ }
+
+ @NeverInline
+ static void test(UnboxableEnum obj) {
+ // The class staticizer will move Companion.method() into CompanionHost.method().
+ // As a result of this transformation, this call site will need to be processed.
+ // To keep track of this, the staticizer will keep a reference to the method
+ // `void test(UnboxableEnum)`, but after enum unboxing this has been rewritten to
+ // `void test(int)`. Therefore, the staticizer's internal structure needs to be
+ // updated after enum unboxing.
+ CompanionHost.COMPANION.method(obj);
+ }
+ }
+
+ @NeverClassInline
+ static class CompanionHost {
+ static final Companion COMPANION = new Companion();
+
+ @NeverInline
+ static void toKeep() {
+ System.out.println("Keep me!");
+ System.out.println("Keep me!");
+ }
+ }
+
+ @NeverClassInline
+ static class Companion {
+ @NeverInline
+ public void method(UnboxableEnum obj) {
+ System.out.println(obj.ordinal());
+ }
+ }
+
+ @NeverClassInline
+ enum UnboxableEnum {
+ A,
+ B,
+ C;
+ }
+}