Merge commit 'b97d5f0c11ca77af6b8e6b0fef52a07b7367e8dd' into dev-release
diff --git a/src/main/java/com/android/tools/r8/ClassFileConsumer.java b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
index 18f68ba..8c52405 100644
--- a/src/main/java/com/android/tools/r8/ClassFileConsumer.java
+++ b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.utils.OutputBuilder;
import com.android.tools.r8.utils.ZipUtils;
import com.google.common.io.Closer;
+import java.io.BufferedOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.OpenOption;
@@ -148,7 +149,9 @@
OpenOption[] options =
new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
try (Closer closer = Closer.create()) {
- try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(archive, options))) {
+ try (ZipOutputStream out =
+ new ZipOutputStream(
+ new BufferedOutputStream(Files.newOutputStream(archive, options)))) {
ZipUtils.writeResourcesToZip(resources, dataResources, closer, out);
}
}
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 8def7ae..2e73927 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.LazyLoadedDexApplication;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
import com.android.tools.r8.inspector.internal.InspectorImpl;
@@ -35,6 +36,7 @@
import com.android.tools.r8.utils.CfgPrinter;
import com.android.tools.r8.utils.ExceptionUtils;
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.ThreadUtils;
import com.android.tools.r8.utils.Timing;
@@ -158,6 +160,16 @@
});
}
+ private static AppView<AppInfo> readApp(
+ AndroidApp inputApp, InternalOptions options, ExecutorService executor, Timing timing)
+ throws IOException {
+ PrefixRewritingMapper rewritePrefix =
+ options.desugaredLibraryConfiguration.createPrefixRewritingMapper(options);
+ LazyLoadedDexApplication app = new ApplicationReader(inputApp, options, timing).read(executor);
+ AppInfo appInfo = AppInfo.createInitialAppInfo(app);
+ return AppView.createForD8(appInfo, rewritePrefix);
+ }
+
private static void run(AndroidApp inputApp, InternalOptions options, ExecutorService executor)
throws IOException {
Timing timing = Timing.create("D8", options);
@@ -165,10 +177,7 @@
// Disable global optimizations.
options.disableGlobalOptimizations();
- DexApplication app = new ApplicationReader(inputApp, options, timing).read(executor);
- PrefixRewritingMapper rewritePrefix =
- options.desugaredLibraryConfiguration.createPrefixRewritingMapper(options);
- AppInfo appInfo = AppInfo.createInitialAppInfo(app);
+ AppView<AppInfo> appView = readApp(inputApp, options, executor, timing);
final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
@@ -177,9 +186,9 @@
// enabling code.
ClassInitializerAssertionEnablingAnalysis analysis =
new ClassInitializerAssertionEnablingAnalysis(
- appInfo.dexItemFactory(), OptimizationFeedbackSimple.getInstance());
+ appView.dexItemFactory(), OptimizationFeedbackSimple.getInstance());
ThreadUtils.processItems(
- appInfo.classes(),
+ appView.appInfo().classes(),
clazz -> {
ProgramMethod classInitializer = clazz.getProgramClassInitializer();
if (classInitializer != null) {
@@ -189,14 +198,11 @@
executor);
}
- AppView<?> appView = AppView.createForD8(appInfo, rewritePrefix);
-
if (options.testing.enableD8ResourcesPassThrough) {
appView.setAppServices(AppServices.builder(appView).build());
}
- IRConverter converter = new IRConverter(appView, timing, printer);
- app = converter.convert(app, executor);
+ new IRConverter(appView, timing, printer).convert(appView, executor);
if (options.printCfg) {
if (options.printCfgFile == null || options.printCfgFile.isEmpty()) {
@@ -223,7 +229,7 @@
// if there were class file inputs.
boolean hasClassResources = false;
boolean hasDexResources = false;
- for (DexProgramClass dexProgramClass : app.classes()) {
+ for (DexProgramClass dexProgramClass : appView.appInfo().classes()) {
if (dexProgramClass.originatesFromClassResource()) {
hasClassResources = true;
if (hasDexResources) {
@@ -237,26 +243,26 @@
}
}
Marker marker = options.getMarker(Tool.D8);
- Set<Marker> markers = new HashSet<>(app.dexItemFactory.extractMarkers());
- if (marker != null && hasClassResources) {
+ Set<Marker> markers = new HashSet<>(appView.dexItemFactory().extractMarkers());
+ // TODO(b/166617364): Don't add an additional marker when desugaring is turned off.
+ if (hasClassResources && (options.desugarState != DesugarState.OFF || markers.isEmpty())) {
markers.add(marker);
}
Marker.checkCompatibleDesugaredLibrary(markers, options.reporter);
- InspectorImpl.runInspections(options.outputInspections, app);
+ InspectorImpl.runInspections(options.outputInspections, appView.appInfo().classes());
if (options.isGeneratingClassFiles()) {
new CfApplicationWriter(
- app,
appView,
- options,
marker,
GraphLens.getIdentityLens(),
- NamingLens.getIdentityLens(),
+ appView.rewritePrefix.isRewriting()
+ ? PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView)
+ : NamingLens.getIdentityLens(),
null)
.write(options.getClassFileConsumer());
} else {
NamingLens namingLens;
- DexApplication finalApp = app;
if (!hasDexResources || !hasClassResources || !appView.rewritePrefix.isRewriting()) {
// All inputs are either dex or cf, or there is nothing to rewrite.
namingLens =
@@ -271,11 +277,14 @@
// desugared library only on cf inputs. We cannot easily rewrite part of the program
// without iterating again the IR. We fall-back to writing one app with rewriting and
// merging it with the other app in rewriteNonDexInputs.
- finalApp = rewriteNonDexInputs(appView, inputApp, options, executor, timing, app);
+ DexApplication app =
+ rewriteNonDexInputs(
+ appView, inputApp, options, executor, timing, appView.appInfo().app());
+ appView.setAppInfo(new AppInfo(app, appView.appInfo().getSyntheticItems().commit(app)));
namingLens = NamingLens.getIdentityLens();
}
new ApplicationWriter(
- finalApp,
+ appView.appInfo().app(),
appView,
options,
marker == null ? null : ImmutableList.copyOf(markers),
@@ -353,17 +362,12 @@
return finalDexApp.build();
}
- static DexApplication optimize(
- DexApplication application,
- AppInfo appInfo,
- InternalOptions options,
- Timing timing,
- ExecutorService executor)
+ static void optimize(
+ AppView<AppInfo> appView, InternalOptions options, Timing timing, ExecutorService executor)
throws IOException, ExecutionException {
final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
- IRConverter converter = new IRConverter(appInfo, timing, printer);
- application = converter.convert(application, executor);
+ new IRConverter(appView, timing, printer).convert(appView, executor);
if (options.printCfg) {
if (options.printCfgFile == null || options.printCfgFile.isEmpty()) {
@@ -376,7 +380,6 @@
}
}
}
- return application;
}
static class ConvertedCfFiles implements DexIndexedConsumer, ProgramResourceProvider {
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index d8fc991..cb00bb7 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -200,6 +200,9 @@
@Override
void validate() {
+ if (isPrintHelp()) {
+ return;
+ }
Reporter reporter = getReporter();
if (getProgramConsumer() instanceof ClassFileConsumer) {
reporter.warning("Compiling to Java class files with D8 is not officially supported");
diff --git a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
index 8e6c389..49ab77b 100644
--- a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
+++ b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.dex.ApplicationWriter;
import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.GraphLens;
@@ -90,15 +91,15 @@
null,
executor,
new DexFileMergerHelper(inputOrdering)::keepFirstProgramClassConflictResolver);
- AppInfo appInfo = AppInfo.createInitialAppInfo(app);
- app = D8.optimize(app, appInfo, options, timing, executor);
+ AppView<AppInfo> appView = AppView.createForD8(AppInfo.createInitialAppInfo(app));
+ D8.optimize(appView, options, timing, executor);
- List<Marker> markers = app.dexItemFactory.extractMarkers();
+ List<Marker> markers = appView.dexItemFactory().extractMarkers();
assert !options.hasMethodsFilter();
ApplicationWriter writer =
new ApplicationWriter(
- app,
+ appView.appInfo().app(),
null,
options,
markers,
diff --git a/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumer.java b/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumer.java
index 2409847..364f2bf 100644
--- a/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumer.java
+++ b/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumer.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.utils.ZipUtils;
import com.google.common.io.ByteStreams;
import com.google.common.io.Closer;
+import java.io.BufferedOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.OpenOption;
@@ -215,7 +216,9 @@
OpenOption[] options =
new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
try (Closer closer = Closer.create()) {
- try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(archive, options))) {
+ try (ZipOutputStream out =
+ new ZipOutputStream(
+ new BufferedOutputStream(Files.newOutputStream(archive, options)))) {
for (ProgramResource resource : resources) {
String primaryClassDescriptor = primaryClassDescriptors.get(resource);
String entryName = getDexFileName(primaryClassDescriptor);
diff --git a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
index 300588f..ec1f516 100644
--- a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
+++ b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.utils.ZipUtils;
import com.google.common.io.ByteStreams;
import com.google.common.io.Closer;
+import java.io.BufferedOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.OpenOption;
@@ -186,7 +187,9 @@
OpenOption[] options =
new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
try (Closer closer = Closer.create()) {
- try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(archive, options))) {
+ try (ZipOutputStream out =
+ new ZipOutputStream(
+ new BufferedOutputStream(Files.newOutputStream(archive, options)))) {
for (int i = 0; i < resources.size(); i++) {
ProgramResource resource = resources.get(i);
String entryName = getDefaultDexFileName(i);
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
index b7ece81..7203f8e 100644
--- a/src/main/java/com/android/tools/r8/DexSplitterHelper.java
+++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.dex.ApplicationWriter;
import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.GraphLens;
@@ -87,7 +88,8 @@
// Run d8 optimize to ensure jumbo strings are handled.
AppInfo appInfo = AppInfo.createInitialAppInfo(featureApp);
- featureApp = D8.optimize(featureApp, appInfo, options, timing, executor);
+ AppView<AppInfo> appView = AppView.createForD8(appInfo);
+ D8.optimize(appView, options, timing, executor);
// We create a specific consumer for each split.
Path outputDir = Paths.get(output).resolve(entry.getKey());
if (!Files.exists(outputDir)) {
@@ -97,7 +99,7 @@
try {
new ApplicationWriter(
- featureApp,
+ appView.appInfo().app(),
null,
options,
markers,
diff --git a/src/main/java/com/android/tools/r8/DiagnosticsHandler.java b/src/main/java/com/android/tools/r8/DiagnosticsHandler.java
index 666e358..de3f800 100644
--- a/src/main/java/com/android/tools/r8/DiagnosticsHandler.java
+++ b/src/main/java/com/android/tools/r8/DiagnosticsHandler.java
@@ -57,4 +57,19 @@
}
System.out.println(info.getDiagnosticMessage());
}
+
+ /**
+ * Modify the level of a diagnostic.
+ *
+ * <p>This modification is allowed only for non-fatal compiler diagnostics.
+ *
+ * <p>Changing a non-error into an error will cause the compiler to exit with a <code>
+ * CompilationFailedException</code> at its next error check point.
+ *
+ * <p>Changing an error into a non-error will allow the compiler to continue compilation. Note
+ * that doing so could very well lead to an internal compiler error due to a broken invariant.
+ */
+ default DiagnosticsLevel modifyDiagnosticsLevel(DiagnosticsLevel level, Diagnostic diagnostic) {
+ return level;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/DiagnosticsLevel.java b/src/main/java/com/android/tools/r8/DiagnosticsLevel.java
new file mode 100644
index 0000000..9f9d26a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/DiagnosticsLevel.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2017, 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;
+
+/** Levels of diagnostics messages reported by the compiler. */
+@Keep
+public enum DiagnosticsLevel {
+ ERROR,
+ WARNING,
+ INFO
+}
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
index 2bfba83..8144903 100644
--- a/src/main/java/com/android/tools/r8/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -303,7 +303,6 @@
addMethodsToHeaderJar(builder, clazz, methods);
});
- DexApplication app = builder.build();
// Write a plain text file with the desugared APIs.
desugaredApisSignatures.sort(Comparator.naturalOrder());
@@ -311,13 +310,11 @@
lintFile(compilationApiLevel, minApiLevel, ".txt"), desugaredApisSignatures);
// Write a header jar with the desugared APIs.
- AppInfo appInfo = AppInfo.createInitialAppInfo(app);
+ AppInfo appInfo = AppInfo.createInitialAppInfo(builder.build());
AppView<?> appView = AppView.createForD8(appInfo);
CfApplicationWriter writer =
new CfApplicationWriter(
- builder.build(),
appView,
- options,
options.getMarker(Tool.L8),
GraphLens.getIdentityLens(),
NamingLens.getIdentityLens(),
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index 99a87d3..aae1486 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -119,31 +119,19 @@
// on it.
options.enableLoadStoreOptimization = false;
- LazyLoadedDexApplication lazyApp =
- new ApplicationReader(inputApp, options, timing).read(executor);
-
- PrefixRewritingMapper rewritePrefix =
- options.desugaredLibraryConfiguration.createPrefixRewritingMapper(options);
-
- DexApplication app = new L8TreePruner(options).prune(lazyApp, rewritePrefix);
- AppInfo appInfo = AppInfo.createInitialAppInfo(app);
-
- AppView<?> appView = AppView.createForL8(appInfo, rewritePrefix);
- IRConverter converter = new IRConverter(appView, timing);
+ AppView<AppInfo> appView = readApp(inputApp, options, executor, timing);
if (!options.testing.disableL8AnnotationRemoval) {
AnnotationRemover.clearAnnotations(appView);
}
- app = converter.convert(app, executor);
- assert appView.appInfo() == appInfo;
+
+ new IRConverter(appView, timing).convert(appView, executor);
NamingLens namingLens = PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView);
- new GenericSignatureRewriter(appView, namingLens).run(appInfo.classes(), executor);
+ new GenericSignatureRewriter(appView, namingLens).run(appView.appInfo().classes(), executor);
new CfApplicationWriter(
- app,
appView,
- options,
options.getMarker(Tool.L8),
GraphLens.getIdentityLens(),
namingLens,
@@ -161,6 +149,19 @@
}
}
+ private static AppView<AppInfo> readApp(
+ AndroidApp inputApp, InternalOptions options, ExecutorService executor, Timing timing)
+ throws IOException {
+ LazyLoadedDexApplication lazyApp =
+ new ApplicationReader(inputApp, options, timing).read(executor);
+
+ PrefixRewritingMapper rewritePrefix =
+ options.desugaredLibraryConfiguration.createPrefixRewritingMapper(options);
+
+ DexApplication app = new L8TreePruner(options).prune(lazyApp, rewritePrefix);
+ return AppView.createForL8(AppInfo.createInitialAppInfo(app), rewritePrefix);
+ }
+
private static void run(String[] args) throws CompilationFailedException {
L8Command command = L8Command.parse(args, CommandLineOrigin.INSTANCE).build();
if (command.isPrintHelp()) {
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index cae584b..a2cf589 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -32,7 +32,7 @@
@Keep
public final class L8Command extends BaseCompilerCommand {
- static final String USAGE_MESSAGE = R8CommandParser.USAGE_MESSAGE;
+ static final String USAGE_MESSAGE = L8CommandParser.USAGE_MESSAGE;
private final D8Command d8Command;
private final R8Command r8Command;
@@ -253,6 +253,9 @@
@Override
void validate() {
+ if (isPrintHelp()) {
+ return;
+ }
Reporter reporter = getReporter();
if (!hasDesugaredLibraryConfiguration()) {
reporter.error("L8 requires a desugared library configuration");
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 38e1556..9d6b199 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -40,6 +40,7 @@
import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerGraphLens;
import com.android.tools.r8.inspector.internal.InspectorImpl;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
@@ -209,7 +210,6 @@
static void writeApplication(
ExecutorService executorService,
- DexApplication application,
AppView<?> appView,
GraphLens graphLens,
InitClassLens initClassLens,
@@ -217,7 +217,7 @@
InternalOptions options,
ProguardMapSupplier proguardMapSupplier)
throws ExecutionException {
- InspectorImpl.runInspections(options.outputInspections, application);
+ InspectorImpl.runInspections(options.outputInspections, appView.appInfo().classes());
try {
Marker marker = options.getMarker(Tool.R8);
assert marker != null;
@@ -227,11 +227,15 @@
markers.remove(marker);
if (options.isGeneratingClassFiles()) {
new CfApplicationWriter(
- application, appView, options, marker, graphLens, namingLens, proguardMapSupplier)
+ appView,
+ marker,
+ graphLens,
+ namingLens,
+ proguardMapSupplier)
.write(options.getClassFileConsumer());
} else {
new ApplicationWriter(
- application,
+ appView.appInfo().app(),
appView,
options,
// Ensure that the marker for this compilation is the first in the list.
@@ -354,6 +358,7 @@
}
}
}
+ options.reporter.failIfPendingErrors();
// Add synthesized -assumenosideeffects from min api if relevant.
if (options.isGeneratingDex()) {
@@ -547,7 +552,11 @@
timing.begin("HorizontalClassMerger");
HorizontalClassMerger merger =
new HorizontalClassMerger(appViewWithLiveness, mainDexClasses);
- merger.run();
+ HorizontalClassMergerGraphLens lens = merger.run();
+ if (lens != null) {
+ appView.setHorizontallyMergedClasses(lens.getHorizontallyMergedClasses());
+ appView.rewriteWithLens(lens);
+ }
timing.end();
}
@@ -905,7 +914,6 @@
// Generate the resulting application resources.
writeApplication(
executorService,
- appView.appInfo().app(),
appView,
appView.graphLens(),
appView.initClassLens(),
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index f2d182d..6efe395 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -407,6 +407,9 @@
@Override
void validate() {
+ if (isPrintHelp()) {
+ return;
+ }
Reporter reporter = getReporter();
if (getProgramConsumer() instanceof DexFilePerClassFileConsumer) {
reporter.error("R8 does not support compiling to a single DEX file per Java class file");
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 8d1eac7..da90a09 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -270,7 +270,8 @@
return; // Class constructor is always OK.
}
if (method.accessFlags.isStatic()) {
- if (!options.canUseDefaultAndStaticInterfaceMethods()) {
+ if (!options.canUseDefaultAndStaticInterfaceMethods()
+ && !options.testing.allowStaticInterfaceMethodsForPreNApiLevel) {
throw options.reporter.fatalError(
new StaticInterfaceMethodDiagnostic(new MethodPosition(method.method)));
}
diff --git a/src/main/java/com/android/tools/r8/dex/Marker.java b/src/main/java/com/android/tools/r8/dex/Marker.java
index dd179c1..b4852a3 100644
--- a/src/main/java/com/android/tools/r8/dex/Marker.java
+++ b/src/main/java/com/android/tools/r8/dex/Marker.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.dex;
import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.errors.DesugaredLibraryMismatchDiagnostic;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
@@ -97,11 +98,7 @@
}
if (desugaredLibraryIdentifiers.size() > 1) {
- reporter.error(
- new StringDiagnostic(
- "The compilation is merging inputs with different desugared library desugaring "
- + desugaredLibraryIdentifiers
- + ", which may lead to unexpected runtime errors."));
+ reporter.error(new DesugaredLibraryMismatchDiagnostic(desugaredLibraryIdentifiers));
}
}
diff --git a/src/main/java/com/android/tools/r8/errors/DesugaredLibraryMismatchDiagnostic.java b/src/main/java/com/android/tools/r8/errors/DesugaredLibraryMismatchDiagnostic.java
new file mode 100644
index 0000000..ba9af51
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/DesugaredLibraryMismatchDiagnostic.java
@@ -0,0 +1,36 @@
+// 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.errors;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import java.util.Set;
+
+public class DesugaredLibraryMismatchDiagnostic implements Diagnostic {
+
+ private final Set<String> desugaredLibraryIdentifiers;
+
+ public DesugaredLibraryMismatchDiagnostic(Set<String> desugaredLibraryIdentifiers) {
+ this.desugaredLibraryIdentifiers = desugaredLibraryIdentifiers;
+ }
+
+ @Override
+ public Origin getOrigin() {
+ return Origin.unknown();
+ }
+
+ @Override
+ public Position getPosition() {
+ return Position.UNKNOWN;
+ }
+
+ @Override
+ public String getDiagnosticMessage() {
+ return "The compilation is merging inputs with different desugared library desugaring "
+ + desugaredLibraryIdentifiers
+ + ", which may lead to unexpected runtime errors.";
+ }
+}
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 ccdfd2f..c6bc6b4 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -8,8 +8,10 @@
import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis.InitializedClassesInInstanceMethods;
import com.android.tools.r8.graph.classmerging.HorizontallyMergedLambdaClasses;
+import com.android.tools.r8.graph.classmerging.MergedClasses;
import com.android.tools.r8.graph.classmerging.MergedClassesCollection;
import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
+import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
import com.android.tools.r8.ir.analysis.proto.GeneratedExtensionRegistryShrinker;
import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker;
import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteShrinker;
@@ -70,6 +72,7 @@
private Predicate<DexType> classesEscapingIntoLibrary = Predicates.alwaysTrue();
private InitializedClassesInInstanceMethods initializedClassesInInstanceMethods;
private HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses;
+ private HorizontallyMergedClasses horizontallyMergedClasses;
private VerticallyMergedClasses verticallyMergedClasses;
private EnumValueInfoMapCollection unboxedEnums = EnumValueInfoMapCollection.empty();
private Set<DexMethod> cfByteCodePassThrough = ImmutableSet.of();
@@ -398,32 +401,48 @@
}
public boolean hasBeenMerged(DexProgramClass clazz) {
- // TODO(b/165227525): Add support for the horizontal class merger here.
- if (horizontallyMergedLambdaClasses != null
- && horizontallyMergedLambdaClasses.hasBeenMerged(clazz)) {
- return true;
- }
- return verticallyMergedClasses != null && verticallyMergedClasses.hasBeenMerged(clazz);
+ return MergedClasses.hasBeenMerged(horizontallyMergedClasses, clazz)
+ || MergedClasses.hasBeenMerged(horizontallyMergedLambdaClasses, clazz)
+ || MergedClasses.hasBeenMerged(verticallyMergedClasses, clazz);
}
- // Get the result of horizontal lambda class merging. Returns null if horizontal lambda class
- // merging has not been run.
+ /**
+ * Get the result of horizontal lambda class merging. Returns null if horizontal lambda class
+ * merging has not been run.
+ */
public HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses() {
return horizontallyMergedLambdaClasses;
}
public void setHorizontallyMergedLambdaClasses(
HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses) {
+ assert this.horizontallyMergedLambdaClasses == null;
this.horizontallyMergedLambdaClasses = horizontallyMergedLambdaClasses;
}
- // Get the result of vertical class merging. Returns null if vertical class merging has not been
- // run.
+ /**
+ * Get the result of horizontal class merging. Returns null if horizontal lambda class merging has
+ * not been run.
+ */
+ public HorizontallyMergedClasses horizontallyMergedClasses() {
+ return horizontallyMergedClasses;
+ }
+
+ public void setHorizontallyMergedClasses(HorizontallyMergedClasses horizontallyMergedClasses) {
+ assert this.horizontallyMergedClasses == null;
+ this.horizontallyMergedClasses = horizontallyMergedClasses;
+ }
+
+ /**
+ * Get the result of vertical class merging. Returns null if vertical class merging has not been
+ * run.
+ */
public VerticallyMergedClasses verticallyMergedClasses() {
return verticallyMergedClasses;
}
public void setVerticallyMergedClasses(VerticallyMergedClasses verticallyMergedClasses) {
+ assert this.verticallyMergedClasses == null;
this.verticallyMergedClasses = verticallyMergedClasses;
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
index 90b940a..318b1bc 100644
--- a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
@@ -117,12 +117,12 @@
@Override
public GraphLensLookupResult lookupMethod(DexMethod method, DexMethod context, Invoke.Type type) {
- return new GraphLensLookupResult(method, type);
+ return GraphLens.getIdentityLens().lookupMethod(method, context, type);
}
@Override
- public RewrittenPrototypeDescription lookupPrototypeChanges(DexMethod method) {
- return RewrittenPrototypeDescription.none();
+ public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(DexMethod method) {
+ return GraphLens.getIdentityLens().lookupPrototypeChangesForMethodDefinition(method);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 2cf3328..91a70a9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -170,6 +170,7 @@
public final DexString endsWithMethodName = createString("endsWith");
public final DexString equalsMethodName = createString("equals");
public final DexString hashCodeMethodName = createString("hashCode");
+ public final DexString identityHashCodeName = createString("identityHashCode");
public final DexString equalsIgnoreCaseMethodName = createString("equalsIgnoreCase");
public final DexString contentEqualsMethodName = createString("contentEquals");
public final DexString indexOfMethodName = createString("indexOf");
@@ -225,6 +226,7 @@
public final DexString fieldDescriptor = createString("Ljava/lang/reflect/Field;");
public final DexString methodDescriptor = createString("Ljava/lang/reflect/Method;");
public final DexString enumDescriptor = createString("Ljava/lang/Enum;");
+ public final DexString javaLangSystemDescriptor = createString("Ljava/lang/System;");
public final DexString annotationDescriptor = createString("Ljava/lang/annotation/Annotation;");
public final DexString objectsDescriptor = createString("Ljava/util/Objects;");
public final DexString collectionsDescriptor = createString("Ljava/util/Collections;");
@@ -353,7 +355,7 @@
public final DexType stringBuilderType = createStaticallyKnownType(stringBuilderDescriptor);
public final DexType stringBufferType = createStaticallyKnownType(stringBufferDescriptor);
- public final DexType javaLangSystemType = createStaticallyKnownType("Ljava/lang/System;");
+ public final DexType javaLangSystemType = createStaticallyKnownType(javaLangSystemDescriptor);
public final DexType javaIoPrintStreamType = createStaticallyKnownType("Ljava/io/PrintStream;");
public final DexType varHandleType = createStaticallyKnownType(varHandleDescriptor);
@@ -464,6 +466,7 @@
public final ClassMethods classMethods = new ClassMethods();
public final ConstructorMethods constructorMethods = new ConstructorMethods();
public final EnumMembers enumMembers = new EnumMembers();
+ public final JavaLangSystemMethods javaLangSystemMethods = new JavaLangSystemMethods();
public final NullPointerExceptionMethods npeMethods = new NullPointerExceptionMethods();
public final IllegalArgumentExceptionMethods illegalArgumentExceptionMethods =
new IllegalArgumentExceptionMethods();
@@ -1269,6 +1272,19 @@
}
}
+ public class JavaLangSystemMethods {
+ public final DexMethod identityHashCode;
+
+ private JavaLangSystemMethods() {
+ identityHashCode =
+ createMethod(
+ javaLangSystemDescriptor,
+ identityHashCodeName,
+ intDescriptor,
+ new DexString[] {objectDescriptor});
+ }
+ }
+
public class EnumMembers {
public final DexField nameField = createField(enumType, stringType, "name");
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index 8a50931..fce08d8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -31,6 +31,10 @@
}
}
+ public DexType getReturnType() {
+ return proto.returnType;
+ }
+
@Override
public <T> T apply(
Function<DexType, T> classConsumer,
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index d803a42..fe85d28 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.DataResourceProvider;
import com.android.tools.r8.graph.LazyLoadedDexApplication.AllClasses;
+import com.android.tools.r8.graph.classmerging.MergedClasses;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Timing;
@@ -137,7 +138,8 @@
.allMatch(
type ->
lens.lookupType(type) == type
- || appView.verticallyMergedClasses().hasBeenMergedIntoSubtype(type));
+ || MergedClasses.hasBeenMerged(appView.verticallyMergedClasses(), type)
+ || MergedClasses.hasBeenMerged(appView.horizontallyMergedClasses(), type));
assert verifyCodeObjectsOwners();
return true;
}
diff --git a/src/main/java/com/android/tools/r8/graph/EnumValueInfoMapCollection.java b/src/main/java/com/android/tools/r8/graph/EnumValueInfoMapCollection.java
index 4ee6be1..362ff7c 100644
--- a/src/main/java/com/android/tools/r8/graph/EnumValueInfoMapCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/EnumValueInfoMapCollection.java
@@ -84,6 +84,10 @@
this.map = map;
}
+ public Set<DexField> enumValues() {
+ return map.keySet();
+ }
+
public int size() {
return map.size();
}
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java
index 07b3205..edce8d8 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -42,16 +42,21 @@
/**
* Result of a method lookup in a GraphLens.
*
- * <p>This provide the new target and the invoke type to use.
+ * <p>This provides the new target and invoke type to use, along with a description of the
+ * prototype changes that have been made to the target method and the corresponding required
+ * changes to the invoke arguments.
*/
public static class GraphLensLookupResult {
private final DexMethod method;
private final Type type;
+ private final RewrittenPrototypeDescription prototypeChanges;
- public GraphLensLookupResult(DexMethod method, Type type) {
+ public GraphLensLookupResult(
+ DexMethod method, Type type, RewrittenPrototypeDescription prototypeChanges) {
this.method = method;
this.type = type;
+ this.prototypeChanges = prototypeChanges;
}
public DexMethod getMethod() {
@@ -61,6 +66,10 @@
public Type getType() {
return type;
}
+
+ public RewrittenPrototypeDescription getPrototypeChanges() {
+ return prototypeChanges;
+ }
}
public static class Builder {
@@ -189,7 +198,8 @@
public abstract GraphLensLookupResult lookupMethod(
DexMethod method, DexMethod context, Type type);
- public abstract RewrittenPrototypeDescription lookupPrototypeChanges(DexMethod method);
+ public abstract RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(
+ DexMethod method);
public abstract DexField lookupField(DexField field);
@@ -434,11 +444,12 @@
@Override
public GraphLensLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
- return new GraphLensLookupResult(method, type);
+ return new GraphLensLookupResult(method, type, RewrittenPrototypeDescription.none());
}
@Override
- public RewrittenPrototypeDescription lookupPrototypeChanges(DexMethod method) {
+ public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(
+ DexMethod method) {
return RewrittenPrototypeDescription.none();
}
@@ -628,24 +639,38 @@
@Override
public GraphLensLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
- DexMethod previousContext =
- originalMethodSignatures != null
- ? originalMethodSignatures.getOrDefault(context, context)
- : context;
- GraphLensLookupResult previous = previousLens.lookupMethod(method, previousContext, type);
- DexMethod newMethod = methodMap.get(previous.getMethod());
+ DexMethod previousContext = internalGetPreviousMethodSignature(context);
+ GraphLensLookupResult lookup = previousLens.lookupMethod(method, previousContext, type);
+ DexMethod newMethod = methodMap.get(lookup.getMethod());
if (newMethod == null) {
- return previous;
+ return lookup;
}
// TODO(sgjesse): Should we always do interface to virtual mapping? Is it a performance win
- // that only subclasses which are known to need it actually do it?
+ // that only subclasses which are known to need it actually do it?
return new GraphLensLookupResult(
- newMethod, mapInvocationType(newMethod, method, previous.getType()));
+ newMethod,
+ mapInvocationType(newMethod, method, lookup.getType()),
+ internalDescribePrototypeChanges(lookup.getPrototypeChanges(), newMethod));
}
@Override
- public RewrittenPrototypeDescription lookupPrototypeChanges(DexMethod method) {
- return previousLens.lookupPrototypeChanges(method);
+ public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(
+ DexMethod method) {
+ DexMethod previous = internalGetPreviousMethodSignature(method);
+ RewrittenPrototypeDescription lookup =
+ previousLens.lookupPrototypeChangesForMethodDefinition(previous);
+ return internalDescribePrototypeChanges(lookup, method);
+ }
+
+ protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
+ RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
+ return prototypeChanges;
+ }
+
+ protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
+ return originalMethodSignatures != null
+ ? originalMethodSignatures.getOrDefault(method, method)
+ : method;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
index 2828097..31ea06f 100644
--- a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
+++ b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
@@ -439,6 +439,9 @@
}
public RewrittenPrototypeDescription withRemovedArguments(ArgumentInfoCollection other) {
+ if (other.isEmpty()) {
+ return this;
+ }
return new RewrittenPrototypeDescription(
extraParameters, rewrittenReturnInfo, argumentInfoCollection.combine(other));
}
diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/HorizontallyMergedLambdaClasses.java b/src/main/java/com/android/tools/r8/graph/classmerging/HorizontallyMergedLambdaClasses.java
index fc8e49b..505dfd5 100644
--- a/src/main/java/com/android/tools/r8/graph/classmerging/HorizontallyMergedLambdaClasses.java
+++ b/src/main/java/com/android/tools/r8/graph/classmerging/HorizontallyMergedLambdaClasses.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.graph.classmerging;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.Set;
@@ -30,7 +29,7 @@
}
@Override
- public boolean hasBeenMerged(DexProgramClass clazz) {
- return sources.contains(clazz.type);
+ public boolean hasBeenMerged(DexType type) {
+ return sources.contains(type);
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/MergedClasses.java b/src/main/java/com/android/tools/r8/graph/classmerging/MergedClasses.java
index 66f9f6a..3f25895 100644
--- a/src/main/java/com/android/tools/r8/graph/classmerging/MergedClasses.java
+++ b/src/main/java/com/android/tools/r8/graph/classmerging/MergedClasses.java
@@ -6,11 +6,31 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
public interface MergedClasses {
boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView);
- boolean hasBeenMerged(DexProgramClass clazz);
+ boolean hasBeenMerged(DexType type);
+
+ /**
+ * Determine if the class has been merged by the merged classes object. If the merged classes is
+ * null then return false.
+ */
+ static boolean hasBeenMerged(MergedClasses mergedClasses, DexProgramClass clazz) {
+ return hasBeenMerged(mergedClasses, clazz.type);
+ }
+
+ /**
+ * Determine if the class has been merged by the merged classes object. If the merged classes is
+ * null then return false.
+ */
+ static boolean hasBeenMerged(MergedClasses mergedClasses, DexType type) {
+ if (mergedClasses != null) {
+ return mergedClasses.hasBeenMerged(type);
+ }
+ return false;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/MergedClassesCollection.java b/src/main/java/com/android/tools/r8/graph/classmerging/MergedClassesCollection.java
index 1ced397..55c34b5 100644
--- a/src/main/java/com/android/tools/r8/graph/classmerging/MergedClassesCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/classmerging/MergedClassesCollection.java
@@ -5,7 +5,7 @@
package com.android.tools.r8.graph.classmerging;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.ArrayList;
import java.util.List;
@@ -27,9 +27,9 @@
}
@Override
- public boolean hasBeenMerged(DexProgramClass clazz) {
+ public boolean hasBeenMerged(DexType type) {
for (MergedClasses mergedClasses : collection) {
- if (mergedClasses.hasBeenMerged(clazz)) {
+ if (mergedClasses.hasBeenMerged(type)) {
return true;
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java b/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java
index d4c730a..f76f9e7 100644
--- a/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java
+++ b/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.graph.classmerging;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.google.common.collect.ImmutableList;
@@ -56,7 +55,7 @@
}
@Override
- public boolean hasBeenMerged(DexProgramClass clazz) {
- return hasBeenMergedIntoSubtype(clazz.type);
+ public boolean hasBeenMerged(DexType type) {
+ return hasBeenMergedIntoSubtype(type);
}
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
new file mode 100644
index 0000000..e9aa061
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -0,0 +1,129 @@
+// 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;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.google.common.base.Equivalence.Wrapper;
+import java.util.Collection;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+/**
+ * The class merger is responsible for moving methods from {@link ClassMerger#toMergeGroup} into the
+ * class {@link ClassMerger#target}. While performing merging, this class tracks which methods have
+ * been moved, as well as which fields have been remapped in the {@link ClassMerger#lensBuilder}.
+ */
+class ClassMerger {
+ private final AppView<?> appView;
+ private final DexProgramClass target;
+ private final Collection<DexProgramClass> toMergeGroup;
+ private final DexItemFactory dexItemFactory;
+ private final HorizontalClassMergerGraphLens.Builder lensBuilder;
+
+ private final Map<DexProto, ConstructorMerger.Builder> constructorMergers;
+
+ ClassMerger(
+ AppView<?> appView,
+ HorizontalClassMergerGraphLens.Builder lensBuilder,
+ DexProgramClass target,
+ Collection<DexProgramClass> toMergeGroup) {
+ this.appView = appView;
+ this.lensBuilder = lensBuilder;
+ this.target = target;
+ this.toMergeGroup = toMergeGroup;
+ this.constructorMergers = new IdentityHashMap<>();
+
+ this.dexItemFactory = appView.dexItemFactory();
+ }
+
+ private Wrapper<DexMethod> bySignature(DexMethod method) {
+ return MethodSignatureEquivalence.get().wrap(method);
+ }
+
+ void addConstructor(DexEncodedMethod method) {
+ assert method.isInstanceInitializer();
+ constructorMergers
+ .computeIfAbsent(method.proto(), ignore -> new ConstructorMerger.Builder())
+ .add(method);
+ }
+
+ private void merge(DexProgramClass toMerge) {
+ toMerge.forEachProgramMethod(
+ programMethod -> {
+ DexEncodedMethod method = programMethod.getDefinition();
+ assert !method.isClassInitializer();
+
+ if (method.isInstanceInitializer()) {
+ addConstructor(method);
+ } else {
+ // TODO(b/166427795): Ensure that overriding relationships are not changed.
+ assert method.isVirtualMethod();
+
+ DexMethod newMethod = renameMethod(programMethod);
+ // TODO(b/165000217): Add all methods to `target` in one go using addVirtualMethods().;
+ target.addVirtualMethod(method.toTypeSubstitutedMethod(newMethod));
+ lensBuilder.moveMethod(method.method, newMethod);
+ }
+ });
+
+ // Clear the members of the class to be merged since they have now been moved to the target.
+ toMerge.setVirtualMethods(null);
+ toMerge.setDirectMethods(null);
+ toMerge.setInstanceFields(null);
+ toMerge.setStaticFields(null);
+ }
+
+ /**
+ * Find a new name for the method.
+ *
+ * @param method The class the method originally belonged to.
+ */
+ DexMethod renameMethod(ProgramMethod method) {
+ return dexItemFactory.createFreshMethodName(
+ method.getDefinition().method.name.toSourceString(),
+ method.getHolderType(),
+ method.getDefinition().proto(),
+ target.type,
+ tryMethod -> target.lookupMethod(tryMethod) == null);
+ }
+
+ void mergeConstructors() {
+ for (ConstructorMerger.Builder builder : constructorMergers.values()) {
+ ConstructorMerger constructorMerger = builder.build(appView, target);
+ constructorMerger.merge(lensBuilder);
+ }
+ }
+
+ /**
+ * To ensure constructor merging happens correctly, add all of the target constructors methods to
+ * constructor mergers.
+ */
+ void addTargetConstructors() {
+ target.forEachProgramDirectMethod(
+ programMethod -> {
+ DexEncodedMethod method = programMethod.getDefinition();
+ if (method.isInstanceInitializer()) {
+ addConstructor(method);
+ }
+ });
+ }
+
+ public void mergeGroup() {
+ addTargetConstructors();
+
+ for (DexProgramClass clazz : toMergeGroup) {
+ merge(clazz);
+ }
+
+ mergeConstructors();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
index d7e942d..aae7b0e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
@@ -42,6 +42,23 @@
this.dexItemFactory = appView.dexItemFactory();
}
+ public static class Builder {
+ private final Collection<DexEncodedMethod> constructors;
+
+ public Builder() {
+ constructors = new ArrayList<>();
+ }
+
+ public Builder add(DexEncodedMethod constructor) {
+ constructors.add(constructor);
+ return this;
+ }
+
+ public ConstructorMerger build(AppView<?> appView, DexProgramClass target) {
+ return new ConstructorMerger(appView, target, constructors);
+ }
+ }
+
private DexMethod moveConstructor(DexEncodedMethod constructor) {
DexMethod method =
dexItemFactory.createFreshMethodName(
@@ -75,7 +92,8 @@
return MethodAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC, true);
}
- public void merge() {
+ /** Synthesize a new method which selects the constructor based on a parameter type. */
+ void mergeMany(HorizontalClassMergerGraphLens.Builder lensBuilder) {
Map<DexType, DexMethod> typeConstructors = new IdentityHashMap<>();
for (DexEncodedMethod constructor : constructors) {
@@ -99,6 +117,40 @@
ParameterAnnotationsList.empty(),
synthesizedCode);
+ // Map each old constructor to the newly synthesized constructor in the graph lens.
+ int constructorId = 0;
+ for (DexEncodedMethod constructor : constructors) {
+ lensBuilder.mapConstructor(constructor.method, newMethod.method, constructorId++);
+ }
+
target.addDirectMethod(newMethod);
}
+
+ /**
+ * The constructor does not conflict with any other constructors. Add the constructor (if any) to
+ * the target directly.
+ */
+ void mergeTrivial(HorizontalClassMergerGraphLens.Builder lensBuilder) {
+ assert constructors.size() <= 1;
+
+ if (!constructors.isEmpty()) {
+ DexEncodedMethod constructor = constructors.iterator().next();
+
+ // Only move the constructor if it is not already in the target type.
+ if (constructor.holder() != target.type) {
+ DexEncodedMethod newConstructor =
+ constructor.toRenamedHolderMethod(target.type, dexItemFactory);
+ target.addDirectMethod(constructor);
+ lensBuilder.moveConstructor(constructor.method, newConstructor.method);
+ }
+ }
+ }
+
+ public void merge(HorizontalClassMergerGraphLens.Builder lensBuilder) {
+ if (constructors.size() <= 1) {
+ mergeTrivial(lensBuilder);
+ } else {
+ mergeMany(lensBuilder);
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index 56da905..60949e5 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -6,12 +6,22 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.horizontalclassmerging.policies.NoFields;
+import com.android.tools.r8.horizontalclassmerging.policies.NoInterfaces;
+import com.android.tools.r8.horizontalclassmerging.policies.NoInternalUtilityClasses;
+import com.android.tools.r8.horizontalclassmerging.policies.NoOverlappingConstructors;
+import com.android.tools.r8.horizontalclassmerging.policies.NoStaticClassInitializer;
+import com.android.tools.r8.horizontalclassmerging.policies.NotEntryPoint;
+import com.android.tools.r8.horizontalclassmerging.policies.SameParentClass;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.MainDexClasses;
+import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.List;
import java.util.Map;
public class HorizontalClassMerger {
@@ -26,13 +36,22 @@
this.appView = appView;
this.mainDexClasses = mainDexClasses;
- Policy[] policies = {
- // TODO: add policies
- };
- this.policyExecutor = new SimplePolicyExecutor(Arrays.asList(policies));
+ List<Policy> policies =
+ ImmutableList.of(
+ new NoFields(),
+ new NoInterfaces(),
+ new NoStaticClassInitializer(),
+ new NotEntryPoint(appView.dexItemFactory()),
+ new NoInternalUtilityClasses(appView.dexItemFactory()),
+ new SameParentClass(),
+ new NoOverlappingConstructors()
+ // TODO: add policies
+ );
+
+ this.policyExecutor = new SimplePolicyExecutor(policies);
}
- public Collection<Collection<DexProgramClass>> run() {
+ public HorizontalClassMergerGraphLens run() {
Map<FieldMultiset, Collection<DexProgramClass>> classes = new HashMap<>();
// Group classes by same field signature using the hash map.
@@ -43,6 +62,38 @@
// Run the policies on all collected classes to produce a final grouping.
Collection<Collection<DexProgramClass>> groups = policyExecutor.run(classes.values());
- return groups;
+ return createLens(groups);
+ }
+
+ // TODO(b/165577835): replace Collection<DexProgramClass> with MergeGroup
+ /**
+ * Merges all class groups using {@link ClassMerger}. Then fix all references to merged classes
+ * using the {@link TreeFixer}. Constructs a graph lens containing all changes while performing
+ * merging.
+ */
+ private HorizontalClassMergerGraphLens createLens(
+ Collection<Collection<DexProgramClass>> groups) {
+ Map<DexType, DexType> mergedClasses = new IdentityHashMap<>();
+ HorizontalClassMergerGraphLens.Builder lensBuilder =
+ new HorizontalClassMergerGraphLens.Builder();
+
+ // TODO(b/166577694): Replace Collection<DexProgramClass> with MergeGroup
+ for (Collection<DexProgramClass> group : groups) {
+ assert !group.isEmpty();
+
+ DexProgramClass target = group.stream().findFirst().get();
+ group.remove(target);
+
+ for (DexProgramClass clazz : group) {
+ mergedClasses.put(clazz.type, target.type);
+ }
+
+ ClassMerger merger = new ClassMerger(appView, lensBuilder, target, group);
+ merger.mergeGroup();
+ }
+
+ HorizontalClassMergerGraphLens lens =
+ new TreeFixer(appView, lensBuilder, mergedClasses).fixupTypeReferences();
+ return lens;
}
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
new file mode 100644
index 0000000..451e550
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
@@ -0,0 +1,129 @@
+// 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;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class HorizontalClassMergerGraphLens extends NestedGraphLens {
+ private final AppView<?> appView;
+
+ private HorizontalClassMergerGraphLens(
+ AppView<?> appView,
+ Map<DexType, DexType> typeMap,
+ Map<DexField, DexField> fieldMap,
+ Map<DexMethod, DexMethod> methodMap,
+ BiMap<DexField, DexField> originalFieldSignatures,
+ BiMap<DexMethod, DexMethod> originalMethodSignatures,
+ GraphLens previousLens) {
+ super(
+ typeMap,
+ methodMap,
+ fieldMap,
+ originalFieldSignatures,
+ originalMethodSignatures,
+ previousLens,
+ appView.dexItemFactory());
+ this.appView = appView;
+ }
+
+ public HorizontallyMergedClasses getHorizontallyMergedClasses() {
+ return new HorizontallyMergedClasses(this.typeMap);
+ }
+
+ public static class Builder {
+ protected final BiMap<DexField, DexField> fieldMap = HashBiMap.create();
+ protected final Map<DexMethod, DexMethod> methodMap = new IdentityHashMap<>();
+ protected final Map<DexMethod, Set<DexMethod>> completeInverseMethodMap =
+ new IdentityHashMap<>();
+
+ private final BiMap<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
+
+ private final Map<DexMethod, Integer> constructorIds = new IdentityHashMap<>();
+
+ Builder() {}
+
+ public HorizontalClassMergerGraphLens build(
+ AppView<?> appView, Map<DexType, DexType> mergedClasses) {
+ if (mergedClasses.isEmpty()) {
+ return null;
+ } else {
+ BiMap<DexField, DexField> originalFieldSignatures = fieldMap.inverse();
+ return new HorizontalClassMergerGraphLens(
+ appView,
+ mergedClasses,
+ fieldMap,
+ methodMap,
+ originalFieldSignatures,
+ originalMethodSignatures,
+ appView.graphLens());
+ }
+ }
+
+ /** Bidirectional mapping from one method to another. */
+ public Builder moveMethod(DexMethod from, DexMethod to) {
+ mapMethod(from, to);
+ originalMethodSignatures.put(to, from);
+ return this;
+ }
+
+ /**
+ * Unidirectional mapping from one method to another. This seems to only be used by synthesized
+ * constructors so is private for now. See {@link Builder#moveConstructor(DexMethod,
+ * DexMethod)}.
+ */
+ private Builder mapMethod(DexMethod from, DexMethod to) {
+ for (DexMethod existingFrom :
+ completeInverseMethodMap.getOrDefault(from, Collections.emptySet())) {
+ methodMap.put(existingFrom, to);
+
+ // We currently assume that a single method can only be remapped twice.
+ assert completeInverseMethodMap
+ .getOrDefault(existingFrom, Collections.emptySet())
+ .isEmpty();
+ }
+ methodMap.put(from, to);
+ completeInverseMethodMap.computeIfAbsent(to, ignore -> new HashSet<>()).add(from);
+ return this;
+ }
+
+ public boolean hasOriginalSignatureMappingFor(DexMethod method) {
+ return originalMethodSignatures.containsKey(method);
+ }
+
+ /**
+ * One way mapping from one constructor to another. This is used for synthesized constructors,
+ * where many constructors are merged into a single constructor. The synthesized constructor
+ * therefore does not have a unique reverse constructor.
+ *
+ * @param constructorId The id that must be appended to the constructor call to ensure the
+ * correct constructor is called.
+ */
+ public Builder mapConstructor(DexMethod from, DexMethod to, int constructorId) {
+ mapMethod(from, to);
+ constructorIds.put(from, constructorId);
+ return this;
+ }
+
+ /**
+ * Bidirectional mapping from one constructor to another. When a single constructor is simply
+ * moved from one class to another, we can uniquely map the new constructor back to the old one.
+ */
+ public Builder moveConstructor(DexMethod from, DexMethod to) {
+ return moveMethod(from, to);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java
new file mode 100644
index 0000000..00632e5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java
@@ -0,0 +1,35 @@
+// 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;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.classmerging.MergedClasses;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.Map;
+
+public class HorizontallyMergedClasses implements MergedClasses {
+ private final Map<DexType, DexType> horizontallyMergedClasses;
+
+ public HorizontallyMergedClasses(Map<DexType, DexType> horizontallyMergedClasses) {
+ this.horizontallyMergedClasses = horizontallyMergedClasses;
+ }
+
+ @Override
+ public boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView) {
+ for (DexType source : horizontallyMergedClasses.keySet()) {
+ assert appView.appInfo().wasPruned(source)
+ : "Expected horizontally merged lambda class `"
+ + source.toSourceString()
+ + "` to be absent";
+ }
+ return true;
+ }
+
+ @Override
+ public boolean hasBeenMerged(DexType type) {
+ return horizontallyMergedClasses.containsKey(type);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
new file mode 100644
index 0000000..cd2cabf
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
@@ -0,0 +1,136 @@
+// 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;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass.FieldSetter;
+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.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.AnnotationFixer;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.OptionalBool;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The tree fixer traverses all program classes and finds and fixes references to old classes which
+ * have been remapped to new classes by the class merger (stored in {@link
+ * TreeFixer#mergedClasses}). While doing so, all updated changes are tracked in {@link
+ * TreeFixer#lensBuilder}.
+ */
+class TreeFixer {
+ private final Map<DexType, DexType> mergedClasses;
+ private final Map<DexProto, DexProto> protoFixupCache = new IdentityHashMap<>();
+ private final HorizontalClassMergerGraphLens.Builder lensBuilder;
+ private final AppView<AppInfoWithLiveness> appView;
+
+ public TreeFixer(
+ AppView<AppInfoWithLiveness> appView,
+ HorizontalClassMergerGraphLens.Builder lensBuilder,
+ Map<DexType, DexType> mergedClasses) {
+ this.mergedClasses = mergedClasses;
+ this.lensBuilder = lensBuilder;
+ this.appView = appView;
+ }
+
+ HorizontalClassMergerGraphLens fixupTypeReferences() {
+ // Globally substitute merged class types in protos and holders.
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ clazz.getMethodCollection().replaceMethods(this::fixupMethod);
+ fixupFields(clazz.staticFields(), clazz::setStaticField);
+ fixupFields(clazz.instanceFields(), clazz::setInstanceField);
+ }
+ HorizontalClassMergerGraphLens lens = lensBuilder.build(appView, mergedClasses);
+
+ if (lens != null) {
+ new AnnotationFixer(lens).run(appView.appInfo().classes());
+ }
+ return lens;
+ }
+
+ private DexEncodedMethod fixupMethod(DexEncodedMethod method) {
+ DexMethod methodReference = method.method;
+ DexMethod newMethodReference = fixupMethod(methodReference);
+ if (newMethodReference == methodReference) {
+ return method;
+ }
+
+ lensBuilder.moveMethod(methodReference, newMethodReference);
+ DexEncodedMethod newMethod = method.toTypeSubstitutedMethod(newMethodReference);
+ if (newMethod.isNonPrivateVirtualMethod()) {
+ // Since we changed the return type or one of the parameters, this method cannot be a
+ // classpath or library method override, since we only class merge program classes.
+ assert !method.isLibraryMethodOverride().isTrue();
+ newMethod.setLibraryMethodOverride(OptionalBool.FALSE);
+ }
+ return newMethod;
+ }
+
+ private void fixupFields(List<DexEncodedField> fields, FieldSetter setter) {
+ if (fields == null) {
+ return;
+ }
+ for (int i = 0; i < fields.size(); i++) {
+ DexEncodedField encodedField = fields.get(i);
+ DexField field = encodedField.field;
+ DexType newType = fixupType(field.type);
+ DexType newHolder = fixupType(field.holder);
+ DexField newField = appView.dexItemFactory().createField(newHolder, newType, field.name);
+ if (newField != encodedField.field) {
+ // TODO(b/165498187): track mapped fields
+ /* lensBuilder.map(field, newField); */
+ setter.setField(i, encodedField.toTypeSubstitutedField(newField));
+ }
+ }
+ }
+
+ private DexMethod fixupMethod(DexMethod method) {
+ return appView
+ .dexItemFactory()
+ .createMethod(fixupType(method.holder), fixupProto(method.proto), method.name);
+ }
+
+ private DexProto fixupProto(DexProto proto) {
+ DexProto result = protoFixupCache.get(proto);
+ if (result == null) {
+ DexType returnType = fixupType(proto.returnType);
+ DexType[] arguments = fixupTypes(proto.parameters.values);
+ result = appView.dexItemFactory().createProto(returnType, arguments);
+ protoFixupCache.put(proto, result);
+ }
+ return result;
+ }
+
+ private DexType fixupType(DexType type) {
+ if (type.isArrayType()) {
+ DexType base = type.toBaseType(appView.dexItemFactory());
+ DexType fixed = fixupType(base);
+ if (base == fixed) {
+ return type;
+ }
+ return type.replaceBaseType(fixed, appView.dexItemFactory());
+ }
+ if (type.isClassType()) {
+ while (mergedClasses.containsKey(type)) {
+ type = mergedClasses.get(type);
+ }
+ }
+ return type;
+ }
+
+ private DexType[] fixupTypes(DexType[] types) {
+ DexType[] result = new DexType[types.length];
+ for (int i = 0; i < result.length; i++) {
+ result[i] = fixupType(types[i]);
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoFields.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoFields.java
new file mode 100644
index 0000000..9ba49a4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoFields.java
@@ -0,0 +1,16 @@
+// 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.SingleClassPolicy;
+
+public class NoFields extends SingleClassPolicy {
+ @Override
+ public boolean canMerge(DexProgramClass program) {
+ // TODO(b/165498187): remove this policy
+ return !program.hasFields();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java
new file mode 100644
index 0000000..7b194a2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java
@@ -0,0 +1,16 @@
+// 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.SingleClassPolicy;
+
+public class NoInterfaces extends SingleClassPolicy {
+
+ @Override
+ public boolean canMerge(DexProgramClass program) {
+ return program.interfaces.isEmpty();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInternalUtilityClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInternalUtilityClasses.java
new file mode 100644
index 0000000..2887849
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInternalUtilityClasses.java
@@ -0,0 +1,25 @@
+// 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.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+import java.util.Collections;
+import java.util.Set;
+
+public class NoInternalUtilityClasses extends SingleClassPolicy {
+ private final Set<DexType> internalUtilityClasses;
+
+ public NoInternalUtilityClasses(DexItemFactory dexItemFactory) {
+ this.internalUtilityClasses = Collections.singleton(dexItemFactory.enumUnboxingUtilityType);
+ }
+
+ @Override
+ public boolean canMerge(DexProgramClass program) {
+ return !internalUtilityClasses.contains(program.type);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoOverlappingConstructors.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoOverlappingConstructors.java
new file mode 100644
index 0000000..112580c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoOverlappingConstructors.java
@@ -0,0 +1,117 @@
+// 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.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+public class NoOverlappingConstructors extends MultiClassPolicy {
+
+ public void removeNonConflicting(Map<DexProto, Set<DexProgramClass>> overlappingConstructors) {
+ Iterator<DexProto> i = overlappingConstructors.keySet().iterator();
+ while (i.hasNext()) {
+ DexProto proto = i.next();
+ if (overlappingConstructors.get(proto).size() == 1) {
+ i.remove();
+ }
+ }
+ }
+
+ private Set<DexProgramClass> sortedClassSet(Collection<DexProgramClass> classes) {
+ Set<DexProgramClass> set =
+ new TreeSet<DexProgramClass>(
+ Comparator.comparing(DexProgramClass::getType, DexType::slowCompareTo));
+ set.addAll(classes);
+ return set;
+ }
+
+ @Override
+ public Collection<Collection<DexProgramClass>> apply(Collection<DexProgramClass> group) {
+ Map<DexProto, Set<DexProgramClass>> overlappingConstructors = new IdentityHashMap<>();
+
+ for (DexProgramClass clazz : group) {
+ clazz.forEachProgramDirectMethod(
+ directMethod -> {
+ DexEncodedMethod method = directMethod.getDefinition();
+ if (method.isInstanceInitializer()) {
+ overlappingConstructors
+ .computeIfAbsent(method.getProto(), ignore -> new HashSet<DexProgramClass>())
+ .add(clazz);
+ }
+ });
+ }
+
+ removeNonConflicting(overlappingConstructors);
+
+ // This is probably related to the graph colouring problem so probably won't be efficient in
+ // worst cases. We assume there won't be that many overlapping constructors so this should be
+ // reasonable. Constructor merging should also make this obsolete.
+ Collection<Set<DexProgramClass>> groups = new LinkedList<>();
+ groups.add(sortedClassSet(group));
+
+ for (Set<DexProgramClass> overlappingClasses : overlappingConstructors.values()) {
+ Collection<Set<DexProgramClass>> newGroups = new LinkedList<>();
+
+ // For every set of classes that cannot be in the same group, generate a new group with the
+ // same constructor and with all remaining constructors.
+
+ for (Set<DexProgramClass> existingGroup : groups) {
+ Set<DexProgramClass> actuallyOverlapping = new HashSet<>(overlappingClasses);
+ actuallyOverlapping.retainAll(existingGroup);
+
+ if (actuallyOverlapping.size() <= 1) {
+ newGroups.add(existingGroup);
+ } else {
+ Set<DexProgramClass> notOverlapping = new HashSet<>(existingGroup);
+ notOverlapping.removeAll(overlappingClasses);
+ for (DexProgramClass overlappingClass : actuallyOverlapping) {
+ Set<DexProgramClass> newGroup = sortedClassSet(notOverlapping);
+ newGroup.add(overlappingClass);
+ newGroups.add(newGroup);
+ }
+ }
+ }
+
+ groups = newGroups;
+ }
+
+ // Ensure each class is only in a single group and remove singleton and empty groups.
+ Set<DexProgramClass> assignedClasses = new HashSet<>();
+
+ Iterator<Set<DexProgramClass>> i = groups.iterator();
+ while (i.hasNext()) {
+ Set<DexProgramClass> newGroup = i.next();
+ newGroup.removeAll(assignedClasses);
+ if (newGroup.size() <= 1) {
+ i.remove();
+ } else {
+ assignedClasses.addAll(newGroup);
+ }
+ }
+
+ // Map to collection
+ Collection<Collection<DexProgramClass>> newGroups = new ArrayList<>();
+ for (Set<DexProgramClass> newGroup : groups) {
+ List<DexProgramClass> newGroupList = new ArrayList<>(newGroup);
+ newGroups.add(new ArrayList<>(newGroup));
+ }
+
+ return newGroups;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoStaticClassInitializer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoStaticClassInitializer.java
new file mode 100644
index 0000000..aeca6c0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoStaticClassInitializer.java
@@ -0,0 +1,20 @@
+// 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.SingleClassPolicy;
+
+/**
+ * Prevent merging of classes with static initializers, as merging these causes side effects. It is
+ * okay for superclasses to have static initializers as all classes are expected to have the same
+ * super class.
+ */
+public class NoStaticClassInitializer extends SingleClassPolicy {
+ @Override
+ public boolean canMerge(DexProgramClass program) {
+ return !program.hasClassInitializer();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotEntryPoint.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotEntryPoint.java
new file mode 100644
index 0000000..4bc65e5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotEntryPoint.java
@@ -0,0 +1,30 @@
+// 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.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+
+public class NotEntryPoint extends SingleClassPolicy {
+ private final DexString main;
+
+ public NotEntryPoint(DexItemFactory factory) {
+ main = factory.createString("main");
+ }
+
+ @Override
+ public boolean canMerge(DexProgramClass program) {
+ // TODO(b/165000217): Account for keep rules instead.
+ for (DexEncodedMethod method : program.directMethods()) {
+ if (method.method.name.equals(main)) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameParentClass.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameParentClass.java
new file mode 100644
index 0000000..4e1987e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameParentClass.java
@@ -0,0 +1,27 @@
+// 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.graph.DexType;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
+import java.util.Collection;
+import java.util.IdentityHashMap;
+import java.util.LinkedList;
+import java.util.Map;
+
+public class SameParentClass extends MultiClassPolicy {
+
+ @Override
+ public Collection<Collection<DexProgramClass>> apply(Collection<DexProgramClass> group) {
+ Map<DexType, Collection<DexProgramClass>> groups = new IdentityHashMap<>();
+ for (DexProgramClass clazz : group) {
+ groups
+ .computeIfAbsent(clazz.superType, ignore -> new LinkedList<DexProgramClass>())
+ .add(clazz);
+ }
+ return groups.values();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/internal/InspectorImpl.java b/src/main/java/com/android/tools/r8/inspector/internal/InspectorImpl.java
index c3d8a64..72e322f 100644
--- a/src/main/java/com/android/tools/r8/inspector/internal/InspectorImpl.java
+++ b/src/main/java/com/android/tools/r8/inspector/internal/InspectorImpl.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.inspector.internal;
-import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.inspector.ClassInspector;
import com.android.tools.r8.inspector.Inspector;
@@ -29,25 +28,25 @@
}
public static void runInspections(
- List<Consumer<InspectorImpl>> inspections, DexApplication application) {
+ List<Consumer<InspectorImpl>> inspections, Collection<DexProgramClass> classes) {
if (inspections == null || inspections.isEmpty()) {
return;
}
- InspectorImpl inspector = new InspectorImpl(application);
+ InspectorImpl inspector = new InspectorImpl(classes);
for (Consumer<InspectorImpl> inspection : inspections) {
inspection.accept(inspector);
}
}
- private final DexApplication application;
+ private final Collection<DexProgramClass> classes;
- public InspectorImpl(DexApplication application) {
- this.application = application;
+ public InspectorImpl(Collection<DexProgramClass> classes) {
+ this.classes = classes;
}
@Override
public void forEachClass(Consumer<ClassInspector> inspection) {
- for (DexProgramClass clazz : application.classes()) {
+ for (DexProgramClass clazz : classes) {
inspection.accept(new ClassInspectorImpl(clazz));
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index 3d8df1a..ebcdfff 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
+import com.android.tools.r8.ir.analysis.value.EnumValuesObjectState;
import com.android.tools.r8.ir.analysis.value.ObjectState;
import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
import com.android.tools.r8.ir.code.ArrayPut;
@@ -25,6 +26,7 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
@@ -141,9 +143,132 @@
* array as long as the array is identified as being the {@code $VALUES} array.
*/
private SingleFieldValue computeSingleEnumFieldValue(Value value) {
+ if (!context.getHolder().isEnum()) {
+ return null;
+ }
assert !value.hasAliasedValue();
- if (!context.getHolder().isEnum()
- || !value.isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
+ if (isEnumValuesArray(value)) {
+ return computeSingleEnumFieldValueForValuesArray(value);
+ }
+ return computeSingleEnumFieldValueForInstance(value);
+ }
+
+ private SingleFieldValue computeSingleEnumFieldValueForValuesArray(Value value) {
+ if (!value.isDefinedByInstructionSatisfying(Instruction::isNewArrayEmpty)) {
+ return null;
+ }
+
+ NewArrayEmpty newArrayEmpty = value.definition.asNewArrayEmpty();
+ if (newArrayEmpty.type.toBaseType(appView.dexItemFactory()) != context.getHolder().type) {
+ return null;
+ }
+ if (value.hasDebugUsers() || value.hasPhiUsers()) {
+ return null;
+ }
+ if (!newArrayEmpty.size().isConstNumber()) {
+ return null;
+ }
+
+ int valuesSize = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue();
+ if (valuesSize == 0) {
+ // No need to compute the state of an empty array.
+ return null;
+ }
+
+ ObjectState[] valuesState = new ObjectState[valuesSize];
+ DexEncodedField valuesField = null;
+ for (Instruction user : value.uniqueUsers()) {
+ switch (user.opcode()) {
+ case ARRAY_PUT:
+ ArrayPut arrayPut = user.asArrayPut();
+ if (arrayPut.array() != value) {
+ return null;
+ }
+ if (!arrayPut.index().isConstNumber()) {
+ return null;
+ }
+ int index = arrayPut.index().getConstInstruction().asConstNumber().getIntValue();
+ if (index < 0 || index >= valuesSize) {
+ return null;
+ }
+ ObjectState objectState = computeEnumInstanceObjectState(arrayPut.value());
+ if (objectState == null || objectState.isEmpty()) {
+ // We need the state of all fields for the analysis to be valuable.
+ return null;
+ }
+ assert verifyValuesArrayIndexMatchesOrdinal(index, objectState);
+ if (valuesState[index] != null) {
+ return null;
+ }
+ valuesState[index] = objectState;
+ break;
+
+ case STATIC_PUT:
+ DexEncodedField field =
+ context.getHolder().lookupStaticField(user.asStaticPut().getField());
+ if (field == null) {
+ return null;
+ }
+ if (valuesField != null) {
+ return null;
+ }
+ valuesField = field;
+ break;
+
+ default:
+ return null;
+ }
+ }
+
+ if (valuesField == null) {
+ return null;
+ }
+
+ for (ObjectState objectState : valuesState) {
+ if (objectState == null) {
+ return null;
+ }
+ }
+
+ return appView
+ .abstractValueFactory()
+ .createSingleFieldValue(valuesField.field, new EnumValuesObjectState(valuesState));
+ }
+
+ private ObjectState computeEnumInstanceObjectState(Value value) {
+ Value root = value.getAliasedValue();
+ if (root.isPhi()) {
+ return ObjectState.empty();
+ }
+ Instruction definition = root.getDefinition();
+ if (definition.isNewInstance()) {
+ return computeObjectState(definition.outValue());
+ }
+ if (definition.isStaticGet()) {
+ // TODO(b/166532388) : Enums with many instance rely on staticGets to set the $VALUES data
+ // instead of directly keeping the values in registers. We could consider analysing these
+ // and answer the analysed object state here.
+ return ObjectState.empty();
+ }
+ return ObjectState.empty();
+ }
+
+ private boolean verifyValuesArrayIndexMatchesOrdinal(int ordinal, ObjectState objectState) {
+ DexEncodedField ordinalField =
+ appView
+ .appInfo()
+ .resolveField(appView.dexItemFactory().enumMembers.ordinalField, context)
+ .getResolvedField();
+ assert ordinalField != null;
+ AbstractValue ordinalState = objectState.getAbstractFieldValue(ordinalField);
+ assert ordinalState != null;
+ assert ordinalState.isSingleNumberValue();
+ assert ordinalState.asSingleNumberValue().getIntValue() == ordinal;
+ return true;
+ }
+
+ private SingleFieldValue computeSingleEnumFieldValueForInstance(Value value) {
+ if (!value.isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/EnumValuesObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/EnumValuesObjectState.java
new file mode 100644
index 0000000..f1ec89c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/EnumValuesObjectState.java
@@ -0,0 +1,82 @@
+// 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.analysis.value;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.Arrays;
+import java.util.Objects;
+
+public class EnumValuesObjectState extends ObjectState {
+
+ private final ObjectState[] state;
+
+ public EnumValuesObjectState(ObjectState[] state) {
+ assert state.length > 0;
+ assert Arrays.stream(state).noneMatch(Objects::isNull);
+ this.state = state;
+ }
+
+ @Override
+ public AbstractValue getAbstractFieldValue(DexEncodedField field) {
+ return UnknownValue.getInstance();
+ }
+
+ public ObjectState getObjectStateForOrdinal(int ordinal) {
+ if (ordinal < 0 || ordinal >= state.length) {
+ return ObjectState.empty();
+ }
+ return state[ordinal];
+ }
+
+ @Override
+ public boolean isEnumValuesObjectState() {
+ return true;
+ }
+
+ @Override
+ public EnumValuesObjectState asEnumValuesObjectState() {
+ return this;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ // Non-empty by construction.
+ return false;
+ }
+
+ @Override
+ public ObjectState rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+ ObjectState[] newState = new ObjectState[state.length];
+ for (int i = 0; i < state.length; i++) {
+ newState[i] = state[i].rewrittenWithLens(appView, lens);
+ }
+ return new EnumValuesObjectState(newState);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (getClass() != o.getClass()) {
+ return false;
+ }
+ EnumValuesObjectState other = (EnumValuesObjectState) o;
+ if (state.length != other.state.length) {
+ return false;
+ }
+ for (int i = 0; i < state.length; i++) {
+ if (!state[i].equals(other.state[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(state);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java
index b724858..4e6d970 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java
@@ -35,6 +35,14 @@
@Override
public abstract int hashCode();
+ public boolean isEnumValuesObjectState() {
+ return false;
+ }
+
+ public EnumValuesObjectState asEnumValuesObjectState() {
+ return null;
+ }
+
public static class Builder {
private final Map<DexField, AbstractValue> state = new IdentityHashMap<>();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
index 0cb75c3..98a7fa4 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
@@ -50,6 +50,10 @@
return value;
}
+ public int getIntValue() {
+ return (int) value;
+ }
+
@Override
public boolean equals(Object o) {
return this == o;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 806917c..f2da1ac 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -457,7 +457,7 @@
private static RewrittenPrototypeDescription lookupPrototypeChanges(
AppView<?> appView, ProgramMethod method) {
RewrittenPrototypeDescription prototypeChanges =
- appView.graphLens().lookupPrototypeChanges(method.getReference());
+ appView.graphLens().lookupPrototypeChangesForMethodDefinition(method.getReference());
if (Log.ENABLED && prototypeChanges.getArgumentInfoCollection().hasRemovedArguments()) {
Log.info(
IRBuilder.class,
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 61a2f18..4c10624 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
@@ -470,12 +470,12 @@
}
}
- public DexApplication convert(DexApplication application, ExecutorService executor)
+ public void convert(AppView<AppInfo> appView, ExecutorService executor)
throws ExecutionException {
removeLambdaDeserializationMethods();
workaroundAbstractMethodOnNonAbstractClassVerificationBug(
executor, OptimizationFeedbackIgnore.getInstance());
-
+ DexApplication application = appView.appInfo().app();
timing.begin("IR conversion");
ThreadUtils.processItems(application.classes(), this::convertMethods, executor);
@@ -496,7 +496,8 @@
handleSynthesizedClassMapping(builder);
timing.end();
- return builder.build();
+ DexApplication app = builder.build();
+ appView.setAppInfo(new AppInfo(app, appView.appInfo().getSyntheticItems().commit(app)));
}
private void handleSynthesizedClassMapping(Builder<?> builder) {
@@ -738,7 +739,6 @@
postMethodProcessorBuilder.put(inliner);
}
if (enumUnboxer != null) {
- enumUnboxer.finishAnalysis();
enumUnboxer.unboxEnums(postMethodProcessorBuilder, executorService, feedback);
}
if (!options.debug) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index bd8ba8d..51905bf 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -227,8 +227,7 @@
DexMethod actualTarget = lensLookup.getMethod();
Invoke.Type actualInvokeType = lensLookup.getType();
if (actualTarget != invokedMethod || invoke.getType() != actualInvokeType) {
- RewrittenPrototypeDescription prototypeChanges =
- graphLens.lookupPrototypeChanges(actualTarget);
+ RewrittenPrototypeDescription prototypeChanges = lensLookup.getPrototypeChanges();
List<Value> newInValues;
ArgumentInfoCollection argumentInfoCollection =
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 88f6f04..2167964 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
@@ -265,9 +265,13 @@
// On pre-L devices static calls to interface methods result in verifier
// rejecting the whole class. We have to create special dispatch classes,
// so the user class is not rejected because it make this call directly.
+ // TODO(b/166247515): If this an incorrect invoke-static without the interface bit
+ // we end up "fixing" the code and remove and ICCE error.
instructions.replaceCurrentInstruction(
- new InvokeStatic(staticAsMethodOfDispatchClass(method),
- invokeStatic.outValue(), invokeStatic.arguments()));
+ new InvokeStatic(
+ staticAsMethodOfDispatchClass(method),
+ invokeStatic.outValue(),
+ invokeStatic.arguments()));
requiredDispatchClasses
.computeIfAbsent(clazz.asLibraryClass(), k -> Sets.newConcurrentHashSet())
.add(appInfo.definitionFor(encodedMethod.holder()).asProgramClass());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index 5a50c02..f400369 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -259,10 +259,13 @@
DexMethod newMethod = rewriter.staticAsMethodOfDispatchClass(origMethod);
ForwardMethodSourceCode.Builder forwardSourceCodeBuilder =
ForwardMethodSourceCode.builder(newMethod);
+ // Create a forwarding method to the library static interface method. The method is added
+ // to the dispatch class, however, the targeted method is still on the interface, so the
+ // interface bit should be set to true.
forwardSourceCodeBuilder
.setTarget(origMethod)
.setInvokeType(Type.STATIC)
- .setIsInterface(false); // We forward to the Companion class, not an interface.
+ .setIsInterface(true);
DexEncodedMethod newEncodedMethod =
new DexEncodedMethod(
newMethod,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 605397a..8deb4fc 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -10,7 +10,6 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ClassAccessFlags;
import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -34,7 +33,6 @@
import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
import com.android.tools.r8.origin.SynthesizedOrigin;
-import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OptionalBool;
import com.google.common.base.Suppliers;
import com.google.common.primitives.Longs;
@@ -541,18 +539,8 @@
return accessibilityBridge;
}
- boolean holderIsInterface() {
- InternalOptions options = appView.options();
- DexMethod implMethod = descriptor.implHandle.asMethod();
- DexClass implMethodHolder = appView.definitionFor(implMethod.holder);
- if (implMethodHolder == null) {
- assert options
- .desugaredLibraryConfiguration
- .getBackportCoreLibraryMember()
- .containsKey(implMethod.holder);
- return false;
- }
- return implMethodHolder.isInterface();
+ boolean isInterface() {
+ return descriptor.implHandle.isInterface;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
index 0d996d5..ac55e68 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
@@ -243,7 +243,7 @@
methodToCall.proto,
argValueTypes,
argRegisters,
- target.holderIsInterface()));
+ target.isInterface()));
// Does the method have return value?
if (enforcedReturnType.isVoidType()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java b/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java
index e7c0b77..9e429a4 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java
@@ -98,33 +98,36 @@
}
@Override
- public RewrittenPrototypeDescription lookupPrototypeChanges(DexMethod method) {
+ protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
+ RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
if (isConstructorBridge(method)) {
// TODO (b/132767654): Try to write a test which breaks that assertion.
- assert previousLens.lookupPrototypeChanges(method).isEmpty();
+ assert prototypeChanges.isEmpty();
// TODO(b/164901008): Fix when the number of arguments overflows.
return RewrittenPrototypeDescription.none().withExtraUnusedNullParameter();
- } else {
- return previousLens.lookupPrototypeChanges(method);
}
+ return prototypeChanges;
}
@Override
public GraphLensLookupResult lookupMethod(DexMethod method, DexMethod context, Invoke.Type type) {
assert originalMethodSignatures == null;
- GraphLensLookupResult previous = previousLens.lookupMethod(method, context, type);
- DexMethod bridge = methodMap.get(previous.getMethod());
+ GraphLensLookupResult lookup = previousLens.lookupMethod(method, context, type);
+ DexMethod bridge = methodMap.get(lookup.getMethod());
if (bridge == null) {
- return previous;
+ return lookup;
}
assert context != null : "Guaranteed by isContextFreeForMethod";
if (bridge.holder == context.holder) {
- return previous;
+ return lookup;
}
if (isConstructorBridge(bridge)) {
- return new GraphLensLookupResult(bridge, Invoke.Type.DIRECT);
+ return new GraphLensLookupResult(
+ bridge,
+ Invoke.Type.DIRECT,
+ internalDescribePrototypeChanges(lookup.getPrototypeChanges(), bridge));
}
- return new GraphLensLookupResult(bridge, Invoke.Type.STATIC);
+ return new GraphLensLookupResult(bridge, Invoke.Type.STATIC, lookup.getPrototypeChanges());
}
public static Builder builder() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index 885a1d0..96e5e9c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -70,21 +70,18 @@
}
@Override
- public RewrittenPrototypeDescription lookupPrototypeChanges(DexMethod method) {
- DexMethod originalMethod = originalMethodSignatures.getOrDefault(method, method);
- RewrittenPrototypeDescription result = previousLens.lookupPrototypeChanges(originalMethod);
- if (originalMethod != method) {
- if (method.proto.returnType.isVoidType() && !originalMethod.proto.returnType.isVoidType()) {
- result = result.withConstantReturn(originalMethod.proto.returnType, appView);
- }
- ArgumentInfoCollection removedArgumentsInfo = removedArgumentsInfoPerMethod.get(method);
- if (removedArgumentsInfo != null) {
- result = result.withRemovedArguments(removedArgumentsInfo);
- }
- } else {
+ protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
+ RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
+ DexMethod previous = internalGetPreviousMethodSignature(method);
+ if (previous == method) {
assert !removedArgumentsInfoPerMethod.containsKey(method);
+ return prototypeChanges;
}
- return result;
+ if (method.getReturnType().isVoidType() && !previous.getReturnType().isVoidType()) {
+ prototypeChanges = prototypeChanges.withConstantReturn(previous.getReturnType(), appView);
+ }
+ return prototypeChanges.withRemovedArguments(
+ removedArgumentsInfoPerMethod.getOrDefault(method, ArgumentInfoCollection.empty()));
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index e6bd21e..0dd84e7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -76,14 +76,10 @@
}
@Override
- public RewrittenPrototypeDescription lookupPrototypeChanges(DexMethod method) {
- DexMethod originalMethod =
- originalMethodSignatures != null
- ? originalMethodSignatures.getOrDefault(method, method)
- : method;
- RewrittenPrototypeDescription result = previousLens.lookupPrototypeChanges(originalMethod);
- ArgumentInfoCollection removedArguments = this.removedArguments.get(method);
- return removedArguments != null ? result.withRemovedArguments(removedArguments) : result;
+ protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
+ RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
+ return prototypeChanges.withRemovedArguments(
+ removedArguments.getOrDefault(method, ArgumentInfoCollection.empty()));
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInstanceFieldData.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInstanceFieldData.java
new file mode 100644
index 0000000..4f4dea9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInstanceFieldData.java
@@ -0,0 +1,111 @@
+// 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.optimize.enums;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import java.util.Map;
+
+/*
+ * My instances represent the values of an enum field for each of the enum instance.
+ * For example:
+ * <code> enum E {
+ * A(10),B(20);
+ * int f;
+ * public E(int f) {
+ * this.f = f;
+ * }
+ * }</code>
+ * <p> The EnumFieldData for the field E#f is A -> 10, B -> 20.
+ * The EnumFieldData may be unknown, for example, if the enum fields are not set with constants.
+ */
+public abstract class EnumInstanceFieldData {
+
+ public abstract boolean isUnknown();
+
+ public boolean isKnown() {
+ return !isUnknown();
+ }
+
+ public EnumInstanceFieldKnownData asEnumFieldKnownData() {
+ return null;
+ }
+
+ public static class EnumInstanceFieldUnknownData extends EnumInstanceFieldData {
+
+ private static final EnumInstanceFieldUnknownData INSTANCE = new EnumInstanceFieldUnknownData();
+
+ public static EnumInstanceFieldUnknownData getInstance() {
+ return INSTANCE;
+ }
+
+ private EnumInstanceFieldUnknownData() {}
+
+ @Override
+ public boolean isUnknown() {
+ return true;
+ }
+ }
+
+ public abstract static class EnumInstanceFieldKnownData extends EnumInstanceFieldData {
+
+ @Override
+ public boolean isUnknown() {
+ return false;
+ }
+
+ public abstract boolean isOrdinal();
+
+ public abstract boolean isMapping();
+
+ @Override
+ public EnumInstanceFieldKnownData asEnumFieldKnownData() {
+ return this;
+ }
+
+ public EnumInstanceFieldMappingData asEnumFieldMappingData() {
+ return null;
+ }
+ }
+
+ public static class EnumInstanceFieldOrdinalData extends EnumInstanceFieldKnownData {
+ @Override
+ public boolean isOrdinal() {
+ return true;
+ }
+
+ @Override
+ public boolean isMapping() {
+ return false;
+ }
+ }
+
+ public static class EnumInstanceFieldMappingData extends EnumInstanceFieldKnownData {
+ private final Map<DexField, AbstractValue> mapping;
+
+ public EnumInstanceFieldMappingData(Map<DexField, AbstractValue> mapping) {
+ this.mapping = mapping;
+ }
+
+ @Override
+ public boolean isOrdinal() {
+ return false;
+ }
+
+ @Override
+ public boolean isMapping() {
+ return true;
+ }
+
+ @Override
+ public EnumInstanceFieldMappingData asEnumFieldMappingData() {
+ return this;
+ }
+
+ public AbstractValue getData(DexField field) {
+ return mapping.get(field);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInstanceFieldDataMap.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInstanceFieldDataMap.java
new file mode 100644
index 0000000..f02e488
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInstanceFieldDataMap.java
@@ -0,0 +1,27 @@
+// 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.optimize.enums;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData;
+import com.google.common.collect.ImmutableMap;
+
+public class EnumInstanceFieldDataMap {
+ private final ImmutableMap<DexType, ImmutableMap<DexField, EnumInstanceFieldKnownData>>
+ instanceFieldMap;
+
+ public EnumInstanceFieldDataMap(
+ ImmutableMap<DexType, ImmutableMap<DexField, EnumInstanceFieldKnownData>> instanceFieldMap) {
+ this.instanceFieldMap = instanceFieldMap;
+ }
+
+ public EnumInstanceFieldKnownData getInstanceFieldData(
+ DexType enumType, DexField enumInstanceField) {
+ assert instanceFieldMap.containsKey(enumType);
+ assert instanceFieldMap.get(enumType).containsKey(enumInstanceField);
+ return instanceFieldMap.get(enumType).get(enumInstanceField);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index 3f910f5..12b42aa 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -30,6 +30,7 @@
import com.android.tools.r8.graph.DexValue.DexValueInt;
import com.android.tools.r8.graph.DexValue.DexValueNull;
import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.EnumValueInfoMapCollection;
import com.android.tools.r8.graph.FieldResolutionResult;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
@@ -42,6 +43,8 @@
import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
import com.android.tools.r8.ir.code.ArrayPut;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.CheckCast;
@@ -49,6 +52,7 @@
import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.InstanceGet;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.InvokeMethod;
@@ -62,6 +66,10 @@
import com.android.tools.r8.ir.conversion.PostMethodProcessor;
import com.android.tools.r8.ir.conversion.PostOptimization;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData;
+import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldMappingData;
+import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldOrdinalData;
+import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldUnknownData;
import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
@@ -69,6 +77,7 @@
import com.android.tools.r8.origin.SynthesizedOrigin;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
@@ -97,6 +106,8 @@
// Map the enum candidates with their dependencies, i.e., the methods to reprocess for the given
// enum if the optimization eventually decides to unbox it.
private final Map<DexType, ProgramMethodSet> enumsUnboxingCandidates;
+ private final Map<DexType, Set<DexField>> requiredEnumInstanceFieldData =
+ new ConcurrentHashMap<>();
private final Set<DexType> enumsToUnboxWithPackageRequirement = Sets.newIdentityHashSet();
private EnumUnboxingRewriter enumUnboxerRewriter;
@@ -118,6 +129,12 @@
enumsUnboxingCandidates = new EnumUnboxingCandidateAnalysis(appView, this).findCandidates();
}
+ private void requiredEnumInstanceFieldData(DexType enumType, DexField field) {
+ requiredEnumInstanceFieldData
+ .computeIfAbsent(enumType, ignored -> Sets.newConcurrentHashSet())
+ .add(field);
+ }
+
private void markEnumAsUnboxable(Reason reason, DexProgramClass enumClass) {
assert enumClass.isEnum();
reportFailure(enumClass.type, reason);
@@ -268,27 +285,30 @@
// We are using the ConstClass of an enum, which typically means the enum cannot be unboxed.
// We however allow unboxing if the ConstClass is only used as an argument to Enum#valueOf, to
// allow unboxing of: MyEnum a = Enum.valueOf(MyEnum.class, "A");.
- if (!enumsUnboxingCandidates.containsKey(constClass.getValue())) {
+ DexType enumType = constClass.getValue();
+ if (!enumsUnboxingCandidates.containsKey(enumType)) {
return;
}
if (constClass.outValue() == null) {
- eligibleEnums.add(constClass.getValue());
+ eligibleEnums.add(enumType);
return;
}
+ DexProgramClass enumClass = appView.definitionFor(enumType).asProgramClass();
if (constClass.outValue().hasPhiUsers()) {
- markEnumAsUnboxable(
- Reason.CONST_CLASS, appView.definitionForProgramType(constClass.getValue()));
+ markEnumAsUnboxable(Reason.CONST_CLASS, enumClass);
return;
}
for (Instruction user : constClass.outValue().uniqueUsers()) {
if (!(user.isInvokeStatic()
&& user.asInvokeStatic().getInvokedMethod() == factory.enumMembers.valueOf)) {
- markEnumAsUnboxable(
- Reason.CONST_CLASS, appView.definitionForProgramType(constClass.getValue()));
+ markEnumAsUnboxable(Reason.CONST_CLASS, enumClass);
return;
}
}
- eligibleEnums.add(constClass.getValue());
+ // The name data is required for the correct mapping from the enum name to the ordinal in the
+ // valueOf utility method.
+ requiredEnumInstanceFieldData(enumType, factory.enumMembers.nameField);
+ eligibleEnums.add(enumType);
}
private void addNullDependencies(IRCode code, Set<Instruction> uses, Set<DexType> eligibleEnums) {
@@ -345,6 +365,7 @@
ExecutorService executorService,
OptimizationFeedbackDelayed feedback)
throws ExecutionException {
+ EnumInstanceFieldDataMap enumInstanceFieldDataMap = finishAnalysis();
// At this point the enumsToUnbox are no longer candidates, they will all be unboxed.
if (enumsUnboxingCandidates.isEmpty()) {
return;
@@ -352,7 +373,7 @@
ImmutableSet<DexType> enumsToUnbox = ImmutableSet.copyOf(this.enumsUnboxingCandidates.keySet());
// Update keep info on any of the enum methods of the removed classes.
updatePinnedItems(enumsToUnbox);
- enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumsToUnbox);
+ enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumsToUnbox, enumInstanceFieldDataMap);
DirectMappedDexApplication.Builder appBuilder = appView.appInfo().app().asDirect().builder();
Map<DexType, DexType> newMethodLocation = synthesizeUnboxedEnumsMethodsLocations(appBuilder);
NestedGraphLens enumUnboxingLens =
@@ -475,12 +496,38 @@
});
}
- public void finishAnalysis() {
+ public EnumInstanceFieldDataMap finishAnalysis() {
analyzeInitializers();
analyzeAccessibility();
+ EnumInstanceFieldDataMap enumInstanceFieldDataMap = analyzeFields();
if (debugLogEnabled) {
reportEnumsAnalysis();
}
+ return enumInstanceFieldDataMap;
+ }
+
+ private EnumInstanceFieldDataMap analyzeFields() {
+ ImmutableMap.Builder<DexType, ImmutableMap<DexField, EnumInstanceFieldKnownData>> builder =
+ ImmutableMap.builder();
+ requiredEnumInstanceFieldData.forEach(
+ (enumType, fields) -> {
+ ImmutableMap.Builder<DexField, EnumInstanceFieldKnownData> typeBuilder =
+ ImmutableMap.builder();
+ if (enumsUnboxingCandidates.containsKey(enumType)) {
+ DexProgramClass enumClass = appView.definitionFor(enumType).asProgramClass();
+ assert enumClass != null;
+ for (DexField field : fields) {
+ EnumInstanceFieldData enumInstanceFieldData = computeEnumFieldData(field, enumClass);
+ if (enumInstanceFieldData.isUnknown()) {
+ markEnumAsUnboxable(Reason.MISSING_INSTANCE_FIELD_DATA, enumClass);
+ return;
+ }
+ typeBuilder.put(field, enumInstanceFieldData.asEnumFieldKnownData());
+ }
+ builder.put(enumType, typeBuilder.build());
+ }
+ });
+ return new EnumInstanceFieldDataMap(builder.build());
}
private void analyzeAccessibility() {
@@ -721,20 +768,26 @@
DexProgramClass enumClass = appView.definitionForProgramType(toUnbox);
assert enumClass != null;
- // Enum candidates have necessarily only one constructor matching enumMethods.constructor
- // signature.
- DexEncodedMethod initializer = enumClass.lookupDirectMethod(factory.enumMembers.constructor);
- if (initializer == null) {
+ boolean hasInstanceInitializer = false;
+ for (DexEncodedMethod directMethod : enumClass.directMethods()) {
+ if (directMethod.isInstanceInitializer()) {
+ hasInstanceInitializer = true;
+ if (directMethod
+ .getOptimizationInfo()
+ .getInstanceInitializerInfo()
+ .mayHaveOtherSideEffectsThanInstanceFieldAssignments()) {
+ markEnumAsUnboxable(Reason.INVALID_INIT, enumClass);
+ break;
+ }
+ }
+ }
+ if (!hasInstanceInitializer) {
// This case typically happens when a programmer uses EnumSet/EnumMap without using the
// enum keep rules. The code is incorrect in this case (EnumSet/EnumMap won't work).
// We bail out.
markEnumAsUnboxable(Reason.NO_INIT, enumClass);
continue;
}
- if (initializer.getOptimizationInfo().mayHaveSideEffects()) {
- markEnumAsUnboxable(Reason.INVALID_INIT, enumClass);
- continue;
- }
if (enumClass.classInitializationMayHaveSideEffects(appView)) {
markEnumAsUnboxable(Reason.INVALID_CLINIT, enumClass);
@@ -798,6 +851,10 @@
}
assert dexClass.isLibraryClass();
if (dexClass.type != factory.enumType) {
+ // System.identityHashCode(Object) is supported for proto enums.
+ if (singleTarget == factory.javaLangSystemMethods.identityHashCode) {
+ return Reason.ELIGIBLE;
+ }
return Reason.UNSUPPORTED_LIBRARY_CALL;
}
// TODO(b/147860220): EnumSet and EnumMap may be interesting to model.
@@ -805,9 +862,10 @@
return Reason.ELIGIBLE;
} else if (singleTarget == factory.enumMembers.equals) {
return Reason.ELIGIBLE;
- } else if (singleTarget == factory.enumMembers.nameMethod) {
- return Reason.ELIGIBLE;
- } else if (singleTarget == factory.enumMembers.toString) {
+ } else if (singleTarget == factory.enumMembers.nameMethod
+ || singleTarget == factory.enumMembers.toString) {
+ assert invokeMethod.asInvokeMethodWithReceiver().getReceiver() == enumValue;
+ requiredEnumInstanceFieldData(enumClass.type, factory.enumMembers.nameField);
return Reason.ELIGIBLE;
} else if (singleTarget == factory.enumMembers.ordinalMethod) {
return Reason.ELIGIBLE;
@@ -837,6 +895,10 @@
if (dexClass == null) {
return Reason.INVALID_FIELD_PUT;
}
+ if (fieldInstruction.isInstancePut()
+ && fieldInstruction.asInstancePut().object() == enumValue) {
+ return Reason.ELIGIBLE;
+ }
// The put value has to be of the field type.
if (field.field.type.toBaseType(factory) != enumClass.type) {
return Reason.TYPE_MISMATCH_FIELD_PUT;
@@ -844,6 +906,14 @@
return Reason.ELIGIBLE;
}
+ if (instruction.isInstanceGet()) {
+ InstanceGet instanceGet = instruction.asInstanceGet();
+ assert instanceGet.getField().holder == enumClass.type;
+ DexField field = instanceGet.getField();
+ requiredEnumInstanceFieldData(enumClass.type, field);
+ return Reason.ELIGIBLE;
+ }
+
// An If using enum as inValue is valid if it matches e == null
// or e == X with X of same enum type as e. Ex: if (e == MyEnum.A).
if (instruction.isIf()) {
@@ -923,6 +993,89 @@
return Reason.OTHER_UNSUPPORTED_INSTRUCTION;
}
+ private EnumInstanceFieldData computeEnumFieldData(
+ DexField instanceField, DexProgramClass enumClass) {
+ DexEncodedField encodedInstanceField =
+ appView.appInfo().resolveFieldOn(enumClass, instanceField).getResolvedField();
+ assert encodedInstanceField != null;
+ boolean canBeOrdinal = instanceField.type.isIntType();
+ Map<DexField, AbstractValue> data = new IdentityHashMap<>();
+ EnumValueInfoMapCollection.EnumValueInfoMap enumValueInfoMap =
+ appView.appInfo().getEnumValueInfoMap(enumClass.type);
+ for (DexField staticField : enumValueInfoMap.enumValues()) {
+ ObjectState enumInstanceState =
+ computeEnumInstanceObjectState(enumClass, staticField, enumValueInfoMap);
+ if (enumInstanceState == null) {
+ // The enum instance is effectively unused. No need to generate anything for it, the path
+ // will never be taken.
+ } else {
+ AbstractValue fieldValue = enumInstanceState.getAbstractFieldValue(encodedInstanceField);
+ if (!(fieldValue.isSingleNumberValue() || fieldValue.isSingleStringValue())) {
+ return EnumInstanceFieldUnknownData.getInstance();
+ }
+ data.put(staticField, fieldValue);
+ if (canBeOrdinal) {
+ int ordinalValue = enumValueInfoMap.getEnumValueInfo(staticField).ordinal;
+ assert fieldValue.isSingleNumberValue();
+ int computedValue = fieldValue.asSingleNumberValue().getIntValue();
+ if (computedValue != ordinalValue) {
+ canBeOrdinal = false;
+ }
+ }
+ }
+ }
+ if (canBeOrdinal) {
+ return new EnumInstanceFieldOrdinalData();
+ }
+ return new EnumInstanceFieldMappingData(data);
+ }
+
+ // We need to access the enum instance object state to figure out if it contains known constant
+ // field values. The enum instance may be accessed in two ways, directly through the enum
+ // static field, or through the enum $VALUES field. If none of them are kept, the instance is
+ // effectively unused. The object state may be stored in the enum static field optimization
+ // info, if kept, or in the $VALUES optimization info, if kept.
+ // If the enum instance is unused, this method answers null.
+ private ObjectState computeEnumInstanceObjectState(
+ DexProgramClass enumClass,
+ DexField staticField,
+ EnumValueInfoMapCollection.EnumValueInfoMap enumValueInfoMap) {
+ // Attempt 1: Get object state from the instance field's optimization info.
+ DexEncodedField encodedStaticField = enumClass.lookupStaticField(staticField);
+ AbstractValue enumInstanceValue = encodedStaticField.getOptimizationInfo().getAbstractValue();
+ if (enumInstanceValue.isSingleFieldValue()) {
+ return enumInstanceValue.asSingleFieldValue().getState();
+ }
+ if (enumInstanceValue.isUnknown()) {
+ return ObjectState.empty();
+ }
+ assert enumInstanceValue.isZero();
+
+ // Attempt 2: Get object state from the values field's optimization info.
+ DexEncodedField valuesField =
+ enumClass.lookupStaticField(
+ factory.createField(
+ enumClass.type,
+ factory.createArrayType(1, enumClass.type),
+ factory.enumValuesFieldName));
+ AbstractValue valuesValue = valuesField.getOptimizationInfo().getAbstractValue();
+ if (valuesValue.isZero()) {
+ // Unused enum instance.
+ return null;
+ }
+ if (valuesValue.isUnknown()) {
+ return ObjectState.empty();
+ }
+ assert valuesValue.isSingleFieldValue();
+ ObjectState valuesState = valuesValue.asSingleFieldValue().getState();
+ if (valuesState.isEnumValuesObjectState()) {
+ return valuesState
+ .asEnumValuesObjectState()
+ .getObjectStateForOrdinal(enumValueInfoMap.getEnumValueInfo(staticField).ordinal);
+ }
+ return ObjectState.empty();
+ }
+
private boolean isFirstInstructionAfterArguments(InvokeMethod invokeMethod, IRCode code) {
BasicBlock basicBlock = code.entryBlock();
for (Instruction instruction : basicBlock.getInstructions()) {
@@ -1000,7 +1153,7 @@
DOWN_CAST,
SUBTYPES,
INTERFACE,
- INSTANCE_FIELD,
+ MANY_INSTANCE_FIELDS,
GENERIC_INVOKE,
DEFAULT_METHOD_INVOKE,
UNEXPECTED_STATIC_FIELD,
@@ -1018,6 +1171,8 @@
COMPARE_TO_INVOKE,
UNSUPPORTED_LIBRARY_CALL,
MISSING_INFO_MAP,
+ MISSING_INSTANCE_FIELD_DATA,
+ INVALID_FIELD_READ,
INVALID_FIELD_PUT,
INVALID_ARRAY_PUT,
FIELD_PUT_ON_ENUM,
@@ -1044,7 +1199,6 @@
// Fix all methods and fields using enums to unbox.
for (DexProgramClass clazz : appView.appInfo().classes()) {
if (enumsToUnbox.contains(clazz.type)) {
- assert clazz.instanceFields().size() == 0;
// Clear the initializers and move the static methods to the new location.
Set<DexEncodedMethod> methodsToRemove = Sets.newIdentityHashSet();
clazz
@@ -1123,14 +1277,23 @@
return encodedMethod;
}
assert !encodedMethod.isClassInitializer();
- DexMethod newMethod =
- factory.createMethod(encodedMethod.holder(), newProto, encodedMethod.getName());
+ // We add the $enumunboxing$ suffix to make sure we do not create a library override.
+ String newMethodName =
+ encodedMethod.getName().toString()
+ + (encodedMethod.isNonPrivateVirtualMethod() ? "$enumunboxing$" : "");
+ DexMethod newMethod = factory.createMethod(encodedMethod.holder(), newProto, newMethodName);
newMethod = ensureUniqueMethod(encodedMethod, newMethod);
int numberOfExtraNullParameters = newMethod.getArity() - encodedMethod.method.getArity();
boolean isStatic = encodedMethod.isStatic();
lensBuilder.move(
encodedMethod.method, isStatic, newMethod, isStatic, numberOfExtraNullParameters);
- return encodedMethod.toTypeSubstitutedMethod(newMethod);
+ DexEncodedMethod newEncodedMethod = encodedMethod.toTypeSubstitutedMethod(newMethod);
+ assert !encodedMethod.isLibraryMethodOverride().isTrue()
+ : "Enum unboxing is changing the signature of a library override in a non unboxed class.";
+ if (newEncodedMethod.isNonPrivateVirtualMethod()) {
+ newEncodedMethod.setLibraryMethodOverride(OptionalBool.FALSE);
+ }
+ return newEncodedMethod;
}
private DexMethod ensureUniqueMethod(DexEncodedMethod encodedMethod, DexMethod newMethod) {
@@ -1213,7 +1376,7 @@
private static class EnumUnboxingLens extends NestedGraphLens {
- private final Map<DexMethod, RewrittenPrototypeDescription> prototypeChanges;
+ private final Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod;
private final Set<DexType> unboxedEnums;
EnumUnboxingLens(
@@ -1224,7 +1387,7 @@
BiMap<DexMethod, DexMethod> originalMethodSignatures,
GraphLens previousLens,
DexItemFactory dexItemFactory,
- Map<DexMethod, RewrittenPrototypeDescription> prototypeChanges,
+ Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod,
Set<DexType> unboxedEnums) {
super(
typeMap,
@@ -1234,17 +1397,18 @@
originalMethodSignatures,
previousLens,
dexItemFactory);
- this.prototypeChanges = prototypeChanges;
+ this.prototypeChangesPerMethod = prototypeChangesPerMethod;
this.unboxedEnums = unboxedEnums;
}
@Override
- public RewrittenPrototypeDescription lookupPrototypeChanges(DexMethod method) {
+ protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
+ RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
// During the second IR processing enum unboxing is the only optimization rewriting
// prototype description, if this does not hold, remove the assertion and merge
// the two prototype changes.
- assert previousLens.lookupPrototypeChanges(method).isEmpty();
- return prototypeChanges.getOrDefault(method, RewrittenPrototypeDescription.none());
+ assert prototypeChanges.isEmpty();
+ return prototypeChangesPerMethod.getOrDefault(method, RewrittenPrototypeDescription.none());
}
@Override
@@ -1264,7 +1428,7 @@
private static class Builder extends NestedGraphLens.Builder {
- private Map<DexMethod, RewrittenPrototypeDescription> prototypeChanges =
+ private Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod =
new IdentityHashMap<>();
public void move(DexMethod from, boolean fromStatic, DexMethod to, boolean toStatic) {
@@ -1299,7 +1463,7 @@
from.proto.returnType == to.proto.returnType
? null
: new RewrittenTypeInfo(from.proto.returnType, to.proto.returnType);
- prototypeChanges.put(
+ prototypeChangesPerMethod.put(
to,
RewrittenPrototypeDescription.createForRewrittenTypes(returnInfo, builder.build())
.withExtraUnusedNullParameters(numberOfExtraNullParameters));
@@ -1318,7 +1482,7 @@
originalMethodSignatures,
previousLens,
dexItemFactory,
- ImmutableMap.copyOf(prototypeChanges),
+ ImmutableMap.copyOf(prototypeChangesPerMethod),
ImmutableSet.copyOf(unboxedEnums));
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
index c4a6bb4..b5a5b62 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
@@ -22,6 +22,11 @@
class EnumUnboxingCandidateAnalysis {
+ // Each time we unbox an enum with instance fields, we need to generate a method with a
+ // switch case to dispatch from the enum to the instance field value. We introduce this heuristic
+ // to avoid unboxing enums with too many instance fields.
+ private static final int MAX_INSTANCE_FIELDS_FOR_UNBOXING = 7;
+
private final AppView<AppInfoWithLiveness> appView;
private final EnumUnboxer enumUnboxer;
private final DexItemFactory factory;
@@ -55,8 +60,8 @@
enumUnboxer.reportFailure(clazz.type, Reason.SUBTYPES);
return false;
}
- if (!clazz.instanceFields().isEmpty()) {
- enumUnboxer.reportFailure(clazz.type, Reason.INSTANCE_FIELD);
+ if (clazz.instanceFields().size() > MAX_INSTANCE_FIELDS_FOR_UNBOXING) {
+ enumUnboxer.reportFailure(clazz.type, Reason.MANY_INSTANCE_FIELDS);
return false;
}
if (!enumHasBasicStaticFields(clazz)) {
@@ -69,23 +74,9 @@
enumUnboxer.reportFailure(clazz.type, Reason.MISSING_INFO_MAP);
return false;
}
-
- // TODO(b/155036467): Fail lazily when an unsupported method is not only present but also used.
- // Only Enums with default initializers and static methods can be unboxed at the moment.
- for (DexEncodedMethod directMethod : clazz.directMethods()) {
- if (directMethod.isInstanceInitializer() && !isStandardEnumInitializer(directMethod)) {
- enumUnboxer.reportFailure(clazz.type, Reason.INVALID_INIT);
- return false;
- }
- }
return true;
}
- private boolean isStandardEnumInitializer(DexEncodedMethod method) {
- return method.isInstanceInitializer()
- && method.method.proto == factory.enumMembers.constructor.proto;
- }
-
// The enum should have the $VALUES static field and only fields directly referencing the enum
// instances.
private boolean enumHasBasicStaticFields(DexProgramClass clazz) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 087e808..54d9aaf 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -27,12 +27,15 @@
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
+import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.ArrayAccess;
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstanceGet;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.MemberType;
@@ -40,6 +43,7 @@
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData;
import com.android.tools.r8.ir.synthetic.EnumUnboxingCfCodeProvider;
import com.android.tools.r8.origin.SynthesizedOrigin;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -65,6 +69,7 @@
private final AppView<AppInfoWithLiveness> appView;
private final DexItemFactory factory;
private final EnumValueInfoMapCollection enumsToUnbox;
+ private final EnumInstanceFieldDataMap unboxedEnumsInstanceFieldData;
private final Map<DexMethod, DexEncodedMethod> utilityMethods = new ConcurrentHashMap<>();
private final Map<DexField, DexEncodedField> extraUtilityFields = new ConcurrentHashMap<>();
@@ -73,14 +78,19 @@
private final DexMethod compareToUtilityMethod;
private final DexMethod valuesUtilityMethod;
- EnumUnboxingRewriter(AppView<AppInfoWithLiveness> appView, Set<DexType> enumsToUnbox) {
+ EnumUnboxingRewriter(
+ AppView<AppInfoWithLiveness> appView,
+ Set<DexType> enumsToUnbox,
+ EnumInstanceFieldDataMap unboxedEnumsInstanceFieldData) {
this.appView = appView;
this.factory = appView.dexItemFactory();
EnumValueInfoMapCollection.Builder builder = EnumValueInfoMapCollection.builder();
for (DexType toUnbox : enumsToUnbox) {
+ assert appView.appInfo().withLiveness().getEnumValueInfoMap(toUnbox) != null;
builder.put(toUnbox, appView.appInfo().withLiveness().getEnumValueInfoMap(toUnbox));
}
this.enumsToUnbox = builder.build();
+ this.unboxedEnumsInstanceFieldData = unboxedEnumsInstanceFieldData;
// Custom methods for java.lang.Enum methods ordinal, equals and compareTo.
this.ordinalUtilityMethod =
@@ -144,7 +154,8 @@
continue;
} else if (invokedMethod == factory.enumMembers.nameMethod
|| invokedMethod == factory.enumMembers.toString) {
- DexMethod toStringMethod = computeDefaultToStringUtilityMethod(enumType);
+ DexMethod toStringMethod =
+ computeInstanceFieldUtilityMethod(enumType, factory.enumMembers.nameField);
iterator.replaceCurrentInstruction(
new InvokeStatic(
toStringMethod, invokeMethod.outValue(), invokeMethod.arguments()));
@@ -176,16 +187,22 @@
convertedEnums.put(invoke, enumType);
continue;
}
+ } else if (invokedMethod == factory.javaLangSystemMethods.identityHashCode) {
+ assert invokeStatic.inValues().size() == 1;
+ Value argument = invokeStatic.getArgument(0);
+ DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
+ if (enumType != null) {
+ invokeStatic.outValue().replaceUsers(argument);
+ iterator.removeOrReplaceByDebugLocalRead();
+ }
}
}
- // Rewrites direct access to enum values into the corresponding int, $VALUES is not
- // supported.
if (instruction.isStaticGet()) {
StaticGet staticGet = instruction.asStaticGet();
DexType holder = staticGet.getField().holder;
if (enumsToUnbox.containsEnum(holder)) {
if (staticGet.outValue() == null) {
- iterator.removeInstructionIgnoreOutValue();
+ iterator.removeOrReplaceByDebugLocalRead();
continue;
}
EnumValueInfoMap enumValueInfoMap = enumsToUnbox.getEnumValueInfoMap(holder);
@@ -218,6 +235,26 @@
}
}
}
+
+ if (instruction.isInstanceGet()) {
+ InstanceGet instanceGet = instruction.asInstanceGet();
+ DexType holder = instanceGet.getField().holder;
+ if (enumsToUnbox.containsEnum(holder)) {
+ DexMethod fieldMethod = computeInstanceFieldMethod(instanceGet.getField());
+ Value rewrittenOutValue =
+ code.createValue(
+ TypeElement.fromDexType(
+ fieldMethod.proto.returnType, Nullability.maybeNull(), appView));
+ InvokeStatic invoke =
+ new InvokeStatic(
+ fieldMethod, rewrittenOutValue, ImmutableList.of(instanceGet.object()));
+ iterator.replaceCurrentInstruction(invoke);
+ if (enumsToUnbox.containsEnum(instanceGet.getField().type)) {
+ convertedEnums.put(invoke, instanceGet.getField().type);
+ }
+ }
+ }
+
// Rewrite array accesses from MyEnum[] (OBJECT) to int[] (INT).
if (instruction.isArrayAccess()) {
ArrayAccess arrayAccess = instruction.asArrayAccess();
@@ -234,9 +271,19 @@
return affectedPhis;
}
+ private DexMethod computeInstanceFieldMethod(DexField field) {
+ EnumInstanceFieldKnownData enumFieldKnownData =
+ unboxedEnumsInstanceFieldData.getInstanceFieldData(field.holder, field);
+ if (enumFieldKnownData.isOrdinal()) {
+ utilityMethods.computeIfAbsent(ordinalUtilityMethod, m -> synthesizeOrdinalMethod());
+ return ordinalUtilityMethod;
+ }
+ return computeInstanceFieldUtilityMethod(field.holder, field);
+ }
+
private void replaceEnumInvoke(
InstructionListIterator iterator,
- InvokeMethodWithReceiver invokeMethod,
+ InvokeMethod invokeMethod,
DexMethod method,
Function<DexMethod, DexEncodedMethod> synthesizor) {
utilityMethods.computeIfAbsent(method, synthesizor);
@@ -309,6 +356,25 @@
return synthesizeUtilityMethod(cfCode, method, true);
}
+ private DexMethod computeInstanceFieldUtilityMethod(DexType enumType, DexField field) {
+ assert enumsToUnbox.containsEnum(enumType);
+ assert field.holder == enumType || field.holder == factory.enumType;
+ String methodName =
+ "get"
+ + (enumType == field.holder ? "" : "Enum$")
+ + field.name
+ + "$$"
+ + compatibleName(enumType);
+ DexMethod fieldMethod =
+ factory.createMethod(
+ factory.enumUnboxingUtilityType,
+ factory.createProto(field.type, factory.intType),
+ methodName);
+ utilityMethods.computeIfAbsent(
+ fieldMethod, m -> synthesizeInstanceFieldMethod(m, enumType, field));
+ return fieldMethod;
+ }
+
private DexMethod computeValueOfUtilityMethod(DexType type) {
assert enumsToUnbox.containsEnum(type);
DexMethod valueOf =
@@ -320,17 +386,6 @@
return valueOf;
}
- private DexMethod computeDefaultToStringUtilityMethod(DexType type) {
- assert enumsToUnbox.containsEnum(type);
- DexMethod toString =
- factory.createMethod(
- factory.enumUnboxingUtilityType,
- factory.createProto(factory.stringType, factory.intType),
- "toString" + compatibleName(type));
- utilityMethods.computeIfAbsent(toString, m -> synthesizeToStringUtilityMethod(m, type));
- return toString;
- }
-
private DexType getEnumTypeOrNull(ArrayAccess arrayAccess) {
ArrayTypeElement arrayType = arrayAccess.array().getType().asArrayType();
if (arrayType == null) {
@@ -397,24 +452,37 @@
DexProgramClass::checksumFromType);
}
- private DexEncodedMethod synthesizeToStringUtilityMethod(DexMethod method, DexType enumType) {
+ private DexEncodedMethod synthesizeInstanceFieldMethod(
+ DexMethod method, DexType enumType, DexField field) {
+ assert method.proto.returnType == field.type;
+ assert unboxedEnumsInstanceFieldData.getInstanceFieldData(enumType, field).isMapping();
CfCode cfCode =
- new EnumUnboxingCfCodeProvider.EnumUnboxingDefaultToStringCfCodeProvider(
+ new EnumUnboxingCfCodeProvider.EnumUnboxingInstanceFieldCfCodeProvider(
appView,
factory.enumUnboxingUtilityType,
- enumType,
- enumsToUnbox.getEnumValueInfoMap(enumType))
+ field.type,
+ enumsToUnbox.getEnumValueInfoMap(enumType),
+ unboxedEnumsInstanceFieldData
+ .getInstanceFieldData(enumType, field)
+ .asEnumFieldMappingData())
.generateCfCode();
return synthesizeUtilityMethod(cfCode, method, false);
}
private DexEncodedMethod synthesizeValueOfUtilityMethod(DexMethod method, DexType enumType) {
+ assert method.proto.returnType == factory.intType;
+ assert unboxedEnumsInstanceFieldData
+ .getInstanceFieldData(enumType, factory.enumMembers.nameField)
+ .isMapping();
CfCode cfCode =
new EnumUnboxingCfCodeProvider.EnumUnboxingValueOfCfCodeProvider(
appView,
factory.enumUnboxingUtilityType,
enumType,
- enumsToUnbox.getEnumValueInfoMap(enumType))
+ enumsToUnbox.getEnumValueInfoMap(enumType),
+ unboxedEnumsInstanceFieldData
+ .getInstanceFieldData(enumType, factory.enumMembers.nameField)
+ .asEnumFieldMappingData())
.generateCfCode();
return synthesizeUtilityMethod(cfCode, method, false);
}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
index 5a50ca4..8b35793 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.cf.code.CfStackInstruction;
import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
import com.android.tools.r8.cf.code.CfThrow;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.DexField;
@@ -26,8 +27,10 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldMappingData;
import java.util.ArrayList;
import java.util.List;
import org.objectweb.asm.Opcodes;
@@ -38,41 +41,64 @@
super(appView, holder);
}
- public static class EnumUnboxingDefaultToStringCfCodeProvider extends EnumUnboxingCfCodeProvider {
+ void addCfInstructionsForAbstractValue(
+ List<CfInstruction> instructions, AbstractValue value, DexType returnType) {
+ // TODO(b/155368026): Support fields and const class fields.
+ // Move this to something similar than SingleValue#createMaterializingInstruction
+ if (value.isSingleStringValue()) {
+ assert returnType == appView.dexItemFactory().stringType;
+ instructions.add(new CfConstString(value.asSingleStringValue().getDexString()));
+ } else if (value.isSingleNumberValue()) {
+ instructions.add(
+ new CfConstNumber(
+ value.asSingleNumberValue().getValue(), ValueType.fromDexType(returnType)));
+ } else {
+ throw new Unreachable("Only Number and String fields in enums are supported.");
+ }
+ }
- private DexType enumType;
- private EnumValueInfoMap map;
+ public static class EnumUnboxingInstanceFieldCfCodeProvider extends EnumUnboxingCfCodeProvider {
- public EnumUnboxingDefaultToStringCfCodeProvider(
- AppView<?> appView, DexType holder, DexType enumType, EnumValueInfoMap map) {
+ private final DexType returnType;
+ private final EnumValueInfoMap enumValueInfoMap;
+ private final EnumInstanceFieldMappingData fieldDataMap;
+
+ public EnumUnboxingInstanceFieldCfCodeProvider(
+ AppView<?> appView,
+ DexType holder,
+ DexType returnType,
+ EnumValueInfoMap enumValueInfoMap,
+ EnumInstanceFieldMappingData fieldDataMap) {
super(appView, holder);
- this.enumType = enumType;
- this.map = map;
+ this.returnType = returnType;
+ this.enumValueInfoMap = enumValueInfoMap;
+ this.fieldDataMap = fieldDataMap;
}
@Override
public CfCode generateCfCode() {
- // Generated static method, for class com.x.MyEnum {A,B} would look like:
+ // Generated static method, for class com.x.MyEnum {A(10),B(20);} would look like:
// String UtilityClass#com.x.MyEnum_toString(int i) {
- // if (i == 1) { return "A";}
- // if (i == 2) { return "B";}
+ // if (i == 1) { return 10;}
+ // if (i == 2) { return 20;}
// throw null;
DexItemFactory factory = appView.dexItemFactory();
List<CfInstruction> instructions = new ArrayList<>();
- // if (i == 1) { return "A";}
- // if (i == 2) { return "B";}
- map.forEach(
+ // if (i == 1) { return 10;}
+ // if (i == 2) { return 20;}
+ enumValueInfoMap.forEach(
(field, enumValueInfo) -> {
- CfLabel dest = new CfLabel();
- instructions.add(new CfLoad(ValueType.fromDexType(factory.intType), 0));
- instructions.add(new CfConstNumber(enumValueInfo.convertToInt(), ValueType.INT));
- instructions.add(new CfIfCmp(If.Type.NE, ValueType.INT, dest));
- // TODO(b/160939354): Should use the value passed to the enum constructor, since this
- // value may be different from the enum field name.
- instructions.add(new CfConstString(field.name));
- instructions.add(new CfReturn(ValueType.OBJECT));
- instructions.add(dest);
+ AbstractValue value = fieldDataMap.getData(field);
+ if (value != null) {
+ CfLabel dest = new CfLabel();
+ instructions.add(new CfLoad(ValueType.fromDexType(factory.intType), 0));
+ instructions.add(new CfConstNumber(enumValueInfo.convertToInt(), ValueType.INT));
+ instructions.add(new CfIfCmp(If.Type.NE, ValueType.INT, dest));
+ addCfInstructionsForAbstractValue(instructions, value, returnType);
+ instructions.add(new CfReturn(ValueType.fromDexType(returnType)));
+ instructions.add(dest);
+ }
});
// throw null;
@@ -87,12 +113,18 @@
private DexType enumType;
private EnumValueInfoMap map;
+ private final EnumInstanceFieldMappingData fieldDataMap;
public EnumUnboxingValueOfCfCodeProvider(
- AppView<?> appView, DexType holder, DexType enumType, EnumValueInfoMap map) {
+ AppView<?> appView,
+ DexType holder,
+ DexType enumType,
+ EnumValueInfoMap map,
+ EnumInstanceFieldMappingData fieldDataMap) {
super(appView, holder);
this.enumType = enumType;
this.map = map;
+ this.fieldDataMap = fieldDataMap;
}
@Override
@@ -125,9 +157,8 @@
(field, enumValueInfo) -> {
CfLabel dest = new CfLabel();
instructions.add(new CfLoad(ValueType.fromDexType(factory.stringType), 0));
- // TODO(b/160939354): Should use the value passed to the enum constructor, since this
- // value may be different from the enum field name.
- instructions.add(new CfConstString(field.name));
+ AbstractValue value = fieldDataMap.getData(field);
+ addCfInstructionsForAbstractValue(instructions, value, factory.stringType);
instructions.add(
new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.stringMembers.equals, false));
instructions.add(new CfIf(If.Type.EQ, ValueType.INT, dest));
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 7e98858..8c895f7 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -78,18 +78,16 @@
public final ProguardMapSupplier proguardMapSupplier;
public CfApplicationWriter(
- DexApplication application,
AppView<?> appView,
- InternalOptions options,
Marker marker,
GraphLens graphLens,
NamingLens namingLens,
ProguardMapSupplier proguardMapSupplier) {
- this.application = application;
+ this.application = appView.appInfo().app();
this.appView = appView;
this.graphLens = graphLens;
this.namingLens = namingLens;
- this.options = options;
+ this.options = appView.options();
assert marker != null;
this.marker = marker;
this.proguardMapSupplier = proguardMapSupplier;
diff --git a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
index e2bf357..b8ddc06 100644
--- a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
@@ -83,9 +83,17 @@
private DexString getSourceFileRenaming(ProguardConfiguration proguardConfiguration) {
// If we should not be keeping the source file, null it out.
- if (!appView.options().forceProguardCompatibility
- && !proguardConfiguration.getKeepAttributes().sourceFile) {
- return null;
+ if (!proguardConfiguration.getKeepAttributes().sourceFile) {
+ // For class files, we always remove the attribute
+ if (appView.options().isGeneratingClassFiles()) {
+ return null;
+ }
+ assert appView.options().isGeneratingDex();
+ // When generating DEX we only remove the attribute for full-mode to ensure that we get
+ // line-numbers printed in stack traces.
+ if (!appView.options().forceProguardCompatibility) {
+ return null;
+ }
}
String renamedSourceFileAttribute = proguardConfiguration.getRenameSourceFileAttribute();
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
index 8883f5b..2e55916 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
@@ -87,14 +87,16 @@
@Override
public GraphLensLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
- GraphLensLookupResult previous = previousLens.lookupMethod(method, context, type);
+ GraphLensLookupResult lookup = previousLens.lookupMethod(method, context, type);
Map<DexMethod, DexMethod> methodMap = methodMaps.getOrDefault(type, Collections.emptyMap());
- DexMethod newMethod = methodMap.get(previous.getMethod());
- if (newMethod != null) {
- return new GraphLensLookupResult(
- newMethod, mapInvocationType(newMethod, method, previous.getType()));
+ DexMethod newMethod = methodMap.get(lookup.getMethod());
+ if (newMethod == null) {
+ return lookup;
}
- return previous;
+ return new GraphLensLookupResult(
+ newMethod,
+ mapInvocationType(newMethod, method, lookup.getType()),
+ lookup.getPrototypeChanges());
}
@Override
diff --git a/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java b/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java
index a2c44ce..1e8eff2 100644
--- a/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java
@@ -41,14 +41,13 @@
@Override
public GraphLensLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
- GraphLensLookupResult previous = previousLens.lookupMethod(method, context, type);
- method = previous.getMethod();
- type = previous.getType();
- if (type == Type.DIRECT && publicizedMethods.contains(method)) {
- assert publicizedMethodIsPresentOnHolder(method, context);
- return new GraphLensLookupResult(method, Type.VIRTUAL);
+ GraphLensLookupResult lookup = previousLens.lookupMethod(method, context, type);
+ if (lookup.getType() == Type.DIRECT && publicizedMethods.contains(lookup.getMethod())) {
+ assert publicizedMethodIsPresentOnHolder(lookup.getMethod(), context);
+ return new GraphLensLookupResult(
+ lookup.getMethod(), Type.VIRTUAL, lookup.getPrototypeChanges());
}
- return super.lookupMethod(method, context, type);
+ return lookup;
}
private boolean publicizedMethodIsPresentOnHolder(DexMethod method, DexMethod context) {
diff --git a/src/main/java/com/android/tools/r8/relocator/Relocator.java b/src/main/java/com/android/tools/r8/relocator/Relocator.java
index e45afd6..bf2bfe8 100644
--- a/src/main/java/com/android/tools/r8/relocator/Relocator.java
+++ b/src/main/java/com/android/tools/r8/relocator/Relocator.java
@@ -89,9 +89,7 @@
new GenericSignatureRewriter(appView, namingLens).run(appInfo.classes(), executor);
new CfApplicationWriter(
- app,
appView,
- options,
new Marker(Tool.Relocator),
GraphLens.getIdentityLens(),
namingLens,
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 9e709fd..17cf952 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -1140,7 +1140,9 @@
// if I has a supertype J. This is due to the fact that invoke-super instructions that
// resolve to a method on an interface never hit an implementation below that interface.
deferredRenamings.mapVirtualMethodToDirectInType(
- oldTarget, new GraphLensLookupResult(newTarget, STATIC), target.type);
+ oldTarget,
+ prototypeChanges -> new GraphLensLookupResult(newTarget, STATIC, prototypeChanges),
+ target.type);
} else {
// If we merge class B into class C, and class C contains an invocation super.m(), then it
// is insufficient to rewrite "invoke-super B.m()" to "invoke-direct C.m$B()" (the method
@@ -1159,7 +1161,9 @@
|| appInfo.lookupSuperTarget(signatureInHolder, holder) != null;
if (resolutionSucceeds) {
deferredRenamings.mapVirtualMethodToDirectInType(
- signatureInHolder, new GraphLensLookupResult(newTarget, DIRECT), target.type);
+ signatureInHolder,
+ prototypeChanges -> new GraphLensLookupResult(newTarget, DIRECT, prototypeChanges),
+ target.type);
} else {
break;
}
@@ -1181,7 +1185,10 @@
|| appInfo.lookupSuperTarget(signatureInHolder, holder) != null;
if (resolutionSucceededBeforeMerge) {
deferredRenamings.mapVirtualMethodToDirectInType(
- signatureInType, new GraphLensLookupResult(newTarget, DIRECT), target.type);
+ signatureInType,
+ prototypeChanges ->
+ new GraphLensLookupResult(newTarget, DIRECT, prototypeChanges),
+ target.type);
}
}
}
@@ -1733,27 +1740,26 @@
// First look up the method using the existing graph lens (for example, the type will have
// changed if the method was publicized by ClassAndMemberPublicizer).
GraphLensLookupResult lookup = appView.graphLens().lookupMethod(method, context, type);
- DexMethod previousMethod = lookup.getMethod();
- Type previousType = lookup.getType();
// Then check if there is a renaming due to the vertical class merger.
- DexMethod newMethod = renamedMembersLens.methodMap.get(previousMethod);
- if (newMethod != null) {
- if (previousType == Type.INTERFACE) {
- // If an interface has been merged into a class, invoke-interface needs to be translated
- // to invoke-virtual.
- DexClass clazz = appInfo.definitionFor(newMethod.holder);
- if (clazz != null && !clazz.accessFlags.isInterface()) {
- assert appInfo.definitionFor(method.holder).accessFlags.isInterface();
- return new GraphLensLookupResult(newMethod, Type.VIRTUAL);
- }
- }
- return new GraphLensLookupResult(newMethod, previousType);
+ DexMethod newMethod = renamedMembersLens.methodMap.get(lookup.getMethod());
+ if (newMethod == null) {
+ return lookup;
}
- return new GraphLensLookupResult(previousMethod, previousType);
+ if (lookup.getType() == Type.INTERFACE) {
+ // If an interface has been merged into a class, invoke-interface needs to be translated
+ // to invoke-virtual.
+ DexClass clazz = appInfo.definitionFor(newMethod.holder);
+ if (clazz != null && !clazz.accessFlags.isInterface()) {
+ assert appInfo.definitionFor(method.holder).accessFlags.isInterface();
+ return new GraphLensLookupResult(newMethod, Type.VIRTUAL, lookup.getPrototypeChanges());
+ }
+ }
+ return new GraphLensLookupResult(newMethod, lookup.getType(), lookup.getPrototypeChanges());
}
@Override
- public RewrittenPrototypeDescription lookupPrototypeChanges(DexMethod method) {
+ public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(
+ DexMethod method) {
throw new Unreachable();
}
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
index db59152..683f1c9 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription;
import com.android.tools.r8.ir.code.Invoke.Type;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
@@ -47,9 +48,14 @@
// For the invocation "invoke-virtual A.m()" in B.m2, this graph lens will return the method B.m.
public class VerticalClassMergerGraphLens extends NestedGraphLens {
+ interface GraphLensLookupResultProvider {
+
+ abstract GraphLensLookupResult get(RewrittenPrototypeDescription prototypeChanges);
+ }
+
private final AppView<?> appView;
- private final Map<DexType, Map<DexMethod, GraphLensLookupResult>>
+ private final Map<DexType, Map<DexMethod, GraphLensLookupResultProvider>>
contextualVirtualToDirectMethodMaps;
private Set<DexMethod> mergedMethods;
private final Map<DexMethod, DexMethod> originalMethodSignaturesForBridges;
@@ -60,7 +66,8 @@
Map<DexField, DexField> fieldMap,
Map<DexMethod, DexMethod> methodMap,
Set<DexMethod> mergedMethods,
- Map<DexType, Map<DexMethod, GraphLensLookupResult>> contextualVirtualToDirectMethodMaps,
+ Map<DexType, Map<DexMethod, GraphLensLookupResultProvider>>
+ contextualVirtualToDirectMethodMaps,
BiMap<DexField, DexField> originalFieldSignatures,
BiMap<DexMethod, DexMethod> originalMethodSignatures,
Map<DexMethod, DexMethod> originalMethodSignaturesForBridges,
@@ -98,24 +105,31 @@
originalMethodSignaturesForBridges.containsKey(context)
? originalMethodSignaturesForBridges.get(context)
: originalMethodSignatures.getOrDefault(context, context);
- GraphLensLookupResult previous = previousLens.lookupMethod(method, previousContext, type);
- if (previous.getType() == Type.SUPER && !mergedMethods.contains(context)) {
- Map<DexMethod, GraphLensLookupResult> virtualToDirectMethodMap =
+ GraphLensLookupResult lookup = previousLens.lookupMethod(method, previousContext, type);
+ if (lookup.getType() == Type.SUPER && !mergedMethods.contains(context)) {
+ Map<DexMethod, GraphLensLookupResultProvider> virtualToDirectMethodMap =
contextualVirtualToDirectMethodMaps.get(context.holder);
if (virtualToDirectMethodMap != null) {
- GraphLensLookupResult lookup = virtualToDirectMethodMap.get(previous.getMethod());
- if (lookup != null) {
+ GraphLensLookupResultProvider result = virtualToDirectMethodMap.get(lookup.getMethod());
+ if (result != null) {
// If the super class A of the enclosing class B (i.e., context.holder())
// has been merged into B during vertical class merging, and this invoke-super instruction
// was resolving to a method in A, then the target method has been changed to a direct
// method and moved into B, so that we need to use an invoke-direct instruction instead of
// invoke-super (or invoke-static, if the method was originally a default interface
// method).
- return lookup;
+ return result.get(lookup.getPrototypeChanges());
}
}
}
- return super.lookupMethod(previous.getMethod(), context, previous.getType());
+ DexMethod newMethod = methodMap.get(lookup.getMethod());
+ if (newMethod == null) {
+ return lookup;
+ }
+ return new GraphLensLookupResult(
+ newMethod,
+ mapInvocationType(newMethod, lookup.getMethod(), lookup.getType()),
+ internalDescribePrototypeChanges(lookup.getPrototypeChanges(), newMethod));
}
@Override
@@ -144,7 +158,7 @@
protected final BiMap<DexField, DexField> fieldMap = HashBiMap.create();
protected final Map<DexMethod, DexMethod> methodMap = new IdentityHashMap<>();
private final ImmutableSet.Builder<DexMethod> mergedMethodsBuilder = ImmutableSet.builder();
- private final Map<DexType, Map<DexMethod, GraphLensLookupResult>>
+ private final Map<DexType, Map<DexMethod, GraphLensLookupResultProvider>>
contextualVirtualToDirectMethodMaps = new IdentityHashMap<>();
private final BiMap<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
@@ -173,17 +187,22 @@
newBuilder.markMethodAsMerged(
builder.getMethodSignatureAfterClassMerging(method, mergedClasses));
}
- for (Map.Entry<DexType, Map<DexMethod, GraphLensLookupResult>> entry :
+ for (Map.Entry<DexType, Map<DexMethod, GraphLensLookupResultProvider>> entry :
builder.contextualVirtualToDirectMethodMaps.entrySet()) {
DexType context = entry.getKey();
assert context == builder.getTypeAfterClassMerging(context, mergedClasses);
- for (Map.Entry<DexMethod, GraphLensLookupResult> innerEntry : entry.getValue().entrySet()) {
+ for (Map.Entry<DexMethod, GraphLensLookupResultProvider> innerEntry :
+ entry.getValue().entrySet()) {
DexMethod from = innerEntry.getKey();
- GraphLensLookupResult rewriting = innerEntry.getValue();
+ GraphLensLookupResult rewriting =
+ innerEntry.getValue().get(RewrittenPrototypeDescription.none());
DexMethod to =
builder.getMethodSignatureAfterClassMerging(rewriting.getMethod(), mergedClasses);
newBuilder.mapVirtualMethodToDirectInType(
- from, new GraphLensLookupResult(to, rewriting.getType()), context);
+ from,
+ prototypeChanges ->
+ new GraphLensLookupResult(to, rewriting.getType(), prototypeChanges),
+ context);
}
}
for (Map.Entry<DexMethod, DexMethod> entry : builder.originalMethodSignatures.entrySet()) {
@@ -267,7 +286,7 @@
}
public boolean hasMappingForSignatureInContext(DexProgramClass context, DexMethod signature) {
- Map<DexMethod, GraphLensLookupResult> virtualToDirectMethodMap =
+ Map<DexMethod, GraphLensLookupResultProvider> virtualToDirectMethodMap =
contextualVirtualToDirectMethodMaps.get(context.type);
if (virtualToDirectMethodMap != null) {
return virtualToDirectMethodMap.containsKey(signature);
@@ -306,8 +325,8 @@
}
public void mapVirtualMethodToDirectInType(
- DexMethod from, GraphLensLookupResult to, DexType type) {
- Map<DexMethod, GraphLensLookupResult> virtualToDirectMethodMap =
+ DexMethod from, GraphLensLookupResultProvider to, DexType type) {
+ Map<DexMethod, GraphLensLookupResultProvider> virtualToDirectMethodMap =
contextualVirtualToDirectMethodMaps.computeIfAbsent(type, key -> new IdentityHashMap<>());
virtualToDirectMethodMap.put(from, to);
}
@@ -319,9 +338,9 @@
originalMethodSignatures.putAll(builder.originalMethodSignatures);
originalMethodSignaturesForBridges.putAll(builder.originalMethodSignaturesForBridges);
for (DexType context : builder.contextualVirtualToDirectMethodMaps.keySet()) {
- Map<DexMethod, GraphLensLookupResult> current =
+ Map<DexMethod, GraphLensLookupResultProvider> current =
contextualVirtualToDirectMethodMaps.get(context);
- Map<DexMethod, GraphLensLookupResult> other =
+ Map<DexMethod, GraphLensLookupResultProvider> other =
builder.contextualVirtualToDirectMethodMaps.get(context);
if (current != null) {
current.putAll(other);
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
index dfb625b..9a521fb 100644
--- a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
import com.google.common.io.ByteStreams;
+import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
@@ -80,8 +81,11 @@
if (stream != null) {
return stream;
}
- stream = new ZipOutputStream(Files.newOutputStream(
- archive, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING));
+ stream =
+ new ZipOutputStream(
+ new BufferedOutputStream(
+ Files.newOutputStream(
+ archive, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)));
return stream;
}
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 bb51cc0..7f54137 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1236,6 +1236,7 @@
public boolean forceLibBackportsInL8CfToCf = false;
public boolean enumUnboxingRewriteJavaCGeneratedMethod = false;
public boolean assertConsistentRenamingOfSignature = false;
+ public boolean allowStaticInterfaceMethodsForPreNApiLevel = false;
// Flag to allow processing of resources in D8. A data resource consumer still needs to be
// specified.
diff --git a/src/main/java/com/android/tools/r8/utils/Reporter.java b/src/main/java/com/android/tools/r8/utils/Reporter.java
index f251b0c..43f4f57 100644
--- a/src/main/java/com/android/tools/r8/utils/Reporter.java
+++ b/src/main/java/com/android/tools/r8/utils/Reporter.java
@@ -3,8 +3,14 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
+import static com.android.tools.r8.DiagnosticsLevel.ERROR;
+import static com.android.tools.r8.DiagnosticsLevel.INFO;
+import static com.android.tools.r8.DiagnosticsLevel.WARNING;
+
import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.DiagnosticsLevel;
+import com.android.tools.r8.errors.Unreachable;
public class Reporter implements DiagnosticsHandler {
@@ -19,14 +25,39 @@
this.clientHandler = clientHandler;
}
+ private void handleDiagnostic(DiagnosticsLevel level, Diagnostic diagnostic) {
+ // To avoid having an entry for fatal error in the public API enum use null to signal
+ // fatal error internally.
+ if (level != null) {
+ DiagnosticsLevel modifiedLevel = clientHandler.modifyDiagnosticsLevel(level, diagnostic);
+ level = modifiedLevel != null ? modifiedLevel : level;
+ } else {
+ level = ERROR;
+ }
+ switch (level) {
+ case INFO:
+ clientHandler.info(diagnostic);
+ break;
+ case WARNING:
+ clientHandler.warning(diagnostic);
+ break;
+ case ERROR:
+ abort = new AbortException(diagnostic);
+ clientHandler.error(diagnostic);
+ break;
+ default:
+ throw new Unreachable();
+ }
+ }
+
@Override
public synchronized void info(Diagnostic info) {
- clientHandler.info(info);
+ handleDiagnostic(INFO, info);
}
@Override
public synchronized void warning(Diagnostic warning) {
- clientHandler.warning(warning);
+ handleDiagnostic(WARNING, warning);
}
public void warning(String message) {
@@ -35,8 +66,7 @@
@Override
public synchronized void error(Diagnostic error) {
- abort = new AbortException(error);
- clientHandler.error(error);
+ handleDiagnostic(ERROR, error);
}
public void error(String message) {
@@ -54,7 +84,7 @@
* @throws AbortException always.
*/
public RuntimeException fatalError(Diagnostic error) {
- error(error);
+ handleDiagnostic(null, error);
throw abort;
}
diff --git a/src/main/java/com/android/tools/r8/utils/ZipUtils.java b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
index acaf11f..71400bd 100644
--- a/src/main/java/com/android/tools/r8/utils/ZipUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.errors.CompilationError;
import com.google.common.io.ByteStreams;
import com.google.common.io.Closer;
+import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -74,7 +75,8 @@
}
public static void zip(Path zipFile, Path inputDirectory) throws IOException {
- try (ZipOutputStream stream = new ZipOutputStream(Files.newOutputStream(zipFile))) {
+ try (ZipOutputStream stream =
+ new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(zipFile)))) {
List<Path> files =
Files.walk(inputDirectory)
.filter(path -> !Files.isDirectory(path))
diff --git a/src/test/java/com/android/tools/r8/IntermediateCfD8TestBuilder.java b/src/test/java/com/android/tools/r8/IntermediateCfD8TestBuilder.java
index fcefd23..609fad5 100644
--- a/src/test/java/com/android/tools/r8/IntermediateCfD8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/IntermediateCfD8TestBuilder.java
@@ -7,10 +7,12 @@
import com.android.tools.r8.debug.DebugTestConfig;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
public class IntermediateCfD8TestBuilder
extends TestBuilder<D8TestRunResult, IntermediateCfD8TestBuilder> {
@@ -31,6 +33,12 @@
D8TestBuilder.create(state, Backend.DEX).setMinApi(apiLevel).setDisableDesugaring(true);
}
+ public IntermediateCfD8TestBuilder addOptionsModification(Consumer<InternalOptions> fn) {
+ cf2cf.addOptionsModification(fn);
+ cf2dex.addOptionsModification(fn);
+ return self();
+ }
+
@Override
IntermediateCfD8TestBuilder self() {
return this;
diff --git a/src/test/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
index 4317ca8..e59ea66 100644
--- a/src/test/java/com/android/tools/r8/JvmTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
@@ -73,12 +73,12 @@
@Override
public JvmTestBuilder addLibraryFiles(Collection<Path> files) {
- throw new Unimplemented("No support for changing the Java runtime library.");
+ return addRunClasspathFiles(files);
}
@Override
public JvmTestBuilder addLibraryClasses(Collection<Class<?>> classes) {
- throw new Unimplemented("No support for changing the Java runtime library.");
+ return addRunClasspathFiles(writeClassesToJar(classes));
}
@Override
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 935de02..3c8a16b 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -256,6 +256,7 @@
return self();
}
+ @Override
public T addMainDexListClasses(Class<?>... classes) {
builder.addMainDexClasses(
Arrays.stream(classes).map(Class::getTypeName).collect(Collectors.toList()));
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 38637cf..1bd188a 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -216,11 +216,18 @@
}
public TestBuilder<? extends TestRunResult<?>, ?> testForDesugaring(TestParameters parameters) {
- return testForDesugaring(parameters.getRuntime().getBackend(), parameters.getApiLevel());
+ return testForDesugaring(
+ parameters.getRuntime().getBackend(), parameters.getApiLevel(), o -> {});
+ }
+
+ public TestBuilder<? extends TestRunResult<?>, ?> testForDesugaring(
+ TestParameters parameters, Consumer<InternalOptions> optionsModification) {
+ return testForDesugaring(
+ parameters.getRuntime().getBackend(), parameters.getApiLevel(), optionsModification);
}
private TestBuilder<? extends TestRunResult<?>, ?> testForDesugaring(
- Backend backend, AndroidApiLevel apiLevel) {
+ Backend backend, AndroidApiLevel apiLevel, Consumer<InternalOptions> optionsModification) {
assert apiLevel != null : "No API level. Add .withAllApiLevelsAlsoForCf() to test parameters?";
TestState state = new TestState(temp);
List<Pair<String, TestBuilder<? extends TestRunResult<?>, ?>>> builders;
@@ -228,13 +235,24 @@
builders =
ImmutableList.of(
new Pair<>("JAVAC", JvmTestBuilder.create(state)),
- new Pair<>("D8/CF", D8TestBuilder.create(state, Backend.CF).setMinApi(apiLevel)));
+ new Pair<>(
+ "D8/CF",
+ D8TestBuilder.create(state, Backend.CF)
+ .setMinApi(apiLevel)
+ .addOptionsModification(optionsModification)));
} else {
assert backend == Backend.DEX;
builders =
ImmutableList.of(
- new Pair<>("D8/DEX", D8TestBuilder.create(state, Backend.DEX).setMinApi(apiLevel)),
- new Pair<>("D8/DEX o D8/CF", IntermediateCfD8TestBuilder.create(state, apiLevel)));
+ new Pair<>(
+ "D8/DEX",
+ D8TestBuilder.create(state, Backend.DEX)
+ .setMinApi(apiLevel)
+ .addOptionsModification(optionsModification)),
+ new Pair<>(
+ "D8/DEX o D8/CF",
+ IntermediateCfD8TestBuilder.create(state, apiLevel)
+ .addOptionsModification(optionsModification)));
}
return TestBuilderCollection.create(state, builders);
}
@@ -1527,7 +1545,7 @@
}
Path path = temp.newFolder().toPath().resolve("classes.jar");
ArchiveConsumer consumer = new ArchiveConsumer(path);
- for (Class clazz : classes) {
+ for (Class<?> clazz : classes) {
consumer.accept(
ByteDataView.of(ToolHelper.getClassAsBytes(clazz)),
DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName()),
@@ -1537,6 +1555,24 @@
return path;
}
+ public Path buildOnDexRuntime(TestParameters parameters, byte[]... classes)
+ throws IOException, CompilationFailedException {
+ if (parameters.isDexRuntime()) {
+ return testForD8()
+ .addProgramClassFileData(classes)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .writeToZip();
+ }
+ Path path = temp.newFolder().toPath().resolve("classes.jar");
+ ArchiveConsumer consumer = new ArchiveConsumer(path);
+ for (byte[] clazz : classes) {
+ consumer.accept(ByteDataView.of(clazz), extractClassDescriptor(clazz), null);
+ }
+ consumer.finished(null);
+ return path;
+ }
+
public static String binaryName(Class<?> clazz) {
return DescriptorUtils.getBinaryNameFromJavaType(typeName(clazz));
}
@@ -1553,6 +1589,10 @@
return AndroidApiLevel.N;
}
+ public static AndroidApiLevel apiLevelWithStaticInterfaceMethodsSupport() {
+ return AndroidApiLevel.N;
+ }
+
public static AndroidApiLevel apiLevelWithInvokeCustomSupport() {
return AndroidApiLevel.O;
}
diff --git a/src/test/java/com/android/tools/r8/TestBaseBuilder.java b/src/test/java/com/android/tools/r8/TestBaseBuilder.java
index 4f512b7..4388754 100644
--- a/src/test/java/com/android/tools/r8/TestBaseBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBaseBuilder.java
@@ -19,7 +19,7 @@
C extends BaseCommand,
B extends BaseCommand.Builder<C, B>,
CR extends TestBaseResult<CR, RR>,
- RR extends TestRunResult,
+ RR extends TestRunResult<RR>,
T extends TestBaseBuilder<C, B, CR, RR, T>>
extends TestBuilder<RR, T> {
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
index 9a874c2..73a1605 100644
--- a/src/test/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.debug.DebugTestConfig;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.utils.ListUtils;
@@ -13,8 +14,9 @@
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ExecutionException;
+import java.util.function.BiFunction;
-public abstract class TestBuilder<RR extends TestRunResult, T extends TestBuilder<RR, T>> {
+public abstract class TestBuilder<RR extends TestRunResult<RR>, T extends TestBuilder<RR, T>> {
private final TestState state;
@@ -134,6 +136,16 @@
return addLibraryFiles(Arrays.asList(files));
}
+ public T addDefaultRuntimeLibrary(TestParameters parameters) {
+ if (parameters.getBackend() == Backend.DEX) {
+ addLibraryFiles(ToolHelper.getFirstSupportedAndroidJar(parameters.getApiLevel()));
+ } else {
+ assert parameters.getBackend() == Backend.CF;
+ addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+ }
+ return self();
+ }
+
public T addClasspathClasses(Class<?>... classes) {
return addClasspathClasses(Arrays.asList(classes));
}
@@ -179,4 +191,10 @@
public T addRunClasspathFiles(Path... files) {
return addRunClasspathFiles(Arrays.asList(files));
}
+
+ public T setDiagnosticsLevelModifier(
+ BiFunction<DiagnosticsLevel, Diagnostic, DiagnosticsLevel> modifier) {
+ getState().setDiagnosticsLevelModifier(modifier);
+ return self();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index da1bc91..6f6263a 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -34,7 +34,7 @@
C extends BaseCompilerCommand,
B extends BaseCompilerCommand.Builder<C, B>,
CR extends TestCompileResult<CR, RR>,
- RR extends TestRunResult,
+ RR extends TestRunResult<RR>,
T extends TestCompilerBuilder<C, B, CR, RR, T>>
extends TestBaseBuilder<C, B, CR, RR, T> {
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
index 54ad442..ab5027b 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
@@ -16,12 +16,14 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.function.BiFunction;
import org.hamcrest.Matcher;
public class TestDiagnosticMessagesImpl implements DiagnosticsHandler, TestDiagnosticMessages {
private final List<Diagnostic> infos = new ArrayList<>();
private final List<Diagnostic> warnings = new ArrayList<>();
private final List<Diagnostic> errors = new ArrayList<>();
+ BiFunction<DiagnosticsLevel, Diagnostic, DiagnosticsLevel> modifier;
@Override
public String toString() {
@@ -66,6 +68,11 @@
}
@Override
+ public DiagnosticsLevel modifyDiagnosticsLevel(DiagnosticsLevel level, Diagnostic diagnostic) {
+ return modifier == null ? level : modifier.apply(level, diagnostic);
+ }
+
+ @Override
public List<Diagnostic> getInfos() {
return infos;
}
@@ -303,4 +310,9 @@
public TestDiagnosticMessages assertAllErrorsMatch(Matcher<Diagnostic> matcher) {
return assertAllDiagnosticsMatches(getErrors(), "error", matcher);
}
+
+ void setDiagnosticsLevelModifier(
+ BiFunction<DiagnosticsLevel, Diagnostic, DiagnosticsLevel> modifier) {
+ this.modifier = modifier;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/TestState.java b/src/test/java/com/android/tools/r8/TestState.java
index 6ba75d7..3e28699 100644
--- a/src/test/java/com/android/tools/r8/TestState.java
+++ b/src/test/java/com/android/tools/r8/TestState.java
@@ -5,6 +5,7 @@
import java.io.IOException;
import java.nio.file.Path;
+import java.util.function.BiFunction;
import org.junit.rules.TemporaryFolder;
public class TestState {
@@ -54,4 +55,9 @@
void setStderr(String stderr) {
this.stderr = stderr;
}
+
+ void setDiagnosticsLevelModifier(
+ BiFunction<DiagnosticsLevel, Diagnostic, DiagnosticsLevel> modifier) {
+ messages.setDiagnosticsLevelModifier(modifier);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 9b83a88fa2..7de9a9a 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.AssemblyWriter;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexItemFactory;
@@ -2086,12 +2087,11 @@
return builder;
}
- public static void writeApplication(DexApplication application, InternalOptions options)
+ public static void writeApplication(AppView<?> appView, InternalOptions options)
throws ExecutionException {
R8.writeApplication(
Executors.newSingleThreadExecutor(),
- application,
- null,
+ appView,
GraphLens.getIdentityLens(),
InitClassLens.getDefault(),
NamingLens.getIdentityLens(),
diff --git a/src/test/java/com/android/tools/r8/cf/StackMapFlagThisUninitTest.java b/src/test/java/com/android/tools/r8/cf/StackMapFlagThisUninitTest.java
new file mode 100644
index 0000000..b3f7fe4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/StackMapFlagThisUninitTest.java
@@ -0,0 +1,131 @@
+// 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.cf;
+
+import static org.hamcrest.CoreMatchers.containsString;
+
+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;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class StackMapFlagThisUninitTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withCfRuntimes().build();
+ }
+
+ public StackMapFlagThisUninitTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(Main.class)
+ .addProgramClassFileData(ADump.dump())
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatMatches(
+ containsString(
+ "Exception in thread \"main\" java.lang.VerifyError: Inconsistent stackmap frames"
+ + " at branch target 22"));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class)
+ .addProgramClassFileData(ADump.dump())
+ .addKeepAllClassesRule()
+ .run(parameters.getRuntime(), Main.class)
+ // TODO(b/166738818): We should generate the correct stack map entry.
+ .assertFailureWithErrorThatMatches(
+ containsString(
+ "Exception in thread \"main\" java.lang.VerifyError: Inconsistent stackmap frames"
+ + " at branch target 22"));
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ new A(0);
+ }
+ }
+
+ public static class A {
+
+ public A(int i) {}
+ }
+
+ // TODO(b/166738818): This is the bytecode generated from the constructor merging in
+ // https://r8-review.googlesource.com/c/r8/+/53180/3.
+ // The important thing here is that the instantiation of this is not performed in one case
+ // and we fail to create a proper stack map entry for uninitializedThis.
+ public static class ADump implements Opcodes {
+
+ public static byte[] dump() {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_8,
+ ACC_PUBLIC | ACC_SUPER,
+ "com/android/tools/r8/cf/StackMapFlagThisUninitTest$A",
+ null,
+ "java/lang/Object",
+ null);
+
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "(I)V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitVarInsn(ILOAD, 1);
+ Label label0 = new Label();
+ methodVisitor.visitJumpInsn(IFEQ, label0);
+ methodVisitor.visitVarInsn(ILOAD, 1);
+ methodVisitor.visitInsn(ICONST_1);
+ Label label1 = new Label();
+ methodVisitor.visitJumpInsn(IF_ICMPNE, label1);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("bar");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitFrame(Opcodes.F_CHOP, 2, null, 0, null);
+ methodVisitor.visitInsn(ACONST_NULL);
+ methodVisitor.visitInsn(ATHROW);
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitFrame(
+ Opcodes.F_APPEND, 1, new Object[] {Opcodes.UNINITIALIZED_THIS}, 0, null);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("foo");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 2);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
index 6414d4b..e42f33d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
@@ -23,6 +23,7 @@
import com.android.tools.r8.utils.codeinspector.TryCatchSubject;
import com.android.tools.r8.utils.codeinspector.TypeSubject;
import com.google.common.collect.ImmutableSet;
+import java.nio.file.Path;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@@ -113,6 +114,41 @@
}
@Test
+ public void testTimeD8Cf() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ // Use D8 to desugar with Java classfile output.
+ Path jar =
+ testForD8(Backend.CF)
+ .addInnerClasses(JavaTimeTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .inspect(this::checkRewrittenInvokes)
+ .writeToZip();
+
+ // Collection keep rules is only implemented in the DEX writer.
+ String desugaredLibraryKeepRules = keepRuleConsumer.get();
+ if (desugaredLibraryKeepRules != null) {
+ assertEquals(0, desugaredLibraryKeepRules.length());
+ desugaredLibraryKeepRules = "-keep class * { *; }";
+ }
+
+ // Convert to DEX without desugaring.
+ testForD8()
+ .addProgramFiles(jar)
+ .setMinApi(parameters.getApiLevel())
+ .disableDesugaring()
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ desugaredLibraryKeepRules,
+ shrinkDesugaredLibrary)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(expectedOutput);
+ }
+
+ @Test
public void testTimeD8() throws Exception {
KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
testForD8()
diff --git a/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticDesugarTest.java b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticDesugarTest.java
new file mode 100644
index 0000000..8bcb3a6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticDesugarTest.java
@@ -0,0 +1,84 @@
+// 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.desugar.staticinterfacemethod;
+
+import static com.android.tools.r8.desugar.staticinterfacemethod.InvokeStaticDesugarTest.Library.foo;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper.DexVm;
+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;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InvokeStaticDesugarTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final String EXPECTED = "Hello World!";
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ public InvokeStaticDesugarTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testDesugar() throws Exception {
+ final TestRunResult<?> runResult =
+ testForDesugaring(parameters)
+ .addLibraryClasses(Library.class)
+ .addProgramClasses(Main.class)
+ .addRunClasspathFiles(compileRunClassPath())
+ .run(parameters.getRuntime(), Main.class);
+ if (parameters.isDexRuntime()
+ && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_HOST)) {
+ runResult.assertFailureWithErrorThatMatches(containsString("java.lang.VerifyError"));
+ } else {
+ runResult.assertSuccessWithOutputLines(EXPECTED);
+ }
+ }
+
+ private Path compileRunClassPath() throws Exception {
+ if (parameters.isCfRuntime()) {
+ return compileToZip(parameters, ImmutableList.of(), Library.class);
+ } else {
+ assert parameters.isDexRuntime();
+ return testForD8(parameters.getBackend())
+ .addProgramClasses(Library.class)
+ .setMinApi(parameters.getApiLevel())
+ .disableDesugaring()
+ .addOptionsModification(
+ options -> {
+ options.testing.allowStaticInterfaceMethodsForPreNApiLevel = true;
+ })
+ .compile()
+ .writeToZip();
+ }
+ }
+
+ public interface Library {
+
+ static void foo() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/StaticInterfaceMethodReferenceTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/StaticInterfaceMethodReferenceTest.java
new file mode 100644
index 0000000..e7fba9d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/StaticInterfaceMethodReferenceTest.java
@@ -0,0 +1,128 @@
+// 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.desugaring.interfacemethods;
+
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class StaticInterfaceMethodReferenceTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final boolean isInterface;
+
+ @Parameterized.Parameters(name = "{0}, itf:{1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+ BooleanUtils.values());
+ }
+
+ public StaticInterfaceMethodReferenceTest(TestParameters parameters, boolean isInterface) {
+ this.parameters = parameters;
+ this.isInterface = isInterface;
+ }
+
+ @Test
+ public void test() throws Exception {
+ TestRunResult<?> result =
+ testForDesugaring(parameters, o -> o.testing.allowInvokeErrors = true)
+ .addProgramClasses(TestClass.class)
+ .addProgramClassFileData(getTarget(isInterface))
+ .run(parameters.getRuntime(), TestClass.class);
+ checkResult(result);
+ }
+
+ @Test
+ public void testTargetMissing() throws Exception {
+ TestRunResult<?> result =
+ testForDesugaring(parameters, o -> o.testing.allowInvokeErrors = true)
+ .addProgramClasses(TestClass.class)
+ .addRunClasspathFiles(buildOnDexRuntime(parameters, getTarget(isInterface)))
+ .run(parameters.getRuntime(), TestClass.class);
+ // Missing target will cause the call to remain as Target::foo rather than Target$-CC::foo.
+ // TODO(b/166726895): Support static interface invoke as no knowledge of Target is needed.
+ if (isInterface
+ && parameters.isDexRuntime()
+ && parameters.getApiLevel().isLessThan(apiLevelWithStaticInterfaceMethodsSupport())) {
+ result.assertFailureWithErrorThatThrows(
+ parameters
+ .getRuntime()
+ .asDex()
+ .getVm()
+ .getVersion()
+ .isOlderThanOrEqual(DexVm.Version.V4_4_4)
+ // On <= 4.4.4 a verify error happens due to the static invoke on an interface.
+ ? VerifyError.class
+ : NoSuchMethodError.class);
+ return;
+ }
+ checkResult(result);
+ }
+
+ private void checkResult(TestRunResult<?> result) {
+ // If the reference is of correct type, or running on JDK8 which allows mismatch the
+ // output is the expected print.
+ if (isInterface
+ || (parameters.isCfRuntime() && parameters.getRuntime().asCf().getVm() == CfVm.JDK8)) {
+ result.assertSuccessWithOutputLines("Target::foo");
+ return;
+ }
+
+ // DEX runtimes do not check method reference types so the code will run.
+ // TODO(b/166732606): Compile to an ICCE? What about the "missing" target case?
+ assertFalse(isInterface);
+ if (parameters.isDexRuntime()) {
+ result.assertSuccessWithOutputLines("Target::foo");
+ return;
+ }
+
+ // Otherwise the run should result in an ICCE.
+ result.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+ }
+
+ private static byte[] getTarget(boolean isInterface) throws Exception {
+ return transformer(Target.class)
+ .setAccessFlags(
+ classAccessFlags -> {
+ if (isInterface) {
+ assert !classAccessFlags.isSuper();
+ assert classAccessFlags.isAbstract();
+ assert classAccessFlags.isInterface();
+ } else {
+ classAccessFlags.unsetAbstract();
+ classAccessFlags.unsetInterface();
+ }
+ })
+ .transform();
+ }
+
+ interface Target {
+
+ static void foo() {
+ System.out.println("Target::foo");
+ }
+ }
+
+ static class TestClass {
+
+ static void call(Runnable fn) {
+ fn.run();
+ }
+
+ public static void main(String[] args) {
+ call(Target::foo);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/diagnostics/ModifyDiagnosticsLevelTest.java b/src/test/java/com/android/tools/r8/diagnostics/ModifyDiagnosticsLevelTest.java
new file mode 100644
index 0000000..8db3e12
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/diagnostics/ModifyDiagnosticsLevelTest.java
@@ -0,0 +1,96 @@
+// 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.diagnostics;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DiagnosticsLevel;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringDiagnostic;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ModifyDiagnosticsLevelTest extends TestBase {
+
+ private static final String MISSING_CLASS_MESSAGE_PREFIX = "Missing class: ";
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public ModifyDiagnosticsLevelTest(TestParameters parameters) {
+ parameters.assertNoneRuntime();
+ }
+
+ @Test
+ public void testWarningToInfo() throws Exception {
+ testForR8(Backend.DEX)
+ .addProgramClasses(TestClass.class)
+ .addKeepMainRule(TestClass.class)
+ .addKeepRules("-ignorewarnings")
+ .setDiagnosticsLevelModifier(
+ (level, diagnostic) -> {
+ if (level == DiagnosticsLevel.WARNING
+ && diagnostic instanceof StringDiagnostic
+ && diagnostic.getDiagnosticMessage().startsWith(MISSING_CLASS_MESSAGE_PREFIX)) {
+ return DiagnosticsLevel.INFO;
+ }
+ return level;
+ })
+ .allowDiagnosticInfoMessages()
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics
+ .assertOnlyInfos()
+ .assertInfosCount(1)
+ .assertInfosMatch(diagnosticMessage(startsWith(MISSING_CLASS_MESSAGE_PREFIX)));
+ });
+ }
+
+ @Test
+ public void testWarningToError() throws Exception {
+ try {
+ testForR8(Backend.DEX)
+ .addProgramClasses(TestClass.class)
+ .addKeepMainRule(TestClass.class)
+ .addKeepRules("-ignorewarnings")
+ .setDiagnosticsLevelModifier(
+ (level, diagnostic) -> {
+ if (level == DiagnosticsLevel.WARNING
+ && diagnostic instanceof StringDiagnostic
+ && diagnostic.getDiagnosticMessage().startsWith(MISSING_CLASS_MESSAGE_PREFIX)) {
+ return DiagnosticsLevel.ERROR;
+ }
+ return level;
+ })
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics
+ .assertOnlyErrors()
+ .assertErrorsCount(1)
+ .assertErrorsMatch(diagnosticMessage(startsWith(MISSING_CLASS_MESSAGE_PREFIX)));
+ });
+ fail("Expected compilation to fail");
+ } catch (CompilationFailedException e) {
+ // Expected.
+ }
+ }
+
+ static class TestClass implements I {
+
+ public static void main(String[] args) {
+ System.out.println("Hello, world!");
+ }
+ }
+
+ interface I {}
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EmptyEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EmptyEnumUnboxingTest.java
new file mode 100644
index 0000000..ddc0806
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EmptyEnumUnboxingTest.java
@@ -0,0 +1,75 @@
+// 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.enumunboxing;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+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 EmptyEnumUnboxingTest extends EnumUnboxingTestBase {
+
+ private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final EnumKeepRules enumKeepRules;
+
+ @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public EmptyEnumUnboxingTest(
+ TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
+ this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
+ }
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ R8TestRunResult run =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(EmptyEnumUnboxingTest.class)
+ .addKeepMainRule(Main.class)
+ .addKeepRules(enumKeepRules.getKeepRules())
+ .enableNeverClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .allowDiagnosticInfoMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectDiagnosticMessages(
+ m ->
+ // TODO(b/166532373): Unbox enum with no cases.
+ assertEnumIsBoxed(MyEnum.class, Main.class.getSimpleName(), m))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
+ }
+
+ @NeverClassInline
+ enum MyEnum {
+ ;
+
+ @NeverInline
+ static void print() {
+ System.out.println("PRINT");
+ }
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ MyEnum.print();
+ System.out.println("PRINT");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
index bdf46e3..89395ef 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
@@ -7,7 +7,6 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.StringContains.containsString;
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestParameters;
@@ -76,32 +75,15 @@
.allowDiagnosticInfoMessages(enumUnboxing)
.setMinApi(parameters.getApiLevel())
.compile();
- if (enumKeepRules.isStudio() && enumValueOptimization && enumUnboxing) {
- // TODO(b/160939354): Enum unboxing synthesizes a toString() method based on field names.
- compile
- .run(parameters.getRuntime(), AlwaysCorrectProgram.class)
- .assertFailureWithErrorThatMatches(containsString("IllegalArgumentException"));
- } else {
compile
.run(parameters.getRuntime(), AlwaysCorrectProgram.class)
.assertSuccessWithOutputLines("0", "1", "2", "0", "1", "2", "0", "1", "2");
- }
- if (!enumKeepRules.isSnap() && enumUnboxing) {
- // TODO(b/160939354): Enum unboxing synthesizes a toString() method based on field names.
- compile
- .run(parameters.getRuntime(), AlwaysCorrectProgram2.class)
- .assertFailureWithErrorThatMatches(containsString("IllegalArgumentException"));
- } else {
compile
.run(parameters.getRuntime(), AlwaysCorrectProgram2.class)
.assertSuccessWithOutputLines("0", "1", "2", "0", "1", "2");
- }
}
private void assertEnumFieldsMinified(CodeInspector codeInspector) throws Exception {
- if (enumKeepRules.isSnap()) {
- return;
- }
ClassSubject clazz = codeInspector.clazz(ToStringLib.LibEnum.class);
assertThat(clazz, isPresent());
for (String fieldName : new String[] {"COFFEE", "BEAN", "SUGAR"}) {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingB160535628Test.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingB160535628Test.java
index 5ccd903..590cc7c 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingB160535628Test.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingB160535628Test.java
@@ -21,16 +21,21 @@
private final TestParameters parameters;
private final boolean missingStaticMethods;
+ private final EnumKeepRules enumKeepRules;
- @Parameterized.Parameters(name = "{0}")
+ @Parameterized.Parameters(name = "{0} missing: {1} keep: {2}")
public static List<Object[]> data() {
return buildParameters(
- getTestParameters().withDexRuntimes().withAllApiLevels().build(), BooleanUtils.values());
+ getTestParameters().withDexRuntimes().withAllApiLevels().build(),
+ BooleanUtils.values(),
+ getAllEnumKeepRules());
}
- public EnumUnboxingB160535628Test(TestParameters parameters, boolean missingStaticMethods) {
+ public EnumUnboxingB160535628Test(
+ TestParameters parameters, boolean missingStaticMethods, EnumKeepRules enumKeepRules) {
this.parameters = parameters;
this.missingStaticMethods = missingStaticMethods;
+ this.enumKeepRules = enumKeepRules;
}
@Test
@@ -44,9 +49,10 @@
.addProgramClasses(ProgramValueOf.class, ProgramStaticMethod.class)
.addProgramFiles(javaLibShrunk)
.addKeepMainRules(ProgramValueOf.class, ProgramStaticMethod.class)
+ .addKeepRules(enumKeepRules.getKeepRules())
.addOptionsModification(
options -> {
- options.enableEnumUnboxing = true;
+ assert options.enableEnumUnboxing;
options.testing.enableEnumUnboxingDebugLogs = true;
})
.allowDiagnosticMessages()
@@ -58,18 +64,23 @@
this::assertEnumUnboxedIfStaticMethodsPresent);
if (missingStaticMethods) {
compile
- .run(parameters.getRuntime(), ProgramValueOf.class)
- .assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"))
- .assertFailureWithErrorThatMatches(containsString("valueOf"));
- compile
.run(parameters.getRuntime(), ProgramStaticMethod.class)
.assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"))
.assertFailureWithErrorThatMatches(containsString("staticMethod"));
} else {
- compile.run(parameters.getRuntime(), ProgramValueOf.class).assertSuccessWithOutputLines("0");
compile
.run(parameters.getRuntime(), ProgramStaticMethod.class)
- .assertSuccessWithOutputLines("42");
+ .assertSuccessWithOutputLines("0", "42");
+ }
+ if (missingStaticMethods && enumKeepRules == EnumKeepRules.NONE) {
+ compile
+ .run(parameters.getRuntime(), ProgramValueOf.class)
+ .assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"))
+ .assertFailureWithErrorThatMatches(containsString("valueOf"));
+ } else {
+ compile
+ .run(parameters.getRuntime(), ProgramValueOf.class)
+ .assertSuccessWithOutputLines("0", "0");
}
}
@@ -77,6 +88,7 @@
return testForR8(Backend.CF)
.addProgramClasses(Lib.class, Lib.LibEnumStaticMethod.class, Lib.LibEnum.class)
.addKeepRules("-keep enum * { <fields>; }")
+ .addKeepRules(enumKeepRules.getKeepRules())
.addKeepRules(missingStaticMethods ? "" : "-keep enum * { static <methods>; }")
.addOptionsModification(
options -> {
@@ -130,13 +142,15 @@
public static class ProgramValueOf {
public static void main(String[] args) {
- System.out.println(Lib.LibEnumStaticMethod.valueOf(Lib.LibEnum.A.name()).ordinal());
+ System.out.println(Lib.LibEnum.A.ordinal());
+ System.out.println(Lib.LibEnum.valueOf(Lib.LibEnum.A.name()).ordinal());
}
}
public static class ProgramStaticMethod {
public static void main(String[] args) {
+ System.out.println(Lib.LibEnumStaticMethod.A.ordinal());
System.out.println(Lib.LibEnumStaticMethod.staticMethod());
}
}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
index 4f724d3..1e5bd5b 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
@@ -65,12 +65,16 @@
}
private void assertClassStaticized(CodeInspector codeInspector) {
+ String renamedMethodName = "method$enumunboxing$";
if (parameters.isCfRuntime()) {
// There is no class staticizer in Cf.
- assertThat(codeInspector.clazz(Companion.class).uniqueMethodWithName("method"), isPresent());
+ assertThat(
+ codeInspector.clazz(Companion.class).uniqueMethodWithName(renamedMethodName),
+ isPresent());
return;
}
- MethodSubject method = codeInspector.clazz(CompanionHost.class).uniqueMethodWithName("method");
+ MethodSubject method =
+ codeInspector.clazz(CompanionHost.class).uniqueMethodWithName(renamedMethodName);
assertThat(method, isPresent());
assertEquals("int", method.getMethod().method.proto.parameters.toString());
}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingSideEffectClInitTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingSideEffectClInitTest.java
index 128466c..89621a2 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingSideEffectClInitTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingSideEffectClInitTest.java
@@ -44,15 +44,7 @@
.setMinApi(parameters.getApiLevel())
.compile()
.inspectDiagnosticMessages(
- m -> {
- // The snap keep rule forces to keep the static MainEnum#e field, so the enum
- // cannot be unboxed anymore.
- if (enumKeepRules.isSnap()) {
- assertEnumIsBoxed(ENUM_CLASS, classToTest.getSimpleName(), m);
- } else {
- assertEnumIsUnboxed(ENUM_CLASS, classToTest.getSimpleName(), m);
- }
- })
+ m -> assertEnumIsUnboxed(ENUM_CLASS, classToTest.getSimpleName(), m))
.run(parameters.getRuntime(), classToTest)
.assertSuccessWithOutputLines("0");
}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
index f26a6be..ab837d3 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
@@ -23,18 +23,10 @@
+ " public static **[] values();\n"
+ " public static ** valueOf(java.lang.String);\n"
+ "}";
- // Default keep rule present in Snap.
- private static final String KEEP_ENUM_SNAP =
- "-keepclassmembers enum * {\n"
- + "<fields>;\n"
- + " public static **[] values();\n"
- + " public static ** valueOf(java.lang.String);\n"
- + "}";
public enum EnumKeepRules {
NONE(""),
- STUDIO(KEEP_ENUM_STUDIO),
- SNAP(KEEP_ENUM_SNAP);
+ STUDIO(KEEP_ENUM_STUDIO);
private final String keepRules;
@@ -46,10 +38,6 @@
return this == STUDIO;
}
- public boolean isSnap() {
- return this == SNAP;
- }
-
EnumKeepRules(String keepRules) {
this.keepRules = keepRules;
}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
index 08ec7bd..8bf8034 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
@@ -5,12 +5,8 @@
package com.android.tools.r8.enumunboxing;
import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.R8FullTestBuilder;
-import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.enumunboxing.FailingEnumUnboxingTest.EnumInstanceFieldMain.EnumInstanceField;
-import com.android.tools.r8.enumunboxing.FailingEnumUnboxingTest.EnumStaticFieldMain.EnumStaticField;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -20,11 +16,6 @@
@RunWith(Parameterized.class)
public class FailingEnumUnboxingTest extends EnumUnboxingTestBase {
- private static final Class<?>[] FAILURES = {
- EnumStaticField.class,
- EnumInstanceField.class,
- };
-
private final TestParameters parameters;
private final boolean enumValueOptimization;
private final EnumKeepRules enumKeepRules;
@@ -43,28 +34,25 @@
@Test
public void testEnumUnboxingFailure() throws Exception {
- R8FullTestBuilder r8FullTestBuilder =
- testForR8(parameters.getBackend()).addInnerClasses(FailingEnumUnboxingTest.class);
- for (Class<?> failure : FAILURES) {
- r8FullTestBuilder.addKeepMainRule(failure.getEnclosingClass());
- }
- R8TestCompileResult compile =
- r8FullTestBuilder
+ R8TestRunResult run =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(FailingEnumUnboxingTest.class)
+ .addKeepMainRule(EnumStaticFieldMain.class)
.enableNeverClassInliningAnnotations()
.addKeepRules(enumKeepRules.getKeepRules())
.addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.allowDiagnosticInfoMessages()
.setMinApi(parameters.getApiLevel())
- .compile();
- for (Class<?> failure : FAILURES) {
- R8TestRunResult run =
- compile
- .inspectDiagnosticMessages(
- m -> assertEnumIsBoxed(failure, failure.getSimpleName(), m))
- .run(parameters.getRuntime(), failure.getEnclosingClass())
- .assertSuccess();
- assertLines2By2Correct(run.getStdOut());
- }
+ .compile()
+ .inspectDiagnosticMessages(
+ m ->
+ assertEnumIsBoxed(
+ EnumStaticFieldMain.EnumStaticField.class,
+ EnumStaticFieldMain.class.getSimpleName(),
+ m))
+ .run(parameters.getRuntime(), EnumStaticFieldMain.class)
+ .assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
}
static class EnumStaticFieldMain {
@@ -84,26 +72,4 @@
static EnumStaticField X = A;
}
}
-
- static class EnumInstanceFieldMain {
-
- @NeverClassInline
- enum EnumInstanceField {
- A(10),
- B(20),
- C(30);
- private int a;
-
- EnumInstanceField(int i) {
- this.a = i;
- }
- }
-
- public static void main(String[] args) {
- System.out.println(EnumInstanceField.A.ordinal());
- System.out.println(0);
- System.out.println(EnumInstanceField.A.a);
- System.out.println(10);
- }
- }
}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
index e42ac65..374895c 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
@@ -1,12 +1,10 @@
// 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.enumunboxing;
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertEquals;
-
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8TestCompileResult;
@@ -22,17 +20,14 @@
@RunWith(Parameterized.class)
public class FailingMethodEnumUnboxingTest extends EnumUnboxingTestBase {
-
private static final Class<?>[] FAILURES = {
InstanceFieldPutObject.class,
StaticFieldPutObject.class,
- ToString.class,
EnumSetTest.class,
FailingPhi.class,
FailingReturnType.class,
FailingParameterType.class
};
-
private final TestParameters parameters;
private final boolean enumValueOptimization;
private final EnumKeepRules enumKeepRules;
@@ -83,18 +78,15 @@
private void assertEnumsAsExpected(CodeInspector inspector) {
// Check all as expected (else we test nothing)
-
assertEquals(
1,
inspector.clazz(InstanceFieldPutObject.class).getDexProgramClass().instanceFields().size());
assertEquals(
1, inspector.clazz(StaticFieldPutObject.class).getDexProgramClass().staticFields().size());
-
assertTrue(inspector.clazz(FailingPhi.class).uniqueMethodWithName("switchOn").isPresent());
}
static class InstanceFieldPutObject {
-
@NeverClassInline
enum MyEnum {
A,
@@ -119,7 +111,6 @@
}
static class StaticFieldPutObject {
-
@NeverClassInline
enum MyEnum {
A,
@@ -142,24 +133,7 @@
}
}
- static class ToString {
-
- @NeverClassInline
- enum MyEnum {
- A,
- B,
- C
- }
-
- public static void main(String[] args) {
- MyEnum e1 = MyEnum.A;
- System.out.println(e1.toString());
- System.out.println("A");
- }
- }
-
static class EnumSetTest {
-
@NeverClassInline
enum MyEnum {
A,
@@ -175,7 +149,6 @@
}
static class FailingPhi {
-
@NeverClassInline
enum MyEnum {
A,
@@ -189,7 +162,6 @@
System.out.println(switchOn(2));
System.out.println("class java.lang.Object");
}
-
// Avoid removing the switch entirely.
@NeverInline
static Object switchOn(int i) {
@@ -205,7 +177,6 @@
}
static class FailingReturnType {
-
@NeverClassInline
enum MyEnum {
A,
@@ -227,7 +198,6 @@
}
static class FailingParameterType {
-
@NeverClassInline
enum MyEnum {
A,
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/InstanceFieldsEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/InstanceFieldsEnumUnboxingTest.java
new file mode 100644
index 0000000..e91be2b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/InstanceFieldsEnumUnboxingTest.java
@@ -0,0 +1,549 @@
+// 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.enumunboxing;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+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 InstanceFieldsEnumUnboxingTest extends EnumUnboxingTestBase {
+
+ private static final Class<?>[] FAILURES = {
+ FailureIntField.class,
+ FailurePrivateIntField.class,
+ FailureBoxedInnerEnumField.class,
+ FailureUnboxedEnumField.class,
+ FailureTooManyUsedFields.class
+ };
+
+ private static final Class<?>[] SUCCESSES = {
+ SuccessUnusedField.class,
+ SuccessIntField.class,
+ SuccessDoubleField.class,
+ SuccessIntFieldOrdinal.class,
+ SuccessIntFieldInitializerInit.class,
+ SuccessStringField.class,
+ SuccessMultiConstructorIntField.class
+ };
+
+ private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final EnumKeepRules enumKeepRules;
+
+ @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public InstanceFieldsEnumUnboxingTest(
+ TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
+ this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
+ }
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ R8TestCompileResult compile =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(InstanceFieldsEnumUnboxingTest.class)
+ .addKeepMainRules(SUCCESSES)
+ .addKeepMainRules(FAILURES)
+ .noMinification()
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .addKeepRules(enumKeepRules.getKeepRules())
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .allowDiagnosticInfoMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile();
+ for (Class<?> failure : FAILURES) {
+ testClass(compile, failure, true);
+ }
+ for (Class<?> success : SUCCESSES) {
+ testClass(compile, success, success != SuccessUnusedField.class);
+ }
+ }
+
+ private void testClass(R8TestCompileResult compile, Class<?> testClass, boolean failure)
+ throws Exception {
+ R8TestRunResult run =
+ compile
+ .inspectDiagnosticMessages(
+ m -> {
+ for (Class<?> declaredClass : testClass.getDeclaredClasses()) {
+ if (declaredClass.isEnum()
+ && !declaredClass.getSimpleName().equals("InnerEnum")) {
+ if (failure) {
+ assertEnumIsBoxed(declaredClass, testClass.getSimpleName(), m);
+ } else {
+ assertEnumIsUnboxed(declaredClass, testClass.getSimpleName(), m);
+ }
+ }
+ }
+ })
+ .run(parameters.getRuntime(), testClass)
+ .assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
+ }
+
+ static class SuccessUnusedField {
+
+ public static void main(String[] args) {
+ System.out.println(getEnumA().ordinal());
+ System.out.println(0);
+ System.out.println(getEnumB().ordinal());
+ System.out.println(1);
+ }
+
+ @NeverInline
+ static EnumField getEnumA() {
+ return System.currentTimeMillis() > 0 ? EnumField.A : EnumField.B;
+ }
+
+ @NeverInline
+ static EnumField getEnumB() {
+ return System.currentTimeMillis() > 0 ? EnumField.B : EnumField.A;
+ }
+
+ @NeverClassInline
+ enum EnumField {
+ A(10),
+ B(20);
+
+ int field;
+
+ EnumField(int i) {
+ this.field = i;
+ }
+ }
+ }
+
+ static class SuccessIntField {
+
+ public static void main(String[] args) {
+ System.out.println(getEnumA().field);
+ System.out.println(10);
+ System.out.println(getEnumB().field);
+ System.out.println(20);
+ }
+
+ @NeverInline
+ static EnumField getEnumA() {
+ return System.currentTimeMillis() > 0 ? EnumField.A : EnumField.B;
+ }
+
+ @NeverInline
+ static EnumField getEnumB() {
+ return System.currentTimeMillis() > 0 ? EnumField.B : EnumField.A;
+ }
+
+ @NeverClassInline
+ enum EnumField {
+ A(10),
+ B(20);
+
+ int field;
+
+ EnumField(int i) {
+ this.field = i;
+ }
+ }
+ }
+
+ static class FailurePrivateIntField {
+
+ public static void main(String[] args) {
+ System.out.println(getEnumA().field);
+ System.out.println(10);
+ System.out.println(getEnumB().field);
+ System.out.println(20);
+ }
+
+ @NeverInline
+ static EnumField getEnumA() {
+ return System.currentTimeMillis() > 0 ? EnumField.A : EnumField.B;
+ }
+
+ @NeverInline
+ static EnumField getEnumB() {
+ return System.currentTimeMillis() > 0 ? EnumField.B : EnumField.A;
+ }
+
+ @NeverClassInline
+ enum EnumField {
+ A(10),
+ B(20);
+
+ private int field;
+
+ EnumField(int i) {
+ this.field = i;
+ }
+ }
+ }
+
+ static class SuccessDoubleField {
+
+ public static void main(String[] args) {
+ System.out.println(getEnumA().field);
+ System.out.println(10.0);
+ System.out.println(getEnumB().field);
+ System.out.println(20.0);
+ }
+
+ @NeverInline
+ static EnumField getEnumA() {
+ return System.currentTimeMillis() > 0 ? EnumField.A : EnumField.B;
+ }
+
+ @NeverInline
+ static EnumField getEnumB() {
+ return System.currentTimeMillis() > 0 ? EnumField.B : EnumField.A;
+ }
+
+ @NeverClassInline
+ enum EnumField {
+ A(10.0),
+ B(20.0);
+
+ double field;
+
+ EnumField(double d) {
+ this.field = d;
+ }
+ }
+ }
+
+ static class SuccessIntFieldInitializerInit {
+
+ public static void main(String[] args) {
+ System.out.println(getEnumA().field);
+ System.out.println(10);
+ System.out.println(getEnumB().field);
+ System.out.println(10);
+ }
+
+ @NeverInline
+ static EnumField getEnumA() {
+ return System.currentTimeMillis() > 0 ? EnumField.A : EnumField.B;
+ }
+
+ @NeverInline
+ static EnumField getEnumB() {
+ return System.currentTimeMillis() > 0 ? EnumField.B : EnumField.A;
+ }
+
+ @NeverClassInline
+ enum EnumField {
+ A,
+ B,
+ C;
+
+ int field;
+
+ EnumField() {
+ this.field = 10;
+ }
+ }
+ }
+
+ // This class test an optimization where the ordinal is re-used instead of the int field.
+ static class SuccessIntFieldOrdinal {
+
+ public static void main(String[] args) {
+ System.out.println(getEnumA().field);
+ System.out.println(0);
+ System.out.println(getEnumB().field);
+ System.out.println(1);
+ }
+
+ @NeverInline
+ static EnumField getEnumA() {
+ return System.currentTimeMillis() > 0 ? EnumField.A : EnumField.B;
+ }
+
+ @NeverInline
+ static EnumField getEnumB() {
+ return System.currentTimeMillis() > 0 ? EnumField.B : EnumField.A;
+ }
+
+ @NeverClassInline
+ enum EnumField {
+ A(0),
+ B(1),
+ C(2);
+
+ int field;
+
+ EnumField(int i) {
+ this.field = i;
+ }
+ }
+ }
+
+ static class FailureIntField {
+
+ public static void main(String[] args) {
+ System.out.println(getEnumA().field);
+ System.out.println(30);
+ System.out.println(getEnumB().field);
+ System.out.println(60);
+ }
+
+ @NeverInline
+ static EnumField getEnumA() {
+ return System.currentTimeMillis() > 0 ? EnumField.A : EnumField.B;
+ }
+
+ @NeverInline
+ static EnumField getEnumB() {
+ return System.currentTimeMillis() > 0 ? EnumField.B : EnumField.A;
+ }
+
+ @NeverClassInline
+ enum EnumField {
+ A(getRandom(10)),
+ B(getRandom(20)),
+ C(getRandom(30));
+
+ @NeverInline
+ static int getRandom(int i) {
+ return i * (System.currentTimeMillis() > 0 ? 3 : -3);
+ }
+
+ int field;
+
+ EnumField(int i) {
+ this.field = i;
+ }
+ }
+ }
+
+ static class FailureTooManyUsedFields {
+
+ public static void main(String[] args) {
+ System.out.println(getEnumA().field0);
+ System.out.println(0);
+ System.out.println(getEnumA().field1);
+ System.out.println(9);
+ System.out.println(getEnumA().field2);
+ System.out.println(8);
+ System.out.println(getEnumA().field3);
+ System.out.println(7);
+ System.out.println(getEnumA().field4);
+ System.out.println(6);
+ System.out.println(getEnumA().field5);
+ System.out.println(5);
+ System.out.println(getEnumA().field6);
+ System.out.println(4);
+ System.out.println(getEnumA().field7);
+ System.out.println(3);
+ System.out.println(getEnumA().field8);
+ System.out.println(2);
+ System.out.println(getEnumA().field9);
+ System.out.println(1);
+ }
+
+ @NeverInline
+ static EnumField getEnumA() {
+ return System.currentTimeMillis() > 0 ? EnumField.A : EnumField.B;
+ }
+
+ @NeverClassInline
+ enum EnumField {
+ A(1, 2, 3, 4, 5, 6, 7, 8, 9, 0),
+ B(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
+
+ int field0;
+ int field1;
+ int field2;
+ int field3;
+ int field4;
+ int field5;
+ int field6;
+ int field7;
+ int field8;
+ int field9;
+
+ EnumField(int i9, int i8, int i7, int i6, int i5, int i4, int i3, int i2, int i1, int i0) {
+ this.field0 = i0;
+ this.field1 = i1;
+ this.field2 = i2;
+ this.field3 = i3;
+ this.field4 = i4;
+ this.field5 = i5;
+ this.field6 = i6;
+ this.field7 = i7;
+ this.field8 = i8;
+ this.field9 = i9;
+ }
+ }
+ }
+
+ static class SuccessMultiConstructorIntField {
+
+ public static void main(String[] args) {
+ System.out.println(getEnumA().field0);
+ System.out.println(10);
+ System.out.println(getEnumA().field1);
+ System.out.println(-1);
+ System.out.println(getEnumB().field0);
+ System.out.println(20);
+ System.out.println(getEnumB().field1);
+ System.out.println(30);
+ }
+
+ @NeverInline
+ static EnumField getEnumA() {
+ return System.currentTimeMillis() > 0 ? EnumField.A : EnumField.B;
+ }
+
+ @NeverInline
+ static EnumField getEnumB() {
+ return System.currentTimeMillis() > 0 ? EnumField.B : EnumField.A;
+ }
+
+ @NeverClassInline
+ enum EnumField {
+ A(10),
+ B(20, 30);
+
+ int field0;
+ int field1;
+
+ EnumField(int i0) {
+ this.field0 = i0;
+ this.field1 = -1;
+ }
+
+ EnumField(int i0, int i1) {
+ this.field0 = i0;
+ this.field1 = i1;
+ }
+ }
+ }
+
+ static class SuccessStringField {
+
+ public static void main(String[] args) {
+ System.out.println(getEnumA().field);
+ System.out.println("AA");
+ System.out.println(getEnumB().field);
+ System.out.println("BB");
+ }
+
+ @NeverInline
+ static EnumField getEnumA() {
+ return System.currentTimeMillis() > 0 ? EnumField.A : EnumField.B;
+ }
+
+ @NeverInline
+ static EnumField getEnumB() {
+ return System.currentTimeMillis() > 0 ? EnumField.B : EnumField.A;
+ }
+
+ @NeverClassInline
+ enum EnumField {
+ A("AA"),
+ B("BB"),
+ C("CC");
+
+ String field;
+
+ EnumField(String s) {
+ this.field = s;
+ }
+ }
+ }
+
+ static class FailureBoxedInnerEnumField {
+
+ public static void main(String[] args) {
+ System.out.println(getEnumA().field);
+ System.out.println("X");
+ System.out.println(getEnumB().field);
+ System.out.println("Y");
+ }
+
+ @NeverInline
+ static EnumField getEnumA() {
+ return System.currentTimeMillis() > 0 ? EnumField.A : EnumField.B;
+ }
+
+ @NeverInline
+ static EnumField getEnumB() {
+ return System.currentTimeMillis() > 0 ? EnumField.B : EnumField.A;
+ }
+
+ @NeverClassInline
+ enum InnerEnum {
+ X,
+ Y,
+ Z;
+ }
+
+ @NeverClassInline
+ enum EnumField {
+ A(InnerEnum.X),
+ B(InnerEnum.Y),
+ C(InnerEnum.Z);
+
+ InnerEnum field;
+
+ EnumField(InnerEnum s) {
+ this.field = s;
+ }
+ }
+ }
+
+ static class FailureUnboxedEnumField {
+
+ public static void main(String[] args) {
+ System.out.println(getEnumA().field.ordinal());
+ System.out.println(0);
+ System.out.println(getEnumB().field.ordinal());
+ System.out.println(1);
+ }
+
+ @NeverInline
+ static EnumField getEnumA() {
+ return System.currentTimeMillis() > 0 ? EnumField.A : EnumField.B;
+ }
+
+ @NeverInline
+ static EnumField getEnumB() {
+ return System.currentTimeMillis() > 0 ? EnumField.B : EnumField.A;
+ }
+
+ @NeverClassInline
+ enum InnerEnum {
+ X,
+ Y,
+ Z;
+ }
+
+ @NeverClassInline
+ enum EnumField {
+ A(InnerEnum.X),
+ B(InnerEnum.Y),
+ C(InnerEnum.Z);
+
+ InnerEnum field;
+
+ EnumField(InnerEnum s) {
+ this.field = s;
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalHashCodeEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalHashCodeEnumUnboxingTest.java
index 79f9176..1946116 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalHashCodeEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalHashCodeEnumUnboxingTest.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.enumunboxing;
import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestParameters;
import java.util.List;
@@ -43,6 +44,7 @@
.addKeepMainRule(classToTest)
.addKeepRules(enumKeepRules.getKeepRules())
.enableNeverClassInliningAnnotations()
+ .enableInliningAnnotations()
.addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.allowDiagnosticInfoMessages()
.setMinApi(parameters.getApiLevel())
@@ -66,8 +68,29 @@
public static void main(String[] args) {
System.out.println(MyEnum.A.ordinal());
System.out.println(0);
- System.out.println(MyEnum.A.hashCode());
+ System.out.println(ordinal(MyEnum.A));
System.out.println(0);
+ System.out.println(ordinal(MyEnum.B));
+ System.out.println(1);
+ System.out.println(MyEnum.A.hashCode());
+ System.out.println(MyEnum.A.hashCode());
+ System.out.println(hash(MyEnum.A));
+ System.out.println(System.identityHashCode(MyEnum.A));
+ System.out.println(hash(null));
+ System.out.println(0);
+ Object o = new Object();
+ System.out.println(System.identityHashCode(o));
+ System.out.println(o.hashCode());
+ }
+
+ @NeverInline
+ private static int hash(MyEnum e) {
+ return System.identityHashCode(e);
+ }
+
+ @NeverInline
+ private static int ordinal(MyEnum e) {
+ return e.ordinal();
}
}
}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/UnusedCaseEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/UnusedCaseEnumUnboxingTest.java
new file mode 100644
index 0000000..a6b7027
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/UnusedCaseEnumUnboxingTest.java
@@ -0,0 +1,98 @@
+// 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.enumunboxing;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class UnusedCaseEnumUnboxingTest extends EnumUnboxingTestBase {
+ private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final EnumKeepRules enumKeepRules;
+
+ @Parameterized.Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public UnusedCaseEnumUnboxingTest(
+ TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
+ this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
+ }
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ R8TestRunResult run =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(UnusedCaseEnumUnboxingTest.class)
+ .addKeepMainRule(Main.class)
+ .enableNeverClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .addKeepRules(enumKeepRules.getKeepRules())
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .allowDiagnosticInfoMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::assertFieldsRemoved)
+ .inspectDiagnosticMessages(
+ m -> assertEnumIsUnboxed(MyEnum.class, Main.class.getSimpleName(), m))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
+ }
+
+ private void assertFieldsRemoved(CodeInspector codeInspector) {
+ codeInspector.clazz(Main.class);
+ }
+
+ @NeverClassInline
+ enum MyEnum {
+ USED1("used1"),
+ UNUSED1("unused1"),
+ USED2("used2"),
+ UNUSED2("unused2");
+
+ final String myField;
+
+ MyEnum(String data) {
+ this.myField = data;
+ }
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ printEnumField(MyEnum.USED1);
+ System.out.println("used1");
+ printEnumField(MyEnum.USED2);
+ System.out.println("used2");
+
+ printOrdinal(MyEnum.USED1);
+ System.out.println("0");
+ printOrdinal(MyEnum.USED2);
+ System.out.println("2");
+ }
+
+ @NeverInline
+ private static void printEnumField(MyEnum e) {
+ System.out.println(e.myField);
+ }
+
+ @NeverInline
+ private static void printOrdinal(MyEnum e) {
+ System.out.println(e.ordinal());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeCompilationBase.java b/src/test/java/com/android/tools/r8/internal/YouTubeCompilationBase.java
index a6dd6b0..c8be865 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeCompilationBase.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeCompilationBase.java
@@ -22,6 +22,7 @@
static final String PG_MAP = "YouTubeRelease_proguard.map";
static final String PG_CONF = "YouTubeRelease_proguard.config";
static final String PG_PROTO_CONF = "YouTubeRelease_proto_safety.pgconf";
+ static final String PG_MISSING_CLASSES_CONF = "YouTubeRelease_proguard_missing_classes.config";
final String base;
@@ -38,9 +39,11 @@
ImmutableList.Builder<Path> builder = ImmutableList.builder();
builder.add(Paths.get(base).resolve(PG_CONF));
builder.add(Paths.get(ToolHelper.PROGUARD_SETTINGS_FOR_INTERNAL_APPS).resolve(PG_CONF));
- Path config = Paths.get(base).resolve(PG_PROTO_CONF);
- if (config.toFile().exists()) {
- builder.add(config);
+ for (String name : new String[] {PG_PROTO_CONF, PG_MISSING_CLASSES_CONF}) {
+ Path config = Paths.get(base).resolve(name);
+ if (config.toFile().exists()) {
+ builder.add(config);
+ }
}
return builder.build();
}
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1533TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1533TreeShakeJarVerificationTest.java
new file mode 100644
index 0000000..632210e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1533TreeShakeJarVerificationTest.java
@@ -0,0 +1,93 @@
+// 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.internal;
+
+import static com.android.tools.r8.ToolHelper.isLocalDevelopment;
+import static com.android.tools.r8.ToolHelper.shouldRunSlowTests;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+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.graph.DexItemFactory;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.analysis.ProtoApplicationStats;
+import java.nio.file.Paths;
+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 YouTubeV1533TreeShakeJarVerificationTest extends YouTubeCompilationBase {
+
+ private static final boolean DUMP = false;
+ private static final int MAX_SIZE = 27500000;
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public YouTubeV1533TreeShakeJarVerificationTest(TestParameters parameters) {
+ super(15, 33);
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ // TODO(b/141603168): Enable this on the bots.
+ assumeTrue(isLocalDevelopment());
+ assumeTrue(shouldRunSlowTests());
+
+ LibrarySanitizer librarySanitizer =
+ new LibrarySanitizer(temp)
+ .addProgramFiles(getProgramFiles())
+ .addLibraryFiles(getLibraryFiles())
+ .sanitize()
+ .assertSanitizedProguardConfigurationIsEmpty();
+
+ R8TestCompileResult compileResult =
+ testForR8(Backend.DEX)
+ .addProgramFiles(getProgramFiles())
+ .addLibraryFiles(librarySanitizer.getSanitizedLibrary())
+ .addKeepRuleFiles(getKeepRuleFiles())
+ .addMainDexRuleFiles(getMainDexRuleFiles())
+ .allowDiagnosticMessages()
+ .allowUnusedProguardConfigurationRules()
+ .setMinApi(AndroidApiLevel.H_MR2)
+ .compile();
+
+ if (ToolHelper.isLocalDevelopment()) {
+ if (DUMP) {
+ long time = System.currentTimeMillis();
+ compileResult.writeToZip(Paths.get("YouTubeV1533-" + time + ".zip"));
+ compileResult.writeProguardMap(Paths.get("YouTubeV1533-" + time + ".map"));
+ }
+
+ DexItemFactory dexItemFactory = new DexItemFactory();
+ ProtoApplicationStats original =
+ new ProtoApplicationStats(dexItemFactory, new CodeInspector(getProgramFiles()));
+ ProtoApplicationStats actual =
+ new ProtoApplicationStats(dexItemFactory, compileResult.inspector(), original);
+ ProtoApplicationStats baseline =
+ new ProtoApplicationStats(
+ dexItemFactory,
+ new CodeInspector(getReleaseApk(), getReleaseProguardMap().toString()));
+ System.out.println(actual.getStats(baseline));
+ }
+
+ int applicationSize = compileResult.app.applicationSize();
+ System.out.println(applicationSize);
+
+ assertTrue(
+ "Expected max size of " + MAX_SIZE + ", got " + applicationSize,
+ applicationSize < MAX_SIZE);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
index a031cf8..b351ca2 100644
--- a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
@@ -100,9 +100,10 @@
return iterator;
}
- private AndroidApp writeDex(DexApplication application, InternalOptions options) {
+ private AndroidApp writeDex() {
try {
- ToolHelper.writeApplication(application, options);
+ InternalOptions options = appView.options();
+ ToolHelper.writeApplication(appView, options);
options.signalFinishedToConsumers();
return consumers.build();
} catch (ExecutionException e) {
@@ -114,7 +115,7 @@
Timing timing = Timing.empty();
IRConverter converter = new IRConverter(appView, timing, null, MainDexClasses.NONE);
converter.replaceCodeForTesting(method, code);
- AndroidApp app = writeDex(application, appView.options());
+ AndroidApp app = writeDex();
return runOnArtRaw(app, DEFAULT_MAIN_CLASS_NAME).stdout;
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index 1d7f782..807b061 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -359,7 +359,9 @@
assertEquals(0, invokeCount);
// The enum parameter may get unboxed.
- m = clazz.uniqueMethodWithName("moreControlFlows");
+ m =
+ clazz.uniqueMethodWithName(
+ parameters.isCfRuntime() ? "moreControlFlows" : "moreControlFlows$enumunboxing$");
assertTrue(m.isPresent());
// Verify that a.b() is resolved to an inline instance-get.
diff --git a/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java b/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java
index 6f5aade..5dfd98f 100644
--- a/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java
@@ -109,11 +109,12 @@
}
private String getDefaultExpectedName(String name) {
- if (!isCompat && !keepSourceFile) {
- return null;
- } else {
- return name;
+ if (!keepSourceFile) {
+ if (parameters.getBackend() == Backend.CF || !isCompat) {
+ return null;
+ }
}
+ return name;
}
private void inspectOutput(
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
index a55e1d5..3959d84 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
@@ -127,6 +127,7 @@
@Test
public void testLineNumberTableOnly() throws Exception {
assumeTrue(compat);
+ assumeTrue(parameters.isDexRuntime());
runTest(
ImmutableList.of("-keepattributes LineNumberTable"), this::checkIsSameExceptForFileName);
}
@@ -134,6 +135,7 @@
@Test
public void testNoLineNumberTable() throws Exception {
assumeTrue(compat);
+ assumeTrue(parameters.isDexRuntime());
runTest(ImmutableList.of(), this::checkIsSameExceptForFileNameAndLineNumber);
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java
index 5562395..2ffe206 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java
@@ -61,6 +61,7 @@
@Test
public void testLineNumberTableOnly() throws Exception {
assumeTrue(compat);
+ assumeTrue(parameters.isDexRuntime());
runTest(
ImmutableList.of("-keepattributes LineNumberTable"),
(StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
@@ -72,6 +73,7 @@
@Test
public void testNoLineNumberTable() throws Exception {
assumeTrue(compat);
+ assumeTrue(parameters.isDexRuntime());
runTest(
ImmutableList.of(),
(StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/VerticalClassMergingRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/VerticalClassMergingRetraceTest.java
index 46da60b..0482945 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/VerticalClassMergingRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/VerticalClassMergingRetraceTest.java
@@ -93,6 +93,7 @@
@Test
public void testLineNumberTableOnly() throws Exception {
assumeTrue(compat);
+ assumeTrue(parameters.isDexRuntime());
runTest(
ImmutableList.of("-keepattributes LineNumberTable"),
(StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
@@ -132,6 +133,7 @@
// at com.android.tools.r8.naming.retraceproguard.MainApp.main(MainApp.java:7)
// since the synthetic bridge belongs to ResourceWrapper.foo.
assumeTrue(compat);
+ assumeTrue(parameters.isDexRuntime());
haveSeenLines.clear();
runTest(
ImmutableList.of(),
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/InliningRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/InliningRetraceTest.java
index aafa229..8d94fc8 100644
--- a/src/test/java/com/android/tools/r8/naming/retraceproguard/InliningRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/InliningRetraceTest.java
@@ -61,6 +61,7 @@
@Test
public void testLineNumberTableOnly() throws Exception {
assumeTrue(compat);
+ assumeTrue(backend == Backend.DEX);
runTest(
ImmutableList.of("-keepattributes LineNumberTable"),
(StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
@@ -72,6 +73,7 @@
@Test
public void testNoLineNumberTable() throws Exception {
assumeTrue(compat);
+ assumeTrue(backend == Backend.DEX);
runTest(
ImmutableList.of(),
(StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java
index 2874bf9..41c6451 100644
--- a/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java
@@ -87,6 +87,7 @@
@Test
public void testLineNumberTableOnly() throws Exception {
assumeTrue(compat);
+ assumeTrue(backend == Backend.DEX);
runTest(
ImmutableList.of("-keepattributes LineNumberTable"),
(StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
@@ -102,6 +103,7 @@
@Test
public void testNoLineNumberTable() throws Exception {
assumeTrue(compat);
+ assumeTrue(backend == Backend.DEX);
haveSeenLines.clear();
runTest(
ImmutableList.of(),
diff --git a/src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/NoFieldMembersTest.java b/src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/EmptyClassTest.java
similarity index 65%
copy from src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/NoFieldMembersTest.java
copy to src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/EmptyClassTest.java
index 8dfe240..e0d9a33 100644
--- a/src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/NoFieldMembersTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/EmptyClassTest.java
@@ -6,20 +6,23 @@
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.*;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
import com.android.tools.r8.utils.BooleanUtils;
-import java.util.*;
+import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
-public class NoFieldMembersTest extends TestBase {
+public class EmptyClassTest extends TestBase {
private final TestParameters parameters;
private final boolean enableHorizontalClassMerging;
- public NoFieldMembersTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
+ public EmptyClassTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
this.parameters = parameters;
this.enableHorizontalClassMerging = enableHorizontalClassMerging;
}
@@ -33,26 +36,20 @@
@Test
public void testR8() throws Exception {
testForR8(parameters.getBackend())
- .addInnerClasses(NoFieldMembersTest.class)
+ .addInnerClasses(EmptyClassTest.class)
.addKeepMainRule(Main.class)
.addOptionsModification(
options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
- .enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
- .assertSuccessWithOutputLines("foo", "bar")
+ .assertSuccess()
.inspect(
codeInspector -> {
if (enableHorizontalClassMerging) {
- // TODO(b/163311975): A and B should be merged
- //
- // Class[] classes = { A.class, B.class };
- // assertEquals(1, Arrays.stream(classes)
- // .filter(a -> codeInspector.clazz(a).isPresent())
- // .count());
assertThat(codeInspector.clazz(A.class), isPresent());
- assertThat(codeInspector.clazz(B.class), isPresent());
+ assertThat(codeInspector.clazz(B.class), not(isPresent()));
+ // TODO(b/165517236): Explicitly check classes have been merged.
} else {
assertThat(codeInspector.clazz(A.class), isPresent());
assertThat(codeInspector.clazz(B.class), isPresent());
@@ -61,27 +58,20 @@
}
@NeverClassInline
- public static class A {
- @NeverInline
- public void foo() {
- System.out.println("foo");
- }
- }
+ public static class A {}
@NeverClassInline
public static class B {
- @NeverInline
- public void bar() {
- System.out.println("bar");
- }
+ // TODO(b/164924717): remove non overlapping constructor requirement
+ public B(String s) {}
}
public static class Main {
public static void main(String[] args) {
A a = new A();
- a.foo();
- B b = new B();
- b.bar();
+ System.out.println(a);
+ B b = new B("");
+ System.out.println(b);
}
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/IdenticalFieldMembersTest.java b/src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/IdenticalFieldMembersTest.java
index 456c4cb..7935860 100644
--- a/src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/IdenticalFieldMembersTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/IdenticalFieldMembersTest.java
@@ -42,18 +42,15 @@
.enableNeverClassInliningAnnotations()
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
- .assertSuccessWithOutputLines("foo A", "bar B")
+ .assertSuccessWithOutputLines("foo A", "bar 2")
.inspect(
codeInspector -> {
if (enableHorizontalClassMerging) {
- // TODO(b/163311975): A and B should be merged
- //
- // Class[] classes = {A.class, B.class};
- // assertEquals(1, Arrays.stream(classes)
- // .filter(a -> codeInspector.clazz(a).isPresent())
- // .count());
assertThat(codeInspector.clazz(A.class), isPresent());
assertThat(codeInspector.clazz(B.class), isPresent());
+ // TODO(b/163311975): A and B should be merged
+ // assertThat(codeInspector.clazz(B.class), not(isPresent()));
+ // TODO(b/165517236): Explicitly check classes have been merged.
} else {
assertThat(codeInspector.clazz(A.class), isPresent());
assertThat(codeInspector.clazz(B.class), isPresent());
@@ -79,8 +76,8 @@
public static class B {
private String field;
- public B(String v) {
- this.field = v;
+ public B(int v) {
+ this.field = Integer.toString(v);
}
@NeverInline
@@ -93,7 +90,7 @@
public static void main(String[] args) {
A a = new A("A");
a.foo();
- B b = new B("B");
+ B b = new B(2);
b.bar();
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/NoOverlappingConstructorsPolicyTest.java b/src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/NoOverlappingConstructorsPolicyTest.java
new file mode 100644
index 0000000..e646087
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/NoOverlappingConstructorsPolicyTest.java
@@ -0,0 +1,102 @@
+// 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.shaking.horizontalclassmerging;
+
+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.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.shaking.horizontalclassmerging.EmptyClassTest.A;
+import com.android.tools.r8.shaking.horizontalclassmerging.EmptyClassTest.B;
+import com.android.tools.r8.shaking.horizontalclassmerging.EmptyClassTest.Main;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NoOverlappingConstructorsPolicyTest extends TestBase {
+ private final TestParameters parameters;
+ private final boolean enableHorizontalClassMerging;
+
+ public NoOverlappingConstructorsPolicyTest(
+ TestParameters parameters, boolean enableHorizontalClassMerging) {
+ this.parameters = parameters;
+ this.enableHorizontalClassMerging = enableHorizontalClassMerging;
+ }
+
+ @Parameterized.Parameters(name = "{0}, horizontalClassMerging:{1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(this.getClass())
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .inspect(
+ codeInspector -> {
+ if (enableHorizontalClassMerging) {
+ assertThat(codeInspector.clazz(A.class), isPresent());
+ assertThat(codeInspector.clazz(B.class), isPresent());
+ assertThat(codeInspector.clazz(C.class), not(isPresent()));
+ // TODO(b/165517236): Explicitly check classes have been merged.
+ } else {
+ assertThat(codeInspector.clazz(A.class), isPresent());
+ assertThat(codeInspector.clazz(B.class), isPresent());
+ assertThat(codeInspector.clazz(C.class), isPresent());
+ }
+ });
+ }
+
+ @NeverClassInline
+ public static class A {
+ public A(String s) {
+ System.out.println(s);
+ }
+ }
+
+ @NeverClassInline
+ public static class B {
+ public B(String s) {
+ System.out.println(s);
+ }
+
+ public B(boolean b) {
+ System.out.println(b);
+ }
+ }
+
+ @NeverClassInline
+ public static class C {
+ public C(boolean b) {
+ System.out.println(b);
+ }
+ }
+
+ public static class Main {
+ public static void main(String[] args) {
+ A a = new A("foo");
+ System.out.println(a);
+ B b1 = new B("");
+ System.out.println(b1);
+ B b2 = new B(false);
+ System.out.println(b2);
+ C c = new C(true);
+ System.out.println(c);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/RemapMethodTest.java b/src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/RemapMethodTest.java
new file mode 100644
index 0000000..97df366
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/RemapMethodTest.java
@@ -0,0 +1,127 @@
+// 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.shaking.horizontalclassmerging;
+
+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;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RemapMethodTest extends TestBase {
+ private final TestParameters parameters;
+ private final boolean enableHorizontalClassMerging;
+
+ public RemapMethodTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
+ this.parameters = parameters;
+ this.enableHorizontalClassMerging = enableHorizontalClassMerging;
+ }
+
+ @Parameterized.Parameters(name = "{0}, horizontalClassMerging:{1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(this.getClass())
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options -> {
+ options.enableHorizontalClassMerging = enableHorizontalClassMerging;
+ options.enableVerticalClassMerging = false;
+ })
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ // .run(parameters.getRuntime(), Main.class)
+ // .assertSuccessWithOutputLines("foo", "foo", "bar", "bar")
+ .inspect(
+ codeInspector -> {
+ assertThat(codeInspector.clazz(A.class), isPresent());
+ assertThat(codeInspector.clazz(C.class), isPresent());
+ if (enableHorizontalClassMerging) {
+ assertThat(codeInspector.clazz(B.class), not(isPresent()));
+ assertThat(codeInspector.clazz(D.class), not(isPresent()));
+ // TODO(b/165517236): Explicitly check classes have been merged.
+ } else {
+ assertThat(codeInspector.clazz(B.class), isPresent());
+ assertThat(codeInspector.clazz(D.class), isPresent());
+ }
+ });
+ }
+
+ @NeverClassInline
+ public static class A {
+ @NeverInline
+ public void foo() {
+ System.out.println("foo");
+ }
+ }
+
+ @NeverClassInline
+ public static class B {
+ // TODO(b/164924717): remove non overlapping constructor requirement
+ public B(String s) {}
+
+ @NeverInline
+ public void bar(D d) {
+ d.bar();
+ }
+ }
+
+ @NeverClassInline
+ public static class Other {
+ String field;
+
+ public Other() {
+ field = "";
+ }
+ }
+
+ @NeverClassInline
+ public static class C extends Other {
+ @NeverInline
+ public void foo() {
+ System.out.println("foo");
+ }
+ }
+
+ @NeverClassInline
+ public static class D extends Other {
+ public D(String s) {
+ System.out.println(s);
+ }
+
+ @NeverInline
+ public void bar() {
+ System.out.println("bar");
+ }
+ }
+
+ public static class Main {
+ public static void main(String[] args) {
+ A a = new A();
+ a.foo();
+ B b = new B("bar");
+ C c = new C();
+ c.foo();
+ D d = new D("bar");
+ b.bar(d);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/NoFieldMembersTest.java b/src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/VirtualMethodNotOverlappingTest.java
similarity index 76%
rename from src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/NoFieldMembersTest.java
rename to src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/VirtualMethodNotOverlappingTest.java
index 8dfe240..711f217 100644
--- a/src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/NoFieldMembersTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/VirtualMethodNotOverlappingTest.java
@@ -6,6 +6,7 @@
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.*;
import com.android.tools.r8.utils.BooleanUtils;
@@ -15,11 +16,12 @@
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
-public class NoFieldMembersTest extends TestBase {
+public class VirtualMethodNotOverlappingTest extends TestBase {
private final TestParameters parameters;
private final boolean enableHorizontalClassMerging;
- public NoFieldMembersTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
+ public VirtualMethodNotOverlappingTest(
+ TestParameters parameters, boolean enableHorizontalClassMerging) {
this.parameters = parameters;
this.enableHorizontalClassMerging = enableHorizontalClassMerging;
}
@@ -33,7 +35,7 @@
@Test
public void testR8() throws Exception {
testForR8(parameters.getBackend())
- .addInnerClasses(NoFieldMembersTest.class)
+ .addInnerClasses(VirtualMethodNotOverlappingTest.class)
.addKeepMainRule(Main.class)
.addOptionsModification(
options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
@@ -45,14 +47,9 @@
.inspect(
codeInspector -> {
if (enableHorizontalClassMerging) {
- // TODO(b/163311975): A and B should be merged
- //
- // Class[] classes = { A.class, B.class };
- // assertEquals(1, Arrays.stream(classes)
- // .filter(a -> codeInspector.clazz(a).isPresent())
- // .count());
assertThat(codeInspector.clazz(A.class), isPresent());
- assertThat(codeInspector.clazz(B.class), isPresent());
+ assertThat(codeInspector.clazz(B.class), not(isPresent()));
+ // TODO(b/165517236): Explicitly check classes have been merged.
} else {
assertThat(codeInspector.clazz(A.class), isPresent());
assertThat(codeInspector.clazz(B.class), isPresent());
@@ -70,6 +67,9 @@
@NeverClassInline
public static class B {
+ // TODO(b/164924717): remove non overlapping constructor requirement
+ public B(String s) {}
+
@NeverInline
public void bar() {
System.out.println("bar");
@@ -80,7 +80,7 @@
public static void main(String[] args) {
A a = new A();
a.foo();
- B b = new B();
+ B b = new B("");
b.bar();
}
}