Reland "Use instance information to iterate instantiated subtypes."
This relands commit 357a6232744eb8aaa1f896545be45bd5cd4989c1.
CL: https://r8-review.googlesource.com/c/r8/+/50141
Bug: 139464956
Bug: 145344105
Bug: 150277553
Change-Id: I2d0730f918a682b105c1c2055d58c24f23cd0aec
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 897763a..93ca81d 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -551,7 +551,7 @@
// We have no debugging info in the code.
return;
}
- int largestPrefix = 1;
+ int largestPrefix = 0;
int existingThisIndex = -1;
for (int i = 0; i < localVariables.size(); i++) {
LocalVariableInfo localVariable = localVariables.get(i);
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index 283777f..fbecfef 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -388,7 +388,8 @@
}
},
lambda -> {
- assert resolvedHolder.isInterface();
+ assert resolvedHolder.isInterface()
+ || resolvedHolder.type == appInfo.dexItemFactory().objectType;
LookupTarget target = lookupVirtualDispatchTarget(lambda, appInfo);
if (target != null) {
if (target.isLambdaTarget()) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index 63f6a89..dc319a8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -97,11 +97,11 @@
if (lookupResult == null || lookupResult.isEmpty()) {
return null;
}
- assert lookupResult.hasMethodTargets();
if (lookupResult.hasLambdaTargets()) {
// TODO(b/150277553): Support lambda targets.
return null;
}
+ assert lookupResult.hasMethodTargets();
DexType staticReceiverType = getInvokedMethod().holder;
DexType refinedReceiverType =
TypeAnalysis.getRefinedReceiverType(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
index 18b62e6..92b23eb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
@@ -218,15 +218,14 @@
resolution.lookupVirtualDispatchTargets(
appView.definitionForProgramType(context), appView.appInfo());
if (lookupResult.isLookupResultSuccess()) {
- // TODO(b/150277553): Add lambda methods to the call graph.
Set<DexEncodedMethod> targets = new HashSet<>();
lookupResult
.asLookupResultSuccess()
.forEach(
methodTarget -> targets.add(methodTarget.getMethod()),
- lambdaTarget -> {
- assert false;
- });
+ lambdaTarget ->
+ // The call target will ultimately be the implementation method.
+ targets.add(lambdaTarget.getImplementationMethod().getMethod()));
return targets;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index d6d698e..85b98ba 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -378,4 +378,8 @@
assert !instructions.hasNext();
nextBlock.copyCatchHandlers(code, blocks, currentBlock, getAppView().options());
}
+
+ public Map<DexType, LambdaClass> getKnownLambdaClasses() {
+ return knownLambdaClasses;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 78136a9..f0189d3 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -46,7 +46,6 @@
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.PredicateSet;
import com.android.tools.r8.utils.Visibility;
-import com.android.tools.r8.utils.WorkList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.ImmutableSortedSet.Builder;
@@ -1313,19 +1312,8 @@
DexType type,
Consumer<DexProgramClass> subTypeConsumer,
Consumer<LambdaDescriptor> callSiteConsumer) {
- WorkList<DexType> workList = WorkList.newIdentityWorkList();
- workList.addIfNotSeen(type);
- while (workList.hasNext()) {
- DexType subType = workList.next();
- DexProgramClass clazz = definitionForProgramType(subType);
- workList.addIfNotSeen(allImmediateSubtypes(subType));
- if (clazz == null) {
- continue;
- }
- if (isInstantiatedOrPinned(clazz)) {
- subTypeConsumer.accept(clazz);
- }
- }
+ objectAllocationInfoCollection.forEachInstantiatedSubType(
+ type, subTypeConsumer, callSiteConsumer, this);
}
public void forEachInstantiatedSubTypeInChain(
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 f636d05..1d712b5 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2726,18 +2726,11 @@
DexEncodedMethod context = lambdaClassAndContext.getSecond();
DexProgramClass programClass = lambdaClass.getOrCreateLambdaClass();
additions.addInstantiatedClass(programClass, context, lambdaClass.addToMainDexList.get());
-
- // Mark all methods on the desugared lambda classes as live.
- for (DexEncodedMethod method : programClass.methods()) {
- additions.addLiveMethod(new ProgramMethod(programClass, method));
- }
-
- // Ensure accessors if needed and mark them live too.
- DexEncodedMethod accessor = lambdaClass.target.ensureAccessibilityIfNeeded(false);
- if (accessor != null) {
- DexProgramClass clazz = getProgramClassOrNull(accessor.holder());
- additions.addLiveMethod(new ProgramMethod(clazz, accessor));
- }
+ // Mark the instance constructor targeted and live.
+ DexEncodedMethod constructor = programClass.lookupDirectMethod(lambdaClass.constructor);
+ KeepReason reason = KeepReason.instantiatedIn(context);
+ markMethodAsTargeted(programClass, constructor, reason);
+ markDirectStaticOrConstructorMethodAsLive(programClass, constructor, reason);
}
// Rewrite all of the invoke-dynamic instructions to lambda class instantiations.
@@ -2774,6 +2767,12 @@
}
private AppInfoWithLiveness createAppInfo(AppInfoWithSubtyping appInfo) {
+ // Once all tracing is done, we generate accessor methods for lambdas.
+ // These are assumed to be simple forwarding or access flag updates, thus no further tracing
+ // is needed. These cannot be generated as part of lambda synthesis as changing a direct method
+ // to a static method will invalidate the reachable method sets for tracing methods.
+ ensureLambdaAccessibility();
+
// Compute the set of dead proto types.
deadProtoTypeCandidates.removeIf(this::isTypeLive);
@@ -2866,6 +2865,36 @@
return appInfoWithLiveness;
}
+ private void ensureLambdaAccessibility() {
+ if (lambdaRewriter == null) {
+ return;
+ }
+ lambdaRewriter
+ .getKnownLambdaClasses()
+ .forEach(
+ (type, lambda) -> {
+ DexProgramClass synthesizedClass = getProgramClassOrNull(type);
+ assert synthesizedClass != null;
+ assert liveTypes.contains(synthesizedClass);
+ if (synthesizedClass == null) {
+ return;
+ }
+ DexMethod method = lambda.descriptor.getMainMethod();
+ if (!liveMethods.contains(synthesizedClass.lookupMethod(method))) {
+ return;
+ }
+ DexEncodedMethod accessor = lambda.target.ensureAccessibilityIfNeeded(false);
+ if (accessor == null) {
+ return;
+ }
+ DexProgramClass accessorClass = getProgramClassOrNull(accessor.holder());
+ assert accessorClass != null;
+ if (accessorClass != null) {
+ liveMethods.add(accessorClass, accessor, graphReporter.fakeReportShouldNotBeUsed());
+ }
+ });
+ }
+
private boolean verifyReferences(DexApplication app) {
WorkList<DexClass> worklist = WorkList.newIdentityWorkList();
for (DexProgramClass clazz : liveTypes.getItems()) {
@@ -3668,12 +3697,16 @@
if (clazz != null && clazz.isInterface()) {
// Add this interface to the set of pinned items to ensure that we do not merge the
// interface into its unique subtype, if any.
+ // TODO(b/145344105): This should be superseded by the unknown interface hierarchy.
pinnedItems.add(clazz.type);
+ KeepReason reason = KeepReason.reflectiveUseIn(method);
+ markInterfaceAsInstantiated(clazz, graphReporter.registerClass(clazz, reason));
// Also pin all of its virtual methods to ensure that the devirtualizer does not perform
// illegal rewritings of invoke-interface instructions into invoke-virtual instructions.
for (DexEncodedMethod virtualMethod : clazz.virtualMethods()) {
pinnedItems.add(virtualMethod.method);
+ markVirtualMethodAsReachable(virtualMethod.method, true, null, reason);
}
}
}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index b8256fe..a642002 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -102,7 +102,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 116, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 115, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -142,7 +142,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 116, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 115, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
diff --git a/src/test/java/com/android/tools/r8/classmerging/InterfaceWithProxyTest.java b/src/test/java/com/android/tools/r8/classmerging/InterfaceWithProxyTest.java
index 03f5803..ba16ddc 100644
--- a/src/test/java/com/android/tools/r8/classmerging/InterfaceWithProxyTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/InterfaceWithProxyTest.java
@@ -18,6 +18,8 @@
@RunWith(Parameterized.class)
public class InterfaceWithProxyTest extends TestBase {
+ private static final String EXPECTED = StringUtils.lines("Hello world!");
+
private final TestParameters parameters;
@Parameterized.Parameters(name = "{0}")
@@ -30,8 +32,15 @@
}
@Test
- public void test() throws Exception {
- String expectedOutput = StringUtils.lines("Hello world!");
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(InterfaceWithProxyTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
testForR8(parameters.getBackend())
.addInnerClasses(InterfaceWithProxyTest.class)
.addKeepMainRule(TestClass.class)
@@ -39,7 +48,7 @@
.enableInliningAnnotations()
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(expectedOutput);
+ .assertSuccessWithOutput(EXPECTED);
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
index e209e44..7a97972 100644
--- a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
@@ -6,19 +6,21 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.D8TestCompileResult;
import com.android.tools.r8.Disassemble;
import com.android.tools.r8.Disassemble.DisassembleCommand;
import com.android.tools.r8.JvmTestBuilder;
import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.debug.DebugTestBase;
import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
import com.android.tools.r8.debug.DebugTestConfig;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
import java.nio.file.Files;
@@ -28,57 +30,85 @@
import java.util.Collections;
import java.util.List;
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 DefaultLambdaWithSelfReferenceTestRunner extends DebugTestBase {
- final Class<?> CLASS = DefaultLambdaWithSelfReferenceTest.class;
- final String EXPECTED = StringUtils.lines("stateful(stateless)");
+ private static final Class<?> CLASS = DefaultLambdaWithSelfReferenceTest.class;
+ private static final String EXPECTED = StringUtils.lines("stateful(stateless)");
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public DefaultLambdaWithSelfReferenceTestRunner(TestParameters parameters) {
+ this.parameters = parameters;
+ }
private void runDebugger(DebugTestConfig config) throws Throwable {
MethodReference main = Reference.methodFromMethod(CLASS.getMethod("main", String[].class));
- Command checkThis = conditional((state) ->
- state.isCfRuntime()
- ? Collections.singletonList(checkLocal("this"))
- : ImmutableList.of(
- checkNoLocal("this"),
- checkLocal("_this")));
+ Command checkThisLambda =
+ conditional(
+ (state) ->
+ parameters.isCfRuntime()
+ ? Collections.singletonList(checkLocal("this"))
+ : ImmutableList.of(checkNoLocal("this"), checkLocal("_this")));
- runDebugTest(config, CLASS,
+ Command checkThisDefaultMethod =
+ conditional(
+ (state) ->
+ parameters.canUseDefaultAndStaticInterfaceMethods()
+ ? Collections.singletonList(checkLocal("this"))
+ : ImmutableList.of(checkNoLocal("this"), checkLocal("_this")));
+
+ runDebugTest(
+ config,
+ CLASS,
breakpoint(main, 26),
run(),
checkLine(26),
stepInto(INTELLIJ_FILTER),
checkLine(16),
// When desugaring, the InterfaceProcessor makes this static on the companion class.
- checkThis,
+ checkThisDefaultMethod,
breakpoint(main, 27),
run(),
checkLine(27),
stepInto(INTELLIJ_FILTER),
checkLine(17),
// When desugaring, the LambdaClass will change this to a static (later moved to companion).
- checkThis,
+ checkThisLambda,
run());
}
@Test
public void testJvm() throws Throwable {
+ assumeTrue(parameters.isCfRuntime());
JvmTestBuilder builder = testForJvm().addTestClasspath();
- builder.run(CLASS).assertSuccessWithOutput(EXPECTED);
+ builder.run(parameters.getRuntime(), CLASS).assertSuccessWithOutput(EXPECTED);
runDebugger(builder.debugConfig());
}
@Test
- public void testR8Cf() throws Throwable {
- R8TestCompileResult compileResult = testForR8(Backend.CF)
- .addProgramClassesAndInnerClasses(CLASS)
- .noMinification()
- .noTreeShaking()
- .debug()
- .compile();
+ public void testR8() throws Throwable {
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramClassesAndInnerClasses(CLASS)
+ .setMinApi(parameters.getApiLevel())
+ .noMinification()
+ .noTreeShaking()
+ .addKeepAllAttributes()
+ .debug()
+ .compile()
+ .assertNoMessages();
compileResult
- // TODO(b/123506120): Add .assertNoMessages()
- .run(CLASS)
+ .run(parameters.getRuntime(), CLASS)
.assertSuccessWithOutput(EXPECTED)
.inspect(inspector -> assertThat(inspector.clazz(CLASS), isPresent()));
runDebugger(compileResult.debugConfig());
@@ -86,14 +116,15 @@
@Test
public void testD8() throws Throwable {
+ assumeTrue(parameters.isDexRuntime());
Path out1 = temp.newFolder().toPath().resolve("out1.zip");
testForD8()
.addProgramClassesAndInnerClasses(CLASS)
- .setMinApi(AndroidApiLevel.K)
+ .setMinApi(parameters.getApiLevel())
.compile()
- // TODO(b/123506120): Add .assertNoMessages()
+ .assertNoMessages()
.writeToZip(out1)
- .run(CLASS)
+ .run(parameters.getRuntime(), CLASS)
.assertSuccessWithOutput(EXPECTED);
Path outPerClassDir = temp.newFolder().toPath();
@@ -109,9 +140,9 @@
.addProgramClasses(CLASS)
.addClasspathFiles(ToolHelper.getClassPathForTests())
.setIntermediate(true)
- .setMinApi(AndroidApiLevel.K)
+ .setMinApi(parameters.getApiLevel())
.compile()
- // TODO(b/123506120): Add .assertNoMessages()
+ .assertNoMessages()
.writeToZip(mainOut);
}
for (Path innerClass : innerClasses) {
@@ -121,21 +152,20 @@
.addProgramFiles(innerClass)
.addClasspathFiles(ToolHelper.getClassPathForTests())
.setIntermediate(true)
- .setMinApi(AndroidApiLevel.K)
+ .setMinApi(parameters.getApiLevel())
.compile()
- // TODO(b/123506120): Add .assertNoMessages()
+ .assertNoMessages()
.writeToZip(out);
}
Path out2 = temp.newFolder().toPath().resolve("out2.zip");
- D8TestCompileResult compiledResult = testForD8()
- .addProgramFiles(outs)
- .compile();
+ D8TestCompileResult compiledResult =
+ testForD8().addProgramFiles(outs).setMinApi(parameters.getApiLevel()).compile();
compiledResult
- // TODO(b/123506120): Add .assertNoMessages()
+ .assertNoMessages()
.writeToZip(out2)
- .run(CLASS)
+ .run(parameters.getRuntime(), CLASS)
.assertSuccessWithOutput(EXPECTED);
runDebugger(compiledResult.debugConfig());
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 530b5b5..7bc8279 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.graph.ClassAccessFlags;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.references.ClassReference;
@@ -228,6 +229,25 @@
});
}
+ public ClassFileTransformer setAccessFlags(Consumer<ClassAccessFlags> fn) {
+ return addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public void visit(
+ int version,
+ int access,
+ String name,
+ String signature,
+ String superName,
+ String[] interfaces) {
+ ClassAccessFlags flags = ClassAccessFlags.fromCfAccessFlags(access);
+ fn.accept(flags);
+ super.visit(
+ version, flags.getAsCfAccessFlags(), name, signature, superName, interfaces);
+ }
+ });
+ }
+
public ClassFileTransformer setNest(Class<?> host, Class<?>... members) {
assert !Arrays.asList(members).contains(host);
return setMinVersion(CfVm.JDK11)