Add test and internal testing option for cf pass through mode
Bug: 154201250
Bug: 152753721
Change-Id: If92521a0821cd08d8fa8ab6378afa122c8c16943
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 484c3e1..7999ea7 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -434,4 +434,9 @@
? OptionalBool.of(appInfo().withLiveness().isSubtype(subtype, supertype))
: OptionalBool.unknown();
}
+
+ public boolean isCfByteCodePassThrough(DexEncodedMethod method) {
+ return options.testing.cfByteCodePassThrough != null
+ && options.testing.cfByteCodePassThrough.test(method);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index b68b839..7a21ff1 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -183,7 +183,7 @@
// Don't add parameter information if the code already has full debug information.
// Note: This fast path can cause a method to loose its parameter info, if the debug info turned
// out to be invalid during IR building.
- if (appView.options().debug) {
+ if (appView.options().debug || appView.isCfByteCodePassThrough(method)) {
return false;
}
assert localVariables.isEmpty();
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 31fd3c0..acb21d7 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
@@ -1585,6 +1585,11 @@
timing.end();
}
+ if (appView.isCfByteCodePassThrough(method)) {
+ // If the code is pass trough, do not finalize by overwriting the existing code.
+ return timing;
+ }
+
printMethod(code, "Optimized IR (SSA)", previous);
timing.begin("Finalize IR");
finalizeIR(code, feedback, timing);
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 62aa29a..e4b20c6 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -73,6 +73,7 @@
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.objectweb.asm.Opcodes;
@@ -1186,6 +1187,8 @@
}
public Consumer<ProgramMethod> callSiteOptimizationInfoInspector = null;
+
+ public Predicate<DexEncodedMethod> cfByteCodePassThrough = null;
}
@VisibleForTesting
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index 8cdce08..d62402b 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -338,7 +338,9 @@
optimizeDexCodePositions(
method, appView, kotlinRemapper, mappedPositions, identityMapping);
}
- } else if (code.isCfCode() && doesContainPositions(code.asCfCode())) {
+ } else if (code.isCfCode()
+ && doesContainPositions(code.asCfCode())
+ && !appView.isCfByteCodePassThrough(method)) {
optimizeCfCodePositions(method, kotlinRemapper, mappedPositions, appView);
}
}
diff --git a/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java b/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
index d881974..2ae20c9 100644
--- a/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
@@ -352,18 +352,18 @@
return descriptorList;
}
- private static byte[] getClassAsBytes(ArchiveClassFileProvider inputJar, String descriptor)
+ public static byte[] getClassAsBytes(ClassFileResourceProvider inputJar, String descriptor)
throws Exception {
return toByteArray(inputJar.getProgramResource(descriptor).getByteStream());
}
- private static String asmToString(byte[] clazz) {
+ public static String asmToString(byte[] clazz) {
StringWriter stringWriter = new StringWriter();
printAsm(new PrintWriter(stringWriter), clazz);
return stringWriter.toString();
}
- private static void printAsm(PrintWriter pw, byte[] clazz) {
+ public static void printAsm(PrintWriter pw, byte[] clazz) {
new ClassReader(clazz).accept(new TraceClassVisitor(null, new ASMifierSorted(), pw), 0);
}
diff --git a/src/test/java/com/android/tools/r8/code/PassThroughTest.java b/src/test/java/com/android/tools/r8/code/PassThroughTest.java
new file mode 100644
index 0000000..bb60719
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/code/PassThroughTest.java
@@ -0,0 +1,160 @@
+// 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.code;
+
+import static junit.framework.Assert.assertSame;
+
+import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.CfFrontendExamplesTest;
+import com.android.tools.r8.ClassFileResourceProvider;
+import com.android.tools.r8.DirectoryClassFileProvider;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class PassThroughTest extends TestBase {
+
+ private final String EXPECTED = StringUtils.lines("0", "foo", "0");
+
+ private final TestParameters parameters;
+ private final boolean keepDebug;
+
+ @Parameters(name = "{0}, keep-debug: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(getTestParameters().withCfRuntimes().build(), BooleanUtils.values());
+ }
+
+ public PassThroughTest(TestParameters parameters, boolean keepDebug) {
+ this.parameters = parameters;
+ this.keepDebug = keepDebug;
+ }
+
+ @Test
+ public void testJmv() throws Exception {
+ CodeInspector inspector =
+ testForJvm()
+ .addProgramClasses(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspector();
+ // Check that reading the same input is actual matches.
+ ClassFileResourceProvider original =
+ DirectoryClassFileProvider.fromDirectory(ToolHelper.getClassPathForTests());
+ verifyInstructionsForMainMatchingExpectation(original, true, true);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ Path outputJar = temp.newFile("output.jar").toPath();
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class)
+ .addKeepMainRule(Main.class)
+ .ifTrue(keepDebug, TestShrinkerBuilder::addKeepAllAttributes)
+ .compile()
+ .writeToZip(outputJar)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED);
+ verifyInstructionsForMainMatchingExpectation(
+ new ArchiveClassFileProvider(outputJar), keepDebug, false);
+ }
+
+ @Test
+ public void testR8ByteCodePassThrough() throws Exception {
+ Path outputJar = temp.newFile("output.jar").toPath();
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class)
+ .addKeepMainRule(Main.class)
+ .ifTrue(keepDebug, TestShrinkerBuilder::addKeepAllAttributes)
+ .addOptionsModification(
+ internalOptions ->
+ internalOptions.testing.cfByteCodePassThrough =
+ method -> method.method.name.toString().equals("main"))
+ .compile()
+ .writeToZip(outputJar)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED);
+ verifyInstructionsForMainMatchingExpectation(
+ new ArchiveClassFileProvider(outputJar), keepDebug, true);
+ }
+
+ private void verifyInstructionsForMainMatchingExpectation(
+ ClassFileResourceProvider actual, boolean checkDebug, boolean expectation) throws Exception {
+ ClassFileResourceProvider original =
+ DirectoryClassFileProvider.fromDirectory(ToolHelper.getClassPathForTests());
+ String descriptor = DescriptorUtils.javaTypeToDescriptor(Main.class.getTypeName());
+ byte[] expectedBytes = CfFrontendExamplesTest.getClassAsBytes(original, descriptor);
+ byte[] actualBytes = CfFrontendExamplesTest.getClassAsBytes(actual, descriptor);
+ if (!Arrays.equals(expectedBytes, actualBytes)) {
+ String expectedString = CfFrontendExamplesTest.asmToString(expectedBytes);
+ String actualString = CfFrontendExamplesTest.asmToString(actualBytes);
+ verifyInstructionsForMainMatchingExpectation(
+ getMethodInstructions(expectedString),
+ getMethodInstructions(actualString),
+ checkDebug,
+ expectation);
+ }
+ }
+
+ private String getMethodInstructions(String asm) {
+ int methodIndexStart =
+ asm.indexOf(
+ "methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, \"main\","
+ + " \"([Ljava/lang/String;)V\", null, null);");
+ int methodIndexEnd = asm.indexOf("}", methodIndexStart);
+ return asm.substring(methodIndexStart, methodIndexEnd);
+ }
+
+ private void verifyInstructionsForMainMatchingExpectation(
+ String originalInstructions,
+ String actualInstructions,
+ boolean checkDebug,
+ boolean expectation) {
+ if (!checkDebug) {
+ originalInstructions =
+ StringUtils.splitLines(originalInstructions).stream()
+ .filter(this::isNotDebugInstruction)
+ .map(instr -> instr + StringUtils.LINE_SEPARATOR)
+ .collect(Collectors.joining());
+ }
+ assertSame(expectation, actualInstructions.equals(originalInstructions));
+ }
+
+ private boolean isNotDebugInstruction(String instruction) {
+ return !(instruction.startsWith("methodVisitor.visitLocalVariable")
+ || instruction.startsWith("methodVisitor.visitLabel")
+ || instruction.startsWith("Label")
+ || instruction.startsWith("methodVisitor.visitLineNumber"));
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ int i = 0;
+ System.out.println(i);
+ int j = 0;
+ String foo = "foo";
+ // Keep the false to have R8 remove it.
+ if (false) {
+ System.out.println(foo);
+ }
+ System.out.println(foo);
+ System.out.println(j);
+ }
+ }
+}