Handle already desugared input for (some part of) library desugaring
Bug: 171867367
Bug: 172433489
Change-Id: I5e88c86e90749e36a6fb9bc7c46302db002d7fcb
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 70db016..1e7ec9c 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -94,6 +94,10 @@
private Map<DexClass, DexValueString> sourceDebugExtensions = new IdentityHashMap<>();
+ // When input has been (partially) desugared these are the classes which has been library
+ // desugared. This information is populated in the IR converter.
+ private Set<DexProgramClass> alreadyLibraryDesugared = null;
+
private AppView(
T appInfo,
WholeProgramOptimizations wholeProgramOptimizations,
@@ -663,4 +667,17 @@
}
});
}
+
+ public void setAlreadyLibraryDesugared(Set<DexProgramClass> alreadyLibraryDesugared) {
+ assert this.alreadyLibraryDesugared == null;
+ this.alreadyLibraryDesugared = alreadyLibraryDesugared;
+ }
+
+ public boolean isAlreadyLibraryDesugared(DexProgramClass clazz) {
+ if (!options().desugarSpecificOptions().allowAllDesugaredInput) {
+ return false;
+ }
+ assert alreadyLibraryDesugared != null;
+ return alreadyLibraryDesugared.contains(clazz);
+ }
}
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 ec95001..0974715 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
@@ -107,10 +107,12 @@
import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -480,7 +482,33 @@
executor, OptimizationFeedbackIgnore.getInstance());
DexApplication application = appView.appInfo().app();
timing.begin("IR conversion");
- ThreadUtils.processItems(application.classes(), this::convertMethods, executor);
+
+ if (appView.options().desugarSpecificOptions().allowAllDesugaredInput) {
+ // Classes which has already been through library desugaring will not go through IR
+ // processing again.
+ LibraryDesugaredChecker libraryDesugaredChecker = new LibraryDesugaredChecker(appView);
+ Set<DexProgramClass> alreadyLibraryDesugared = Sets.newConcurrentHashSet();
+ ThreadUtils.processItems(
+ application.classes(),
+ clazz -> {
+ if (libraryDesugaredChecker.isClassLibraryDesugared(clazz)) {
+ if (appView.options().desugarSpecificOptions().allowAllDesugaredInput) {
+ alreadyLibraryDesugared.add(clazz);
+ } else {
+ throw new CompilationError(
+ "Code for "
+ + clazz.getType().getDescriptor()
+ + "has already been library desugared.");
+ }
+ } else {
+ convertMethods(clazz);
+ }
+ },
+ executor);
+ appView.setAlreadyLibraryDesugared(alreadyLibraryDesugared);
+ } else {
+ ThreadUtils.processItems(application.classes(), this::convertMethods, executor);
+ }
// Build a new application with jumbo string info,
Builder<?> builder = application.builder();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LibraryDesugaredChecker.java b/src/main/java/com/android/tools/r8/ir/conversion/LibraryDesugaredChecker.java
new file mode 100644
index 0000000..7707d9e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LibraryDesugaredChecker.java
@@ -0,0 +1,183 @@
+// 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.ir.conversion;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
+
+public class LibraryDesugaredChecker {
+ private final AppView<?> appView;
+ private final DexString jDollarDescriptorPrefix;
+
+ LibraryDesugaredChecker(AppView<?> appView) {
+ this.appView = appView;
+ this.jDollarDescriptorPrefix = appView.dexItemFactory().createString("Lj$/");
+ }
+
+ public boolean isClassLibraryDesugared(DexProgramClass clazz) {
+ IsLibraryDesugaredTracer tracer =
+ new IsLibraryDesugaredTracer(appView, jDollarDescriptorPrefix, clazz);
+ tracer.run();
+ return tracer.isLibraryDesugared();
+ }
+
+ private static class IsLibraryDesugaredTracer extends UseRegistry {
+
+ private final DexString jDollarDescriptorPrefix;
+ private final AppView<?> appView;
+ private final DexProgramClass clazz;
+ private boolean isLibraryDesugared = false;
+
+ public IsLibraryDesugaredTracer(
+ AppView<?> appView, DexString jDollarDescriptorPrefix, DexProgramClass clazz) {
+ super(appView.dexItemFactory());
+ this.jDollarDescriptorPrefix = jDollarDescriptorPrefix;
+ this.appView = appView;
+ this.clazz = clazz;
+ }
+
+ public void run() {
+ registerClass(clazz);
+ }
+
+ public boolean isLibraryDesugared() {
+ return isLibraryDesugared;
+ }
+
+ private void registerClass(DexProgramClass clazz) {
+ if (clazz.superType != null) {
+ registerTypeReference(clazz.superType);
+ }
+ for (DexType implementsType : clazz.interfaces.values) {
+ registerTypeReference(implementsType);
+ }
+ if (isLibraryDesugared) {
+ return;
+ }
+ for (DexEncodedMethod method : clazz.methods()) {
+ registerMethod(new ProgramMethod(clazz, method));
+ if (isLibraryDesugared) {
+ return;
+ }
+ }
+ clazz.forEachField(this::registerField);
+ }
+
+ private void registerType(DexType type) {
+ isLibraryDesugared =
+ isLibraryDesugared || type.descriptor.startsWith(jDollarDescriptorPrefix);
+ }
+
+ private void registerField(DexField field) {
+ registerType(field.getHolderType());
+ registerType(field.getType());
+ }
+
+ private void registerMethod(DexMethod method) {
+ for (DexType type : method.getParameters().values) {
+ registerTypeReference(type);
+ }
+ registerTypeReference(method.getReturnType());
+ }
+
+ private void registerField(DexEncodedField field) {
+ registerField(field.getReference());
+ }
+
+ private void registerMethod(ProgramMethod method) {
+ registerMethod(method.getReference());
+ for (DexAnnotation annotation : method.getDefinition().annotations().annotations) {
+ if (annotation.annotation.type == appView.dexItemFactory().annotationThrows) {
+ DexValueArray dexValues = annotation.annotation.elements[0].value.asDexValueArray();
+ for (DexValue dexValType : dexValues.getValues()) {
+ registerType(dexValType.asDexValueType().value);
+ }
+ }
+ }
+ method.registerCodeReferences(this);
+ }
+
+ @Override
+ public void registerInitClass(DexType type) {
+ registerType(type);
+ }
+
+ @Override
+ public void registerInvokeVirtual(DexMethod method) {
+ registerMethod(method);
+ }
+
+ @Override
+ public void registerInvokeDirect(DexMethod method) {
+ registerMethod(method);
+ }
+
+ @Override
+ public void registerInvokeStatic(DexMethod method) {
+ registerMethod(method);
+ }
+
+ @Override
+ public void registerInvokeInterface(DexMethod method) {
+ registerMethod(method);
+ }
+
+ @Override
+ public void registerInvokeStatic(DexMethod method, boolean itf) {
+ registerMethod(method);
+ }
+
+ @Override
+ public void registerInvokeSuper(DexMethod method) {
+ registerMethod(method);
+ }
+
+ @Override
+ public void registerInstanceFieldRead(DexField field) {
+ registerField(field);
+ }
+
+ @Override
+ public void registerInstanceFieldWrite(DexField field) {
+ registerField(field);
+ }
+
+ @Override
+ public void registerNewInstance(DexType type) {
+ registerType(type);
+ }
+
+ @Override
+ public void registerStaticFieldRead(DexField field) {
+ registerField(field);
+ }
+
+ @Override
+ public void registerStaticFieldWrite(DexField field) {
+ registerField(field);
+ }
+
+ @Override
+ public void registerTypeReference(DexType type) {
+ registerType(type);
+ }
+
+ @Override
+ public void registerInstanceOf(DexType type) {
+ registerType(type);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 0884ab5..a61f62e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -437,7 +437,7 @@
iface -> {
for (int i = 0; i < extraInterfaceSignatures.size(); i++) {
if (extraInterfaceSignatures.get(i).type() == iface) {
- if (!appView.options().desugarSpecificOptions().allowDesugaredInput) {
+ if (!appView.options().desugarSpecificOptions().allowAllDesugaredInput) {
throw new CompilationError(
"Code has already been library desugared. Interface "
+ iface.getDescriptor()
@@ -476,6 +476,9 @@
ClassInfo superInfo,
MethodSignatures signatures,
Builder<DexEncodedMethod> additionalForwards) {
+ if (clazz.isProgramClass() && appView.isAlreadyLibraryDesugared(clazz.asProgramClass())) {
+ return;
+ }
for (Wrapper<DexMethod> wrapper : signatures.signatures) {
resolveForwardForSignature(
clazz,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
index 214c17a..e04505d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -446,6 +446,9 @@
}
SortedProgramMethodSet addedMethods = SortedProgramMethodSet.create();
for (DexProgramClass clazz : appView.appInfo().classes()) {
+ if (appView.isAlreadyLibraryDesugared(clazz)) {
+ continue;
+ }
if (clazz.superType == null) {
assert clazz.type == appView.dexItemFactory().objectType : clazz.type.toSourceString();
continue;
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 ddcb3c7..862cb47 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
@@ -1022,6 +1022,9 @@
// results, since each class implementing an emulated interface should also implement the
// rewritten one.
private void transformEmulatedInterfaces(DexProgramClass clazz) {
+ if (appView.isAlreadyLibraryDesugared(clazz)) {
+ return;
+ }
List<GenericSignature.ClassTypeSignature> newInterfaces = new ArrayList<>();
GenericSignature.ClassSignature classSignature = clazz.getClassSignature();
for (int i = 0; i < clazz.interfaces.size(); i++) {
@@ -1074,6 +1077,9 @@
// First we compute all desugaring *without* introducing forwarding methods.
for (DexProgramClass clazz : builder.getProgramClasses()) {
if (shouldProcess(clazz, flavour, false)) {
+ if (appView.isAlreadyLibraryDesugared(clazz)) {
+ continue;
+ }
processor.processClass(clazz);
}
}
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 97ef2dc..58c9f2d 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -361,6 +361,13 @@
LensCodeRewriterUtils rewriter,
ClassWriter writer,
ImmutableMap<DexString, DexValue> defaults) {
+ NamingLens namingLens = this.namingLens;
+
+ // For "pass through" classes which has already been library desugared use the identity lens.
+ if (appView.isAlreadyLibraryDesugared(method.getHolder())) {
+ namingLens = NamingLens.getIdentityLens();
+ }
+
DexEncodedMethod definition = method.getDefinition();
int access = definition.getAccessFlags().getAsCfAccessFlags();
if (definition.isDeprecated()) {
@@ -383,7 +390,7 @@
writeAnnotations(visitor::visitAnnotation, definition.annotations().annotations);
writeParameterAnnotations(visitor, definition.parameterAnnotationsList);
if (!definition.shouldNotHaveCode()) {
- writeCode(method, classFileVersion, rewriter, visitor);
+ writeCode(method, classFileVersion, namingLens, rewriter, visitor);
}
visitor.visitEnd();
}
@@ -520,6 +527,7 @@
private void writeCode(
ProgramMethod method,
CfVersion classFileVersion,
+ NamingLens namingLens,
LensCodeRewriterUtils rewriter,
MethodVisitor visitor) {
CfCode code = method.getDefinition().getCode().asCfCode();
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 052b249..47c084e 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1184,8 +1184,11 @@
// b/172508621
public boolean sortMethodsOnCfOutput =
System.getProperty("com.android.tools.r8.sortMethodsOnCfWriting") != null;
- public boolean allowDesugaredInput =
- System.getProperty("com.android.tools.r8.allowDesugaredInput") != null;
+ // Desugaring is not fully idempotent. With this option turned on all desugared input is
+ // allowed, and if it is detected that the desugared input cannot be reprocessed, that input
+ // will be passed-through without the problematic rewritings applied.
+ public boolean allowAllDesugaredInput =
+ System.getProperty("com.android.tools.r8.allowAllDesugaredInput") != null;
}
public static class CallSiteOptimizationOptions {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IteratorTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IteratorTest.java
index 2a968a0..46cb2bd 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IteratorTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IteratorTest.java
@@ -112,7 +112,9 @@
diagnosticMessage(
containsString(
"Code has already been library desugared. "
- + "Interface Lj$/util/Iterator; is already implemented"))));
+ + "Interface Lj$/util/Iterator; is already implemented by "
+ + "Lcom/android/tools/r8/desugar/desugaredlibrary/"
+ + "IteratorTest$MyIterator;"))));
fail("Expected failure");
} catch (CompilationFailedException e) {
// Expected.
@@ -124,7 +126,7 @@
testForD8(Backend.CF)
.addOptionsModification(
options ->
- options.desugarSpecificOptions().allowDesugaredInput =
+ options.desugarSpecificOptions().allowAllDesugaredInput =
parameters.getApiLevel().isLessThan(apiLevelNotRequiringDesugaring))
.setMinApi(parameters.getApiLevel())
.addProgramFiles(firstJar)
@@ -141,9 +143,8 @@
assertEquals(
canUseDefaultAndStaticInterfaceMethods ? 0 : 1,
info.getInterfaces().stream().filter(name -> name.equals("j$/util/Iterator")).count());
- // TODO(b/171867367): This should only be 2.
assertEquals(
- canUseDefaultAndStaticInterfaceMethods ? 1 : 3,
+ canUseDefaultAndStaticInterfaceMethods ? 1 : 2,
info.getMethodNames().stream().filter(name -> name.equals("forEachRemaining")).count());
if (parameters.getRuntime().isDex()) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java
index 210216f..cb8c24f 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java
@@ -127,6 +127,8 @@
// Use D8 to desugar with Java classfile output.
Path secondJar =
testForD8(Backend.CF)
+ .addOptionsModification(
+ options -> options.desugarSpecificOptions().allowAllDesugaredInput = true)
.setMinApi(parameters.getApiLevel())
.addProgramFiles(firstJar)
.addLibraryClasses(CustomLibClass.class)
@@ -141,8 +143,7 @@
assertEquals(
Impl.class.getTypeName(),
DescriptorUtils.getJavaTypeFromBinaryName(info.getClassBinaryName()));
- // TODO(b/171867367): This should only be 2.
- assertEquals(3, info.getMethodNames().stream().filter(name -> name.equals("foo")).count());
+ assertEquals(2, info.getMethodNames().stream().filter(name -> name.equals("foo")).count());
// Convert to DEX without desugaring and run.
testForD8()