Add argument propagator test inspector
Change-Id: I974341af6709379ba0f29f200269ea878d8ab333
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
index 354cc9a..ad6e869 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
@@ -122,6 +122,7 @@
// Unset the scanner since all code objects have been scanned at this point.
assert appView.isAllCodeProcessed();
MethodStateCollectionByReference codeScannerResult = codeScanner.getMethodStates();
+ appView.testing().argumentPropagatorEventConsumer.acceptCodeScannerResult(codeScannerResult);
codeScanner = null;
timing.begin("Compute optimization info");
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorEventConsumer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorEventConsumer.java
new file mode 100644
index 0000000..c02d1ad
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorEventConsumer.java
@@ -0,0 +1,33 @@
+// 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 com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
+
+public interface ArgumentPropagatorEventConsumer {
+
+ static ArgumentPropagatorEventConsumer emptyConsumer() {
+ return new ArgumentPropagatorEventConsumer() {
+ @Override
+ public void acceptCodeScannerResult(MethodStateCollectionByReference methodStates) {
+ // Intentionally empty.
+ }
+ };
+ }
+
+ void acceptCodeScannerResult(MethodStateCollectionByReference methodStates);
+
+ default ArgumentPropagatorEventConsumer andThen(
+ ArgumentPropagatorEventConsumer nextEventConsumer) {
+ ArgumentPropagatorEventConsumer self = this;
+ return new ArgumentPropagatorEventConsumer() {
+ @Override
+ public void acceptCodeScannerResult(MethodStateCollectionByReference methodStates) {
+ self.acceptCodeScannerResult(methodStates);
+ nextEventConsumer.acceptCodeScannerResult(methodStates);
+ }
+ };
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollection.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollection.java
index cbc73da..3a54fe8 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollection.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollection.java
@@ -86,7 +86,11 @@
}
public MethodState get(ProgramMethod method) {
- return methodStates.getOrDefault(getKey(method), MethodState.bottom());
+ return get(getKey(method));
+ }
+
+ public MethodState get(K method) {
+ return methodStates.getOrDefault(method, MethodState.bottom());
}
public boolean isEmpty() {
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 5ee21c1..f391fb1 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -50,6 +50,7 @@
import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.ir.optimize.enums.EnumDataMap;
import com.android.tools.r8.naming.MapVersion;
+import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorEventConsumer;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
import com.android.tools.r8.references.ClassReference;
@@ -1484,6 +1485,9 @@
public static int NO_LIMIT = -1;
+ public ArgumentPropagatorEventConsumer argumentPropagatorEventConsumer =
+ ArgumentPropagatorEventConsumer.emptyConsumer();
+
// Force writing the specified bytes as the DEX version content.
public byte[] forceDexVersionBytes = null;
diff --git a/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java b/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
index 37deccc..9818d3e 100644
--- a/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
@@ -7,6 +7,8 @@
import static com.android.tools.r8.utils.ClassReferenceUtils.getClassReferenceComparator;
import static com.android.tools.r8.utils.TypeReferenceUtils.getTypeReferenceComparator;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.references.ArrayReference;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.FieldReference;
@@ -87,6 +89,15 @@
}
}
+ public static DexMethod toDexMethod(
+ MethodReference methodReference, DexItemFactory dexItemFactory) {
+ return dexItemFactory.createMethod(
+ ClassReferenceUtils.toDexType(methodReference.getHolderClass(), dexItemFactory),
+ TypeReferenceUtils.toDexProto(
+ methodReference.getFormalTypes(), methodReference.getReturnType(), dexItemFactory),
+ methodReference.getMethodName());
+ }
+
public static String toSourceStringWithoutHolderAndReturnType(MethodReference methodReference) {
return toSourceString(methodReference, false, false);
}
diff --git a/src/main/java/com/android/tools/r8/utils/TypeReferenceUtils.java b/src/main/java/com/android/tools/r8/utils/TypeReferenceUtils.java
index 9d322e6..a5145f4 100644
--- a/src/main/java/com/android/tools/r8/utils/TypeReferenceUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/TypeReferenceUtils.java
@@ -37,6 +37,15 @@
return COMPARATOR;
}
+ public static DexProto toDexProto(
+ List<TypeReference> formalTypes, TypeReference returnType, DexItemFactory dexItemFactory) {
+ return toDexProto(
+ formalTypes,
+ returnType,
+ dexItemFactory,
+ classReference -> ClassReferenceUtils.toDexType(classReference, dexItemFactory));
+ }
+
/**
* Converts the given {@param formalTypes} and {@param returnType} to a {@link DexProto}.
*
@@ -55,6 +64,13 @@
formalType -> toDexType(formalType, dexItemFactory, classReferenceConverter)));
}
+ public static DexType toDexType(TypeReference typeReference, DexItemFactory dexItemFactory) {
+ return toDexType(
+ typeReference,
+ dexItemFactory,
+ classReference -> ClassReferenceUtils.toDexType(classReference, dexItemFactory));
+ }
+
/**
* Converts the given {@param typeReference} to a {@link DexType}.
*
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 1e1b55f..3702430 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -10,6 +10,8 @@
import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.debug.DebugTestConfig;
+import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorEventConsumer;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
import com.android.tools.r8.testing.AndroidBuildVersion;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
@@ -18,6 +20,7 @@
import com.android.tools.r8.utils.ForwardingOutputStream;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ThrowingOutputStream;
+import com.android.tools.r8.utils.codeinspector.ArgumentPropagatorCodeScannerResultInspector;
import com.android.tools.r8.utils.codeinspector.EnumUnboxingInspector;
import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
@@ -103,6 +106,23 @@
B builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
throws CompilationFailedException;
+ public T addArgumentPropagatorCodeScannerResultInspector(
+ ThrowableConsumer<ArgumentPropagatorCodeScannerResultInspector> inspector) {
+ return addOptionsModification(
+ options ->
+ options.testing.argumentPropagatorEventConsumer =
+ options.testing.argumentPropagatorEventConsumer.andThen(
+ new ArgumentPropagatorEventConsumer() {
+ @Override
+ public void acceptCodeScannerResult(
+ MethodStateCollectionByReference methodStates) {
+ inspector.acceptWithRuntimeException(
+ new ArgumentPropagatorCodeScannerResultInspector(
+ options.dexItemFactory(), methodStates));
+ }
+ }));
+ }
+
public T addOptionsModification(Consumer<InternalOptions> optionsConsumer) {
if (optionsConsumer != null) {
this.optionsConsumer = this.optionsConsumer.andThen(optionsConsumer);
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ImpreciseReceiverWithUnknownArgumentInformationWidenedToUnknownTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ImpreciseReceiverWithUnknownArgumentInformationWidenedToUnknownTest.java
new file mode 100644
index 0000000..0359d25
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ImpreciseReceiverWithUnknownArgumentInformationWidenedToUnknownTest.java
@@ -0,0 +1,87 @@
+// 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 org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.BooleanBox;
+import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
+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 ImpreciseReceiverWithUnknownArgumentInformationWidenedToUnknownTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection parameters() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ // TODO(b/190154391): The state for A.test() should be unknown.
+ @Test(expected = CompilationFailedException.class)
+ public void test() throws Exception {
+ BooleanBox inspected = new BooleanBox();
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addArgumentPropagatorCodeScannerResultInspector(
+ inspector ->
+ inspector
+ .assertHasUnknownMethodState(
+ Reference.methodFromMethod(A.class.getDeclaredMethod("test")))
+ .assertHasBottomMethodState(
+ Reference.methodFromMethod(B.class.getDeclaredMethod("test")))
+ .apply(ignore -> inspected.set()))
+ .addOptionsModification(
+ options ->
+ options
+ .callSiteOptimizationOptions()
+ .setEnableExperimentalArgumentPropagation(true))
+ .addVerticallyMergedClassesInspector(
+ VerticallyMergedClassesInspector::assertNoClassesMerged)
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A");
+ assertTrue(inspected.isTrue());
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ A aOrB = System.currentTimeMillis() >= 0 ? new A() : new B();
+ aOrB.test();
+ }
+ }
+
+ @NoVerticalClassMerging
+ static class A {
+
+ void test() {
+ System.out.println("A");
+ }
+ }
+
+ static class B extends A {
+
+ @Override
+ void test() {
+ System.out.println("B");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ParameterWithUnknownArgumentInformationWidenedToUnknownTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ParameterWithUnknownArgumentInformationWidenedToUnknownTest.java
new file mode 100644
index 0000000..2b87e0b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ParameterWithUnknownArgumentInformationWidenedToUnknownTest.java
@@ -0,0 +1,81 @@
+// 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 org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+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.references.Reference;
+import com.android.tools.r8.utils.BooleanBox;
+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 ParameterWithUnknownArgumentInformationWidenedToUnknownTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection parameters() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ // TODO(b/190154391): The state for Main.test() should be unknown.
+ @Test(expected = CompilationFailedException.class)
+ public void test() throws Exception {
+ BooleanBox inspected = new BooleanBox();
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addArgumentPropagatorCodeScannerResultInspector(
+ inspector ->
+ inspector
+ .assertHasUnknownMethodState(
+ Reference.methodFromMethod(Main.class.getDeclaredMethod("test", A.class)))
+ .apply(ignore -> inspected.set()))
+ .addOptionsModification(
+ options ->
+ options
+ .callSiteOptimizationOptions()
+ .setEnableExperimentalArgumentPropagation(true))
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(null, "A");
+ assertTrue(inspected.isTrue());
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ A alwaysNull = null;
+ A neverNull = new A();
+ test(alwaysNull);
+ test(neverNull);
+ }
+
+ @NeverInline
+ static void test(A a) {
+ System.out.println(a);
+ }
+ }
+
+ static class A {
+
+ @Override
+ public String toString() {
+ return "A";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ArgumentPropagatorCodeScannerResultInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ArgumentPropagatorCodeScannerResultInspector.java
new file mode 100644
index 0000000..b427284
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ArgumentPropagatorCodeScannerResultInspector.java
@@ -0,0 +1,62 @@
+// 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.utils.codeinspector;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import java.util.function.Predicate;
+
+public class ArgumentPropagatorCodeScannerResultInspector {
+
+ private final DexItemFactory dexItemFactory;
+ private final MethodStateCollectionByReference methodStates;
+
+ public ArgumentPropagatorCodeScannerResultInspector(
+ DexItemFactory dexItemFactory, MethodStateCollectionByReference methodStates) {
+ this.dexItemFactory = dexItemFactory;
+ this.methodStates = methodStates;
+ }
+
+ public ArgumentPropagatorCodeScannerResultInspector apply(
+ ThrowableConsumer<ArgumentPropagatorCodeScannerResultInspector> consumer) {
+ consumer.acceptWithRuntimeException(this);
+ return this;
+ }
+
+ public ArgumentPropagatorCodeScannerResultInspector assertHasBottomMethodState(
+ MethodReference methodReference) {
+ return assertHasMethodStateThatMatches(
+ "Expected method state for "
+ + MethodReferenceUtils.toSourceString(methodReference)
+ + " to be bottom",
+ methodReference,
+ MethodState::isBottom);
+ }
+
+ public ArgumentPropagatorCodeScannerResultInspector assertHasUnknownMethodState(
+ MethodReference methodReference) {
+ return assertHasMethodStateThatMatches(
+ "Expected method state for "
+ + MethodReferenceUtils.toSourceString(methodReference)
+ + " to be unknown",
+ methodReference,
+ MethodState::isUnknown);
+ }
+
+ public ArgumentPropagatorCodeScannerResultInspector assertHasMethodStateThatMatches(
+ String message, MethodReference methodReference, Predicate<MethodState> predicate) {
+ DexMethod method = MethodReferenceUtils.toDexMethod(methodReference, dexItemFactory);
+ MethodState methodState = methodStates.get(method);
+ assertTrue(message + ", was: " + methodState, predicate.test(methodState));
+ return this;
+ }
+}