Merge "Adding a lense for interface method desugaring method mappings."
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 9b90df2..9828bed 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -97,7 +97,7 @@
public abstract DexMethod getRenamedMethodSignature(DexMethod originalMethod);
- public final DexEncodedMethod mapDexEncodedMethod(
+ public DexEncodedMethod mapDexEncodedMethod(
AppInfo appInfo, DexEncodedMethod originalEncodedMethod) {
DexMethod newMethod = getRenamedMethodSignature(originalEncodedMethod.method);
if (newMethod != originalEncodedMethod.method) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodDesugaringLense.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodDesugaringLense.java
new file mode 100644
index 0000000..a8616fb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodDesugaringLense.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2018, 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.ir.desugar;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+
+class InterfaceMethodDesugaringLense extends NestedGraphLense {
+ private final Map<DexEncodedMethod, DexEncodedMethod> methodsWithMovedCode;
+
+ InterfaceMethodDesugaringLense(
+ BiMap<DexMethod, DexMethod> methodMapping,
+ Map<DexEncodedMethod, DexEncodedMethod> methodsWithMovedCode,
+ GraphLense previous, DexItemFactory factory) {
+ super(
+ ImmutableMap.of(),
+ methodMapping,
+ ImmutableMap.of(),
+ ImmutableBiMap.of(),
+ methodMapping.inverse(),
+ previous,
+ factory);
+ this.methodsWithMovedCode = methodsWithMovedCode;
+ }
+
+ @Override
+ public DexEncodedMethod mapDexEncodedMethod(AppInfo appInfo, DexEncodedMethod original) {
+ return super.mapDexEncodedMethod(
+ appInfo, methodsWithMovedCode.getOrDefault(original, original));
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 76a80d4..3883f4d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -368,6 +368,12 @@
processor.process(clazz.asProgramClass());
}
}
+ if (converter.enableWholeProgramOptimizations &&
+ (!processor.methodsWithMovedCode.isEmpty() || !processor.movedMethods.isEmpty())) {
+ converter.setGraphLense(
+ new InterfaceMethodDesugaringLense(processor.movedMethods,
+ processor.methodsWithMovedCode, converter.getGraphLense(), factory));
+ }
return processor.companionClasses;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index c538e86..e5900d9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -19,6 +19,8 @@
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
@@ -40,6 +42,9 @@
// All created companion classes indexed by interface classes.
final Map<DexProgramClass, DexProgramClass> companionClasses = new IdentityHashMap<>();
+ final BiMap<DexMethod, DexMethod> movedMethods = HashBiMap.create();
+ final Map<DexEncodedMethod, DexEncodedMethod> methodsWithMovedCode = new IdentityHashMap<>();
+
InterfaceProcessor(InterfaceMethodRewriter rewriter) {
this.rewriter = rewriter;
}
@@ -74,12 +79,15 @@
assert (dexCode.getDebugInfo() == null)
|| (companionMethod.getArity() == dexCode.getDebugInfo().parameters.length);
- companionMethods.add(new DexEncodedMethod(companionMethod,
- newFlags, virtual.annotations, virtual.parameterAnnotationsList, code));
-
// Make the method abstract.
virtual.accessFlags.setAbstract();
- virtual.removeCode();
+ virtual.removeCode(); // Remove code first to void ownership.
+
+ DexEncodedMethod implMethod = new DexEncodedMethod(
+ companionMethod, newFlags, virtual.annotations, virtual.parameterAnnotationsList, code);
+ companionMethods.add(implMethod);
+
+ methodsWithMovedCode.put(virtual, implMethod);
}
// Remove bridge methods.
@@ -105,27 +113,29 @@
newFlags.setPublic();
}
+ DexMethod oldMethod = direct.method;
if (isStaticMethod(direct)) {
assert originalFlags.isPrivate() || originalFlags.isPublic()
: "Static interface method " + direct.toSourceString() + " is expected to "
+ "either be public or private in " + iface.origin;
- companionMethods.add(new DexEncodedMethod(
- rewriter.staticAsMethodOfCompanionClass(direct.method), newFlags,
+ DexMethod companionMethod = rewriter.staticAsMethodOfCompanionClass(oldMethod);
+ companionMethods.add(new DexEncodedMethod(companionMethod, newFlags,
direct.annotations, direct.parameterAnnotationsList, direct.getCode()));
+ movedMethods.put(oldMethod, companionMethod);
} else {
if (originalFlags.isPrivate()) {
- assert !rewriter.factory.isClassConstructor(direct.method)
+ assert !rewriter.factory.isClassConstructor(oldMethod)
: "Unexpected private constructor " + direct.toSourceString()
+ " in " + iface.origin;
newFlags.setStatic();
- DexMethod companionMethod = rewriter.privateAsMethodOfCompanionClass(direct.method);
+ DexMethod companionMethod = rewriter.privateAsMethodOfCompanionClass(oldMethod);
Code code = direct.getCode();
if (code == null) {
throw new CompilationError("Code is missing for private instance "
- + "interface method: " + direct.method.toSourceString(), iface.origin);
+ + "interface method: " + oldMethod.toSourceString(), iface.origin);
}
DexCode dexCode = code.asDexCode();
// TODO(ager): Should we give the new first parameter an actual name? Maybe 'this'?
@@ -135,11 +145,12 @@
companionMethods.add(new DexEncodedMethod(companionMethod,
newFlags, direct.annotations, direct.parameterAnnotationsList, code));
+ movedMethods.put(oldMethod, companionMethod);
} else {
// Since there are no interface constructors at this point,
// this should only be class constructor.
- assert rewriter.factory.isClassConstructor(direct.method);
+ assert rewriter.factory.isClassConstructor(oldMethod);
remainingMethods.add(direct);
}
}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 2af67a4..8337f6e 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -64,6 +64,7 @@
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.function.Function;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.junit.Assume;
import org.junit.rules.TemporaryFolder;
@@ -758,8 +759,13 @@
}
public static List<Path> getClassFilesForTestDirectory(Path directory) throws IOException {
+ return getClassFilesForTestDirectory(directory, null);
+ }
+
+ public static List<Path> getClassFilesForTestDirectory(
+ Path directory, Predicate<Path> filter) throws IOException {
return Files.walk(directory)
- .filter(path -> path.toString().endsWith(".class"))
+ .filter(path -> path.toString().endsWith(".class") && (filter == null || filter.test(path)))
.collect(Collectors.toList());
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b112247415/B112247415.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b112247415/B112247415.java
new file mode 100644
index 0000000..2e6fb4c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b112247415/B112247415.java
@@ -0,0 +1,109 @@
+// Copyright (c) 2018, 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.ir.optimize.outliner.b112247415;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.InvokeVirtual;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+class TestClass {
+ interface Act {
+ default String get(StringBuilder builder, String arg) {
+ builder.append(arg).append(arg).append(arg);
+ return builder.toString();
+ }
+ }
+
+ public static void main(String[] args) {
+ System.out.println(get(new TestClass().toActOverridden(), new StringBuilder(), "a"));
+ System.out.println(get(new TestClass().toActDefault(), new StringBuilder(), "b"));
+ }
+
+ static String get(Act act, StringBuilder builder, String arg) {
+ act.get(builder, arg);
+ return builder.toString();
+ }
+
+ Act toActOverridden() {
+ return new Act() {
+ @Override
+ public String get(StringBuilder builder, String arg) {
+ builder.append(arg).append(arg).append(arg);
+ return builder.toString();
+ }
+ };
+ }
+
+ Act toActDefault() {
+ return new Act() {
+ };
+ }
+}
+
+@RunWith(VmTestRunner.class)
+public class B112247415 extends TestBase {
+
+ @Test
+ public void test() throws Exception {
+ String javaResult = runOnJava(TestClass.class);
+
+ R8Command.Builder builder = R8Command.builder();
+ builder.addProgramFiles(ToolHelper.getClassFilesForTestDirectory(
+ ToolHelper.getPackageDirectoryForTestPackage(TestClass.Act.class.getPackage()),
+ path -> path.getFileName().toString().startsWith("TestClass")));
+ builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
+ String config = keepMainProguardConfiguration(TestClass.class);
+ builder.addProguardConfiguration(ImmutableList.of(config), Origin.unknown());
+ AndroidApp app = ToolHelper.runR8(builder.build(), options -> {
+ // To trigger outliner, set # of expected outline candidate as threshold.
+ options.outline.threshold = 2;
+ options.enableInlining = false;
+ options.enableMinification = false;
+ });
+ ProcessResult result = runOnArtRaw(app, TestClass.class);
+ assertEquals(0, result.exitCode);
+ assertEquals(javaResult, result.stdout);
+
+ CodeInspector inspector = new CodeInspector(app);
+ for (FoundClassSubject clazz : inspector.allClasses()) {
+ clazz.getDexClass().forEachMethod(encodedMethod -> {
+ Code code = encodedMethod.getCode();
+ if (code != null && !encodedMethod.method.name.toString().startsWith("outline")) {
+ verifyAbsenceOfStringBuilderAppend(code.asDexCode().instructions);
+ }
+ });
+ }
+ }
+
+ private void verifyAbsenceOfStringBuilderAppend(Instruction[] instructions) {
+ for (Instruction instr : instructions) {
+ if (instr instanceof InvokeVirtual) {
+ InvokeVirtual invokeVirtual = (InvokeVirtual) instr;
+ DexMethod invokedMethod = invokeVirtual.getMethod();
+ if (invokedMethod.getHolder().getName().endsWith("StringBuilder")) {
+ assertNotEquals("append", invokedMethod.name.toString());
+ }
+ }
+ }
+ }
+}