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(); } }