Move desugaring of backport methods to the enqueuer.
Bug: 150693139
Change-Id: If68b026b6498ac5a76fab76412ec45920ab60e0c
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 a972aed..aee51a6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -67,6 +67,7 @@
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.naming.MemberNaming.Signature;
import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.position.MethodPosition;
import com.android.tools.r8.shaking.AnnotationRemover;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.BooleanUtils;
@@ -1366,6 +1367,10 @@
return method;
}
+ public MethodPosition getPosition() {
+ return new MethodPosition(method.asMethodReference());
+ }
+
@Override
public boolean isDexEncodedMethod() {
checkIfObsolete();
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
index a1fe5c7..0765824 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.MethodPosition;
/** Type representing a method definition in the programs compilation unit and its holder. */
public final class ProgramMethod extends DexClassAndMethod
@@ -81,4 +82,8 @@
assert holder.isProgramClass();
return holder.asProgramClass();
}
+
+ public MethodPosition getPosition() {
+ return getDefinition().getPosition();
+ }
}
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 b7964c8..ba89d0c 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
@@ -269,10 +269,13 @@
? new InterfaceMethodRewriter(appView, this)
: null;
this.twrCloseResourceRewriter =
- ((options.desugarState == DesugarState.ON) && enableTwrCloseResourceDesugaring())
+ (options.desugarState == DesugarState.ON && enableTwrCloseResourceDesugaring())
? new TwrCloseResourceRewriter(appView, this)
: null;
- this.backportedMethodRewriter = new BackportedMethodRewriter(appView);
+ this.backportedMethodRewriter =
+ (options.desugarState == DesugarState.ON && !appView.enableWholeProgramOptimizations())
+ ? new BackportedMethodRewriter(appView)
+ : null;
this.desugaredLibraryRetargeter =
options.desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()
? null
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index b45fe42..04fb914 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -37,6 +37,7 @@
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.DesugarState;
+import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.Timing;
import java.io.IOException;
import java.util.ArrayList;
@@ -106,9 +107,25 @@
}
public boolean desugar(ProgramMethod method, AppInfoWithClassHierarchy appInfo) {
+ return desugar(method, appInfo, synthesizedMethods::add);
+ }
+
+ public boolean desugar(
+ ProgramMethod method, AppInfoWithClassHierarchy appInfo, Consumer<ProgramMethod> consumer) {
if (!enabled) {
return false;
}
+ if (method.getDefinition().getCode().isDexCode()) {
+ appView
+ .options()
+ .reporter
+ .error(
+ new StringDiagnostic(
+ "Unsupported attempt to desugar DEX code",
+ method.getOrigin(),
+ method.getPosition()));
+ return false;
+ }
CfCode code = method.getDefinition().getCode().asCfCode();
ListIterator<CfInstruction> iterator = code.getInstructions().listIterator();
boolean replaced = false;
@@ -127,8 +144,7 @@
iterator = mutableInstructions.listIterator(iterator.previousIndex());
iterator.next();
}
- provider.rewriteInvoke(
- invoke, iterator, method.getHolder(), appInfo, synthesizedMethods::add);
+ provider.rewriteInvoke(invoke, iterator, method.getHolder(), appInfo, consumer);
replaced = true;
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index ec4e675..629506c 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -78,6 +78,7 @@
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
import com.android.tools.r8.ir.desugar.LambdaClass;
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
@@ -340,11 +341,13 @@
private final GraphReporter graphReporter;
private final LambdaRewriter lambdaRewriter;
+ private final BackportedMethodRewriter backportRewriter;
private final DesugaredLibraryConversionWrapperAnalysis desugaredLibraryWrapperAnalysis;
private final Map<DexType, Pair<LambdaClass, ProgramMethod>> lambdaClasses =
new IdentityHashMap<>();
private final Map<DexEncodedMethod, Map<DexCallSite, LambdaClass>> lambdaCallSites =
new IdentityHashMap<>();
+ private final Map<DexMethod, ProgramMethod> methodsWithBackports = new IdentityHashMap<>();
private final Set<DexProgramClass> classesWithSerializableLambdas = Sets.newIdentityHashSet();
Enqueuer(
@@ -383,6 +386,8 @@
liveMethods = new LiveMethodsSet(graphReporter::registerMethod);
liveFields = new LiveFieldsSet(graphReporter::registerField);
lambdaRewriter = options.desugarState == DesugarState.ON ? new LambdaRewriter(appView) : null;
+ backportRewriter =
+ options.desugarState == DesugarState.ON ? new BackportedMethodRewriter(appView) : null;
objectAllocationInfoCollection =
ObjectAllocationInfoCollectionImpl.builder(mode.isInitialTreeShaking(), graphReporter);
@@ -1077,6 +1082,10 @@
private void traceInvokeDirect(
DexMethod invokedMethod, ProgramMethod context, KeepReason reason) {
+ if (registerBackportInvoke(invokedMethod, context)) {
+ return;
+ }
+
if (!registerMethodWithTargetAndContext(
methodAccessInfoCollection::registerInvokeDirectInContext, invokedMethod, context)) {
return;
@@ -1098,6 +1107,10 @@
private void traceInvokeInterface(
DexMethod method, ProgramMethod context, KeepReason keepReason) {
+ if (registerBackportInvoke(method, context)) {
+ return;
+ }
+
if (!registerMethodWithTargetAndContext(
methodAccessInfoCollection::registerInvokeInterfaceInContext, method, context)) {
return;
@@ -1117,8 +1130,20 @@
traceInvokeStatic(invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context));
}
+ private boolean registerBackportInvoke(DexMethod invokedMethod, ProgramMethod context) {
+ if (backportRewriter != null && backportRewriter.needsDesugaring(invokedMethod)) {
+ methodsWithBackports.putIfAbsent(context.getReference(), context);
+ return true;
+ }
+ return false;
+ }
+
private void traceInvokeStatic(
DexMethod invokedMethod, ProgramMethod context, KeepReason reason) {
+ if (registerBackportInvoke(invokedMethod, context)) {
+ return;
+ }
+
DexItemFactory dexItemFactory = appView.dexItemFactory();
if (dexItemFactory.classMethods.isReflectiveClassLookup(invokedMethod)
|| dexItemFactory.atomicFieldUpdaterMethods.isFieldUpdater(invokedMethod)) {
@@ -1150,6 +1175,9 @@
}
void traceInvokeSuper(DexMethod invokedMethod, ProgramMethod context) {
+ if (registerBackportInvoke(invokedMethod, context)) {
+ return;
+ }
// We have to revisit super invokes based on the context they are found in. The same
// method descriptor will hit different targets, depending on the context it is used in.
DexMethod actualTarget = getInvokeSuperTarget(invokedMethod, context);
@@ -1174,6 +1202,10 @@
private void traceInvokeVirtual(
DexMethod invokedMethod, ProgramMethod context, KeepReason reason) {
+ if (registerBackportInvoke(invokedMethod, context)) {
+ return;
+ }
+
if (invokedMethod == appView.dexItemFactory().classMethods.newInstance
|| invokedMethod == appView.dexItemFactory().constructorMethods.newInstance) {
pendingReflectiveUses.add(context);
@@ -2892,6 +2924,7 @@
synthesizeInterfaceMethodBridges(additions);
synthesizeLambdas(additions);
synthesizeLibraryConversionWrappers(additions);
+ synthesizeBackports(additions);
if (additions.isEmpty()) {
return;
}
@@ -2923,6 +2956,12 @@
syntheticInterfaceMethodBridges.clear();
}
+ private void synthesizeBackports(SyntheticAdditions additions) {
+ for (ProgramMethod method : methodsWithBackports.values()) {
+ backportRewriter.desugar(method, appInfo, additions::addLiveMethod);
+ }
+ }
+
private void synthesizeLambdas(SyntheticAdditions additions) {
if (lambdaRewriter == null || lambdaClasses.isEmpty()) {
assert lambdaCallSites.isEmpty();
diff --git a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
index abd2a99..974fdb1 100644
--- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
@@ -248,6 +248,12 @@
}
private MergeGroup getMergeGroup(DexProgramClass clazz) {
+ if (appView.getSyntheticItems().isSyntheticClass(clazz)) {
+ // In principle it would be valid to merge synthetic classes into program classes.
+ // Doing so may however increase size as static methods will not be de-duplicated
+ // at synthetic finalization.
+ return MergeGroup.DONT_MERGE;
+ }
if (appView.appInfo().getNoStaticClassMergingSet().contains(clazz.type)) {
return MergeGroup.DONT_MERGE;
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index 9dd8bee..f8c58b7 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -418,6 +418,10 @@
SyntheticMethodDefinition method = (SyntheticMethodDefinition) definition;
if (SyntheticMethodBuilder.isValidSyntheticMethod(method.getMethod().getDefinition())) {
methods.add(method);
+ } else {
+ // Failing this check indicates that an optimization has modified the synthetic in a
+ // disruptive way.
+ assert false;
}
}
return methods;
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index 6f5c49d..6a5e9a3 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -308,7 +308,15 @@
ImmutableMap.Builder<DexType, SyntheticReference> rewrittenItems = ImmutableMap.builder();
for (SyntheticReference reference : nonLecacySyntheticItems.values()) {
SyntheticReference rewritten = reference.rewrite(lens);
- rewrittenItems.put(rewritten.getHolder(), rewritten);
+ // If the reference has been rewritten the compiler has changed it and we drop it from the
+ // set of synthetics.
+ if (reference == rewritten) {
+ rewrittenItems.put(rewritten.getHolder(), rewritten);
+ } else {
+ // If the item is rewritten, it should be moved to another holder as the synthetic holder
+ // is no longer part of the synthetic collection.
+ assert reference.getHolder() != rewritten.getHolder();
+ }
}
// No pending item should need rewriting.
assert legacyPendingClasses.keySet().equals(lens.rewriteTypes(legacyPendingClasses.keySet()));
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
index 86704ea..571331b 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
@@ -35,8 +35,8 @@
return null;
}
assert clazz.isProgramClass();
- ProgramMethod definition = clazz.asProgramClass().lookupProgramMethod(this.method);
- return new SyntheticMethodDefinition(getContext(), definition);
+ ProgramMethod definition = clazz.asProgramClass().lookupProgramMethod(method);
+ return definition != null ? new SyntheticMethodDefinition(getContext(), definition) : null;
}
@Override
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 67721e4..d14254e 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -1032,6 +1032,12 @@
.put("552-checker-primitive-typeprop", TestCondition.match(TestCondition.R8DEX_COMPILER))
// Test with invalid register usage: invoke-static {v0,v0}, foo(IL)V
.put("557-checker-ref-equivalent", TestCondition.match(TestCondition.R8DEX_COMPILER))
+ // Test with smali code that calls a method that needs to be desugared.
+ // The smali code is only present in the non-legacy test distrubution, so this only fails
+ // when running the "default" runtime.
+ .put(
+ "567-checker-compare",
+ TestCondition.match(TestCondition.runtimes(Runtime.ART_DEFAULT)))
// This test is starting from invalid dex code. It splits up a double value and uses
// the first register of a double with the second register of another double.
.put("800-smali", TestCondition.match(TestCondition.R8DEX_COMPILER))
@@ -2398,7 +2404,7 @@
CompilationMode compilationMode)
throws Throwable {
if (specification.expectedToFailWithX8) {
- expectException(CompilationError.class);
+ expectedException = true;
try {
executeCompilerUnderTest(
compilerUnderTest,
@@ -2406,7 +2412,8 @@
resultDir.getCanonicalPath(),
compilationMode,
new CompilationOptions(specification));
- } catch (CompilationFailedException e) {
+ } catch (CompilationFailedException | CompilationError e) {
+ expectException(CompilationError.class);
throw new CompilationError(e.getMessage(), e);
}
System.err.println("Should have failed R8/D8 compilation with a CompilationError.");
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
index 5a90d52..d288099 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
import com.android.tools.r8.utils.TestDescriptionWatcher;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
@@ -172,6 +173,10 @@
}
private boolean shouldCompileFail() {
+ if (input == Input.DX && getFailingCompileDxToDex().contains(mainClass)) {
+ assert output == Output.DEX;
+ return true;
+ }
if (output == Output.CF && getFailingCompileCf().contains(mainClass)) {
return true;
}
@@ -261,6 +266,10 @@
protected abstract Set<String> getFailingCompileCfToDex();
+ protected Set<String> getFailingCompileDxToDex() {
+ return ImmutableSet.of();
+ }
+
// TODO(mathiasr): Add CompilerSet for CfToDex so we can fold this into getFailingRun().
protected abstract Set<String> getFailingRunCfToDex();
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 5eaef9e..04ff03f 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -158,6 +158,15 @@
}
@Override
+ protected Set<String> getFailingCompileDxToDex() {
+ return new ImmutableSet.Builder<String>()
+ .add("regress_72361252.Test") // requires desugar
+ .add("regress_70703087.Test") // requires desugar
+ .add("regress_70737019.Test") // requires desugar
+ .build();
+ }
+
+ @Override
protected Set<String> getFailingCompileCf() {
return new ImmutableSet.Builder<String>()
.build();
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
index d4346cc..b4a4c48 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
@@ -7,6 +7,7 @@
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.TestBase;
@@ -19,6 +20,7 @@
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.SyntheticItemsTestUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
@@ -69,7 +71,7 @@
.minification(minify)
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED)
- .inspect(this::checkNoInternalSyntheticNames);
+ .inspect(this::checkNoOriginalsAndNoInternalSynthetics);
}
@Test
@@ -79,7 +81,7 @@
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED)
- .inspect(this::checkNoInternalSyntheticNames)
+ .inspect(this::checkNoOriginalsAndNoInternalSynthetics)
.inspect(this::checkExpectedSynthetics);
}
@@ -133,7 +135,7 @@
.writeToZip(out3)
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED)
- .inspect(this::checkNoInternalSyntheticNames)
+ .inspect(this::checkNoOriginalsAndNoInternalSynthetics)
.inspect(inspector -> assertEquals(syntheticsInParts, getSyntheticMethods(inspector)));
// Finally do a non-intermediate merge.
@@ -142,7 +144,7 @@
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED)
- .inspect(this::checkNoInternalSyntheticNames)
+ .inspect(this::checkNoOriginalsAndNoInternalSynthetics)
.inspect(
inspector -> {
if (intermediate) {
@@ -179,16 +181,28 @@
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED)
- .inspect(this::checkNoInternalSyntheticNames)
+ .inspect(this::checkNoOriginalsAndNoInternalSynthetics)
.inspect(this::checkExpectedSynthetics);
}
- private void checkNoInternalSyntheticNames(CodeInspector inspector) {
+ private void checkNoOriginalsAndNoInternalSynthetics(CodeInspector inspector) {
inspector.forAllClasses(
clazz -> {
assertThat(
clazz.getFinalName(),
not(containsString(SyntheticItems.INTERNAL_SYNTHETIC_CLASS_SEPARATOR)));
+ if (!clazz.getOriginalName().equals(MiniAssert.class.getTypeName())) {
+ clazz.forAllMethods(
+ method ->
+ assertTrue(
+ "Unexpected static invoke to java.lang method:\n"
+ + method.getMethod().codeToString(),
+ method
+ .streamInstructions()
+ .filter(InstructionSubject::isInvokeStatic)
+ .noneMatch(
+ i -> i.getMethod().qualifiedName().startsWith("java.lang"))));
+ }
});
}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/NullCheckEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/NullCheckEnumUnboxingTest.java
index 25a3434..0f313cf 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/NullCheckEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/NullCheckEnumUnboxingTest.java
@@ -8,7 +8,6 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.AndroidApiLevel;
import java.util.List;
import java.util.Objects;
import org.junit.Test;
@@ -51,13 +50,10 @@
.inspectDiagnosticMessages(
m -> {
assertEnumIsUnboxed(MyEnum.class, MyEnum.class.getSimpleName(), m);
- // MyEnum19 is unboxed only if minAPI > 19 because Objects#requiredNonNull is then
- // present.
- if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K)) {
- assertEnumIsUnboxed(MyEnum19.class, MyEnum19.class.getSimpleName(), m);
- } else {
- assertEnumIsBoxed(MyEnum19.class, MyEnum19.class.getSimpleName(), m);
- }
+ // MyEnum19 is always unboxed. If minAPI > 19 the unboxer will identify
+ // Objects#requiredNonNull usage. For 19 and prior, the backport code should not
+ // prohibit the unboxing either.
+ assertEnumIsUnboxed(MyEnum19.class, MyEnum19.class.getSimpleName(), m);
})
.run(parameters.getRuntime(), MainNullTest.class)
.assertSuccess();