Test mixed argument removal and enum unboxing
Change-Id: Ic9741cccedd20570bee1d35488b93feb2a3e6aa0
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 c68adac..b6c7d02 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -88,6 +88,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -1627,12 +1628,19 @@
public Builder adjustOptimizationInfoAfterRemovingThisParameter(
AppView<AppInfoWithLiveness> appView) {
- if (optimizationInfo.isMutableOptimizationInfo()) {
- optimizationInfo
- .asMutableMethodOptimizationInfo()
- .adjustOptimizationInfoAfterRemovingThisParameter(appView);
- }
- return this;
+ return modifyOptimizationInfo(
+ (newMethod, optimizationInfo) ->
+ optimizationInfo.adjustOptimizationInfoAfterRemovingThisParameter(appView));
+ }
+
+ public Builder modifyOptimizationInfo(
+ BiConsumer<DexEncodedMethod, MutableMethodOptimizationInfo> consumer) {
+ return addBuildConsumer(
+ newMethod -> {
+ if (optimizationInfo.isMutableOptimizationInfo()) {
+ consumer.accept(newMethod, optimizationInfo.asMutableMethodOptimizationInfo());
+ }
+ });
}
public Builder setCode(Code code) {
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 4304fdd..bd96228 100644
--- a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
+++ b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
@@ -15,12 +15,14 @@
import com.android.tools.r8.utils.BooleanUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Ordering;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
import it.unimi.dsi.fastutil.ints.Int2ReferenceRBTreeMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import it.unimi.dsi.fastutil.ints.IntBidirectionalIterator;
import it.unimi.dsi.fastutil.ints.IntSortedSet;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
@@ -222,6 +224,10 @@
return this == EMPTY;
}
+ public Iterator<Entry<ArgumentInfo>> iterator() {
+ return argumentInfos.int2ReferenceEntrySet().iterator();
+ }
+
public boolean hasRemovedArguments() {
for (ArgumentInfo value : argumentInfos.values()) {
if (value.isRemovedArgumentInfo()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index 13b7140..53bb277 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -66,6 +66,8 @@
void setEnumUnboxerMethodClassification(
ProgramMethod method, EnumUnboxerMethodClassification enumUnboxerMethodClassification);
+ void unsetEnumUnboxerMethodClassification(ProgramMethod method);
+
void setInstanceInitializerInfoCollection(
DexEncodedMethod method, InstanceInitializerInfoCollection instanceInitializerInfoCollection);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/CheckNotNullEnumUnboxerMethodClassification.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/CheckNotNullEnumUnboxerMethodClassification.java
index fd892c1..77d6178 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/CheckNotNullEnumUnboxerMethodClassification.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/CheckNotNullEnumUnboxerMethodClassification.java
@@ -4,8 +4,13 @@
package com.android.tools.r8.ir.optimize.enums.classification;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfo;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.IteratorUtils;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
+import java.util.Iterator;
public final class CheckNotNullEnumUnboxerMethodClassification
extends EnumUnboxerMethodClassification {
@@ -31,6 +36,47 @@
}
@Override
+ public EnumUnboxerMethodClassification fixupAfterParameterRemoval(
+ ArgumentInfoCollection removedParameters) {
+ if (removedParameters.getArgumentInfo(argumentIndex).isRemovedArgumentInfo()) {
+ // If the null-checked argument is removed from the parameters of the method, then we can no
+ // longer classify this method as a check-not-null method. This is OK in terms of enum
+ // unboxing, since after the parameter removal enums at the call site will no longer have the
+ // check-not-null invoke instruction as a user.
+ //
+ // Note that when we materialize the enum instance in the check-not-null method, it is
+ // important that this method is reprocessed by enum unboxing (or that materialized instance
+ // would not be unboxed). This is guaranteed by argument removal: Since we have removed a
+ // parameter from the method, we will need to reprocess its code in the second optimization
+ // pass.
+ return unknown();
+ }
+
+ int numberOfArgumentsRemovedBeforeThis = 0;
+
+ Iterator<Entry<ArgumentInfo>> iterator = removedParameters.iterator();
+ while (iterator.hasNext()) {
+ Entry<ArgumentInfo> entry = iterator.next();
+ int argumentIndexForInfo = entry.getIntKey();
+ if (argumentIndexForInfo >= getArgumentIndex()) {
+ break;
+ }
+ ArgumentInfo argumentInfo = entry.getValue();
+ if (argumentInfo.isRemovedArgumentInfo()) {
+ numberOfArgumentsRemovedBeforeThis++;
+ }
+ }
+
+ assert IteratorUtils.allRemainingMatchDestructive(
+ iterator, entry -> entry.getIntKey() >= getArgumentIndex());
+
+ return numberOfArgumentsRemovedBeforeThis > 0
+ ? new CheckNotNullEnumUnboxerMethodClassification(
+ getArgumentIndex() - numberOfArgumentsRemovedBeforeThis)
+ : this;
+ }
+
+ @Override
public boolean isCheckNotNullClassification() {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/EnumUnboxerMethodClassification.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/EnumUnboxerMethodClassification.java
index 2b34291..5c93c36 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/EnumUnboxerMethodClassification.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/EnumUnboxerMethodClassification.java
@@ -4,12 +4,17 @@
package com.android.tools.r8.ir.optimize.enums.classification;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
+
public abstract class EnumUnboxerMethodClassification {
public static UnknownEnumUnboxerMethodClassification unknown() {
return UnknownEnumUnboxerMethodClassification.getInstance();
}
+ public abstract EnumUnboxerMethodClassification fixupAfterParameterRemoval(
+ ArgumentInfoCollection removedParameters);
+
public EnumUnboxerMethodClassification fixupAfterRemovingThisParameter() {
// Only static methods are currently classified by the enum unboxer.
assert isUnknownClassification();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/UnknownEnumUnboxerMethodClassification.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/UnknownEnumUnboxerMethodClassification.java
index 9f51d0a..e88239b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/UnknownEnumUnboxerMethodClassification.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/UnknownEnumUnboxerMethodClassification.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.ir.optimize.enums.classification;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
+
public final class UnknownEnumUnboxerMethodClassification extends EnumUnboxerMethodClassification {
private static final UnknownEnumUnboxerMethodClassification INSTANCE =
@@ -16,6 +18,12 @@
}
@Override
+ public EnumUnboxerMethodClassification fixupAfterParameterRemoval(
+ ArgumentInfoCollection removedParameters) {
+ return this;
+ }
+
+ @Override
public boolean isUnknownClassification() {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
index 3efe7f6..4263b35 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
@@ -261,6 +261,10 @@
this.enumUnboxerMethodClassification = enumUnboxerMethodClassification;
}
+ void unsetEnumUnboxerMethodClassification() {
+ this.enumUnboxerMethodClassification = EnumUnboxerMethodClassification.unknown();
+ }
+
@Override
public TypeElement getDynamicUpperBoundType() {
return returnsObjectWithUpperBoundType;
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 c9390d7..bd275db 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
@@ -272,6 +272,11 @@
}
@Override
+ public synchronized void unsetEnumUnboxerMethodClassification(ProgramMethod method) {
+ getMethodOptimizationInfoForUpdating(method).unsetEnumUnboxerMethodClassification();
+ }
+
+ @Override
public synchronized void setInstanceInitializerInfoCollection(
DexEncodedMethod method,
InstanceInitializerInfoCollection instanceInitializerInfoCollection) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index 623d8d2..1894532 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -122,6 +122,9 @@
ProgramMethod method, EnumUnboxerMethodClassification enumUnboxerMethodClassification) {}
@Override
+ public void unsetEnumUnboxerMethodClassification(ProgramMethod method) {}
+
+ @Override
public void setInstanceInitializerInfoCollection(
DexEncodedMethod method,
InstanceInitializerInfoCollection instanceInitializerInfoCollection) {}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index fe00440..10649fb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -187,7 +187,15 @@
@Override
public void setEnumUnboxerMethodClassification(
ProgramMethod method, EnumUnboxerMethodClassification enumUnboxerMethodClassification) {
- // Ignored.
+ method
+ .getDefinition()
+ .getMutableOptimizationInfo()
+ .setEnumUnboxerMethodClassification(enumUnboxerMethodClassification);
+ }
+
+ @Override
+ public void unsetEnumUnboxerMethodClassification(ProgramMethod method) {
+ method.getDefinition().getMutableOptimizationInfo().unsetEnumUnboxerMethodClassification();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java
index 346e3fc..fc74192 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java
@@ -8,6 +8,11 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.MethodCollection;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
+import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.ThreadUtils;
import java.util.concurrent.ExecutionException;
@@ -55,6 +60,25 @@
methodReferenceAfterParameterRemoval,
builder -> {
// TODO(b/190154391): fixup parameter annotations, if any.
+ ArgumentInfoCollection removedParameters =
+ graphLens.getRemovedParameters(methodReferenceAfterParameterRemoval);
+ builder.modifyOptimizationInfo(
+ (newMethod, optimizationInfo) -> {
+ OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance();
+ ProgramMethod programMethod = new ProgramMethod(clazz, newMethod);
+ // TODO(b/190154391): test this.
+ EnumUnboxerMethodClassification rewrittenEnumUnboxerMethodClassification =
+ optimizationInfo
+ .getEnumUnboxerMethodClassification()
+ .fixupAfterParameterRemoval(removedParameters);
+ if (rewrittenEnumUnboxerMethodClassification.isCheckNotNullClassification()) {
+ feedback.setEnumUnboxerMethodClassification(
+ programMethod, rewrittenEnumUnboxerMethodClassification);
+ } else {
+ // Bypass monotonicity check.
+ feedback.unsetEnumUnboxerMethodClassification(programMethod);
+ }
+ });
});
});
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java
index c60640c..77ec495 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java
@@ -32,6 +32,11 @@
return new Builder(appView);
}
+ public ArgumentInfoCollection getRemovedParameters(DexMethod method) {
+ assert method != internalGetPreviousMethodSignature(method);
+ return removedParameters.getOrDefault(method, ArgumentInfoCollection.empty());
+ }
+
@Override
protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
@@ -40,8 +45,7 @@
assert !removedParameters.containsKey(method);
return prototypeChanges;
}
- return prototypeChanges.withRemovedArguments(
- removedParameters.getOrDefault(method, ArgumentInfoCollection.empty()));
+ return prototypeChanges.withRemovedArguments(getRemovedParameters(method));
}
@Override
diff --git a/src/main/java/com/android/tools/r8/utils/IntObjToObjFunction.java b/src/main/java/com/android/tools/r8/utils/IntObjToObjFunction.java
index 9c07bed..b0ed4ee 100644
--- a/src/main/java/com/android/tools/r8/utils/IntObjToObjFunction.java
+++ b/src/main/java/com/android/tools/r8/utils/IntObjToObjFunction.java
@@ -6,5 +6,5 @@
public interface IntObjToObjFunction<S, T> {
- S apply(int i, T obj);
+ T apply(int i, S obj);
}
diff --git a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
index 9420440..a34ec96 100644
--- a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
@@ -165,6 +165,16 @@
return !anyRemainingMatch(iterator, remaining -> !predicate.test(remaining));
}
+ public static <T> boolean allRemainingMatchDestructive(
+ Iterator<T> iterator, Predicate<T> predicate) {
+ while (iterator.hasNext()) {
+ if (!predicate.test(iterator.next())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
public static <T> boolean anyRemainingMatch(ListIterator<T> iterator, Predicate<T> predicate) {
T state = peekNext(iterator);
boolean result = false;
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/MixedArgumentRemovalAndEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/MixedArgumentRemovalAndEnumUnboxingTest.java
new file mode 100644
index 0000000..ad4c08a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/MixedArgumentRemovalAndEnumUnboxingTest.java
@@ -0,0 +1,99 @@
+// Copyright (c) 2021, 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.argumentpropagation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+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.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MixedArgumentRemovalAndEnumUnboxingTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection parameters() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options ->
+ options
+ .callSiteOptimizationOptions()
+ .setEnableExperimentalArgumentPropagation(true))
+ .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class))
+ .enableInliningAnnotations()
+ // TODO(b/173398086): uniqueMethodWithName() does not work with argument removal.
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, isPresent());
+
+ MethodSubject mainMethodSubject = mainClassSubject.mainMethod();
+ assertThat(mainMethodSubject, isPresent());
+ assertTrue(
+ mainMethodSubject
+ .streamInstructions()
+ .noneMatch(InstructionSubject::isConstNull));
+
+ MethodSubject testMethodSubject = mainClassSubject.uniqueMethodWithName("test");
+ assertThat(testMethodSubject, isPresent());
+ assertEquals(2, testMethodSubject.getProgramMethod().getReference().getArity());
+ assertEquals(
+ "int", testMethodSubject.getProgramMethod().getParameter(0).getTypeName());
+ assertEquals(
+ "int", testMethodSubject.getProgramMethod().getParameter(1).getTypeName());
+ assertTrue(
+ testMethodSubject.streamInstructions().noneMatch(InstructionSubject::isIf));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A", "B");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ MyEnum alwaysA = System.currentTimeMillis() >= 1 ? MyEnum.A : MyEnum.B;
+ MyEnum alwaysB = System.currentTimeMillis() >= 1 ? MyEnum.B : MyEnum.A;
+ test(null, alwaysA, null, alwaysB);
+ }
+
+ @NeverInline
+ static void test(Object alwaysNull, MyEnum alwaysA, Object alsoAlwaysNull, MyEnum alwaysB) {
+ if (alwaysNull == null && alsoAlwaysNull == null) {
+ System.out.println(alwaysA.name());
+ System.out.println(alwaysB.name());
+ }
+ }
+ }
+
+ enum MyEnum {
+ A,
+ B
+ }
+}