Relax limitations of extra class merger
Change-Id: I5803acbe54601988f8b3c6e0a735b5a2d879b6e7
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 43a0826..c7c65f7 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -721,6 +721,10 @@
SyntheticFinalization.finalizeWithClassHierarchy(appView);
}
+ // Clear the reference type lattice element cache. This is required since class merging may
+ // need to build IR.
+ appView.dexItemFactory().clearTypeElementsCache();
+
// Run horizontal class merging. This runs even if shrinking is disabled to ensure synthetics
// are always merged.
HorizontalClassMerger.createForFinalClassMerging(appView)
@@ -918,9 +922,6 @@
DexMethod originalMethod =
appView.graphLens().getOriginalMethodSignature(method.getReference());
if (originalMethod != method.getReference()) {
- DexMethod originalMethod2 =
- appView.graphLens().getOriginalMethodSignature(method.getReference());
- appView.graphLens().getOriginalMethodSignature(method.getReference());
DexEncodedMethod definition = method.getDefinition();
Code code = definition.getCode();
if (code == null) {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
index 28e3dd8..6969f94 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -8,7 +8,6 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
import com.android.tools.r8.horizontalclassmerging.policies.AllInstantiatedOrUninstantiated;
-import com.android.tools.r8.horizontalclassmerging.policies.AtMostOneClassInitializer;
import com.android.tools.r8.horizontalclassmerging.policies.CheckAbstractClasses;
import com.android.tools.r8.horizontalclassmerging.policies.CheckSyntheticClasses;
import com.android.tools.r8.horizontalclassmerging.policies.LimitClassGroups;
@@ -27,14 +26,14 @@
import com.android.tools.r8.horizontalclassmerging.policies.NoIndirectRuntimeTypeChecks;
import com.android.tools.r8.horizontalclassmerging.policies.NoInnerClasses;
import com.android.tools.r8.horizontalclassmerging.policies.NoInstanceFieldAnnotations;
-import com.android.tools.r8.horizontalclassmerging.policies.NoInstanceInitializers;
+import com.android.tools.r8.horizontalclassmerging.policies.NoInstanceInitializerMerging;
import com.android.tools.r8.horizontalclassmerging.policies.NoInterfaces;
import com.android.tools.r8.horizontalclassmerging.policies.NoKeepRules;
import com.android.tools.r8.horizontalclassmerging.policies.NoKotlinMetadata;
import com.android.tools.r8.horizontalclassmerging.policies.NoNativeMethods;
-import com.android.tools.r8.horizontalclassmerging.policies.NoNonPrivateVirtualMethods;
import com.android.tools.r8.horizontalclassmerging.policies.NoServiceLoaders;
import com.android.tools.r8.horizontalclassmerging.policies.NoVerticallyMergedClasses;
+import com.android.tools.r8.horizontalclassmerging.policies.NoVirtualMethodMerging;
import com.android.tools.r8.horizontalclassmerging.policies.NotMatchedByNoHorizontalClassMerging;
import com.android.tools.r8.horizontalclassmerging.policies.OnlyDirectlyConnectedOrUnrelatedInterfaces;
import com.android.tools.r8.horizontalclassmerging.policies.PreserveMethodCharacteristics;
@@ -82,12 +81,6 @@
new NoDeadEnumLiteMaps(appViewWithLiveness, mode),
new NoIllegalInlining(appViewWithLiveness, mode),
new NoVerticallyMergedClasses(appViewWithLiveness, mode));
- } else {
- assert mode.isFinal();
- // TODO(b/181846319): Allow constructors, as long as the constructor protos remain unchanged
- // (in particular, we can't add nulls at constructor call sites).
- // TODO(b/181846319): Allow virtual methods, as long as they do not require any merging.
- builder.add(new NoInstanceInitializers(mode), new NoNonPrivateVirtualMethods(mode));
}
if (appView.options().horizontalClassMergerOptions().isRestrictedToSynthetics()) {
@@ -170,7 +163,10 @@
} else {
assert mode.isFinal();
// TODO(b/185472598): Add support for merging class initializers with dex code.
- builder.add(new AtMostOneClassInitializer(mode), new NoConstructorCollisions(appView, mode));
+ builder.add(
+ new NoInstanceInitializerMerging(mode),
+ new NoVirtualMethodMerging(appView, mode),
+ new NoConstructorCollisions(appView, mode));
}
addMultiClassPoliciesForInterfaceMerging(appView, mode, builder);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/AtMostOneClassInitializer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/AtMostOneClassInitializer.java
deleted file mode 100644
index a2b5887..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/AtMostOneClassInitializer.java
+++ /dev/null
@@ -1,27 +0,0 @@
-// 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.horizontalclassmerging.policies;
-
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
-
-public class AtMostOneClassInitializer extends AtMostOneClassThatMatchesPolicy {
-
- public AtMostOneClassInitializer(Mode mode) {
- // TODO(b/182124475): Allow merging groups with multiple <clinit> methods in the final round of
- // merging.
- assert mode.isFinal();
- }
-
- @Override
- boolean atMostOneOf(DexProgramClass clazz) {
- return clazz.hasClassInitializer();
- }
-
- @Override
- public String getName() {
- return "AtMostOneClassInitializer";
- }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceInitializers.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceInitializers.java
deleted file mode 100644
index 0acfe13..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceInitializers.java
+++ /dev/null
@@ -1,29 +0,0 @@
-// 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.horizontalclassmerging.policies;
-
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
-import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
-
-public class NoInstanceInitializers extends SingleClassPolicy {
-
- public NoInstanceInitializers(Mode mode) {
- // TODO(b/181846319): Allow constructors, as long as the constructor protos remain unchanged
- // (in particular, we can't add nulls at constructor call sites).
- assert mode.isFinal();
- }
-
- @Override
- public boolean canMerge(DexProgramClass clazz) {
- return !clazz.getMethodCollection().hasDirectMethods(DexEncodedMethod::isInstanceInitializer);
- }
-
- @Override
- public String getName() {
- return "NoInstanceInitializers";
- }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoNonPrivateVirtualMethods.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoNonPrivateVirtualMethods.java
deleted file mode 100644
index 81358f1..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoNonPrivateVirtualMethods.java
+++ /dev/null
@@ -1,27 +0,0 @@
-// 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.horizontalclassmerging.policies;
-
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
-import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
-
-public class NoNonPrivateVirtualMethods extends SingleClassPolicy {
-
- public NoNonPrivateVirtualMethods(Mode mode) {
- // TODO(b/181846319): Allow virtual methods as long as they do not require any merging.
- assert mode.isFinal();
- }
-
- @Override
- public boolean canMerge(DexProgramClass clazz) {
- return clazz.isInterface() || !clazz.getMethodCollection().hasVirtualMethods();
- }
-
- @Override
- public String getName() {
- return "NoNonPrivateVirtualMethods";
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index 7bd9cf9..6917a40 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -325,6 +325,10 @@
return false;
}
ProgramMethod context = code.context();
+ if (!toBeReplaced.instructionMayHaveSideEffects(appView, context)) {
+ removeOrReplaceByDebugLocalRead();
+ return true;
+ }
if (toBeReplaced.instructionMayHaveSideEffects(
appView, context, Instruction.SideEffectAssumption.CLASS_ALREADY_INITIALIZED)) {
return false;
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 1a721c9..fea728d 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
@@ -182,7 +182,6 @@
* (i.e., whether we are running R8). See {@link AppView#enableWholeProgramOptimizations()}.
*/
public IRConverter(AppView<?> appView, Timing timing, CfgPrinter printer) {
- assert appView.appInfo().hasLiveness() || appView.graphLens().isIdentityLens();
assert appView.options() != null;
assert appView.options().programConsumer != null;
assert timing != null;
diff --git a/src/test/java/com/android/tools/r8/R8CompatTestBuilder.java b/src/test/java/com/android/tools/r8/R8CompatTestBuilder.java
index 568aa74..8074250 100644
--- a/src/test/java/com/android/tools/r8/R8CompatTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8CompatTestBuilder.java
@@ -5,7 +5,6 @@
import com.android.tools.r8.R8Command.Builder;
import com.android.tools.r8.TestBase.Backend;
-import java.nio.file.Path;
public class R8CompatTestBuilder extends R8TestBuilder<R8CompatTestBuilder> {
@@ -21,6 +20,11 @@
}
@Override
+ public boolean isR8CompatTestBuilder() {
+ return true;
+ }
+
+ @Override
R8CompatTestBuilder self() {
return this;
}
diff --git a/src/test/java/com/android/tools/r8/R8FullTestBuilder.java b/src/test/java/com/android/tools/r8/R8FullTestBuilder.java
index 3343329..a8f6d60 100644
--- a/src/test/java/com/android/tools/r8/R8FullTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8FullTestBuilder.java
@@ -24,6 +24,11 @@
}
@Override
+ public boolean isR8TestBuilder() {
+ return true;
+ }
+
+ @Override
R8FullTestBuilder self() {
return this;
}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 488480c..608f635 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -108,7 +108,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 18, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 16, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -147,7 +147,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 18, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 16, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index aad62cb..c421c83 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -44,6 +44,14 @@
return false;
}
+ public boolean isR8TestBuilder() {
+ return false;
+ }
+
+ public boolean isR8CompatTestBuilder() {
+ return false;
+ }
+
@Override
public boolean isTestShrinkerBuilder() {
return true;
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
index 76ebd58..07ad8f0 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
@@ -9,24 +9,35 @@
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.code.InvokeVirtual;
import com.android.tools.r8.code.ReturnVoid;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
-import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
-import java.nio.file.Path;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class B77836766 extends TestBase {
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public B77836766(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
/**
* The below Jasmin code mimics the following Kotlin code:
*
@@ -93,15 +104,18 @@
ClassBuilder itf2 = jasminBuilder.addInterface("Itf2");
itf2.addAbstractMethod("foo", ImmutableList.of("Ljava/lang/Integer;"), "V");
- ClassBuilder cls2 = jasminBuilder.addClass("Cls2", absCls.name, itf2.name);
+ ClassBuilder cls2Class = jasminBuilder.addClass("Cls2", absCls.name, itf2.name);
// Mimic Kotlin's "internal" class
- cls2.setAccess("");
- cls2.addBridgeMethod("foo", ImmutableList.of("Ljava/lang/Integer;"), "V",
+ cls2Class.setAccess("");
+ cls2Class.addBridgeMethod(
+ "foo",
+ ImmutableList.of("Ljava/lang/Integer;"),
+ "V",
".limit stack 2",
".limit locals 2",
"aload_0",
"aload_1",
- "invokevirtual " + cls2.name + "/foo(Ljava/lang/Object;)V",
+ "invokevirtual " + cls2Class.name + "/foo(Ljava/lang/Object;)V",
"return");
ClassBuilder mainClass = jasminBuilder.addClass("Main");
@@ -115,54 +129,66 @@
"aload_0",
"ldc \"Hello\"",
"invokevirtual " + cls1.name + "/foo(Ljava/lang/String;)V",
- "new " + cls2.name,
+ "new " + cls2Class.name,
"dup",
- "invokespecial " + cls2.name + "/<init>()V",
+ "invokespecial " + cls2Class.name + "/<init>()V",
"astore_0",
"aload_0",
"iconst_0",
"invokestatic java/lang/Integer/valueOf(I)Ljava/lang/Integer;",
- "invokevirtual " + cls2.name + "/foo(Ljava/lang/Integer;)V",
- "return"
- );
+ "invokevirtual " + cls2Class.name + "/foo(Ljava/lang/Integer;)V",
+ "return");
- final String mainClassName = mainClass.name;
- String proguardConfig = keepMainProguardConfiguration(mainClass.name, false, false);
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(jasminBuilder.buildClasses())
+ .addKeepMainRule(mainClass.name)
+ .addOptionsModification(this::configure)
+ .noHorizontalClassMerging(cls2Class.name)
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject absSubject = inspector.clazz(absCls.name);
+ assertThat(absSubject, isPresent());
+ ClassSubject cls1Subject = inspector.clazz(cls1.name);
+ assertThat(cls1Subject, isPresent());
+ ClassSubject cls2Subject = inspector.clazz(cls2Class.name);
+ assertThat(cls2Subject, isPresent());
- AndroidApp processedApp = runAndVerifyOnJvmAndArt(jasminBuilder, mainClassName, proguardConfig);
+ // Cls1#foo and Cls2#foo should not refer to each other.
+ // They can invoke their own bridge method or AbsCls#foo (via member rebinding).
- CodeInspector inspector = new CodeInspector(processedApp);
- ClassSubject absSubject = inspector.clazz(absCls.name);
- assertThat(absSubject, isPresent());
- ClassSubject cls1Subject = inspector.clazz(cls1.name);
- assertThat(cls1Subject, isPresent());
- ClassSubject cls2Subject = inspector.clazz(cls2.name);
- assertThat(cls2Subject, isPresent());
+ // Cls2#foo has been moved to AbsCls#foo as a result of bridge hoisting.
+ MethodSubject fooInCls2 = cls2Subject.method("void", "foo", "java.lang.Integer");
+ assertThat(fooInCls2, not(isPresent()));
- // Cls1#foo and Cls2#foo should not refer to each other.
- // They can invoke their own bridge method or AbsCls#foo (via member rebinding).
+ MethodSubject fooFromCls2InAbsCls =
+ absSubject.method("void", "foo", "java.lang.Integer");
+ assertThat(fooFromCls2InAbsCls, isPresent());
- // Cls2#foo has been moved to AbsCls#foo as a result of bridge hoisting.
- MethodSubject fooInCls2 = cls2Subject.method("void", "foo", "java.lang.Integer");
- assertThat(fooInCls2, not(isPresent()));
+ // Cls1#foo has been moved to AbsCls#foo as a result of bridge hoisting.
+ MethodSubject fooInCls1 = cls1Subject.method("void", "foo", "java.lang.String");
+ assertThat(fooInCls1, not(isPresent()));
- MethodSubject fooFromCls2InAbsCls = absSubject.method("void", "foo", "java.lang.Integer");
- assertThat(fooFromCls2InAbsCls, isPresent());
- DexCode code = fooFromCls2InAbsCls.getMethod().getCode().asDexCode();
- checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
- InvokeVirtual invoke = (InvokeVirtual) code.instructions[0];
- assertEquals(absSubject.getDexProgramClass().type, invoke.getMethod().holder);
+ MethodSubject fooFromCls1InAbsCls =
+ absSubject.method("void", "foo", "java.lang.String");
+ assertThat(fooFromCls1InAbsCls, isPresent());
- // Cls1#foo has been moved to AbsCls#foo as a result of bridge hoisting.
- MethodSubject fooInCls1 = cls1Subject.method("void", "foo", "java.lang.String");
- assertThat(fooInCls1, not(isPresent()));
+ if (parameters.isDexRuntime()) {
+ DexCode code = fooFromCls2InAbsCls.getMethod().getCode().asDexCode();
+ checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
+ InvokeVirtual invoke = (InvokeVirtual) code.instructions[0];
+ assertEquals(absSubject.getDexProgramClass().type, invoke.getMethod().holder);
- MethodSubject fooFromCls1InAbsCls = absSubject.method("void", "foo", "java.lang.String");
- assertThat(fooFromCls1InAbsCls, isPresent());
- code = fooFromCls1InAbsCls.getMethod().getCode().asDexCode();
- checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
- invoke = (InvokeVirtual) code.instructions[0];
- assertEquals(absSubject.getDexProgramClass().type, invoke.getMethod().holder);
+ code = fooFromCls1InAbsCls.getMethod().getCode().asDexCode();
+ checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
+ invoke = (InvokeVirtual) code.instructions[0];
+ assertEquals(absSubject.getDexProgramClass().type, invoke.getMethod().holder);
+ }
+ })
+ .run(parameters.getRuntime(), mainClass.name)
+ .assertSuccessWithOutput("Hello0");
}
/**
@@ -199,13 +225,17 @@
ClassBuilder itf = jasminBuilder.addInterface("ItfInteger");
itf.addAbstractMethod("foo", ImmutableList.of("Ljava/lang/Integer;"), "V");
- ClassBuilder cls1 = jasminBuilder.addClass("DerivedInteger", baseCls.name, itf.name);
- cls1.addBridgeMethod("foo", ImmutableList.of("Ljava/lang/Integer;"), "V",
+ ClassBuilder derivedIntegerClass =
+ jasminBuilder.addClass("DerivedInteger", baseCls.name, itf.name);
+ derivedIntegerClass.addBridgeMethod(
+ "foo",
+ ImmutableList.of("Ljava/lang/Integer;"),
+ "V",
".limit stack 2",
".limit locals 2",
"aload_0",
"aload_1",
- "invokevirtual " + cls1.name + "/foo(Ljava/lang/Object;)V",
+ "invokevirtual " + derivedIntegerClass.name + "/foo(Ljava/lang/Object;)V",
"return");
ClassBuilder cls2 = jasminBuilder.addClass("DerivedString", baseCls.name);
@@ -221,14 +251,14 @@
mainClass.addMainMethod(
".limit stack 5",
".limit locals 2",
- "new " + cls1.name,
+ "new " + derivedIntegerClass.name,
"dup",
- "invokespecial " + cls1.name + "/<init>()V",
+ "invokespecial " + derivedIntegerClass.name + "/<init>()V",
"astore_0",
"aload_0",
"iconst_0",
"invokestatic java/lang/Integer/valueOf(I)Ljava/lang/Integer;",
- "invokevirtual " + cls1.name + "/foo(Ljava/lang/Integer;)V",
+ "invokevirtual " + derivedIntegerClass.name + "/foo(Ljava/lang/Integer;)V",
"new " + cls2.name,
"dup",
"invokespecial " + cls2.name + "/<init>()V",
@@ -236,41 +266,51 @@
"aload_0",
"ldc \"Bar\"",
"invokevirtual " + cls2.name + "/bar(Ljava/lang/String;)V",
- "return"
- );
+ "return");
- final String mainClassName = mainClass.name;
- String proguardConfig = keepMainProguardConfiguration(mainClass.name, false, false);
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(jasminBuilder.buildClasses())
+ .addKeepMainRule(mainClass.name)
+ .addOptionsModification(this::configure)
+ .noHorizontalClassMerging(derivedIntegerClass.name)
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject baseSubject = inspector.clazz(baseCls.name);
+ assertThat(baseSubject, isPresent());
+ ClassSubject cls1Subject = inspector.clazz(derivedIntegerClass.name);
+ assertThat(cls1Subject, isPresent());
+ ClassSubject cls2Subject = inspector.clazz(cls2.name);
+ assertThat(cls2Subject, isPresent());
- AndroidApp processedApp = runAndVerifyOnJvmAndArt(jasminBuilder, mainClassName, proguardConfig);
+ // Cls1#foo and Cls2#bar should refer to Base#foo.
- CodeInspector inspector = new CodeInspector(processedApp);
- ClassSubject baseSubject = inspector.clazz(baseCls.name);
- assertThat(baseSubject, isPresent());
- ClassSubject cls1Subject = inspector.clazz(cls1.name);
- assertThat(cls1Subject, isPresent());
- ClassSubject cls2Subject = inspector.clazz(cls2.name);
- assertThat(cls2Subject, isPresent());
+ MethodSubject barInCls2 = cls2Subject.method("void", "bar", "java.lang.String");
+ assertThat(barInCls2, isPresent());
- // Cls1#foo and Cls2#bar should refer to Base#foo.
+ // Cls1#foo has been moved to Base#foo as a result of bridge hoisting.
+ MethodSubject fooInCls1 = cls1Subject.method("void", "foo", "java.lang.Integer");
+ assertThat(fooInCls1, not(isPresent()));
- MethodSubject barInCls2 = cls2Subject.method("void", "bar", "java.lang.String");
- assertThat(barInCls2, isPresent());
- DexCode code = barInCls2.getMethod().getCode().asDexCode();
- checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
- InvokeVirtual invoke = (InvokeVirtual) code.instructions[0];
- assertEquals(baseSubject.getDexProgramClass().type, invoke.getMethod().holder);
+ MethodSubject fooInBase = baseSubject.method("void", "foo", "java.lang.Integer");
+ assertThat(fooInBase, isPresent());
- // Cls1#foo has been moved to Base#foo as a result of bridge hoisting.
- MethodSubject fooInCls1 = cls1Subject.method("void", "foo", "java.lang.Integer");
- assertThat(fooInCls1, not(isPresent()));
+ if (parameters.isDexRuntime()) {
+ DexCode code = barInCls2.getMethod().getCode().asDexCode();
+ checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
+ InvokeVirtual invoke = (InvokeVirtual) code.instructions[0];
+ assertEquals(baseSubject.getDexProgramClass().type, invoke.getMethod().holder);
- MethodSubject fooInBase = baseSubject.method("void", "foo", "java.lang.Integer");
- assertThat(fooInBase, isPresent());
- code = fooInBase.getMethod().getCode().asDexCode();
- checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
- invoke = (InvokeVirtual) code.instructions[0];
- assertEquals(baseSubject.getDexProgramClass().type, invoke.getMethod().holder);
+ code = fooInBase.getMethod().getCode().asDexCode();
+ checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
+ invoke = (InvokeVirtual) code.instructions[0];
+ assertEquals(baseSubject.getDexProgramClass().type, invoke.getMethod().holder);
+ }
+ })
+ .run(parameters.getRuntime(), mainClass.name)
+ .assertSuccessWithOutput("0Bar");
}
/**
@@ -337,25 +377,34 @@
"return"
);
- final String mainClassName = mainClass.name;
- String proguardConfig = keepMainProguardConfiguration(mainClass.name, false, false);
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(jasminBuilder.buildClasses())
+ .addKeepMainRule(mainClass.name)
+ .addOptionsModification(this::configure)
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject baseSubject = inspector.clazz(baseCls.name);
+ assertThat(baseSubject, isPresent());
+ ClassSubject subSubject = inspector.clazz(subCls.name);
+ assertThat(subSubject, isPresent());
- AndroidApp processedApp = runAndVerifyOnJvmAndArt(jasminBuilder, mainClassName, proguardConfig);
+ // DerivedString2#bar should refer to Base#foo.
- CodeInspector inspector = new CodeInspector(processedApp);
- ClassSubject baseSubject = inspector.clazz(baseCls.name);
- assertThat(baseSubject, isPresent());
- ClassSubject subSubject = inspector.clazz(subCls.name);
- assertThat(subSubject, isPresent());
+ MethodSubject barInSub = subSubject.method("void", "bar", "java.lang.String");
+ assertThat(barInSub, isPresent());
- // DerivedString2#bar should refer to Base#foo.
-
- MethodSubject barInSub = subSubject.method("void", "bar", "java.lang.String");
- assertThat(barInSub, isPresent());
- DexCode code = barInSub.getMethod().getCode().asDexCode();
- checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
- InvokeVirtual invoke = (InvokeVirtual) code.instructions[0];
- assertEquals(baseSubject.getDexProgramClass().type, invoke.getMethod().holder);
+ if (parameters.isDexRuntime()) {
+ DexCode code = barInSub.getMethod().getCode().asDexCode();
+ checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
+ InvokeVirtual invoke = (InvokeVirtual) code.instructions[0];
+ assertEquals(baseSubject.getDexProgramClass().type, invoke.getMethod().holder);
+ }
+ })
+ .run(parameters.getRuntime(), mainClass.name)
+ .assertSuccessWithOutput("0Bar");
}
/*
@@ -413,40 +462,33 @@
"invokevirtual " + cls.name + "/bar(Ljava/lang/String;)V",
"return"
);
- final String mainClassName = mainClass.name;
- String proguardConfig = keepMainProguardConfiguration(mainClass.name, false, false);
- AndroidApp processedApp = runAndVerifyOnJvmAndArt(jasminBuilder, mainClassName, proguardConfig);
- CodeInspector inspector = new CodeInspector(processedApp);
- ClassSubject baseSubject = inspector.clazz(cls.name);
- assertThat(baseSubject, isPresent());
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(jasminBuilder.buildClasses())
+ .addKeepMainRule(mainClass.name)
+ .addOptionsModification(this::configure)
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject baseSubject = inspector.clazz(cls.name);
+ assertThat(baseSubject, isPresent());
- // Base#bar should remain as-is, i.e., refer to Base#foo(Object).
+ // Base#bar should remain as-is, i.e., refer to Base#foo(Object).
- MethodSubject barInSub = baseSubject.method("void", "bar", "java.lang.String");
- assertThat(barInSub, isPresent());
- DexCode code = barInSub.getMethod().getCode().asDexCode();
- checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
- InvokeVirtual invoke = (InvokeVirtual) code.instructions[0];
- assertEquals(baseSubject.getDexProgramClass().type, invoke.getMethod().holder);
- }
+ MethodSubject barInSub = baseSubject.method("void", "bar", "java.lang.String");
+ assertThat(barInSub, isPresent());
- private AndroidApp runAndVerifyOnJvmAndArt(
- JasminBuilder jasminBuilder, String mainClassName, String proguardConfig) throws Exception {
- // Run input program on java.
- Path outputDirectory = temp.newFolder().toPath();
- jasminBuilder.writeClassFiles(outputDirectory);
- ProcessResult javaResult = ToolHelper.runJava(outputDirectory, mainClassName);
- assertEquals(0, javaResult.exitCode);
-
- AndroidApp processedApp = compileWithR8(jasminBuilder.build(), proguardConfig, this::configure);
-
- // Run processed (output) program on ART
- ProcessResult artResult = runOnArtRaw(processedApp, mainClassName);
- assertEquals(javaResult.stdout, artResult.stdout);
- assertEquals(-1, artResult.stderr.indexOf("VerifyError"));
-
- return processedApp;
+ if (parameters.isDexRuntime()) {
+ DexCode code = barInSub.getMethod().getCode().asDexCode();
+ checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
+ InvokeVirtual invoke = (InvokeVirtual) code.instructions[0];
+ assertEquals(baseSubject.getDexProgramClass().type, invoke.getMethod().holder);
+ }
+ })
+ .run(parameters.getRuntime(), mainClass.name)
+ .assertSuccessWithOutput("0Bar");
}
private void configure(InternalOptions options) {
diff --git a/src/test/java/com/android/tools/r8/classmerging/StatelessSingletonClassesMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/StatelessSingletonClassesMergingTest.java
new file mode 100644
index 0000000..e6752f8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/StatelessSingletonClassesMergingTest.java
@@ -0,0 +1,74 @@
+// Copyright (c) 2021, 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.classmerging;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class StatelessSingletonClassesMergingTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public StatelessSingletonClassesMergingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector -> inspector.assertIsCompleteMergeGroup(A.class, B.class))
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .noClassStaticizing()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A", "B");
+ }
+
+ static class Main {
+ public static void main(String[] args) {
+ A.INSTANCE.f();
+ B.INSTANCE.g();
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ static final A INSTANCE = new A();
+
+ @NeverInline
+ void f() {
+ System.out.println("A");
+ }
+ }
+
+ @NeverClassInline
+ static class B {
+
+ static final B INSTANCE = new B();
+
+ @NeverInline
+ void g() {
+ System.out.println("B");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorCantInlineTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorCantInlineTest.java
index 07cea40..1cd81d7 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorCantInlineTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorCantInlineTest.java
@@ -4,9 +4,9 @@
package com.android.tools.r8.classmerging.horizontal;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.IsNot.not;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
@@ -30,10 +30,10 @@
.assertSuccessWithOutputLines("c", "foo: foo")
.inspect(
codeInspector -> {
- assertThat(codeInspector.clazz(A.class), not(isPresent()));
- assertThat(codeInspector.clazz(B.class), isPresent());
+ assertThat(codeInspector.clazz(A.class), isAbsent());
+ assertThat(codeInspector.clazz(B.class), isAbsent());
assertThat(codeInspector.clazz(C.class), isPresent());
- assertThat(codeInspector.clazz(D.class), not(isPresent()));
+ assertThat(codeInspector.clazz(D.class), isAbsent());
});
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/InstantiatedAndUninstantiatedClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/InstantiatedAndUninstantiatedClassMergingTest.java
index d6b6643..7bc50fa 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/InstantiatedAndUninstantiatedClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/InstantiatedAndUninstantiatedClassMergingTest.java
@@ -5,13 +5,13 @@
package com.android.tools.r8.classmerging.horizontal;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8TestBuilder;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
import org.junit.Test;
public class InstantiatedAndUninstantiatedClassMergingTest extends HorizontalClassMergingTestBase {
@@ -36,14 +36,14 @@
.addKeepMainRule(TestClass.class)
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
- .addHorizontallyMergedClassesInspector(
- HorizontallyMergedClassesInspector::assertNoClassesMerged)
.setMinApi(parameters.getApiLevel())
.compile()
.inspect(
inspector -> {
assertThat(inspector.clazz(Instantiated.class), isPresent());
- assertThat(inspector.clazz(Uninstantiated.class), isPresent());
+ assertThat(
+ inspector.clazz(Uninstantiated.class),
+ notIf(isPresent(), testBuilder.isR8CompatTestBuilder()));
})
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutputLines("Instantiated", "Uninstantiated");
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java
index 955ff04..e2eef5f 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.NeverClassInline;
@@ -26,13 +27,18 @@
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector ->
+ inspector
+ .assertIsCompleteMergeGroup(I.class, J.class)
+ .applyIf(
+ !parameters.canUseDefaultAndStaticInterfaceMethods(),
+ i -> i.assertIsCompleteMergeGroup(B1.class, B2.class))
+ .assertNoOtherClassesMerged())
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoVerticalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
- .addHorizontallyMergedClassesInspector(
- inspector ->
- inspector.assertIsCompleteMergeGroup(I.class, J.class).assertNoOtherClassesMerged())
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("J", "B2")
.inspect(
@@ -41,7 +47,9 @@
assertThat(codeInspector.clazz(J.class), isAbsent());
assertThat(codeInspector.clazz(A.class), isPresent());
assertThat(codeInspector.clazz(B1.class), isPresent());
- assertThat(codeInspector.clazz(B2.class), isPresent());
+ assertThat(
+ codeInspector.clazz(B2.class),
+ onlyIf(parameters.canUseDefaultAndStaticInterfaceMethods(), isPresent()));
assertThat(codeInspector.clazz(C1.class), isPresent());
assertThat(codeInspector.clazz(C2.class), isPresent());
});
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java
index 3cbaed0..f07d6b6 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -51,6 +52,7 @@
.addKeepMainRule(TestClass.class)
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
.enableNoVerticalClassMergingAnnotations()
.addOptionsModification(
options -> {
@@ -112,5 +114,6 @@
@NeverClassInline
static class C extends A {}
+ @NoHorizontalClassMerging
static class Uninstantiated {}
}
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
index 85c8459..3897351 100644
--- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
@@ -118,10 +118,6 @@
for (DataEntryResource dataResource : getOriginalDataResources()) {
ImmutableList<String> object =
dataResourceConsumer.get(getExpectedRenamingFor(dataResource.getName(), mapper));
- if (object == null) {
- object =
- dataResourceConsumer.get(getExpectedRenamingFor(dataResource.getName(), mapper));
- }
assertNotNull("Resource not renamed as expected: " + dataResource.getName(), object);
}
});
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
index b39fd97..8179e48 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
@@ -10,6 +10,7 @@
import static org.junit.Assert.fail;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ThrowableConsumer;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
@@ -68,6 +69,14 @@
return horizontallyMergedClasses.getTargets();
}
+ public HorizontallyMergedClassesInspector applyIf(
+ boolean condition, ThrowableConsumer<HorizontallyMergedClassesInspector> consumer) {
+ if (condition) {
+ consumer.acceptWithRuntimeException(this);
+ }
+ return this;
+ }
+
public HorizontallyMergedClassesInspector assertMergedInto(Class<?> from, Class<?> target) {
assertEquals(
horizontallyMergedClasses.getMergeTargetOrDefault(toDexType(from)), toDexType(target));