Merge commit '875dd27341cfbec28ba37d0a6fa7a93696460d7e' into dev-release
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 7c9cae3..a872ae8 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -57,10 +57,10 @@
def CheckForAddedDisassemble(input_api, output_api):
results = []
for (file, line_nr, line) in input_api.RightHandSideLines():
- if 'disassemble()' in line:
+ if file.LocalPath().endswith('.java') and '.disassemble()' in line:
results.append(
output_api.PresubmitError(
- '%s:%s %s' % (file.LocalPath(), line_nr, line)))
+ 'Test call to disassemble\n%s:%s %s' % (file.LocalPath(), line_nr, line)))
return results
def CheckForCopyRight(input_api, output_api, branch):
diff --git a/build.gradle b/build.gradle
index 693cdba..7429345 100644
--- a/build.gradle
+++ b/build.gradle
@@ -284,6 +284,7 @@
def r8LibPath = "$buildDir/libs/r8lib.jar"
def r8LibExludeDepsPath = "$buildDir/libs/r8lib-exclude-deps.jar"
+def r8DesugaredPath = "$buildDir/libs/r8desugared.jar"
def r8LibGeneratedKeepRulesPath = "$buildDir/generated/keep.txt"
def r8LibTestPath = "$buildDir/classes/r8libtest"
def java11ClassFiles = "build/classes/java/mainJava11"
@@ -748,7 +749,7 @@
outputs.file output
workingDir = projectDir
inputs.files r8Task.outputs.files + r8WithDeps.outputs.files
- commandLine baseR8CommandLine([
+ commandLine baseCompilerCommandLine([
"relocator",
"--input",
r8Task.outputs.files[0],
@@ -890,11 +891,21 @@
}
}
+def baseCompilerCommandLine(compiler, args = []) {
+ // Execute r8 commands against a stable r8 with dependencies.
+ // TODO(b/139725780): See if we can remove or lower the heap size (-Xmx8g).
+ return [org.gradle.internal.jvm.Jvm.current().getJavaExecutable(),
+ "-Xmx8g", "-ea", "-jar", r8WithDeps.outputs.files[0]] + compiler + args
+}
+
def baseR8CommandLine(args = []) {
// Execute r8 commands against a stable r8 with dependencies.
- // TODO(b/139725780): See if we can remove or lower the heap size (-Xmx6g).
- return [org.gradle.internal.jvm.Jvm.current().getJavaExecutable(),
- "-Xmx8g", "-ea", "-jar", r8WithDeps.outputs.files[0]] + args
+ return baseCompilerCommandLine("r8", args)
+}
+
+def baseD8CommandLine(args = []) {
+ // Execute r8 commands against a stable r8 with dependencies.
+ return baseCompilerCommandLine("d8", args)
}
def r8CfCommandLine(input, output, pgConfs = [], args = ["--release"], libs = []) {
@@ -908,6 +919,16 @@
return baseR8CommandLine(allArgs)
}
+def d8CfCommandLine(input, output, args = ["--release"], libs = []) {
+ def allArgs = [
+ "--classfile",
+ input,
+ "--output", output,
+ "--lib", "third_party/openjdk/openjdk-rt-1.8/rt.jar"
+ ] + args + libs.collectMany { ["--lib", it] }
+ return baseD8CommandLine(allArgs)
+}
+
def r8LibCreateTask(name, pgConfs = [], r8Task, output, args = ["--release"], libs = []) {
return tasks.create("r8Lib${name}", Exec) {
inputs.files ([pgConfs, r8WithRelocatedDeps.outputs, r8Task.outputs, libs])
@@ -942,7 +963,7 @@
outputs.file output
workingDir = projectDir
inputs.files (testJarSources.outputs.files + r8WithDeps.outputs.files)
- commandLine baseR8CommandLine([
+ commandLine baseCompilerCommandLine([
"relocator",
"--input",
testJarSources.outputs.files[0],
@@ -958,7 +979,7 @@
// TODO(b/154785341): We should remove this.
standardOutput new FileOutputStream(r8LibGeneratedKeepRulesPath)
}
- // Depend on r8WithDeps for running baseR8CommandLine.
+ // Depend on r8WithDeps for running baseCompilerCommandLine.
dependsOn r8WithDeps
dependsOn r8NoManifestWithRelocatedDeps
dependsOn testJar
@@ -968,7 +989,7 @@
r8NoManifestWithRelocatedDeps.outputs,
testJar.outputs])
outputs.file r8LibGeneratedKeepRulesPath
- commandLine baseR8CommandLine([
+ commandLine baseCompilerCommandLine([
"printuses",
"--keeprules-allowobfuscation",
"third_party/openjdk/openjdk-rt-1.8/rt.jar",
@@ -1008,6 +1029,18 @@
outputs.file r8LibExludeDepsPath
}
+task R8Desugared(type: Exec) {
+ dependsOn downloadOpenJDKrt
+ dependsOn r8NoManifestWithRelocatedDeps
+ inputs.files r8NoManifestWithRelocatedDeps.outputs.files
+ commandLine d8CfCommandLine(
+ r8NoManifestWithRelocatedDeps.outputs.files[0],
+ r8DesugaredPath,
+ ["--release"])
+ workingDir = projectDir
+ outputs.file r8DesugaredPath
+}
+
task sourceJar(type: Jar, dependsOn: classes) {
classifier = 'src'
from sourceSets.main.allSource
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index cf475fa..a34b8ac 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -395,7 +395,7 @@
// Assert some of R8 optimizations are disabled.
assert !internal.enableInlining;
assert !internal.enableClassInlining;
- assert !internal.enableHorizontalClassMerging;
+ assert !internal.enableStaticClassMerging;
assert !internal.enableVerticalClassMerging;
assert !internal.enableClassStaticizer;
assert !internal.enableEnumValueOptimization;
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index bacd375..243b37a 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -49,13 +49,12 @@
try {
DirectMappedDexApplication application =
new ApplicationReader(app, options, timing).read(executor).toDirect();
- AppView<? extends AppInfoWithClassHierarchy> appView =
- AppView.createForR8(new AppInfoWithClassHierarchy(application));
+ AppView<? extends AppInfoWithClassHierarchy> appView = AppView.createForR8(application);
appView.setAppServices(AppServices.builder(appView).build());
MainDexListBuilder.checkForAssumedLibraryTypes(appView.appInfo());
- SubtypingInfo subtypingInfo = new SubtypingInfo(application.allClasses(), application);
+ SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
RootSet mainDexRootSet =
new RootSetBuilder(appView, subtypingInfo, options.mainDexKeepRules).run(executor);
@@ -71,7 +70,7 @@
EnqueuerFactory.createForMainDexTracing(appView, subtypingInfo, graphConsumer);
Set<DexProgramClass> liveTypes = enqueuer.traceMainDex(mainDexRootSet, executor, timing);
// LiveTypes is the result.
- MainDexClasses mainDexClasses = new MainDexListBuilder(liveTypes, application).run();
+ MainDexClasses mainDexClasses = new MainDexListBuilder(liveTypes, appView).run();
List<String> result =
mainDexClasses.getClasses().stream()
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 48f2f7a..cae584b 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -167,7 +167,7 @@
// Assert some of R8 optimizations are disabled.
assert !internal.enableInlining;
assert !internal.enableClassInlining;
- assert !internal.enableHorizontalClassMerging;
+ assert !internal.enableStaticClassMerging;
assert !internal.enableVerticalClassMerging;
assert !internal.enableClassStaticizer;
assert !internal.enableEnumValueOptimization;
diff --git a/src/main/java/com/android/tools/r8/PrintSeeds.java b/src/main/java/com/android/tools/r8/PrintSeeds.java
index 3f8b70e..b8a426c 100644
--- a/src/main/java/com/android/tools/r8/PrintSeeds.java
+++ b/src/main/java/com/android/tools/r8/PrintSeeds.java
@@ -87,10 +87,9 @@
try {
DirectMappedDexApplication application =
new ApplicationReader(command.getInputApp(), options, timing).read(executor).toDirect();
- AppView<? extends AppInfoWithClassHierarchy> appView =
- AppView.createForR8(new AppInfoWithClassHierarchy(application));
+ AppView<AppInfoWithClassHierarchy> appView = AppView.createForR8(application);
appView.setAppServices(AppServices.builder(appView).build());
- SubtypingInfo subtypingInfo = new SubtypingInfo(application.allClasses(), application);
+ SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
RootSet rootSet =
new RootSetBuilder(appView, subtypingInfo, options.getProguardConfiguration().getRules())
.run(executor);
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index c94d67c..45ce043 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -182,6 +182,12 @@
return false;
}
+ @Override
+ public boolean registerInstanceOf(DexType type) {
+ addType(type);
+ return false;
+ }
+
private void addType(DexType type) {
if (isTargetType(type) && types.add(type)) {
DexClass clazz = appInfo.definitionFor(type);
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 93f229c..c006eb5 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -118,6 +118,7 @@
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -268,6 +269,10 @@
new R8(options).run(app, executor);
}
+ private static DirectMappedDexApplication getDirectApp(AppView<?> appView) {
+ return appView.appInfo().app().asDirect();
+ }
+
private void run(AndroidApp inputApp, ExecutorService executorService) throws IOException {
assert options.programConsumer != null;
if (options.quiet) {
@@ -279,15 +284,17 @@
"Running R8 version " + Version.LABEL + " with assertions enabled."));
}
try {
- DirectMappedDexApplication application =
- new ApplicationReader(inputApp, options, timing).read(executorService).toDirect();
+ AppView<AppInfoWithClassHierarchy> appView;
+ {
+ DirectMappedDexApplication application =
+ new ApplicationReader(inputApp, options, timing).read(executorService).toDirect();
- // Now that the dex-application is fully loaded, close any internal archive providers.
- inputApp.closeInternalArchiveProviders();
+ // Now that the dex-application is fully loaded, close any internal archive providers.
+ inputApp.closeInternalArchiveProviders();
- AppView<AppInfoWithClassHierarchy> appView =
- AppView.createForR8(new AppInfoWithClassHierarchy(application));
- appView.setAppServices(AppServices.builder(appView).build());
+ appView = AppView.createForR8(application);
+ appView.setAppServices(AppServices.builder(appView).build());
+ }
// Check for potentially having pass-through of Cf-code for kotlin libraries.
options.enableCfByteCodePassThrough =
@@ -303,7 +310,7 @@
InterfaceMethodRewriter.checkForAssumedLibraryTypes(appView.appInfo(), options);
BackportedMethodRewriter.registerAssumedLibraryTypes(options);
if (options.enableEnumUnboxing) {
- if (application.definitionFor(options.itemFactory.enumUnboxingUtilityType) != null) {
+ if (appView.definitionFor(options.itemFactory.enumUnboxingUtilityType) != null) {
// The enum unboxing utility class can be created only during cf to dex compilation.
// If this is true, we are recompiling the dex application with R8 (compilation-steps).
options.enableEnumUnboxing = false;
@@ -318,7 +325,7 @@
Set<DexType> missingClasses = null;
try {
// TODO(b/154849103): Find a better way to determine missing classes.
- missingClasses = new SubtypingInfo(application.allClasses(), appView).getMissingClasses();
+ missingClasses = new SubtypingInfo(appView).getMissingClasses();
missingClasses = filterMissingClasses(
missingClasses, options.getProguardConfiguration().getDontWarnPatterns());
if (!missingClasses.isEmpty()) {
@@ -351,7 +358,7 @@
options.itemFactory, AndroidApiLevel.getAndroidApiLevel(options.minApiLevel)));
}
}
- SubtypingInfo subtypingInfo = new SubtypingInfo(application.allClasses(), application);
+ SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
appView.setRootSet(
new RootSetBuilder(
appView,
@@ -364,7 +371,6 @@
options.isShrinking() ? AnnotationRemover.builder() : null;
AppView<AppInfoWithLiveness> appViewWithLiveness =
runEnqueuer(annotationRemoverBuilder, executorService, appView, subtypingInfo);
- application = appViewWithLiveness.appInfo().app().asDirect();
assert appView.rootSet().verifyKeptFieldsAreAccessedAndLive(appViewWithLiveness.appInfo());
assert appView.rootSet().verifyKeptMethodsAreTargetedAndLive(appViewWithLiveness.appInfo());
assert appView.rootSet().verifyKeptTypesAreLive(appViewWithLiveness.appInfo());
@@ -392,15 +398,12 @@
shrinker -> shrinker.run(Mode.INITIAL_TREE_SHAKING));
TreePruner pruner = new TreePruner(appViewWithLiveness);
- application = pruner.run(application);
+ DirectMappedDexApplication prunedApp = pruner.run();
if (options.enableEnumUnboxing) {
DexProgramClass utilityClass =
EnumUnboxingRewriter.synthesizeEmptyEnumUnboxingUtilityClass(appView);
- // We cannot know at this point if the class will be on the main dex list,
- // updated later. Since this is inserted in the app at this point, we do not need
- // to use any synthesized class hack and add the class as a program class.
- application = application.builder().addProgramClass(utilityClass).build();
+ prunedApp = prunedApp.builder().addProgramClass(utilityClass).build();
}
// Recompute the subtyping information.
@@ -410,7 +413,7 @@
.appInfo()
.withLiveness()
.prunedCopyFrom(
- application,
+ prunedApp,
removedClasses,
pruner.getMethodsToKeepForConfigurationDebugging()));
appView.setAppServices(appView.appServices().prunedCopy(removedClasses));
@@ -431,7 +434,7 @@
}
assert appView.appInfo().hasLiveness();
- assert verifyNoJarApplicationReaders(application.classes());
+ assert verifyNoJarApplicationReaders(appView.appInfo().classes());
// Build conservative main dex content after first round of tree shaking. This is used
// by certain optimizations to avoid introducing additional class references into main dex
// classes, as that can cause the final number of main dex methods to grow.
@@ -440,8 +443,7 @@
if (!options.mainDexKeepRules.isEmpty()) {
assert appView.graphLens().isIdentityLens();
// Find classes which may have code executed before secondary dex files installation.
- SubtypingInfo subtypingInfo =
- new SubtypingInfo(appView.appInfo().app().asDirect().allClasses(), appView);
+ SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
mainDexRootSet =
new RootSetBuilder(appView, subtypingInfo, options.mainDexKeepRules)
.run(executorService);
@@ -450,7 +452,7 @@
EnqueuerFactory.createForMainDexTracing(appView, subtypingInfo)
.traceMainDex(mainDexRootSet, executorService, timing);
// Calculate the automatic main dex list according to legacy multidex constraints.
- mainDexClasses = new MainDexListBuilder(mainDexBaseClasses, application).run();
+ mainDexClasses = new MainDexListBuilder(mainDexBaseClasses, appView).run();
appView.appInfo().unsetObsolete();
}
@@ -464,7 +466,11 @@
SubtypingInfo subtypingInfo = appViewWithLiveness.appInfo().computeSubtypingInfo();
GraphLens publicizedLens =
ClassAndMemberPublicizer.run(
- executorService, timing, application, appViewWithLiveness, subtypingInfo);
+ executorService,
+ timing,
+ appViewWithLiveness.appInfo().app(),
+ appViewWithLiveness,
+ subtypingInfo);
boolean changed = appView.setGraphLens(publicizedLens);
if (changed) {
// We can now remove visibility bridges. Note that we do not need to update the
@@ -481,13 +487,8 @@
if (options.shouldDesugarNests()) {
timing.begin("NestBasedAccessDesugaring");
R8NestBasedAccessDesugaring analyzer = new R8NestBasedAccessDesugaring(appViewWithLiveness);
- NestedPrivateMethodLens lens = analyzer.run(executorService, application.builder());
- if (lens != null) {
- boolean changed = appView.setGraphLens(lens);
- assert changed;
- appViewWithLiveness.setAppInfo(
- appViewWithLiveness.appInfo().rewrittenWithLens(application.asDirect(), lens));
- }
+ NestedPrivateMethodLens lens = analyzer.run(executorService);
+ appView.rewriteWithLens(lens);
timing.end();
} else {
timing.begin("NestReduction");
@@ -504,32 +505,27 @@
if (!isKotlinLibraryCompilationWithInlinePassThrough
&& options.getProguardConfiguration().isOptimizing()) {
- if (options.enableHorizontalClassMerging) {
+ if (options.enableStaticClassMerging) {
timing.begin("HorizontalStaticClassMerger");
StaticClassMerger staticClassMerger =
new StaticClassMerger(appViewWithLiveness, options, mainDexClasses);
NestedGraphLens lens = staticClassMerger.run();
- if (lens != null) {
- boolean changed = appView.setGraphLens(lens);
- assert changed;
- appViewWithLiveness.setAppInfo(
- appViewWithLiveness.appInfo().rewrittenWithLens(application.asDirect(), lens));
- }
+ appView.rewriteWithLens(lens);
timing.end();
}
if (options.enableVerticalClassMerging) {
timing.begin("VerticalClassMerger");
VerticalClassMerger verticalClassMerger =
new VerticalClassMerger(
- application, appViewWithLiveness, executorService, timing, mainDexClasses);
+ getDirectApp(appViewWithLiveness),
+ appViewWithLiveness,
+ executorService,
+ timing,
+ mainDexClasses);
VerticalClassMergerGraphLens lens = verticalClassMerger.run();
if (lens != null) {
- boolean changed = appView.setGraphLens(lens);
- assert changed;
appView.setVerticallyMergedClasses(verticalClassMerger.getMergedClasses());
- application = application.asDirect().rewrittenWithLens(lens);
- appViewWithLiveness.setAppInfo(
- appViewWithLiveness.appInfo().rewrittenWithLens(application.asDirect(), lens));
+ appView.rewriteWithLens(lens);
}
timing.end();
}
@@ -542,13 +538,8 @@
appViewWithLiveness,
new MethodPoolCollection(appViewWithLiveness, subtypingInfo))
.run(executorService, timing);
- if (lens != null) {
- boolean changed = appView.setGraphLens(lens);
- assert changed;
- assert application.asDirect().verifyNothingToRewrite(appView, lens);
- appViewWithLiveness.setAppInfo(
- appViewWithLiveness.appInfo().rewrittenWithLens(application.asDirect(), lens));
- }
+ assert lens == null || getDirectApp(appView).verifyNothingToRewrite(appView, lens);
+ appView.rewriteWithLens(lens);
timing.end();
}
if (options.enableUninstantiatedTypeOptimization) {
@@ -560,13 +551,8 @@
new MethodPoolCollection(appViewWithLiveness, subtypingInfo),
executorService,
timing);
- if (lens != null) {
- boolean changed = appView.setGraphLens(lens);
- assert changed;
- assert application.asDirect().verifyNothingToRewrite(appView, lens);
- appViewWithLiveness.setAppInfo(
- appViewWithLiveness.appInfo().rewrittenWithLens(application.asDirect(), lens));
- }
+ assert lens == null || getDirectApp(appView).verifyNothingToRewrite(appView, lens);
+ appView.rewriteWithLens(lens);
timing.end();
}
}
@@ -585,11 +571,20 @@
appView.setAppServices(appView.appServices().rewrittenWithLens(appView.graphLens()));
+ // Collect the already pruned types before creating a new app info without liveness.
+ // TODO: we should avoid removing liveness.
+ Set<DexType> prunedTypes = appView.withLiveness().appInfo().getPrunedTypes();
+
+ // TODO: move to appview.
+ EnumValueInfoMapCollection enumValueInfoMapCollection =
+ appViewWithLiveness.appInfo().getEnumValueInfoMapCollection();
+
timing.begin("Create IR");
CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
try {
IRConverter converter = new IRConverter(appView, timing, printer, mainDexClasses);
- application = converter.optimize(executorService).asDirect();
+ DexApplication application = converter.optimize(executorService).asDirect();
+ appView.setAppInfo(appView.appInfo().rebuild(previous -> application));
} finally {
timing.end();
}
@@ -601,7 +596,7 @@
// graph lens entirely, though, since it is needed for mapping all field and method signatures
// back to the original program.
timing.begin("AppliedGraphLens construction");
- appView.setGraphLens(new AppliedGraphLens(appView, application.classes()));
+ appView.setGraphLens(new AppliedGraphLens(appView));
timing.end();
if (options.printCfg) {
@@ -616,15 +611,7 @@
}
}
- // Collect the already pruned types before creating a new app info without liveness.
- Set<DexType> prunedTypes = appView.withLiveness().appInfo().getPrunedTypes();
-
- // TODO: move to appview.
- EnumValueInfoMapCollection enumValueInfoMapCollection =
- appViewWithLiveness.appInfo().getEnumValueInfoMapCollection();
-
if (!options.mainDexKeepRules.isEmpty()) {
- appView.setAppInfo(new AppInfoWithClassHierarchy(application));
// No need to build a new main dex root set
assert mainDexRootSet != null;
GraphConsumer mainDexKeptGraphConsumer = options.mainDexKeptGraphConsumer;
@@ -636,15 +623,13 @@
Enqueuer enqueuer =
EnqueuerFactory.createForMainDexTracing(
- appView,
- new SubtypingInfo(application.allClasses(), application),
- mainDexKeptGraphConsumer);
+ appView, new SubtypingInfo(appView), mainDexKeptGraphConsumer);
// Find classes which may have code executed before secondary dex files installation.
// Live types is the tracing result.
Set<DexProgramClass> mainDexBaseClasses =
enqueuer.traceMainDex(mainDexRootSet, executorService, timing);
// Calculate the automatic main dex list according to legacy multidex constraints.
- mainDexClasses = new MainDexListBuilder(mainDexBaseClasses, application).run();
+ mainDexClasses = new MainDexListBuilder(mainDexBaseClasses, appView).run();
final MainDexClasses finalMainDexClasses = mainDexClasses;
processWhyAreYouKeepingAndCheckDiscarded(
@@ -671,8 +656,6 @@
executorService);
}
- appView.setAppInfo(new AppInfoWithClassHierarchy(application));
-
if (options.shouldRerunEnqueuer()) {
timing.begin("Post optimization code stripping");
try {
@@ -689,7 +672,7 @@
Enqueuer enqueuer =
EnqueuerFactory.createForFinalTreeShaking(
appView,
- new SubtypingInfo(application.allClasses(), application),
+ new SubtypingInfo(appView),
keptGraphConsumer,
missingClasses,
prunedTypes);
@@ -702,7 +685,7 @@
timing)
.withEnumValueInfoMaps(enumValueInfoMapCollection));
// Rerunning the enqueuer should not give rise to any method rewritings.
- assert enqueuer.buildGraphLens(appView) == appView.graphLens();
+ assert enqueuer.buildGraphLens(appView) == null;
appView.withGeneratedMessageLiteBuilderShrinker(
shrinker ->
shrinker.rewriteDeadBuilderReferencesFromDynamicMethods(
@@ -717,7 +700,7 @@
DefaultTreePrunerConfiguration.getInstance());
TreePruner pruner = new TreePruner(appViewWithLiveness, treePrunerConfiguration);
- application = pruner.run(application);
+ DirectMappedDexApplication application = pruner.run();
Set<DexType> removedClasses = pruner.getRemovedClasses();
if (options.usageInformationConsumer != null) {
@@ -789,12 +772,6 @@
}
}
- // Add automatic main dex classes to an eventual manual list of classes.
- if (!options.mainDexKeepRules.isEmpty()) {
- application =
- application.builder().addToMainDexList(mainDexClasses.getClasses()).build().asDirect();
- }
-
// Perform minification.
NamingLens namingLens;
if (options.getProguardConfiguration().hasApplyMappingFile()) {
@@ -818,19 +795,19 @@
new KotlinMetadataRewriter(appView, namingLens).run(executorService);
timing.end();
- assert verifyMovedMethodsHaveOriginalMethodPosition(appView, application);
+ assert verifyMovedMethodsHaveOriginalMethodPosition(appView, getDirectApp(appView));
timing.begin("Line number remapping");
// When line number optimization is turned off the identity mapping for line numbers is
// used. We still run the line number optimizer to collect line numbers and inline frame
// information for the mapping file.
ClassNameMapper classNameMapper =
- LineNumberOptimizer.run(appView, application, inputApp, namingLens);
+ LineNumberOptimizer.run(appView, getDirectApp(appView), inputApp, namingLens);
timing.end();
// Overwrite SourceFile if specified. This step should be done after IR conversion.
timing.begin("Rename SourceFile");
- new SourceFileRewriter(appView, application).run();
+ new SourceFileRewriter(appView, appView.appInfo().app()).run();
timing.end();
// If a method filter is present don't produce output since the application is likely partial.
@@ -852,18 +829,33 @@
assert !options.isShrinking();
}
+ // Add automatic main dex classes to an eventual manual list of classes.
+ if (!options.mainDexKeepRules.isEmpty()) {
+ MainDexClasses finalMainDexClasses = mainDexClasses;
+ appView.setAppInfo(
+ appView
+ .appInfo()
+ .rebuild(
+ application ->
+ application
+ .builder()
+ .addToMainDexList(finalMainDexClasses.getClasses())
+ .build()
+ .asDirect()));
+ }
+
// Validity checks.
- assert application.asDirect().verifyCodeObjectsOwners();
- assert application.classes().stream().allMatch(clazz -> clazz.isValid(options));
+ assert getDirectApp(appView).verifyCodeObjectsOwners();
+ assert appView.appInfo().classes().stream().allMatch(clazz -> clazz.isValid(options));
if (options.isShrinking()
|| options.isMinifying()
|| options.getProguardConfiguration().hasApplyMappingFile()) {
- assert appView.rootSet().verifyKeptItemsAreKept(application, appView.appInfo());
+ assert appView.rootSet().verifyKeptItemsAreKept(appView.appInfo().app(), appView.appInfo());
}
assert appView
.graphLens()
.verifyMappingToOriginalProgram(
- application.classesWithDeterministicOrder(),
+ appView.appInfo().classesWithDeterministicOrder(),
new ApplicationReader(inputApp.withoutMainDexList(), options, timing)
.read(executorService),
appView.dexItemFactory());
@@ -883,7 +875,7 @@
// Generate the resulting application resources.
writeApplication(
executorService,
- application,
+ appView.appInfo().app(),
appView,
appView.graphLens(),
appView.initClassLens(),
@@ -979,7 +971,8 @@
options.getProguardConfiguration().getDontWarnPatterns(),
executorService,
timing));
- appView.setGraphLens(enqueuer.buildGraphLens(appView));
+ NestedGraphLens lens = enqueuer.buildGraphLens(appView);
+ appView.rewriteWithLens(lens);
if (InternalOptions.assertionsEnabled()) {
// Register the dead proto types. These are needed to verify that no new missing types are
// reported and that no dead proto types are referenced in the generated application.
@@ -1022,8 +1015,7 @@
// If there is no kept-graph info, re-run the enqueueing to compute it.
if (whyAreYouKeepingConsumer == null) {
whyAreYouKeepingConsumer = new WhyAreYouKeepingConsumer(null);
- SubtypingInfo subtypingInfo =
- new SubtypingInfo(appView.appInfo().app().asDirect().allClasses(), appView);
+ SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
if (forMainDex) {
enqueuer =
EnqueuerFactory.createForMainDexTracing(
@@ -1052,7 +1044,7 @@
throw new CompilationError("Discard checks failed.");
}
- private static boolean verifyNoJarApplicationReaders(List<DexProgramClass> classes) {
+ private static boolean verifyNoJarApplicationReaders(Collection<DexProgramClass> classes) {
for (DexProgramClass clazz : classes) {
for (DexEncodedMethod method : clazz.methods()) {
if (method.getCode() != null) {
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 5abbc25..f2d182d 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -842,7 +842,7 @@
? LineNumberOptimization.ON
: LineNumberOptimization.OFF;
- assert internal.enableHorizontalClassMerging || !proguardConfiguration.isOptimizing();
+ assert internal.enableStaticClassMerging || !proguardConfiguration.isOptimizing();
assert !internal.enableTreeShakingOfLibraryMethodOverrides;
assert internal.enableVerticalClassMerging || !proguardConfiguration.isOptimizing();
if (internal.debug) {
@@ -851,7 +851,7 @@
internal.getProguardConfiguration().getKeepAttributes().localVariableTypeTable = true;
internal.enableInlining = false;
internal.enableClassInlining = false;
- internal.enableHorizontalClassMerging = false;
+ internal.enableStaticClassMerging = false;
internal.enableVerticalClassMerging = false;
internal.enableClassStaticizer = false;
internal.outline.enabled = false;
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index a7949db..510249b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -280,7 +280,7 @@
private Type invokeTypeForInvokeSpecialToNonInitMethodOnHolder(
AppView<?> appView, CfSourceCode code) {
boolean desugaringEnabled = appView.options().isInterfaceMethodDesugaringEnabled();
- DexEncodedMethod encodedMethod = lookupMethod(appView, method);
+ DexEncodedMethod encodedMethod = lookupMethodOnHolder(appView, method);
if (encodedMethod == null) {
// The method is not defined on the class, we can use super to target. When desugaring
// default interface methods, it is expected they are targeted with invoke-direct.
@@ -303,11 +303,13 @@
"Failed to compile unsupported use of invokespecial", code.getOrigin());
}
- private DexEncodedMethod lookupMethod(AppView<?> appView, DexMethod method) {
+ private DexEncodedMethod lookupMethodOnHolder(AppView<?> appView, DexMethod method) {
GraphLensLookupResult lookupResult =
appView.graphLens().lookupMethod(method, method, Type.DIRECT);
DexMethod rewrittenMethod = lookupResult.getMethod();
- DexProgramClass clazz = appView.definitionForProgramType(rewrittenMethod.holder);
+ // Directly lookup the program type for holder. This bypasses lookup order as well as looks
+ // directly on the application data, which bypasses and indirection or validation.
+ DexProgramClass clazz = appView.appInfo().unsafeDirectProgramTypeLookup(rewrittenMethod.holder);
assert clazz != null;
return clazz.lookupMethod(rewrittenMethod);
}
diff --git a/src/main/java/com/android/tools/r8/code/InstanceOf.java b/src/main/java/com/android/tools/r8/code/InstanceOf.java
index 4550406..36d7a0b 100644
--- a/src/main/java/com/android/tools/r8/code/InstanceOf.java
+++ b/src/main/java/com/android/tools/r8/code/InstanceOf.java
@@ -49,7 +49,7 @@
@Override
public void registerUse(UseRegistry registry) {
- registry.registerTypeReference(getType());
+ registry.registerInstanceOf(getType());
}
public DexType getType() {
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 60b8220..ee67876 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -150,8 +150,7 @@
return definition;
}
- @Override
- public DexProgramClass definitionForProgramType(DexType type) {
+ public DexProgramClass unsafeDirectProgramTypeLookup(DexType type) {
return app.programDefinitionFor(type);
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index a0fd03b..481af1e 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -31,6 +31,7 @@
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
+import java.util.function.Function;
/* Specific subclass of AppInfo designed to support desugaring in D8. Desugaring requires a
* minimal amount of knowledge in the overall program, provided through classpath. Basic
@@ -57,6 +58,10 @@
return new AppInfoWithClassHierarchy(appInfo);
}
+ public AppInfoWithClassHierarchy rebuild(Function<DexApplication, DexApplication> fn) {
+ return new AppInfoWithClassHierarchy(fn.apply(app()));
+ }
+
@Override
public boolean hasClassHierarchy() {
assert checkIfObsolete();
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 7b0c80f..3bacf89 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.graph.DexValue.DexValueString;
+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.MergedClassesCollection;
@@ -124,7 +125,8 @@
return new AppView<>(appInfo, WholeProgramOptimizations.OFF, mapper);
}
- public static <T extends AppInfo> AppView<T> createForR8(T appInfo) {
+ public static AppView<AppInfoWithClassHierarchy> createForR8(DexApplication application) {
+ AppInfoWithClassHierarchy appInfo = new AppInfoWithClassHierarchy(application);
return new AppView<>(
appInfo, WholeProgramOptimizations.ON, defaultPrefixRewritingMapper(appInfo));
}
@@ -230,11 +232,6 @@
return appInfo().definitionFor(type);
}
- @Override
- public final DexProgramClass definitionForProgramType(DexType type) {
- return appInfo.app().programDefinitionFor(type);
- }
-
public OptionalBool isInterface(DexType type) {
assert type.isClassType();
// Without whole program information we should not assume anything about any other class than
@@ -430,7 +427,7 @@
public boolean validateUnboxedEnumsHaveBeenPruned() {
for (DexType unboxedEnum : unboxedEnums.enumSet()) {
- assert definitionForProgramType(unboxedEnum) == null
+ assert appInfo.definitionForWithoutExistenceAssert(unboxedEnum) == null
: "Enum " + unboxedEnum + " has been unboxed but is still in the program.";
assert appInfo().withLiveness().wasPruned(unboxedEnum)
: "Enum " + unboxedEnum + " has been unboxed but was not pruned.";
@@ -471,4 +468,19 @@
public boolean hasCfByteCodePassThroughMethods() {
return !cfByteCodePassThrough.isEmpty();
}
+
+ public void rewriteWithLens(NestedGraphLens lens) {
+ rewriteWithLens(lens, withLiveness());
+ }
+
+ private static void rewriteWithLens(NestedGraphLens lens, AppView<AppInfoWithLiveness> appView) {
+ if (lens == null) {
+ return;
+ }
+ boolean changed = appView.setGraphLens(lens);
+ assert changed;
+ DirectMappedDexApplication application = appView.appInfo().app().asDirect();
+ assert application.verifyWithLens(lens);
+ appView.setAppInfo(appView.appInfo().rewrittenWithLens(application, lens));
+ }
}
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 b602135..90b940a 100644
--- a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
@@ -27,11 +27,10 @@
private final Map<DexMethod, DexMethod> originalMethodSignaturesForBridges =
new IdentityHashMap<>();
- public AppliedGraphLens(
- AppView<? extends AppInfoWithClassHierarchy> appView, Iterable<DexProgramClass> classes) {
+ public AppliedGraphLens(AppView<? extends AppInfoWithClassHierarchy> appView) {
this.appView = appView;
- for (DexProgramClass clazz : classes) {
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
// Record original type names.
{
DexType type = clazz.type;
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index d10c69f..f1ddad4 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -31,7 +31,7 @@
private final boolean writeAnnotations;
private final boolean writeIR;
private final boolean writeCode;
- private final AppInfoWithClassHierarchy appInfo;
+ private final AppInfo appInfo;
private final Kotlin kotlin;
private final Timing timing = new Timing("AssemblyWriter");
@@ -48,7 +48,7 @@
this.writeIR = writeIR;
this.writeCode = writeCode;
if (writeIR) {
- this.appInfo = new AppInfoWithClassHierarchy(application.toDirect());
+ this.appInfo = new AppInfo(application.toDirect());
if (options.programConsumer == null) {
// Use class-file backend, since the CF frontend for testing does not support desugaring of
// synchronized methods for the DEX backend (b/109789541).
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 24237bb..16533fe 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -20,8 +20,8 @@
import com.android.tools.r8.errors.InvalidDebugInfoException;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.NumberGenerator;
import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.conversion.CfSourceCode;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.ir.conversion.MethodProcessor;
@@ -298,7 +298,7 @@
ProgramMethod context,
ProgramMethod method,
AppView<?> appView,
- ValueNumberGenerator valueNumberGenerator,
+ NumberGenerator valueNumberGenerator,
Position callerPosition,
Origin origin,
MethodProcessor methodProcessor) {
@@ -313,7 +313,7 @@
ProgramMethod context,
ProgramMethod method,
AppView<?> appView,
- ValueNumberGenerator generator,
+ NumberGenerator valueNumberGenerator,
Position callerPosition,
Origin origin,
MethodProcessor methodProcessor) {
@@ -323,13 +323,13 @@
context,
method,
appView,
- generator,
+ valueNumberGenerator,
callerPosition,
origin,
methodProcessor);
} else {
return internalBuildWithLocals(
- context, method, appView, generator, callerPosition, origin, methodProcessor);
+ context, method, appView, valueNumberGenerator, callerPosition, origin, methodProcessor);
}
}
@@ -338,7 +338,7 @@
ProgramMethod context,
ProgramMethod method,
AppView<?> appView,
- ValueNumberGenerator generator,
+ NumberGenerator valueNumberGenerator,
Position callerPosition,
Origin origin,
MethodProcessor methodProcessor) {
@@ -348,7 +348,7 @@
context,
method,
appView,
- generator,
+ valueNumberGenerator,
callerPosition,
origin,
methodProcessor);
@@ -359,7 +359,7 @@
context,
method,
appView,
- generator,
+ valueNumberGenerator,
callerPosition,
origin,
methodProcessor);
@@ -372,7 +372,7 @@
ProgramMethod context,
ProgramMethod method,
AppView<?> appView,
- ValueNumberGenerator generator,
+ NumberGenerator valueNumberGenerator,
Position callerPosition,
Origin origin,
MethodProcessor methodProcessor) {
@@ -389,7 +389,7 @@
methodProcessor == null
? IRBuilder.create(method, appView, source, origin)
: IRBuilder.createForInlining(
- method, appView, source, origin, methodProcessor, generator);
+ method, appView, source, origin, methodProcessor, valueNumberGenerator);
return builder.build(context);
}
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index 9f384ca..bb11c69 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -7,8 +7,8 @@
import com.android.tools.r8.dex.MixedSectionCollection;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.NumberGenerator;
import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.optimize.Outliner.OutlineCode;
import com.android.tools.r8.naming.ClassNameMapper;
@@ -23,7 +23,7 @@
ProgramMethod context,
ProgramMethod method,
AppView<?> appView,
- ValueNumberGenerator valueNumberGenerator,
+ NumberGenerator valueNumberGenerator,
Position callerPosition,
Origin origin,
MethodProcessor methodProcessor) {
diff --git a/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java b/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java
index 7d6b095..a267d5b 100644
--- a/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java
@@ -69,4 +69,9 @@
public boolean registerTypeReference(DexType type) {
return true;
}
+
+ @Override
+ public boolean registerInstanceOf(DexType type) {
+ return true;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 76bead8..ae3fe1e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -12,8 +12,8 @@
import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.NumberGenerator;
import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.conversion.DexSourceCode;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.ir.conversion.MethodProcessor;
@@ -229,7 +229,7 @@
ProgramMethod context,
ProgramMethod method,
AppView<?> appView,
- ValueNumberGenerator valueNumberGenerator,
+ NumberGenerator valueNumberGenerator,
Position callerPosition,
Origin origin,
MethodProcessor methodProcessor) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java b/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
index 2ec6dbf..cc91bcd 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
@@ -6,15 +6,76 @@
public interface DexDefinitionSupplier {
- DexClass definitionFor(DexType type);
-
- DexProgramClass definitionForProgramType(DexType type);
-
- default <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
- DexClass definitionForHolder(DexEncodedMember<D, R> member) {
- return definitionForHolder(member.toReference());
+ /**
+ * Lookup for the definition of a type independent of context.
+ *
+ * <p>This will make use of the compilers program, library and classpath precedence.
+ *
+ * @param type Type to look up the defintion for.
+ * @return Definition of the type or null if no definition exists.
+ */
+ default DexClass contextIndependentDefinitionFor(DexType type) {
+ return definitionFor(type);
}
+ /**
+ * Lookup for the definition of a type from a given context.
+ *
+ * <p>This ensures that a context overrides the usual lookup precedence if looking up itself.
+ *
+ * @param type Type to look up a definition for.
+ * @param context Context from which the lookup is taking place.
+ * @return Definition of the type or null if no definition exists.
+ */
+ default DexClass definitionFor(DexType type, DexProgramClass context) {
+ return type == context.type ? context : contextIndependentDefinitionFor(type);
+ }
+
+ /**
+ * Lookup for the program definition of a type from a given context.
+ *
+ * <p>This ensures that a context overrides the usual lookup precedence if looking up itself.
+ *
+ * @param type Type to look up a definition for.
+ * @param context Context from which the lookup is taking place.
+ * @return Definition of the type if it is a program type or null if not or no definition exists.
+ */
+ default DexProgramClass programDefinitionFor(DexType type, DexProgramClass context) {
+ return DexProgramClass.asProgramClassOrNull(definitionFor(type, context));
+ }
+
+ default <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+ DexClass definitionForHolder(DexEncodedMember<D, R> member, ProgramMethod context) {
+ return definitionForHolder(member.toReference(), context.getHolder());
+ }
+
+ default <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+ DexClass definitionForHolder(DexEncodedMember<D, R> member, DexProgramClass context) {
+ return definitionForHolder(member.toReference(), context);
+ }
+
+ default <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+ DexClass definitionForHolder(DexMember<D, R> member, ProgramMethod context) {
+ return definitionFor(member.holder, context.getHolder());
+ }
+
+ default <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+ DexClass definitionForHolder(DexMember<D, R> member, DexProgramClass context) {
+ return definitionFor(member.holder, context);
+ }
+
+ // Use definitionFor with a context or contextIndependentDefinitionFor without.
+ @Deprecated
+ DexClass definitionFor(DexType type);
+
+ // Use programDefinitionFor with a context.
+ @Deprecated
+ default DexProgramClass definitionForProgramType(DexType type) {
+ return DexProgramClass.asProgramClassOrNull(definitionFor(type));
+ }
+
+ // Use definitionForHolder with a context.
+ @Deprecated
default <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
DexClass definitionForHolder(DexMember<D, R> member) {
return definitionFor(member.holder);
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 019d295..cab8542 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -180,6 +180,10 @@
obsolete = true;
}
+ public CompilationState getCompilationState() {
+ return compilationState;
+ }
+
public DexEncodedMethod getDefaultInterfaceMethodImplementation() {
return defaultInterfaceMethodImplementation;
}
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 8b2bd65..aa1d5a9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -462,7 +462,7 @@
public final AssertionErrorMethods assertionErrorMethods = new AssertionErrorMethods();
public final ClassMethods classMethods = new ClassMethods();
public final ConstructorMethods constructorMethods = new ConstructorMethods();
- public final EnumMethods enumMethods = new EnumMethods();
+ public final EnumMembers enumMembers = new EnumMembers();
public final NullPointerExceptionMethods npeMethods = new NullPointerExceptionMethods();
public final IllegalArgumentExceptionMethods illegalArgumentExceptionMethods =
new IllegalArgumentExceptionMethods();
@@ -688,7 +688,7 @@
// If not, that library method should not be added here because it literally has side effects.
public Map<DexMethod, Predicate<InvokeMethod>> libraryMethodsWithoutSideEffects =
Streams.<Pair<DexMethod, Predicate<InvokeMethod>>>concat(
- Stream.of(new Pair<>(enumMethods.constructor, alwaysTrue())),
+ Stream.of(new Pair<>(enumMembers.constructor, alwaysTrue())),
Stream.of(new Pair<>(npeMethods.init, alwaysTrue())),
Stream.of(new Pair<>(npeMethods.initWithMessage, alwaysTrue())),
Stream.of(new Pair<>(objectMembers.constructor, alwaysTrue())),
@@ -1268,11 +1268,14 @@
}
}
- public class EnumMethods {
+ public class EnumMembers {
+
+ public final DexField nameField = createField(enumType, stringType, "name");
+ public final DexField ordinalField = createField(enumType, intType, "ordinal");
public final DexMethod valueOf;
- public final DexMethod ordinal;
- public final DexMethod name;
+ public final DexMethod ordinalMethod;
+ public final DexMethod nameMethod;
public final DexMethod toString;
public final DexMethod compareTo;
public final DexMethod equals;
@@ -1283,25 +1286,17 @@
public final DexMethod finalize =
createMethod(enumType, createProto(voidType), finalizeMethodName);
- private EnumMethods() {
+ private EnumMembers() {
valueOf =
createMethod(
enumDescriptor,
valueOfMethodName,
enumDescriptor,
new DexString[] {classDescriptor, stringDescriptor});
- ordinal =
- createMethod(
- enumDescriptor,
- ordinalMethodName,
- intDescriptor,
- DexString.EMPTY_ARRAY);
- name =
- createMethod(
- enumDescriptor,
- nameMethodName,
- stringDescriptor,
- DexString.EMPTY_ARRAY);
+ ordinalMethod =
+ createMethod(enumDescriptor, ordinalMethodName, intDescriptor, DexString.EMPTY_ARRAY);
+ nameMethod =
+ createMethod(enumDescriptor, nameMethodName, stringDescriptor, DexString.EMPTY_ARRAY);
toString =
createMethod(
enumDescriptor,
@@ -1321,6 +1316,15 @@
createMethod(enumDescriptor, hashCodeMethodName, intDescriptor, DexString.EMPTY_ARRAY);
}
+ public void forEachField(Consumer<DexField> fn) {
+ fn.accept(nameField);
+ fn.accept(ordinalField);
+ }
+
+ public boolean isNameOrOrdinalField(DexField field) {
+ return field == nameField || field == ordinalField;
+ }
+
public boolean isValuesMethod(DexMethod method, DexClass enumClass) {
assert enumClass.isEnum();
return method.holder == enumClass.type
diff --git a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
index 35d8146..7711eb3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
@@ -63,6 +63,10 @@
assert kind == Kind.CF : "Invalid kind " + kind + " for library-path class " + type;
}
+ public static DexLibraryClass asLibraryClassOrNull(DexClass clazz) {
+ return clazz != null ? clazz.asLibraryClass() : null;
+ }
+
private static boolean verifyLibraryMethod(DexEncodedMethod method) {
assert !method.isClassInitializer();
assert !method.isPrivateMethod();
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 3a78118..612329c 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -80,19 +80,14 @@
}
@Override
- public DexProgramClass definitionForProgramType(DexType type) {
- return programDefinitionFor(type);
- }
-
- @Override
public DexItemFactory dexItemFactory() {
return dexItemFactory;
}
@Override
public DexProgramClass programDefinitionFor(DexType type) {
- DexClass clazz = definitionFor(type);
- return clazz instanceof DexProgramClass ? clazz.asProgramClass() : null;
+ // The direct mapped application has no duplicates so this coincides with definitionFor.
+ return DexProgramClass.asProgramClassOrNull(definitionFor(type));
}
@Override
@@ -120,12 +115,10 @@
return "DexApplication (direct)";
}
- public DirectMappedDexApplication rewrittenWithLens(GraphLens lens) {
- // As a side effect, this will rebuild the program classes and library classes maps.
- DirectMappedDexApplication rewrittenApplication = builder().build().asDirect();
- assert rewrittenApplication.mappingIsValid(lens, allClasses.keySet());
- assert rewrittenApplication.verifyCodeObjectsOwners();
- return rewrittenApplication;
+ public boolean verifyWithLens(GraphLens lens) {
+ assert mappingIsValid(lens, allClasses.keySet());
+ assert verifyCodeObjectsOwners();
+ return true;
}
public boolean verifyNothingToRewrite(AppView<?> appView, GraphLens lens) {
@@ -144,6 +137,9 @@
// original type will point to a definition that was renamed.
for (DexType type : types) {
DexType renamed = graphLens.lookupType(type);
+ if (renamed.isIntType()) {
+ continue;
+ }
if (renamed != type) {
if (definitionFor(type) == null && definitionFor(renamed) != null) {
continue;
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 6c93947..b0e0312 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -54,8 +54,8 @@
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.MemberType;
import com.android.tools.r8.ir.code.Monitor;
+import com.android.tools.r8.ir.code.NumberGenerator;
import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.naming.ClassNameMapper;
@@ -218,7 +218,7 @@
ProgramMethod context,
ProgramMethod method,
AppView<?> appView,
- ValueNumberGenerator valueNumberGenerator,
+ NumberGenerator valueNumberGenerator,
Position callerPosition,
Origin origin,
MethodProcessor methodProcessor) {
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
index 7127794..1721860 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -88,7 +88,7 @@
return programClasses.get(type);
}
- public DexLibraryClass libraryDefintionFor(DexType type) {
+ public DexLibraryClass libraryDefinitionFor(DexType type) {
assert type.isClassType() : "Cannot lookup library definition for type: " + type;
return libraryClasses.get(type);
}
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
index 88c781c..6ff5f1d 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -4,8 +4,8 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.NumberGenerator;
import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.origin.Origin;
@@ -25,7 +25,7 @@
public IRCode buildInliningIR(
ProgramMethod context,
AppView<?> appView,
- ValueNumberGenerator valueNumberGenerator,
+ NumberGenerator valueNumberGenerator,
Position callerPosition,
Origin origin,
MethodProcessor methodProcessor) {
diff --git a/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java b/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java
index 662ccb0..b73d2bc 100644
--- a/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java
@@ -41,7 +41,15 @@
// Set of missing classes, discovered during subtypeMap computation.
private final Set<DexType> missingClasses = Sets.newIdentityHashSet();
- public SubtypingInfo(Collection<DexClass> classes, DexDefinitionSupplier definitions) {
+ public SubtypingInfo(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ this(appView.appInfo());
+ }
+
+ public SubtypingInfo(AppInfoWithClassHierarchy appInfo) {
+ this(appInfo.app().asDirect().allClasses(), appInfo);
+ }
+
+ private SubtypingInfo(Collection<DexClass> classes, DexDefinitionSupplier definitions) {
factory = definitions.dexItemFactory();
// Recompute subtype map if we have modified the graph.
populateSubtypeMap(classes, definitions::definitionFor, factory);
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 1e89e13..de4021e 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -57,6 +57,8 @@
public abstract boolean registerTypeReference(DexType type);
+ public abstract boolean registerInstanceOf(DexType type);
+
public boolean registerConstClass(DexType type) {
return registerTypeReference(type);
}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerCheckCastAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerCheckCastAnalysis.java
new file mode 100644
index 0000000..503e74e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerCheckCastAnalysis.java
@@ -0,0 +1,12 @@
+// 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.graph.analysis;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface EnqueuerCheckCastAnalysis {
+ void traceCheckCast(DexType type, ProgramMethod context);
+}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerInstanceOfAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerInstanceOfAnalysis.java
new file mode 100644
index 0000000..abda4d1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerInstanceOfAnalysis.java
@@ -0,0 +1,12 @@
+// 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.graph.analysis;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface EnqueuerInstanceOfAnalysis {
+ void traceInstanceOf(DexType type, ProgramMethod context);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
index 5b1dd91..0df3c80 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
@@ -202,8 +202,8 @@
}
private void recordAllFieldPutsProcessed(
- DexEncodedField field, OptimizationFeedbackDelayed feedback) {
- DexProgramClass clazz = asProgramClassOrNull(appView.definitionForHolder(field));
+ DexEncodedField field, ProgramMethod context, OptimizationFeedbackDelayed feedback) {
+ DexProgramClass clazz = asProgramClassOrNull(appView.definitionForHolder(field, context));
if (clazz == null) {
assert false;
return;
@@ -282,7 +282,8 @@
// therefore important that the optimization info has been flushed in advance.
assert feedback.noUpdatesLeft();
for (ProgramMethod method : wave) {
- fieldAccessGraph.markProcessed(method, field -> recordAllFieldPutsProcessed(field, feedback));
+ fieldAccessGraph.markProcessed(
+ method, field -> recordAllFieldPutsProcessed(field, method, feedback));
objectAllocationGraph.markProcessed(
method, clazz -> recordAllAllocationsSitesProcessed(clazz, feedback));
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
index dd450d6..f06a1c7 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
@@ -241,5 +241,10 @@
public boolean registerTypeReference(DexType type) {
return false;
}
+
+ @Override
+ public boolean registerInstanceOf(DexType type) {
+ return false;
+ }
}
}
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 81df82a..3d8df1a 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
@@ -229,7 +229,14 @@
initializationInfos.forEach(
appView,
(field, initializationInfo) -> {
- if (!appView.appInfo().isInstanceFieldWrittenOnlyInInstanceInitializers(field)) {
+ // If the instance field is not written only in the instance initializer, then we can't
+ // conclude that this field will have a constant value.
+ //
+ // We have special handling for library fields that satisfy the property that they are
+ // only written in their corresponding instance initializers. This is needed since we
+ // don't analyze these instance initializers in the Enqueuer, as they are in the library.
+ if (!appView.appInfo().isInstanceFieldWrittenOnlyInInstanceInitializers(field)
+ && !appView.dexItemFactory().enumMembers.isNameOrOrdinalField(field.toReference())) {
return;
}
if (initializationInfo.isArgumentInitializationInfo()) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractState.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractState.java
new file mode 100644
index 0000000..673411a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractState.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.ir.analysis.framework.intraprocedural;
+
+import com.android.tools.r8.ir.code.BasicBlock;
+
+/** The abstract state of the dataflow analysis, which is computed for each {@link BasicBlock}. */
+public abstract class AbstractState<StateType extends AbstractState<StateType>>
+ implements TransferFunctionResult<StateType> {
+
+ public abstract StateType join(StateType state);
+
+ @Override
+ public abstract boolean equals(Object other);
+
+ @Override
+ public abstract int hashCode();
+
+ @Override
+ public boolean isAbstractState() {
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java
new file mode 100644
index 0000000..68a8a24
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java
@@ -0,0 +1,45 @@
+// 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.framework.intraprocedural;
+
+import com.android.tools.r8.ir.code.BasicBlock;
+
+/**
+ * The result returned by {@link IntraproceduralDataflowAnalysis#run(BasicBlock)}.
+ *
+ * <p>The result can be either a {@link SuccessfulDataflowAnalysisResult}, which represents the fact
+ * that the dataflow analysis ran to completion and that the (least) fixpoint was computed, or a
+ * {@link FailedDataflowAnalysisResult}, in which case the dataflow analysis was aborted before a
+ * fixpoint was reached.
+ *
+ * <p>The result currently does not hold any data about the result of the analysis, but this would
+ * be natural to add once needed.
+ */
+public abstract class DataflowAnalysisResult {
+
+ public boolean isSuccessfulAnalysisResult() {
+ return false;
+ }
+
+ public boolean isFailedAnalysisResult() {
+ return false;
+ }
+
+ public static class SuccessfulDataflowAnalysisResult extends DataflowAnalysisResult {
+
+ @Override
+ public boolean isSuccessfulAnalysisResult() {
+ return true;
+ }
+ }
+
+ public static class FailedDataflowAnalysisResult extends DataflowAnalysisResult {
+
+ @Override
+ public boolean isFailedAnalysisResult() {
+ return true;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/FailedTransferFunctionResult.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/FailedTransferFunctionResult.java
new file mode 100644
index 0000000..4341265
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/FailedTransferFunctionResult.java
@@ -0,0 +1,24 @@
+// 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.framework.intraprocedural;
+
+import com.android.tools.r8.errors.Unreachable;
+
+/** Used by the {@link TransferFunction} to signal that the dataflow analysis should be aborted. */
+public class FailedTransferFunctionResult<StateType extends AbstractState<StateType>>
+ implements TransferFunctionResult<StateType> {
+
+ public FailedTransferFunctionResult() {}
+
+ @Override
+ public StateType asAbstractState() {
+ throw new Unreachable();
+ }
+
+ @Override
+ public boolean isFailedTransferResult() {
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java
new file mode 100644
index 0000000..f290cf1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java
@@ -0,0 +1,81 @@
+// 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.framework.intraprocedural;
+
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult.FailedDataflowAnalysisResult;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult.SuccessfulDataflowAnalysisResult;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.utils.WorkList;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+/**
+ * This defines a simple fixpoint solver for running an intraprocedural dataflow analysis.
+ *
+ * <p>The solver computes an {@link AbstractState} for each {@link BasicBlock} using the {@link
+ * TransferFunction} which defines the abstract semantics for each instruction.
+ *
+ * <p>Once the fixpoint is reached the analysis returns a {@link SuccessfulDataflowAnalysisResult}.
+ * If the supplied {@link TransferFunction} returns a {@link FailedTransferFunctionResult} for a
+ * given instruction and abstract state, then the analysis return a {@link
+ * FailedDataflowAnalysisResult}.
+ */
+public class IntraproceduralDataflowAnalysis<StateType extends AbstractState<StateType>> {
+
+ private final StateType bottom;
+
+ // The transfer function that defines the abstract semantics for each instruction.
+ private final TransferFunction<StateType> transfer;
+
+ // The state of the analysis.
+ private final Map<BasicBlock, StateType> blockExitStates = new IdentityHashMap<>();
+
+ public IntraproceduralDataflowAnalysis(StateType bottom, TransferFunction<StateType> transfer) {
+ this.bottom = bottom;
+ this.transfer = transfer;
+ }
+
+ public DataflowAnalysisResult run(BasicBlock root) {
+ return run(WorkList.newIdentityWorkList(root));
+ }
+
+ private DataflowAnalysisResult run(WorkList<BasicBlock> worklist) {
+ while (worklist.hasNext()) {
+ BasicBlock block = worklist.next();
+ // Compute the abstract state upon entry to the basic block, by joining all the predecessor
+ // exit states.
+ StateType state = computeBlockEntryState(block);
+ for (Instruction instruction : block.getInstructions()) {
+ TransferFunctionResult<StateType> transferResult = transfer.apply(instruction, state);
+ if (transferResult.isFailedTransferResult()) {
+ return new FailedDataflowAnalysisResult();
+ }
+ assert transferResult.isAbstractState();
+ state = transferResult.asAbstractState();
+ }
+ // Update the block exit state, and re-enqueue all successor blocks if the abstract state
+ // changed.
+ if (setBlockExitState(block, state)) {
+ worklist.addAll(block.getSuccessors());
+ }
+ }
+ return new SuccessfulDataflowAnalysisResult();
+ }
+
+ private StateType computeBlockEntryState(BasicBlock block) {
+ StateType result = bottom;
+ for (BasicBlock predecessor : block.getPredecessors()) {
+ StateType predecessorState = blockExitStates.getOrDefault(predecessor, bottom);
+ result = result.join(predecessorState);
+ }
+ return result;
+ }
+
+ private boolean setBlockExitState(BasicBlock block, StateType state) {
+ StateType previous = blockExitStates.put(block, state);
+ return !state.equals(previous);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunction.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunction.java
new file mode 100644
index 0000000..98ede7c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunction.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.ir.analysis.framework.intraprocedural;
+
+import com.android.tools.r8.ir.code.Instruction;
+
+/**
+ * A transfer function that defines the abstract semantics of the instructions in the program
+ * according to some abstract state {@link StateType}.
+ */
+public interface TransferFunction<StateType extends AbstractState<StateType>> {
+
+ TransferFunctionResult<StateType> apply(Instruction instruction, StateType state);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunctionResult.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunctionResult.java
new file mode 100644
index 0000000..183d79a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunctionResult.java
@@ -0,0 +1,31 @@
+// 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.framework.intraprocedural;
+
+import com.android.tools.r8.ir.code.Instruction;
+
+/**
+ * The result of applying the {@link TransferFunction} to an {@link Instruction} and an {@link
+ * AbstractState}.
+ *
+ * <p>The result can either be a new {@link AbstractState} or a failure, in which case the dataflow
+ * analysis is aborted.
+ */
+public interface TransferFunctionResult<StateType extends AbstractState<StateType>> {
+
+ default boolean isAbstractState() {
+ return false;
+ }
+
+ StateType asAbstractState();
+
+ default boolean isFailedTransferResult() {
+ return false;
+ }
+
+ default FailedTransferFunctionResult<StateType> asFailedTransferResult() {
+ return null;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 77988b8..476135f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -107,8 +107,8 @@
private final ProgramMethod method;
public LinkedList<BasicBlock> blocks;
- public final ValueNumberGenerator valueNumberGenerator;
- public final ValueNumberGenerator basicBlockNumberGenerator;
+ public final NumberGenerator valueNumberGenerator;
+ public final NumberGenerator basicBlockNumberGenerator;
private int usedMarkingColors = 0;
private boolean numbered = false;
@@ -127,8 +127,8 @@
InternalOptions options,
ProgramMethod method,
LinkedList<BasicBlock> blocks,
- ValueNumberGenerator valueNumberGenerator,
- ValueNumberGenerator basicBlockNumberGenerator,
+ NumberGenerator valueNumberGenerator,
+ NumberGenerator basicBlockNumberGenerator,
IRMetadata metadata,
Origin origin) {
assert metadata != null;
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index 81af63c..3b8fc7f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -189,7 +189,7 @@
.resolveMethodOnClass(dexItemFactory.objectMembers.finalize, clazz);
if (finalizeResolutionResult.isSingleResolution()) {
DexMethod finalizeMethod = finalizeResolutionResult.getSingleTarget().method;
- if (finalizeMethod != dexItemFactory.enumMethods.finalize
+ if (finalizeMethod != dexItemFactory.enumMembers.finalize
&& finalizeMethod != dexItemFactory.objectMembers.finalize) {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ValueNumberGenerator.java b/src/main/java/com/android/tools/r8/ir/code/NumberGenerator.java
similarity index 91%
rename from src/main/java/com/android/tools/r8/ir/code/ValueNumberGenerator.java
rename to src/main/java/com/android/tools/r8/ir/code/NumberGenerator.java
index 722f30e..5b01dfa 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ValueNumberGenerator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NumberGenerator.java
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
-public class ValueNumberGenerator {
+public class NumberGenerator {
private int nextValueNumber = 0;
public int next() {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
index 8119a9c..07783f5 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
@@ -373,6 +373,11 @@
}
@Override
+ public boolean registerInstanceOf(DexType type) {
+ return false;
+ }
+
+ @Override
public void registerCallSite(DexCallSite callSite) {
registerMethodHandle(
callSite.bootstrapMethod, MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
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 c36eb8e..6b7b402 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
@@ -92,6 +92,7 @@
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.Not;
import com.android.tools.r8.ir.code.NumberConversion;
+import com.android.tools.r8.ir.code.NumberGenerator;
import com.android.tools.r8.ir.code.NumericType;
import com.android.tools.r8.ir.code.Or;
import com.android.tools.r8.ir.code.Phi;
@@ -107,7 +108,6 @@
import com.android.tools.r8.ir.code.Throw;
import com.android.tools.r8.ir.code.Ushr;
import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.code.ValueTypeConstraint;
import com.android.tools.r8.ir.code.Xor;
@@ -391,8 +391,8 @@
private BasicBlock currentBlock = null;
private int currentInstructionOffset = -1;
- final private ValueNumberGenerator valueNumberGenerator;
- private final ValueNumberGenerator basicBlockNumberGenerator;
+ private final NumberGenerator valueNumberGenerator;
+ private final NumberGenerator basicBlockNumberGenerator;
private final ProgramMethod method;
private ProgramMethod context;
public final AppView<?> appView;
@@ -437,7 +437,7 @@
source,
origin,
lookupPrototypeChanges(appView, method),
- new ValueNumberGenerator());
+ new NumberGenerator());
}
public static IRBuilder createForInlining(
@@ -446,7 +446,7 @@
SourceCode source,
Origin origin,
MethodProcessor processor,
- ValueNumberGenerator valueNumberGenerator) {
+ NumberGenerator valueNumberGenerator) {
RewrittenPrototypeDescription protoChanges =
processor.shouldApplyCodeRewritings(method)
? lookupPrototypeChanges(appView, method)
@@ -475,7 +475,7 @@
SourceCode source,
Origin origin,
RewrittenPrototypeDescription prototypeChanges,
- ValueNumberGenerator valueNumberGenerator) {
+ NumberGenerator valueNumberGenerator) {
assert source != null;
assert valueNumberGenerator != null;
this.method = method;
@@ -484,7 +484,7 @@
this.origin = origin;
this.prototypeChanges = prototypeChanges;
this.valueNumberGenerator = valueNumberGenerator;
- this.basicBlockNumberGenerator = new ValueNumberGenerator();
+ this.basicBlockNumberGenerator = new NumberGenerator();
}
public DexEncodedMethod getMethod() {
@@ -2673,7 +2673,7 @@
return type != NumericType.FLOAT && type != NumericType.DOUBLE && type != NumericType.LONG;
}
- public ValueNumberGenerator getValueNumberGenerator() {
+ public NumberGenerator getValueNumberGenerator() {
return valueNumberGenerator;
}
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 3f74b74..6cbf350 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
@@ -739,7 +739,11 @@
}
if (enumUnboxer != null) {
enumUnboxer.finishAnalysis();
- enumUnboxer.unboxEnums(postMethodProcessorBuilder, executorService, feedback);
+ enumUnboxer.unboxEnums(
+ postMethodProcessorBuilder,
+ executorService,
+ feedback,
+ classStaticizer == null ? Collections.emptySet() : classStaticizer.getCandidates());
}
if (!options.debug) {
new TrivialFieldAccessReprocessor(appView.withLiveness(), postMethodProcessorBuilder)
@@ -1763,6 +1767,11 @@
|| definition.getOptimizationInfo().isReachabilitySensitive()) {
return false;
}
+ if (appView.options().enableEnumUnboxing && method.getHolder().isEnum()) {
+ // Although the method is pinned, we compute the inlining constraint for enum unboxing,
+ // but the inliner won't be able to inline the method (marked as pinned).
+ return true;
+ }
if (appView.appInfo().hasLiveness()
&& appView.appInfo().withLiveness().isPinned(method.getReference())) {
return false;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index bf8fd80..5b1485d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -64,7 +64,7 @@
private final Collection<CodeOptimization> defaultCodeOptimizations;
private final LongLivedProgramMethodSetBuilder<?> methodsToReprocess =
- LongLivedProgramMethodSetBuilder.create();
+ LongLivedProgramMethodSetBuilder.createForIdentitySet();
private final Map<DexEncodedMethod, Collection<CodeOptimization>> optimizationsMap =
new IdentityHashMap<>();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 6e41b1a..3709176 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -347,9 +347,9 @@
return knownLambdaClasses;
}
- public GraphLens buildMappingLens(AppView<?> appView) {
+ public NestedGraphLens buildMappingLens(AppView<?> appView) {
if (originalMethodSignatures.isEmpty()) {
- return appView.graphLens();
+ return null;
}
return new LambdaRewriterLens(
originalMethodSignatures, appView.graphLens(), appView.dexItemFactory());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
index bbe2ae0..c7f6f2c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
@@ -194,10 +194,16 @@
DexProgramClass::checksumFromType);
}
+ void synthesizeNestConstructor() {
+ synthesizeNestConstructor(null);
+ }
+
void synthesizeNestConstructor(DexApplication.Builder<?> builder) {
if (nestConstructorUsed) {
appView.appInfo().addSynthesizedClass(nestConstructor);
- builder.addSynthesizedClass(nestConstructor, true);
+ if (builder != null) {
+ builder.addSynthesizedClass(nestConstructor, true);
+ }
}
}
@@ -455,6 +461,12 @@
// Unrelated to access based control.
return false;
}
+
+ @Override
+ public boolean registerInstanceOf(DexType type) {
+ // Unrelated to access based control.
+ return false;
+ }
}
public static final class DexFieldWithAccess {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
index e9ac116..d275f4a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.ir.desugar;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
@@ -33,16 +32,14 @@
super(appView);
}
- public NestedPrivateMethodLens run(
- ExecutorService executorService, DexApplication.Builder<?> appBuilder)
- throws ExecutionException {
+ public NestedPrivateMethodLens run(ExecutorService executorService) throws ExecutionException {
assert !appView.options().canUseNestBasedAccess()
|| appView.options().testing.enableForceNestBasedAccessDesugaringForTest;
computeAndProcessNestsConcurrently(executorService);
NestedPrivateMethodLens.Builder lensBuilder = NestedPrivateMethodLens.builder();
addDeferredBridgesAndMapMethods(lensBuilder);
clearNestAttributes();
- synthesizeNestConstructor(appBuilder);
+ synthesizeNestConstructor();
return lensBuilder.build(appView, getNestConstructorType());
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index 6fe0808..a078c9b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -121,7 +121,7 @@
InvokeSuper invoke = current.asInvokeSuper();
DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, context);
if (singleTarget != null) {
- DexClass holder = appView.definitionForHolder(singleTarget);
+ DexClass holder = appView.definitionForHolder(singleTarget, context);
assert holder != null;
DexMethod invokedMethod = invoke.getInvokedMethod();
DexEncodedMethod newSingleTarget =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index e7104f3..f172fa9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -253,8 +253,10 @@
}
// Inline the class instance.
+ Set<Value> affectedValues = Sets.newIdentityHashSet();
try {
- anyInlinedMethods |= processor.processInlining(code, defaultOracle, inliningIRProvider);
+ anyInlinedMethods |=
+ processor.processInlining(code, affectedValues, defaultOracle, inliningIRProvider);
} catch (IllegalClassInlinerStateException e) {
// We introduced a user that we cannot handle in the class inliner as a result of force
// inlining. Abort gracefully from class inlining without removing the instance.
@@ -270,7 +272,6 @@
assert inliningIRProvider.verifyIRCacheIsEmpty();
// Restore normality.
- Set<Value> affectedValues = Sets.newIdentityHashSet();
code.removeAllDeadAndTrivialPhis(affectedValues);
if (!affectedValues.isEmpty()) {
new TypeAnalysis(appView).narrowing(affectedValues);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index acd7477..ec1f830 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -392,7 +392,10 @@
//
// Returns `true` if at least one method was inlined.
boolean processInlining(
- IRCode code, Supplier<InliningOracle> defaultOracle, InliningIRProvider inliningIRProvider)
+ IRCode code,
+ Set<Value> affectedValues,
+ Supplier<InliningOracle> defaultOracle,
+ InliningIRProvider inliningIRProvider)
throws IllegalClassInlinerStateException {
// Verify that `eligibleInstance` is not aliased.
assert eligibleInstance == eligibleInstance.getAliasedValue();
@@ -417,7 +420,7 @@
anyInlinedMethods |= forceInlineDirectMethodInvocations(code, inliningIRProvider);
anyInlinedMethods |= forceInlineIndirectMethodInvocations(code, inliningIRProvider);
removeAliasIntroducingInstructionsLinkedToEligibleInstance();
- removeMiscUsages(code);
+ removeMiscUsages(code, affectedValues);
removeFieldReads(code);
removeFieldWrites();
removeInstruction(root);
@@ -625,7 +628,7 @@
}
// Remove miscellaneous users before handling field reads.
- private void removeMiscUsages(IRCode code) {
+ private void removeMiscUsages(IRCode code, Set<Value> affectedValues) {
boolean needToRemoveUnreachableBlocks = false;
for (Instruction user : eligibleInstance.uniqueUsers()) {
if (user.isInvokeMethod()) {
@@ -702,7 +705,7 @@
}
if (needToRemoveUnreachableBlocks) {
- code.removeUnreachableBlocks();
+ affectedValues.addAll(code.removeUnreachableBlocks());
}
}
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 c902868..a74b4b6 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
@@ -51,12 +51,14 @@
import com.android.tools.r8.ir.conversion.IRConverter;
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.info.FieldOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.IntBox;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
@@ -67,6 +69,9 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
@@ -82,6 +87,7 @@
// 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 Set<DexType> enumsToUnboxWithPackageRequirement = Sets.newIdentityHashSet();
private EnumUnboxingRewriter enumUnboxerRewriter;
@@ -266,7 +272,7 @@
}
for (Instruction user : constClass.outValue().uniqueUsers()) {
if (!(user.isInvokeStatic()
- && user.asInvokeStatic().getInvokedMethod() == factory.enumMethods.valueOf)) {
+ && user.asInvokeStatic().getInvokedMethod() == factory.enumMembers.valueOf)) {
markEnumAsUnboxable(
Reason.CONST_CLASS, appView.definitionForProgramType(constClass.getValue()));
return;
@@ -327,7 +333,8 @@
public void unboxEnums(
PostMethodProcessor.Builder postBuilder,
ExecutorService executorService,
- OptimizationFeedbackDelayed feedback)
+ OptimizationFeedbackDelayed feedback,
+ Set<DexType> hostsToAvoidIfPossible)
throws ExecutionException {
// At this point the enumsToUnbox are no longer candidates, they will all be unboxed.
if (enumsUnboxingCandidates.isEmpty()) {
@@ -337,12 +344,13 @@
// Update keep info on any of the enum methods of the removed classes.
updatePinnedItems(enumsToUnbox);
enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumsToUnbox);
- NestedGraphLens enumUnboxingLens = new TreeFixer(enumsToUnbox).fixupTypeReferences();
+ Map<DexType, DexType> newMethodLocation =
+ findNewMethodLocationOfUnboxableEnums(enumsToUnbox, hostsToAvoidIfPossible);
+ NestedGraphLens enumUnboxingLens =
+ new TreeFixer(enumsToUnbox).fixupTypeReferences(newMethodLocation);
appView.setUnboxedEnums(enumUnboxerRewriter.getEnumsToUnbox());
GraphLens previousLens = appView.graphLens();
- appView.setGraphLens(enumUnboxingLens);
- appView.setAppInfo(
- appView.appInfo().rewrittenWithLens(appView.appInfo().app().asDirect(), enumUnboxingLens));
+ appView.rewriteWithLens(enumUnboxingLens);
// Update optimization info.
feedback.fixupOptimizationInfos(
appView,
@@ -379,6 +387,84 @@
postBuilder.rewrittenWithLens(appView, previousLens);
}
+ // Some enums may have methods which require to stay in the current package for accessibility,
+ // in this case we find another class than the enum unboxing utility class to host these methods.
+ private Map<DexType, DexType> findNewMethodLocationOfUnboxableEnums(
+ Set<DexType> enumsToUnbox, Set<DexType> hostsToAvoidIfPossible) {
+ if (enumsToUnboxWithPackageRequirement.isEmpty()) {
+ return Collections.emptyMap();
+ }
+ Map<DexType, DexType> newMethodLocationMap = new IdentityHashMap<>();
+ Map<String, DexProgramClass> packageToClassMap =
+ getPackageToClassMapExcluding(enumsToUnbox, hostsToAvoidIfPossible);
+ for (DexType toUnbox : enumsToUnboxWithPackageRequirement) {
+ DexProgramClass packageClass = packageToClassMap.get(toUnbox.getPackageDescriptor());
+ if (packageClass != null) {
+ newMethodLocationMap.put(toUnbox, packageClass.type);
+ }
+ }
+ enumsToUnboxWithPackageRequirement.clear();
+ return newMethodLocationMap;
+ }
+
+ // We are looking for another class in the same package as the unboxed enum to host the unboxed
+ // enum methods. We go through all classes, and for each package where a host is needed, we
+ // select a class.
+ private Map<String, DexProgramClass> getPackageToClassMapExcluding(
+ Set<DexType> enumsToUnbox, Set<DexType> hostsToAvoidIfPossible) {
+ HashSet<String> relevantPackages = new HashSet<>();
+ for (DexType toUnbox : enumsToUnbox) {
+ relevantPackages.add(toUnbox.getPackageDescriptor());
+ }
+
+ Map<String, DexProgramClass> packageToClassMap = new HashMap<>();
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ String packageDescriptor = clazz.type.getPackageDescriptor();
+ if (relevantPackages.contains(packageDescriptor) && !enumsToUnbox.contains(clazz.type)) {
+ DexProgramClass previousClass = packageToClassMap.get(packageDescriptor);
+ if (previousClass == null) {
+ packageToClassMap.put(packageDescriptor, clazz);
+ } else {
+ packageToClassMap.put(
+ packageDescriptor, selectHost(clazz, previousClass, hostsToAvoidIfPossible));
+ }
+ }
+ }
+
+ return packageToClassMap;
+ }
+
+ // We are trying to select a host for the enum unboxing methods, but multiple candidates are
+ // available. We need to pick one of the two classes and the result has to be deterministic.
+ // We follow the heuristics, in order:
+ // 1. don't pick a class from hostToAvoidIfPossible if possible
+ // 2. pick the class with the least number of methods
+ // 3. pick the first class name in alphabetical order for determinism.
+ private DexProgramClass selectHost(
+ DexProgramClass class1, DexProgramClass class2, Set<DexType> hostsToAvoidIfPossible) {
+ boolean avoid1 = hostsToAvoidIfPossible.contains(class1.type);
+ boolean avoid2 = hostsToAvoidIfPossible.contains(class2.type);
+ if (avoid1 && !avoid2) {
+ return class2;
+ }
+ if (avoid2 && !avoid1) {
+ return class1;
+ }
+ int size1 = class1.getMethodCollection().size();
+ int size2 = class2.getMethodCollection().size();
+ if (size1 < size2) {
+ return class1;
+ }
+ if (size2 < size1) {
+ return class2;
+ }
+ assert class1.type != class2.type;
+ if (class1.type.slowCompareTo(class2.type) < 0) {
+ return class1;
+ }
+ return class2;
+ }
+
private void updatePinnedItems(Set<DexType> enumsToUnbox) {
appView
.appInfo()
@@ -395,13 +481,92 @@
}
public void finishAnalysis() {
+ analyzeInitializers();
+ analyzeAccessibility();
+ if (debugLogEnabled) {
+ reportEnumsAnalysis();
+ }
+ }
+
+ private void analyzeAccessibility() {
+ // Unboxing an enum will require to move its methods to a different class, which may impact
+ // accessibility. For a quick analysis we simply reuse the inliner analysis.
+ for (DexType toUnbox : enumsUnboxingCandidates.keySet()) {
+ DexProgramClass enumClass = appView.definitionFor(toUnbox).asProgramClass();
+ Constraint classConstraint = analyzeAccessibilityInClass(enumClass);
+ if (classConstraint == Constraint.NEVER) {
+ markEnumAsUnboxable(Reason.ACCESSIBILITY, enumClass);
+ } else if (classConstraint == Constraint.PACKAGE) {
+ enumsToUnboxWithPackageRequirement.add(toUnbox);
+ }
+ }
+ }
+
+ private Constraint analyzeAccessibilityInClass(DexProgramClass enumClass) {
+ Constraint classConstraint = Constraint.ALWAYS;
+ for (DexEncodedMethod method : enumClass.methods()) {
+ // Enum initializer are analyzed in analyzeInitializers instead.
+ if (!method.isInitializer()) {
+ Constraint methodConstraint = constraintForEnumUnboxing(method);
+ classConstraint = meet(classConstraint, methodConstraint);
+ if (classConstraint == Constraint.NEVER) {
+ return classConstraint;
+ }
+ }
+ }
+ return classConstraint;
+ }
+
+ private Constraint meet(Constraint constraint1, Constraint constraint2) {
+ if (constraint1 == Constraint.NEVER || constraint2 == Constraint.NEVER) {
+ return Constraint.NEVER;
+ }
+ if (constraint1 == Constraint.ALWAYS) {
+ return constraint2;
+ }
+ if (constraint2 == Constraint.ALWAYS) {
+ return constraint1;
+ }
+ assert constraint1 == Constraint.PACKAGE && constraint2 == Constraint.PACKAGE;
+ return Constraint.PACKAGE;
+ }
+
+ public Constraint constraintForEnumUnboxing(DexEncodedMethod method) {
+ // TODO(b/160939354): Use a UseRegistry instead of inlining constraints.
+ assert appView.definitionForProgramType(method.holder()) != null;
+ assert appView.definitionForProgramType(method.holder()).isEnum();
+ assert appView.definitionForProgramType(method.holder()).isEffectivelyFinal(appView);
+ assert appView.definitionForProgramType(method.holder()).superType
+ == appView.dexItemFactory().enumType;
+ assert !appView.definitionForProgramType(method.holder()).isInANest()
+ : "Unboxable enum is in a nest, this should not happen in cf to dex compilation, R8 needs"
+ + " to take care of nest access control when relocating unboxable enums methods";
+ switch (method.getCompilationState()) {
+ case PROCESSED_INLINING_CANDIDATE_ANY:
+ return Constraint.ALWAYS;
+ case PROCESSED_INLINING_CANDIDATE_SAME_NEST:
+ assert false;
+ // fall through
+ case PROCESSED_INLINING_CANDIDATE_SUBCLASS:
+ // There is no such thing as subclass access in this context, enums analyzed here have no
+ // subclasses, inherit directly from java.lang.Enum and are not analyzed if they call
+ // the protected methods in java.lang.Enum and java.lang.Object.
+ case PROCESSED_INLINING_CANDIDATE_SAME_CLASS:
+ case PROCESSED_INLINING_CANDIDATE_SAME_PACKAGE:
+ return Constraint.PACKAGE;
+ default:
+ return Constraint.NEVER;
+ }
+ }
+
+ private void analyzeInitializers() {
for (DexType toUnbox : enumsUnboxingCandidates.keySet()) {
DexProgramClass enumClass = appView.definitionForProgramType(toUnbox);
assert enumClass != null;
// Enum candidates have necessarily only one constructor matching enumMethods.constructor
// signature.
- DexEncodedMethod initializer = enumClass.lookupDirectMethod(factory.enumMethods.constructor);
+ DexEncodedMethod initializer = enumClass.lookupDirectMethod(factory.enumMembers.constructor);
if (initializer == null) {
// 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).
@@ -418,9 +583,6 @@
markEnumAsUnboxable(Reason.INVALID_CLINIT, enumClass);
}
}
- if (debugLogEnabled) {
- reportEnumsAnalysis();
- }
}
private Reason instructionAllowEnumUnboxing(
@@ -482,19 +644,19 @@
return Reason.UNSUPPORTED_LIBRARY_CALL;
}
// TODO(b/147860220): EnumSet and EnumMap may be interesting to model.
- if (singleTarget == factory.enumMethods.compareTo) {
+ if (singleTarget == factory.enumMembers.compareTo) {
return Reason.ELIGIBLE;
- } else if (singleTarget == factory.enumMethods.equals) {
+ } else if (singleTarget == factory.enumMembers.equals) {
return Reason.ELIGIBLE;
- } else if (singleTarget == factory.enumMethods.name) {
+ } else if (singleTarget == factory.enumMembers.nameMethod) {
return Reason.ELIGIBLE;
- } else if (singleTarget == factory.enumMethods.toString) {
+ } else if (singleTarget == factory.enumMembers.toString) {
return Reason.ELIGIBLE;
- } else if (singleTarget == factory.enumMethods.ordinal) {
+ } else if (singleTarget == factory.enumMembers.ordinalMethod) {
return Reason.ELIGIBLE;
- } else if (singleTarget == factory.enumMethods.hashCode) {
+ } else if (singleTarget == factory.enumMembers.hashCode) {
return Reason.ELIGIBLE;
- } else if (singleTarget == factory.enumMethods.constructor) {
+ } else if (singleTarget == factory.enumMembers.constructor) {
// Enum constructor call is allowed only if first call of an enum initializer.
if (code.method().isInstanceInitializer()
&& code.method().holder() == enumClass.type
@@ -675,6 +837,7 @@
public enum Reason {
ELIGIBLE,
+ ACCESSIBILITY,
ANNOTATION,
PINNED,
DOWN_CAST,
@@ -710,7 +873,8 @@
private class TreeFixer {
- private final List<DexEncodedMethod> unboxedEnumsMethods = new ArrayList<>();
+ private final Map<DexType, List<DexEncodedMethod>> unboxedEnumsMethods =
+ new IdentityHashMap<>();
private final EnumUnboxingLens.Builder lensBuilder = EnumUnboxingLens.builder();
private final Set<DexType> enumsToUnbox;
@@ -718,13 +882,13 @@
this.enumsToUnbox = enumsToUnbox;
}
- private NestedGraphLens fixupTypeReferences() {
+ private NestedGraphLens fixupTypeReferences(Map<DexType, DexType> newMethodLocation) {
assert enumUnboxerRewriter != null;
// 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 utility class.
+ // Clear the initializers and move the static methods to the new location.
Set<DexEncodedMethod> methodsToRemove = Sets.newIdentityHashSet();
clazz
.methods()
@@ -733,14 +897,22 @@
if (m.isInitializer()) {
clearEnumToUnboxMethod(m);
} else {
- unboxedEnumsMethods.add(
- fixupEncodedMethodToUtility(m, factory.enumUnboxingUtilityType));
+ DexType newHolder =
+ newMethodLocation.getOrDefault(
+ clazz.type, factory.enumUnboxingUtilityType);
+ List<DexEncodedMethod> movedMethods =
+ unboxedEnumsMethods.computeIfAbsent(newHolder, k -> new ArrayList<>());
+ movedMethods.add(fixupEncodedMethodToUtility(m, newHolder));
methodsToRemove.add(m);
}
});
clazz.getMethodCollection().removeMethods(methodsToRemove);
} else {
- clazz.getMethodCollection().replaceMethods(this::fixupEncodedMethod);
+ IntBox index = new IntBox(0);
+ clazz
+ .getMethodCollection()
+ .replaceMethods(
+ encodedMethod -> fixupEncodedMethod(encodedMethod, index.getAndIncrement()));
fixupFields(clazz.staticFields(), clazz::setStaticField);
fixupFields(clazz.instanceFields(), clazz::setInstanceField);
}
@@ -751,7 +923,11 @@
DexProgramClass utilityClass =
appView.definitionForProgramType(factory.enumUnboxingUtilityType);
assert utilityClass != null : "Should have been synthesized upfront";
- utilityClass.addDirectMethods(unboxedEnumsMethods);
+ unboxedEnumsMethods.forEach(
+ (newHolderType, movedMethods) -> {
+ DexProgramClass newHolderClass = appView.definitionFor(newHolderType).asProgramClass();
+ newHolderClass.addDirectMethods(movedMethods);
+ });
return lensBuilder.build(factory, appView.graphLens(), enumsToUnbox);
}
@@ -777,6 +953,7 @@
DexProto proto =
encodedMethod.isStatic() ? method.proto : factory.prependHolderToProto(method);
DexMethod newMethod = factory.createMethod(newHolder, fixupProto(proto), newMethodName);
+ assert appView.definitionFor(encodedMethod.holder()).lookupMethod(newMethod) == null;
lensBuilder.move(method, encodedMethod.isStatic(), newMethod, true);
encodedMethod.accessFlags.promoteToPublic();
encodedMethod.accessFlags.promoteToStatic();
@@ -785,9 +962,17 @@
return encodedMethod.toTypeSubstitutedMethod(newMethod);
}
- private DexEncodedMethod fixupEncodedMethod(DexEncodedMethod encodedMethod) {
- DexMethod newMethod = fixupMethod(encodedMethod.method);
- if (newMethod != encodedMethod.method) {
+ private DexEncodedMethod fixupEncodedMethod(DexEncodedMethod encodedMethod, int index) {
+ DexProto newProto = fixupProto(encodedMethod.proto());
+ if (newProto != encodedMethod.proto()) {
+ DexString newMethodName =
+ factory.createString(
+ EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_METHOD_PREFIX
+ + index
+ + "$"
+ + encodedMethod.getName().toString());
+ DexMethod newMethod = factory.createMethod(encodedMethod.holder(), newProto, newMethodName);
+ assert appView.definitionFor(encodedMethod.holder()).lookupMethod(newMethod) == null;
boolean isStatic = encodedMethod.isStatic();
lensBuilder.move(encodedMethod.method, isStatic, newMethod, isStatic);
return encodedMethod.toTypeSubstitutedMethod(newMethod);
@@ -817,14 +1002,6 @@
}
}
- private DexMethod fixupMethod(DexMethod method) {
- return fixupMethod(method, method.holder, method.name);
- }
-
- private DexMethod fixupMethod(DexMethod method, DexType newHolder, DexString newMethodName) {
- return factory.createMethod(newHolder, fixupProto(method.proto), newMethodName);
- }
-
private DexProto fixupProto(DexProto proto) {
DexType returnType = fixupType(proto.returnType);
DexType[] arguments = fixupTypes(proto.parameters.values);
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 08ba8b4..c4a6bb4 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
@@ -83,7 +83,7 @@
private boolean isStandardEnumInitializer(DexEncodedMethod method) {
return method.isInstanceInitializer()
- && method.method.proto == factory.enumMethods.constructor.proto;
+ && method.method.proto == factory.enumMembers.constructor.proto;
}
// The enum should have the $VALUES static field and only fields directly referencing the enum
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 1832a2a..087e808 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
@@ -129,21 +129,21 @@
DexMethod invokedMethod = invokeMethod.getInvokedMethod();
DexType enumType = getEnumTypeOrNull(invokeMethod.getReceiver(), convertedEnums);
if (enumType != null) {
- if (invokedMethod == factory.enumMethods.ordinal
- || invokedMethod == factory.enumMethods.hashCode) {
+ if (invokedMethod == factory.enumMembers.ordinalMethod
+ || invokedMethod == factory.enumMembers.hashCode) {
replaceEnumInvoke(
iterator, invokeMethod, ordinalUtilityMethod, m -> synthesizeOrdinalMethod());
continue;
- } else if (invokedMethod == factory.enumMethods.equals) {
+ } else if (invokedMethod == factory.enumMembers.equals) {
replaceEnumInvoke(
iterator, invokeMethod, equalsUtilityMethod, m -> synthesizeEqualsMethod());
continue;
- } else if (invokedMethod == factory.enumMethods.compareTo) {
+ } else if (invokedMethod == factory.enumMembers.compareTo) {
replaceEnumInvoke(
iterator, invokeMethod, compareToUtilityMethod, m -> synthesizeCompareToMethod());
continue;
- } else if (invokedMethod == factory.enumMethods.name
- || invokedMethod == factory.enumMethods.toString) {
+ } else if (invokedMethod == factory.enumMembers.nameMethod
+ || invokedMethod == factory.enumMembers.toString) {
DexMethod toStringMethod = computeDefaultToStringUtilityMethod(enumType);
iterator.replaceCurrentInstruction(
new InvokeStatic(
@@ -155,7 +155,7 @@
} else if (instruction.isInvokeStatic()) {
InvokeStatic invokeStatic = instruction.asInvokeStatic();
DexMethod invokedMethod = invokeStatic.getInvokedMethod();
- if (invokedMethod == factory.enumMethods.valueOf
+ if (invokedMethod == factory.enumMembers.valueOf
&& invokeStatic.inValues().get(0).isConstClass()) {
DexType enumType =
invokeStatic.inValues().get(0).getConstInstruction().asConstClass().getValue();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
index 789f4d9..ebe815d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.AppView;
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;
import com.android.tools.r8.graph.DexItemFactory;
@@ -18,6 +19,10 @@
import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
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.analysis.value.SingleNumberValue;
+import com.android.tools.r8.ir.analysis.value.SingleStringValue;
import com.android.tools.r8.ir.code.ArrayGet;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
@@ -72,15 +77,9 @@
if (current.isInvokeMethodWithReceiver()) {
InvokeMethodWithReceiver methodWithReceiver = current.asInvokeMethodWithReceiver();
DexMethod invokedMethod = methodWithReceiver.getInvokedMethod();
- boolean isOrdinalInvoke = invokedMethod == factory.enumMethods.ordinal;
- boolean isNameInvoke = invokedMethod == factory.enumMethods.name;
- boolean isToStringInvoke = invokedMethod == factory.enumMethods.toString;
-
- // TODO(b/160667929): Re-enable name()/toString() optimizations.
- if (!isOrdinalInvoke) {
- continue;
- }
-
+ boolean isOrdinalInvoke = invokedMethod == factory.enumMembers.ordinalMethod;
+ boolean isNameInvoke = invokedMethod == factory.enumMembers.nameMethod;
+ boolean isToStringInvoke = invokedMethod == factory.enumMembers.toString;
if (!isOrdinalInvoke && !isNameInvoke && !isToStringInvoke) {
continue;
}
@@ -89,57 +88,98 @@
if (receiver.isPhi()) {
continue;
}
- Instruction definition = receiver.getDefinition();
- if (!definition.isStaticGet()) {
- continue;
- }
- DexField enumField = definition.asStaticGet().getField();
- EnumValueInfoMap valueInfoMap =
- appView.appInfo().withLiveness().getEnumValueInfoMap(enumField.type);
- if (valueInfoMap == null) {
+
+ StaticGet staticGet = receiver.getDefinition().asStaticGet();
+ if (staticGet == null) {
continue;
}
- // The receiver value is identified as being from a constant enum field lookup by the fact
- // that it is a static-get to a field whose type is the same as the enclosing class (which
- // is known to be an enum type). An enum may still define a static field using the enum type
- // so ensure the field is present in the ordinal map for final validation.
- EnumValueInfo valueInfo = valueInfoMap.getEnumValueInfo(enumField);
- if (valueInfo == null) {
+ DexField field = staticGet.getField();
+ DexEncodedField definition = field.lookupOnClass(appView.definitionForHolder(field));
+ if (definition == null) {
+ continue;
+ }
+
+ AbstractValue abstractValue = definition.getOptimizationInfo().getAbstractValue();
+ if (!abstractValue.isSingleFieldValue()) {
+ continue;
+ }
+
+ ObjectState objectState = abstractValue.asSingleFieldValue().getState();
+ if (objectState.isEmpty()) {
continue;
}
Value outValue = methodWithReceiver.outValue();
if (isOrdinalInvoke) {
- iterator.replaceCurrentInstruction(new ConstNumber(outValue, valueInfo.ordinal));
- } else if (isNameInvoke) {
- Value newValue =
- code.createValue(TypeElement.stringClassType(appView, definitelyNotNull()));
- iterator.replaceCurrentInstruction(
- new ConstString(
- newValue, enumField.name, ThrowingInfo.defaultForConstString(appView.options())));
- newValue.addAffectedValuesTo(affectedValues);
- } else {
- assert isToStringInvoke;
- DexClass enumClazz = appView.appInfo().definitionFor(enumField.type);
- if (!enumClazz.accessFlags.isFinal()) {
- continue;
+ DexField ordinalField = appView.dexItemFactory().enumMembers.ordinalField;
+ DexEncodedField ordinalDefinition =
+ ordinalField.lookupOnClass(appView.definitionForHolder(ordinalField));
+ if (ordinalDefinition != null) {
+ SingleNumberValue ordinalValue =
+ objectState.getAbstractFieldValue(ordinalDefinition).asSingleNumberValue();
+ if (ordinalValue != null) {
+ iterator.replaceCurrentInstruction(
+ new ConstNumber(outValue, ordinalValue.getValue()));
+ }
}
- DexEncodedMethod singleTarget =
- appView
- .appInfo()
- .resolveMethodOnClass(factory.objectMembers.toString, valueInfo.type)
- .getSingleTarget();
- if (singleTarget != null && singleTarget.method != factory.enumMethods.toString) {
- continue;
- }
- Value newValue =
- code.createValue(TypeElement.stringClassType(appView, definitelyNotNull()));
- iterator.replaceCurrentInstruction(
- new ConstString(
- newValue, enumField.name, ThrowingInfo.defaultForConstString(appView.options())));
- newValue.addAffectedValuesTo(affectedValues);
+ continue;
}
+
+ DexField nameField = appView.dexItemFactory().enumMembers.nameField;
+ DexEncodedField nameDefinition =
+ nameField.lookupOnClass(appView.definitionForHolder(nameField));
+ if (nameField == null) {
+ continue;
+ }
+
+ SingleStringValue nameValue =
+ objectState.getAbstractFieldValue(nameDefinition).asSingleStringValue();
+ if (nameValue == null) {
+ continue;
+ }
+
+ if (isNameInvoke) {
+ Value newValue =
+ code.createValue(TypeElement.stringClassType(appView, definitelyNotNull()));
+ iterator.replaceCurrentInstruction(
+ new ConstString(
+ newValue,
+ nameValue.getDexString(),
+ ThrowingInfo.defaultForConstString(appView.options())));
+ newValue.addAffectedValuesTo(affectedValues);
+ continue;
+ }
+
+ assert isToStringInvoke;
+
+ DexClass enumClazz = appView.appInfo().definitionFor(field.type);
+ if (!enumClazz.isFinal()) {
+ continue;
+ }
+
+ EnumValueInfo valueInfo = appView.appInfo().withLiveness().getEnumValueInfo(field);
+ if (valueInfo == null) {
+ continue;
+ }
+
+ DexEncodedMethod singleTarget =
+ appView
+ .appInfo()
+ .resolveMethodOnClass(factory.objectMembers.toString, valueInfo.type)
+ .getSingleTarget();
+ if (singleTarget != null && singleTarget.method != factory.enumMembers.toString) {
+ continue;
+ }
+
+ Value newValue =
+ code.createValue(TypeElement.stringClassType(appView, definitelyNotNull()));
+ iterator.replaceCurrentInstruction(
+ new ConstString(
+ newValue,
+ nameValue.getDexString(),
+ ThrowingInfo.defaultForConstString(appView.options())));
+ newValue.addAffectedValuesTo(affectedValues);
} else if (current.isArrayLength()) {
// Rewrites MyEnum.values().length to a constant int.
Instruction arrayDefinition = current.asArrayLength().array().getAliasedValue().definition;
@@ -148,7 +188,7 @@
DexProgramClass enumClass = appView.definitionForProgramType(invokedMethod.holder);
if (enumClass != null
&& enumClass.isEnum()
- && factory.enumMethods.isValuesMethod(invokedMethod, enumClass)) {
+ && factory.enumMembers.isValuesMethod(invokedMethod, enumClass)) {
EnumValueInfoMap enumValueInfoMap =
appView.appInfo().withLiveness().getEnumValueInfoMap(invokedMethod.holder);
if (enumValueInfoMap != null) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 5542908..b189a57 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -563,7 +563,7 @@
return null;
}
// java.lang.Enum.<init>() and java.lang.Object.<init>() are considered trivial.
- if (invokedMethod == dexItemFactory.enumMethods.constructor
+ if (invokedMethod == dexItemFactory.enumMembers.constructor
|| invokedMethod == dexItemFactory.objectMembers.constructor) {
builder.setParent(invokedMethod);
break;
@@ -1098,7 +1098,7 @@
.resolveMethodOnClass(appView.dexItemFactory().objectMembers.finalize, clazz);
DexEncodedMethod target = resolutionResult.getSingleTarget();
return target != null
- && target.method != dexItemFactory.enumMethods.finalize
+ && target.method != dexItemFactory.enumMembers.finalize
&& target.method != dexItemFactory.objectMembers.finalize;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 822e0c5..62edebf 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -162,7 +162,7 @@
@Override
public void setInstanceInitializerInfo(
DexEncodedMethod method, InstanceInitializerInfo instanceInitializerInfo) {
- // Ignored.
+ method.getMutableOptimizationInfo().setInstanceInitializerInfo(instanceInitializerInfo);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
index d26de6d..5979031 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
@@ -47,9 +47,10 @@
recordInitializationInfo(field.field, info);
}
- public void recordInitializationInfo(DexField field, InstanceFieldInitializationInfo info) {
+ public Builder recordInitializationInfo(DexField field, InstanceFieldInitializationInfo info) {
assert !infos.containsKey(field);
infos.put(field, info);
+ return this;
}
public InstanceFieldInitializationInfoCollection build() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
index 85cb117..baf75b5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
@@ -8,8 +8,8 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.NumberGenerator;
import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.origin.Origin;
import java.util.IdentityHashMap;
@@ -19,7 +19,7 @@
private final AppView<?> appView;
private final ProgramMethod context;
- private final ValueNumberGenerator valueNumberGenerator;
+ private final NumberGenerator valueNumberGenerator;
private final MethodProcessor methodProcessor;
private final Map<InvokeMethod, IRCode> cache = new IdentityHashMap<>();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index 4cba255..a716bd4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -196,7 +196,7 @@
// from it. In this case we will reprocess method that does not need patching, but it
// should not be happening very frequently and we ignore possible overhead.
private final LongLivedProgramMethodSetBuilder<SortedProgramMethodSet> methodsToReprocess =
- LongLivedProgramMethodSetBuilder.createSorted();
+ LongLivedProgramMethodSetBuilder.createForSortedSet();
private final AppView<AppInfoWithLiveness> appView;
private final Kotlin kotlin;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
index cdc1793..658fcb5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
@@ -38,7 +38,7 @@
InvokeMethod invoke,
DexEncodedMethod singleTarget,
Set<Value> affectedValues) {
- if (singleTarget.method == appView.dexItemFactory().enumMethods.valueOf
+ if (singleTarget.method == appView.dexItemFactory().enumMembers.valueOf
&& invoke.inValues().get(0).isConstClass()) {
insertAssumeDynamicType(code, instructionIterator, invoke);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryFieldSynthesis.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryFieldSynthesis.java
new file mode 100644
index 0000000..3f3277f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryFieldSynthesis.java
@@ -0,0 +1,47 @@
+// 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.library;
+
+import static com.android.tools.r8.graph.DexLibraryClass.asLibraryClassOrNull;
+
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexLibraryClass;
+import com.android.tools.r8.graph.FieldAccessFlags;
+
+/**
+ * This class synthesizes library fields that we rely on for modeling.
+ *
+ * <p>For example, we synthesize the field `java.lang.String java.lang.Enum.name` if it is not
+ * present. We use this to model that the constructor `void java.lang.Enum.<init>(java.lang.String,
+ * int)` initializes `java.lang.String java.lang.Enum.name` to the first argument of the
+ * constructor.
+ */
+public class LibraryFieldSynthesis {
+
+ public static void synthesizeEnumFields(AppView<?> appView) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ DexLibraryClass enumClass =
+ asLibraryClassOrNull(appView.definitionFor(dexItemFactory.enumType));
+ if (enumClass != null) {
+ dexItemFactory.enumMembers.forEachField(
+ field -> {
+ DexEncodedField definition = enumClass.lookupField(field);
+ if (definition == null) {
+ enumClass.appendInstanceField(
+ new DexEncodedField(
+ field,
+ FieldAccessFlags.fromCfAccessFlags(
+ Constants.ACC_PRIVATE | Constants.ACC_FINAL),
+ DexAnnotationSet.empty(),
+ null));
+ }
+ });
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
index e7e0fe6..d0412b7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
@@ -11,12 +11,16 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexItemFactory.EnumMembers;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
import com.android.tools.r8.ir.analysis.value.ObjectState;
import com.android.tools.r8.ir.optimize.info.LibraryOptimizationInfoInitializerFeedback;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory;
+import com.android.tools.r8.ir.optimize.info.initializer.NonTrivialInstanceInitializerInfo;
import com.google.common.collect.Sets;
import java.util.BitSet;
import java.util.Set;
@@ -38,6 +42,7 @@
}
void run(Set<DexEncodedField> finalLibraryFields) {
+ modelInstanceInitializers();
modelStaticFinalLibraryFields(finalLibraryFields);
modelLibraryMethodsReturningNonNull();
modelLibraryMethodsReturningReceiver();
@@ -48,6 +53,28 @@
return modeledLibraryTypes;
}
+ private void modelInstanceInitializers() {
+ EnumMembers enumMembers = dexItemFactory.enumMembers;
+ DexEncodedMethod enumConstructor = lookupMethod(enumMembers.constructor);
+ if (enumConstructor != null) {
+ LibraryFieldSynthesis.synthesizeEnumFields(appView);
+ InstanceFieldInitializationInfoFactory factory =
+ appView.instanceFieldInitializationInfoFactory();
+ InstanceFieldInitializationInfoCollection fieldInitializationInfos =
+ InstanceFieldInitializationInfoCollection.builder()
+ .recordInitializationInfo(
+ enumMembers.nameField, factory.createArgumentInitializationInfo(1))
+ .recordInitializationInfo(
+ enumMembers.ordinalField, factory.createArgumentInitializationInfo(2))
+ .build();
+ feedback.setInstanceInitializerInfo(
+ enumConstructor,
+ NonTrivialInstanceInitializerInfo.builder(fieldInitializationInfos)
+ .setParent(dexItemFactory.objectMembers.constructor)
+ .build());
+ }
+ }
+
private void modelStaticFinalLibraryFields(Set<DexEncodedField> finalLibraryFields) {
for (DexEncodedField field : finalLibraryFields) {
if (field.isStatic()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
index 323dd11..c4466cd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
@@ -113,6 +113,11 @@
this.converter = converter;
}
+ // This set is an approximation and can be used only for heuristics.
+ public final Set<DexType> getCandidates() {
+ return candidates.keySet();
+ }
+
// Before doing any usage-based analysis we collect a set of classes that can be
// candidates for staticizing. This analysis is very simple, but minimizes the
// set of eligible classes staticizer tracks and thus time and memory it needs.
@@ -274,8 +279,8 @@
if (instruction.isNewInstance()) {
// Check the class being initialized against valid staticizing candidates.
NewInstance newInstance = instruction.asNewInstance();
- CandidateInfo candidateInfo = processInstantiation(context, iterator, newInstance);
- if (candidateInfo != null) {
+ CandidateInfo info = processInstantiation(context, iterator, newInstance);
+ if (info != null) {
alreadyProcessed.addAll(newInstance.outValue().aliasedUsers());
// For host class initializers having eligible instantiation we also want to
// ensure that the rest of the initializer consist of code w/o side effects.
@@ -283,15 +288,13 @@
// effects, otherwise we can still staticize, but cannot remove singleton reads.
while (iterator.hasNext()) {
if (!isAllowedInHostClassInitializer(context.getHolderType(), iterator.next(), code)) {
- candidateInfo.preserveRead.set(true);
+ info.preserveRead.set(true);
iterator.previous();
break;
}
// Ignore just read instruction.
}
- referencedFrom
- .computeIfAbsent(candidateInfo, ignore -> LongLivedProgramMethodSetBuilder.create())
- .add(context);
+ addReferencedFrom(info, context);
}
continue;
}
@@ -311,9 +314,7 @@
// Check the field being read: make sure all usages are valid.
CandidateInfo info = processStaticFieldRead(instruction.asStaticGet());
if (info != null) {
- referencedFrom
- .computeIfAbsent(info, ignore -> LongLivedProgramMethodSetBuilder.create())
- .add(context);
+ addReferencedFrom(info, context);
// If the candidate is still valid, ignore all usages in further analysis.
Value value = instruction.outValue();
if (value != null) {
@@ -327,9 +328,7 @@
// Check if it is a static singleton getter.
CandidateInfo info = processInvokeStatic(instruction.asInvokeStatic());
if (info != null) {
- referencedFrom
- .computeIfAbsent(info, ignore -> LongLivedProgramMethodSetBuilder.create())
- .add(context);
+ addReferencedFrom(info, context);
// If the candidate is still valid, ignore all usages in further analysis.
Value value = instruction.outValue();
if (value != null) {
@@ -368,6 +367,13 @@
}
}
+ private void addReferencedFrom(CandidateInfo info, ProgramMethod context) {
+ LongLivedProgramMethodSetBuilder<?> builder =
+ referencedFrom.computeIfAbsent(
+ info, ignore -> LongLivedProgramMethodSetBuilder.createConcurrentForIdentitySet());
+ builder.add(context);
+ }
+
private boolean isAllowedInHostClassInitializer(
DexType host, Instruction insn, IRCode code) {
return (insn.isStaticPut() && insn.asStaticPut().getField().holder == host) ||
@@ -761,5 +767,10 @@
}
return true;
}
+
+ @Override
+ public boolean registerInstanceOf(DexType type) {
+ return registerTypeReference(type);
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java
new file mode 100644
index 0000000..0cafbcd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java
@@ -0,0 +1,172 @@
+// 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.string;
+
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractState;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.FailedTransferFunctionResult;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.IntraproceduralDataflowAnalysis;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.TransferFunction;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.TransferFunctionResult;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.SetUtils;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+/**
+ * This defines a simple program analysis that determines if there is a path from a call to append()
+ * on a StringBuilder back to itself in the control flow graph.
+ *
+ * <p>The analysis explicitly allows paths from a call to append() back to itself that go through a
+ * call to toString() on the builder. This ensures that we can still optimize builders that are
+ * fully enclosed in a loop: <code>
+ * while (true) {
+ * System.out.println(new StringBuilder().append("42").toString());
+ * }
+ * </code>
+ */
+class StringBuilderAppendFlowAnalysis {
+
+ /**
+ * Returns true if there is a call to {@code append()} on {@param builder}, which is inside a
+ * loop.
+ */
+ static boolean hasAppendInstructionInLoop(
+ Value builder, StringBuilderOptimizationConfiguration configuration) {
+ IntraproceduralDataflowAnalysis<AbstractStateImpl> analysis =
+ new IntraproceduralDataflowAnalysis<>(
+ AbstractStateImpl.bottom(), new TransferFunctionImpl(builder, configuration));
+ DataflowAnalysisResult result = analysis.run(builder.definition.getBlock());
+ return result.isFailedAnalysisResult();
+ }
+
+ /** This defines the state that we keep track of for each {@link BasicBlock}. */
+ private static class AbstractStateImpl extends AbstractState<AbstractStateImpl> {
+
+ private static final AbstractStateImpl BOTTOM = new AbstractStateImpl();
+
+ // The set of invoke instructions that call append(), which is on a path to the current program
+ // point.
+ private final Set<InvokeVirtual> liveAppendInstructions;
+
+ private AbstractStateImpl() {
+ this(Sets.newIdentityHashSet());
+ }
+
+ private AbstractStateImpl(Set<InvokeVirtual> liveAppendInstructions) {
+ this.liveAppendInstructions = liveAppendInstructions;
+ }
+
+ public static AbstractStateImpl bottom() {
+ return BOTTOM;
+ }
+
+ private AbstractStateImpl addLiveAppendInstruction(InvokeVirtual invoke) {
+ Set<InvokeVirtual> newLiveAppendInstructions =
+ SetUtils.newIdentityHashSet(liveAppendInstructions);
+ newLiveAppendInstructions.add(invoke);
+ return new AbstractStateImpl(newLiveAppendInstructions);
+ }
+
+ private boolean isAppendInstructionLive(InvokeVirtual invoke) {
+ return liveAppendInstructions.contains(invoke);
+ }
+
+ @Override
+ public AbstractStateImpl asAbstractState() {
+ return this;
+ }
+
+ @Override
+ public AbstractStateImpl join(AbstractStateImpl state) {
+ if (liveAppendInstructions.isEmpty()) {
+ return state;
+ }
+ if (state.liveAppendInstructions.isEmpty()) {
+ return this;
+ }
+ Set<InvokeVirtual> newLiveAppendInstructions =
+ SetUtils.newIdentityHashSet(liveAppendInstructions, state.liveAppendInstructions);
+ return new AbstractStateImpl(newLiveAppendInstructions);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == null || getClass() != other.getClass()) {
+ return false;
+ }
+ AbstractStateImpl state = (AbstractStateImpl) other;
+ return liveAppendInstructions.equals(state.liveAppendInstructions);
+ }
+
+ @Override
+ public int hashCode() {
+ return liveAppendInstructions.hashCode();
+ }
+ }
+
+ /**
+ * This defines the transfer function for the analysis.
+ *
+ * <p>If a call to {@code append()} on the builder is seen, then that invoke instruction is added
+ * to the abstract state.
+ *
+ * <p>If a call to {@code toString()} on the builder i seen, then the abstract state is reset to
+ * bottom.
+ */
+ private static class TransferFunctionImpl implements TransferFunction<AbstractStateImpl> {
+
+ private final Value builder;
+ private final StringBuilderOptimizationConfiguration configuration;
+
+ private TransferFunctionImpl(
+ Value builder, StringBuilderOptimizationConfiguration configuration) {
+ this.builder = builder;
+ this.configuration = configuration;
+ }
+
+ @Override
+ public TransferFunctionResult<AbstractStateImpl> apply(
+ Instruction instruction, AbstractStateImpl state) {
+ if (instruction.isInvokeMethod()) {
+ return apply(state, instruction.asInvokeMethod());
+ }
+ return state;
+ }
+
+ private TransferFunctionResult<AbstractStateImpl> apply(
+ AbstractStateImpl state, InvokeMethod invoke) {
+ if (isAppendOnBuilder(invoke)) {
+ assert invoke.isInvokeVirtual();
+ InvokeVirtual appendInvoke = invoke.asInvokeVirtual();
+ if (state.isAppendInstructionLive(appendInvoke)) {
+ return new FailedTransferFunctionResult<>();
+ }
+ return state.addLiveAppendInstruction(appendInvoke);
+ }
+ if (isToStringOnBuilder(invoke)) {
+ return AbstractStateImpl.bottom();
+ }
+ return state;
+ }
+
+ private boolean isAppendOnBuilder(InvokeMethod invoke) {
+ DexMethod invokedMethod = invoke.getInvokedMethod();
+ return configuration.isAppendMethod(invokedMethod)
+ && invoke.getArgument(0).getAliasedValue() == builder;
+ }
+
+ private boolean isToStringOnBuilder(InvokeMethod invoke) {
+ DexMethod invokedMethod = invoke.getInvokedMethod();
+ return configuration.isToStringMethod(invokedMethod)
+ && invoke.getArgument(0).getAliasedValue() == builder;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
index a01be35..cd9fe4e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
@@ -608,7 +608,7 @@
assert perInstrState != null;
BuilderState builderState = perInstrState.get(instr);
assert builderState != null;
- String element = toCompileTimeString(builderState);
+ String element = toCompileTimeString(builder, builderState);
assert element != null;
Value stringValue =
code.createValue(
@@ -653,7 +653,7 @@
if (builderState == null) {
return false;
}
- String element = toCompileTimeString(builderState);
+ String element = toCompileTimeString(builder, builderState);
if (element == null) {
return false;
}
@@ -663,7 +663,7 @@
// Find the trivial chain of builder-append*-toString.
// Note that we can't determine a compile-time constant string if there are any ambiguity.
@VisibleForTesting
- String toCompileTimeString(BuilderState state) {
+ String toCompileTimeString(Value builder, BuilderState state) {
boolean continuedForLogging = false;
LinkedList<String> contents = new LinkedList<>();
while (state != null) {
@@ -710,6 +710,10 @@
if (contents.isEmpty()) {
return null;
}
+ if (StringBuilderAppendFlowAnalysis.hasAppendInstructionInLoop(
+ builder, optimizationConfiguration)) {
+ return null;
+ }
String result = StringUtils.join(contents, "");
int estimate = estimateSizeReduction(contents);
return estimate > result.length() ? result : null;
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
index 9402fbb..da4daec 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
@@ -13,8 +13,8 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.NumberGenerator;
import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.SourceCode;
@@ -48,7 +48,7 @@
ProgramMethod context,
ProgramMethod method,
AppView<?> appView,
- ValueNumberGenerator valueNumberGenerator,
+ NumberGenerator valueNumberGenerator,
Position callerPosition,
Origin origin,
MethodProcessor methodProcessor) {
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 5e9d63f..5a50ca4 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
@@ -68,6 +68,8 @@
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);
@@ -123,6 +125,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));
instructions.add(
new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.stringMembers.equals, false));
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
index 24947bf..00e689c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
@@ -48,6 +48,7 @@
private final KotlinTypeReference anonymousObjectOrigin;
private final String packageName;
private final KotlinLocalDelegatedPropertyInfo localDelegatedProperties;
+ private final int[] metadataVersion;
private KotlinClassInfo(
int flags,
@@ -63,7 +64,8 @@
KotlinVersionRequirementInfo versionRequirements,
KotlinTypeReference anonymousObjectOrigin,
String packageName,
- KotlinLocalDelegatedPropertyInfo localDelegatedProperties) {
+ KotlinLocalDelegatedPropertyInfo localDelegatedProperties,
+ int[] metadataVersion) {
this.flags = flags;
this.name = name;
this.moduleName = moduleName;
@@ -78,11 +80,13 @@
this.anonymousObjectOrigin = anonymousObjectOrigin;
this.packageName = packageName;
this.localDelegatedProperties = localDelegatedProperties;
+ this.metadataVersion = metadataVersion;
}
public static KotlinClassInfo create(
KmClass kmClass,
String packageName,
+ int[] metadataVersion,
DexClass hostClass,
DexItemFactory factory,
Reporter reporter,
@@ -129,7 +133,8 @@
getAnonymousObjectOrigin(kmClass, factory),
packageName,
KotlinLocalDelegatedPropertyInfo.create(
- JvmExtensionsKt.getLocalDelegatedProperties(kmClass), factory, reporter));
+ JvmExtensionsKt.getLocalDelegatedProperties(kmClass), factory, reporter),
+ metadataVersion);
}
private static KotlinTypeReference getAnonymousObjectOrigin(
@@ -293,6 +298,11 @@
}
@Override
+ public int[] getMetadataVersion() {
+ return metadataVersion;
+ }
+
+ @Override
public void trace(DexDefinitionSupplier definitionSupplier) {
forEachApply(constructorsWithNoBacking, constructor -> constructor::trace, definitionSupplier);
declarationContainerInfo.trace(definitionSupplier);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java
index fdb326e..1d968d2 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java
@@ -59,4 +59,6 @@
KotlinClassHeader rewrite(DexClass clazz, AppView<?> appView, NamingLens namingLens);
String getPackageName();
+
+ int[] getMetadataVersion();
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index 281950c..45cbd27 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -112,10 +112,12 @@
Reporter reporter,
Consumer<DexEncodedMethod> keepByteCode) {
String packageName = kMetadata.getHeader().getPackageName();
+ int[] metadataVersion = kMetadata.getHeader().getMetadataVersion();
if (kMetadata instanceof KotlinClassMetadata.Class) {
return KotlinClassInfo.create(
((KotlinClassMetadata.Class) kMetadata).toKmClass(),
packageName,
+ metadataVersion,
clazz,
factory,
reporter,
@@ -125,6 +127,7 @@
return KotlinFileFacadeInfo.create(
(KotlinClassMetadata.FileFacade) kMetadata,
packageName,
+ metadataVersion,
clazz,
factory,
reporter,
@@ -132,12 +135,16 @@
} else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassFacade) {
// multi-file class with the same @JvmName.
return KotlinMultiFileClassFacadeInfo.create(
- (KotlinClassMetadata.MultiFileClassFacade) kMetadata, packageName, factory);
+ (KotlinClassMetadata.MultiFileClassFacade) kMetadata,
+ packageName,
+ metadataVersion,
+ factory);
} else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassPart) {
// A single file, which is part of multi-file class.
return KotlinMultiFileClassPartInfo.create(
(KotlinClassMetadata.MultiFileClassPart) kMetadata,
packageName,
+ metadataVersion,
clazz,
factory,
reporter,
@@ -146,6 +153,7 @@
return KotlinSyntheticClassInfo.create(
(KotlinClassMetadata.SyntheticClass) kMetadata,
packageName,
+ metadataVersion,
clazz,
kotlin,
factory,
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
index 886281f..b012758 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
@@ -22,15 +22,19 @@
private final KotlinPackageInfo packageInfo;
private final String packageName;
+ private final int[] metadataVersion;
- private KotlinFileFacadeInfo(KotlinPackageInfo packageInfo, String packageName) {
+ private KotlinFileFacadeInfo(
+ KotlinPackageInfo packageInfo, String packageName, int[] metadataVersion) {
this.packageInfo = packageInfo;
this.packageName = packageName;
+ this.metadataVersion = metadataVersion;
}
public static KotlinFileFacadeInfo create(
FileFacade kmFileFacade,
String packageName,
+ int[] metadataVersion,
DexClass clazz,
DexItemFactory factory,
Reporter reporter,
@@ -38,7 +42,8 @@
return new KotlinFileFacadeInfo(
KotlinPackageInfo.create(
kmFileFacade.toKmPackage(), clazz, factory, reporter, keepByteCode),
- packageName);
+ packageName,
+ metadataVersion);
}
@Override
@@ -66,6 +71,11 @@
}
@Override
+ public int[] getMetadataVersion() {
+ return metadataVersion;
+ }
+
+ @Override
public void trace(DexDefinitionSupplier definitionSupplier) {
packageInfo.trace(definitionSupplier);
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
index 680bd5c..5327873 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
@@ -8,7 +8,6 @@
import static com.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO;
import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinitionSupplier;
@@ -132,11 +131,6 @@
}
@Override
- public DexProgramClass definitionForProgramType(DexType type) {
- throw new Unreachable("Should not be called");
- }
-
- @Override
public DexItemFactory dexItemFactory() {
return baseSupplier.dexItemFactory();
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
index 5e11d43..ace8717 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -27,6 +27,11 @@
public class KotlinMetadataRewriter {
+ // Due to a bug with nested classes and the lookup of RequirementVersion, we bump all metadata
+ // versions to 1.4 if compiled with kotlin 1.3 (1.1.16). For more information, see b/161885097 for
+ // more information.
+ private static final int[] METADATA_VERSION_1_4 = new int[] {1, 4, 0};
+
private static final class WriteMetadataFieldInfo {
final boolean writeKind;
final boolean writeMetadataVersion;
@@ -111,7 +116,10 @@
KotlinClassHeader kotlinClassHeader = kotlinInfo.rewrite(clazz, appView, lens);
DexAnnotation newMeta =
createKotlinMetadataAnnotation(
- kotlinClassHeader, kotlinInfo.getPackageName(), writeMetadataFieldInfo);
+ kotlinClassHeader,
+ kotlinInfo.getPackageName(),
+ getMaxVersion(METADATA_VERSION_1_4, kotlinInfo.getMetadataVersion()),
+ writeMetadataFieldInfo);
clazz.setAnnotations(
clazz.annotations().rewrite(anno -> anno == oldMeta ? newMeta : anno));
} catch (Throwable t) {
@@ -136,12 +144,15 @@
}
private DexAnnotation createKotlinMetadataAnnotation(
- KotlinClassHeader header, String packageName, WriteMetadataFieldInfo writeMetadataFieldInfo) {
+ KotlinClassHeader header,
+ String packageName,
+ int[] metadataVersion,
+ WriteMetadataFieldInfo writeMetadataFieldInfo) {
List<DexAnnotationElement> elements = new ArrayList<>();
if (writeMetadataFieldInfo.writeMetadataVersion) {
elements.add(
new DexAnnotationElement(
- kotlin.metadata.metadataVersion, createIntArray(header.getMetadataVersion())));
+ kotlin.metadata.metadataVersion, createIntArray(metadataVersion)));
}
if (writeMetadataFieldInfo.writeByteCodeVersion) {
elements.add(
@@ -197,4 +208,23 @@
}
return new DexValueArray(values);
}
+
+ // We are not sure that the format is <Major>-<Minor>-<Patch>, the format can be: <Major>-<Minor>.
+ private int[] getMaxVersion(int[] one, int[] other) {
+ assert one.length == 2 || one.length == 3;
+ assert other.length == 2 || other.length == 3;
+ if (one[0] != other[0]) {
+ return one[0] > other[0] ? one : other;
+ }
+ if (one[1] != other[1]) {
+ return one[1] > other[1] ? one : other;
+ }
+ int patchOne = one.length >= 3 ? one[2] : 0;
+ int patchOther = other.length >= 3 ? other[2] : 0;
+ if (patchOne != patchOther) {
+ return patchOne > patchOther ? one : other;
+ }
+ // They are equal up to patch, just return one.
+ return one;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
index 6ef7a0d..fad37c6 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
@@ -58,6 +58,11 @@
}
@Override
+ public int[] getMetadataVersion() {
+ throw new Unreachable("Should never be called");
+ }
+
+ @Override
public boolean isNoKotlinInformation() {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
index 34c5248..ec31d79 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
@@ -23,20 +23,25 @@
private final List<KotlinTypeReference> partClassNames;
private final String packageName;
+ private final int[] metadataVersion;
private KotlinMultiFileClassFacadeInfo(
- List<KotlinTypeReference> partClassNames, String packageName) {
+ List<KotlinTypeReference> partClassNames, String packageName, int[] metadataVersion) {
this.partClassNames = partClassNames;
this.packageName = packageName;
+ this.metadataVersion = metadataVersion;
}
static KotlinMultiFileClassFacadeInfo create(
- MultiFileClassFacade kmMultiFileClassFacade, String packageName, DexItemFactory factory) {
+ MultiFileClassFacade kmMultiFileClassFacade,
+ String packageName,
+ int[] metadataVersion,
+ DexItemFactory factory) {
ImmutableList.Builder<KotlinTypeReference> builder = ImmutableList.builder();
for (String partClassName : kmMultiFileClassFacade.getPartClassNames()) {
builder.add(KotlinTypeReference.fromBinaryName(partClassName, factory));
}
- return new KotlinMultiFileClassFacadeInfo(builder.build(), packageName);
+ return new KotlinMultiFileClassFacadeInfo(builder.build(), packageName, metadataVersion);
}
@Override
@@ -69,6 +74,11 @@
}
@Override
+ public int[] getMetadataVersion() {
+ return metadataVersion;
+ }
+
+ @Override
public void trace(DexDefinitionSupplier definitionSupplier) {
forEachApply(partClassNames, type -> type::trace, definitionSupplier);
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
index ca97d0b..6b99d15 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
@@ -24,17 +24,23 @@
private final String facadeClassName;
private final KotlinPackageInfo packageInfo;
private final String packageName;
+ private final int[] metadataVersion;
private KotlinMultiFileClassPartInfo(
- String facadeClassName, KotlinPackageInfo packageInfo, String packageName) {
+ String facadeClassName,
+ KotlinPackageInfo packageInfo,
+ String packageName,
+ int[] metadataVersion) {
this.facadeClassName = facadeClassName;
this.packageInfo = packageInfo;
this.packageName = packageName;
+ this.metadataVersion = metadataVersion;
}
static KotlinMultiFileClassPartInfo create(
MultiFileClassPart classPart,
String packageName,
+ int[] metadataVersion,
DexClass clazz,
DexItemFactory factory,
Reporter reporter,
@@ -42,7 +48,8 @@
return new KotlinMultiFileClassPartInfo(
classPart.getFacadeClassName(),
KotlinPackageInfo.create(classPart.toKmPackage(), clazz, factory, reporter, keepByteCode),
- packageName);
+ packageName,
+ metadataVersion);
}
@Override
@@ -71,6 +78,11 @@
}
@Override
+ public int[] getMetadataVersion() {
+ return metadataVersion;
+ }
+
+ @Override
public void trace(DexDefinitionSupplier definitionSupplier) {
packageInfo.trace(definitionSupplier);
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
index 79054f4..41147a5 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
@@ -21,6 +21,7 @@
private final KotlinLambdaInfo lambda;
private final String packageName;
+ private final int[] metadataVersion;
public enum Flavour {
KotlinStyleLambda,
@@ -30,15 +31,18 @@
private final Flavour flavour;
- private KotlinSyntheticClassInfo(KotlinLambdaInfo lambda, Flavour flavour, String packageName) {
+ private KotlinSyntheticClassInfo(
+ KotlinLambdaInfo lambda, Flavour flavour, String packageName, int[] metadataVersion) {
this.lambda = lambda;
this.flavour = flavour;
this.packageName = packageName;
+ this.metadataVersion = metadataVersion;
}
static KotlinSyntheticClassInfo create(
SyntheticClass syntheticClass,
String packageName,
+ int[] metadataVersion,
DexClass clazz,
Kotlin kotlin,
DexItemFactory factory,
@@ -51,7 +55,8 @@
return new KotlinSyntheticClassInfo(
lambda != null ? KotlinLambdaInfo.create(clazz, lambda, factory, reporter) : null,
getFlavour(syntheticClass, clazz, kotlin),
- packageName);
+ packageName,
+ metadataVersion);
}
public boolean isLambda() {
@@ -100,6 +105,11 @@
return packageName;
}
+ @Override
+ public int[] getMetadataVersion() {
+ return metadataVersion;
+ }
+
private static Flavour getFlavour(
KotlinClassMetadata.SyntheticClass metadata, DexClass clazz, Kotlin kotlin) {
// Returns KotlinStyleLambda if the given clazz is a Kotlin-style lambda:
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
index 6e0168f..c19bcd2 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
@@ -87,10 +87,7 @@
.visit(appView.appInfo().classes(), clazz -> processClass(clazz, subtypingInfo));
if (!lensBuilder.isEmpty()) {
BridgeHoistingLens lens = lensBuilder.build(appView);
- boolean changed = appView.setGraphLens(lens);
- assert changed;
- appView.setAppInfo(
- appView.appInfo().rewrittenWithLens(appView.appInfo().app().asDirect(), lens));
+ appView.rewriteWithLens(lens);
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/InvokeSingleTargetExtractor.java b/src/main/java/com/android/tools/r8/optimize/InvokeSingleTargetExtractor.java
index 292f616..37df60c 100644
--- a/src/main/java/com/android/tools/r8/optimize/InvokeSingleTargetExtractor.java
+++ b/src/main/java/com/android/tools/r8/optimize/InvokeSingleTargetExtractor.java
@@ -102,6 +102,11 @@
return invalid();
}
+ @Override
+ public boolean registerInstanceOf(DexType type) {
+ return invalid();
+ }
+
public enum InvokeKind {
VIRTUAL,
STATIC,
diff --git a/src/main/java/com/android/tools/r8/retrace/Retrace.java b/src/main/java/com/android/tools/r8/retrace/Retrace.java
index 0689227..70f4a81 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.Keep;
+import com.android.tools.r8.Version;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.retrace.RetraceCommand.Builder;
import com.android.tools.r8.retrace.RetraceCommand.ProguardMapProducer;
@@ -62,6 +63,10 @@
if (help != null) {
return null;
}
+ Boolean version = OptionsParsing.tryParseBoolean(context, "--version");
+ if (version != null) {
+ return null;
+ }
Boolean info = OptionsParsing.tryParseBoolean(context, "--info");
if (info != null) {
// This is already set in the diagnostics handler.
@@ -195,7 +200,11 @@
new RetraceDiagnosticsHandler(new DiagnosticsHandler() {}, printInfo);
Builder builder = parseArguments(mappedArgs, retraceDiagnosticsHandler);
if (builder == null) {
- // --help was an argument to list
+ // --help or --version was an argument to list
+ if (Arrays.asList(mappedArgs).contains("--version")) {
+ System.out.println("Retrace " + Version.getVersionString());
+ return;
+ }
assert Arrays.asList(mappedArgs).contains("--help");
System.out.print(USAGE_MESSAGE);
return;
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 5f68d46..3197121 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.graph.EnumValueInfoMapCollection;
+import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfo;
import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
import com.android.tools.r8.graph.FieldAccessInfo;
import com.android.tools.r8.graph.FieldAccessInfoCollection;
@@ -747,6 +748,12 @@
return enumValueInfoMaps.getEnumValueInfoMap(enumType);
}
+ public EnumValueInfo getEnumValueInfo(DexField field) {
+ assert checkIfObsolete();
+ EnumValueInfoMap map = enumValueInfoMaps.getEnumValueInfoMap(field.type);
+ return map != null ? map.getEnumValueInfo(field) : null;
+ }
+
public Int2ReferenceMap<DexField> getSwitchMap(DexField field) {
assert checkIfObsolete();
return switchMaps.get(field);
@@ -1356,7 +1363,7 @@
}
public SubtypingInfo computeSubtypingInfo() {
- return new SubtypingInfo(app().asDirect().allClasses(), this);
+ return new SubtypingInfo(this);
}
public boolean mayHaveFinalizeMethodDirectlyOrIndirectly(ClassTypeElement type) {
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
index 7c0afe9..9a488d1 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -129,6 +129,11 @@
}
@Override
+ public boolean registerInstanceOf(DexType type) {
+ return enqueuer.traceInstanceOf(type, context);
+ }
+
+ @Override
public void registerMethodHandle(DexMethodHandle methodHandle, MethodHandleUse use) {
super.registerMethodHandle(methodHandle, use);
enqueuer.traceMethodHandle(methodHandle, use, context);
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 281561b..6ed64fe 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -57,7 +57,7 @@
import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
import com.android.tools.r8.graph.FieldAccessInfoImpl;
import com.android.tools.r8.graph.FieldResolutionResult;
-import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.graph.LookupLambdaTarget;
import com.android.tools.r8.graph.LookupTarget;
@@ -72,6 +72,8 @@
import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
import com.android.tools.r8.graph.analysis.DesugaredLibraryConversionWrapperAnalysis;
import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
+import com.android.tools.r8.graph.analysis.EnqueuerCheckCastAnalysis;
+import com.android.tools.r8.graph.analysis.EnqueuerInstanceOfAnalysis;
import com.android.tools.r8.graph.analysis.EnqueuerInvokeAnalysis;
import com.android.tools.r8.ir.analysis.proto.ProtoEnqueuerUseRegistry;
import com.android.tools.r8.ir.analysis.proto.schema.ProtoEnqueuerExtension;
@@ -184,6 +186,8 @@
private Set<EnqueuerAnalysis> analyses = Sets.newIdentityHashSet();
private Set<EnqueuerInvokeAnalysis> invokeAnalyses = Sets.newIdentityHashSet();
+ private Set<EnqueuerInstanceOfAnalysis> instanceOfAnalyses = Sets.newIdentityHashSet();
+ private Set<EnqueuerCheckCastAnalysis> checkCastAnalyses = Sets.newIdentityHashSet();
// Don't hold a direct pointer to app info (use appView).
private AppInfoWithClassHierarchy appInfo;
@@ -436,6 +440,16 @@
return this;
}
+ public Enqueuer registerInstanceOfAnalysis(EnqueuerInstanceOfAnalysis analysis) {
+ instanceOfAnalyses.add(analysis);
+ return this;
+ }
+
+ public Enqueuer registerCheckCastAnalysis(EnqueuerCheckCastAnalysis analysis) {
+ checkCastAnalyses.add(analysis);
+ return this;
+ }
+
public void setAnnotationRemoverBuilder(AnnotationRemover.Builder annotationRemoverBuilder) {
this.annotationRemoverBuilder = annotationRemoverBuilder;
}
@@ -589,6 +603,12 @@
return clazz != null && clazz.isProgramClass() ? clazz.asProgramClass() : null;
}
+ private DexProgramClass getProgramClassOrNullFromReflectiveAccess(DexType type) {
+ // This is using appInfo.definitionForWithoutExistenceAssert() to avoid that we report
+ // reflectively accessed types as missing.
+ return asProgramClassOrNull(appInfo().definitionForWithoutExistenceAssert(type));
+ }
+
private void warnIfLibraryTypeInheritsFromProgramType(DexLibraryClass clazz) {
if (clazz.superType != null) {
ensureFromLibraryOrThrow(clazz.superType, clazz);
@@ -899,6 +919,7 @@
}
boolean traceCheckCast(DexType type, ProgramMethod currentMethod) {
+ checkCastAnalyses.forEach(analysis -> analysis.traceCheckCast(type, currentMethod));
return traceConstClassOrCheckCast(type, currentMethod);
}
@@ -1021,6 +1042,11 @@
return true;
}
+ boolean traceInstanceOf(DexType type, ProgramMethod currentMethod) {
+ instanceOfAnalyses.forEach(analysis -> analysis.traceInstanceOf(type, currentMethod));
+ return traceTypeReference(type, currentMethod);
+ }
+
boolean traceInvokeDirect(DexMethod invokedMethod, ProgramMethod context) {
boolean skipTracing =
registerDeferredActionForDeadProtoBuilder(
@@ -1109,7 +1135,7 @@
pendingReflectiveUses.add(context);
}
// See comment in handleJavaLangEnumValueOf.
- if (invokedMethod == dexItemFactory.enumMethods.valueOf) {
+ if (invokedMethod == dexItemFactory.enumMembers.valueOf) {
pendingReflectiveUses.add(context);
}
// Handling of application services.
@@ -2733,8 +2759,8 @@
return appInfoWithLiveness;
}
- public GraphLens buildGraphLens(AppView<?> appView) {
- return lambdaRewriter != null ? lambdaRewriter.buildMappingLens(appView) : appView.graphLens();
+ public NestedGraphLens buildGraphLens(AppView<?> appView) {
+ return lambdaRewriter != null ? lambdaRewriter.buildMappingLens(appView) : null;
}
private void keepClassWithRules(DexProgramClass clazz, Set<ProguardKeepRuleBase> rules) {
@@ -2871,12 +2897,15 @@
}
// Now all additions are computed, the application is atomically extended with those additions.
- Builder appBuilder = appInfo.app().asDirect().builder();
- additions.amendApplication(appBuilder);
- DirectMappedDexApplication app = appBuilder.build();
- appInfo = new AppInfoWithClassHierarchy(app);
+ appInfo =
+ appInfo.rebuild(
+ app -> {
+ Builder appBuilder = app.asDirect().builder();
+ additions.amendApplication(appBuilder);
+ return appBuilder.build();
+ });
appView.setAppInfo(appInfo);
- subtypingInfo = new SubtypingInfo(app.allClasses(), app);
+ subtypingInfo = new SubtypingInfo(appView);
// Finally once all synthesized items "exist" it is now safe to continue tracing. The new work
// items are enqueued and the fixed point will continue once this subroutine returns.
@@ -3652,7 +3681,7 @@
handleJavaLangReflectConstructorNewInstance(method, invoke);
return;
}
- if (invokedMethod == dexItemFactory.enumMethods.valueOf) {
+ if (invokedMethod == dexItemFactory.enumMembers.valueOf) {
handleJavaLangEnumValueOf(method, invoke);
return;
}
@@ -3672,11 +3701,7 @@
return;
}
if (identifierItem.isDexType()) {
- // This is using appView.definitionFor() to avoid that we report reflectively accessed types
- // as missing.
- DexProgramClass clazz =
- asProgramClassOrNull(
- appInfo().definitionForWithoutExistenceAssert(identifierItem.asDexType()));
+ DexProgramClass clazz = getProgramClassOrNullFromReflectiveAccess(identifierItem.asDexType());
if (clazz == null) {
return;
}
@@ -3756,7 +3781,7 @@
return;
}
- DexProgramClass clazz = getProgramClassOrNull(instantiatedType);
+ DexProgramClass clazz = getProgramClassOrNullFromReflectiveAccess(instantiatedType);
if (clazz == null) {
return;
}
@@ -4224,11 +4249,6 @@
}
@Override
- public DexProgramClass definitionForProgramType(DexType type) {
- return enqueuer.getProgramClassOrNull(type);
- }
-
- @Override
public DexItemFactory dexItemFactory() {
return appView.dexItemFactory();
}
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
index 205cf71..0382893 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
@@ -175,6 +175,11 @@
consumer.accept(type);
return true;
}
+
+ @Override
+ public boolean registerInstanceOf(DexType type) {
+ return registerTypeReference(type);
+ }
}
private class AnnotationDirectReferenceCollector implements IndexedItemCollection {
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
index fc34925..c122cd7 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
@@ -6,13 +6,13 @@
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexClass;
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.graph.DirectMappedDexApplication;
import com.android.tools.r8.utils.SetUtils;
import java.util.IdentityHashMap;
import java.util.Map;
@@ -28,9 +28,8 @@
public class MainDexListBuilder {
private final Set<DexType> roots;
- private final AppInfoWithClassHierarchy appInfo;
+ private final AppView<? extends AppInfoWithClassHierarchy> appView;
private final Map<DexType, Boolean> annotationTypeContainEnum;
- private final DirectMappedDexApplication dexApplication;
private final MainDexClasses.Builder mainDexClassesBuilder;
public static void checkForAssumedLibraryTypes(AppInfo appInfo) {
@@ -48,17 +47,21 @@
/**
* @param roots Classes which code may be executed before secondary dex files loading.
- * @param application the dex appplication.
+ * @param appView the dex appplication.
*/
- public MainDexListBuilder(Set<DexProgramClass> roots, DirectMappedDexApplication application) {
- this.dexApplication = application;
- this.appInfo = new AppInfoWithClassHierarchy(dexApplication);
+ public MainDexListBuilder(
+ Set<DexProgramClass> roots, AppView<? extends AppInfoWithClassHierarchy> appView) {
+ this.appView = appView;
// Only consider program classes for the root set.
this.roots = SetUtils.mapIdentityHashSet(roots, DexProgramClass::getType);
- mainDexClassesBuilder = MainDexClasses.builder(appInfo).addRoots(this.roots);
+ mainDexClassesBuilder = MainDexClasses.builder(appView.appInfo()).addRoots(this.roots);
annotationTypeContainEnum = new IdentityHashMap<>();
}
+ private AppInfoWithClassHierarchy appInfo() {
+ return appView.appInfo();
+ }
+
public MainDexClasses run() {
traceMainDexDirectDependencies();
traceRuntimeAnnotationsWithEnumForMainDex();
@@ -66,7 +69,7 @@
}
private void traceRuntimeAnnotationsWithEnumForMainDex() {
- for (DexProgramClass clazz : dexApplication.classes()) {
+ for (DexProgramClass clazz : appInfo().classes()) {
DexType dexType = clazz.type;
if (mainDexClassesBuilder.contains(dexType)) {
continue;
@@ -91,7 +94,7 @@
private boolean isAnnotationWithEnum(DexType dexType) {
Boolean value = annotationTypeContainEnum.get(dexType);
if (value == null) {
- DexClass clazz = appInfo.definitionFor(dexType);
+ DexClass clazz = appView.definitionFor(dexType);
if (clazz == null) {
// Information is missing lets be conservative.
value = true;
@@ -102,7 +105,7 @@
for (DexEncodedMethod method : clazz.virtualMethods()) {
DexProto proto = method.method.proto;
if (proto.parameters.isEmpty()) {
- DexType valueType = proto.returnType.toBaseType(appInfo.dexItemFactory());
+ DexType valueType = proto.returnType.toBaseType(appView.dexItemFactory());
if (valueType.isClassType()) {
if (isEnum(valueType)) {
value = true;
@@ -122,16 +125,15 @@
private boolean isEnum(DexType valueType) {
return valueType.isClassType()
- && appInfo.isSubtype(valueType, appInfo.dexItemFactory().enumType);
+ && appInfo().isSubtype(valueType, appView.dexItemFactory().enumType);
}
private boolean isAnnotation(DexType valueType) {
- return appInfo.isSubtype(valueType, appInfo.dexItemFactory().annotationType);
+ return appInfo().isSubtype(valueType, appView.dexItemFactory().annotationType);
}
private void traceMainDexDirectDependencies() {
- new MainDexDirectReferenceTracer(appInfo, this::addDirectDependency)
- .run(roots);
+ new MainDexDirectReferenceTracer(appInfo(), this::addDirectDependency).run(roots);
}
private void addAnnotationsWithEnum(DexProgramClass clazz) {
@@ -141,7 +143,7 @@
for (DexEncodedMethod method : clazz.virtualMethods()) {
DexProto proto = method.method.proto;
if (proto.parameters.isEmpty()) {
- DexType valueType = proto.returnType.toBaseType(appInfo.dexItemFactory());
+ DexType valueType = proto.returnType.toBaseType(appView.dexItemFactory());
if (isEnum(valueType)) {
addDirectDependency(valueType);
}
@@ -156,13 +158,13 @@
private void addDirectDependency(DexType type) {
// Consider only component type of arrays
- type = type.toBaseType(appInfo.dexItemFactory());
+ type = type.toBaseType(appView.dexItemFactory());
if (!type.isClassType() || mainDexClassesBuilder.contains(type)) {
return;
}
- DexClass clazz = appInfo.definitionFor(type);
+ DexClass clazz = appView.definitionFor(type);
// No library classes in main-dex.
if (clazz == null || clazz.isNotProgramClass()) {
return;
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index ec2ced1..be956c3 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -57,7 +57,8 @@
: UsagePrinter.DONT_PRINT;
}
- public DirectMappedDexApplication run(DirectMappedDexApplication application) {
+ public DirectMappedDexApplication run() {
+ DirectMappedDexApplication application = appView.appInfo().app().asDirect();
Timing timing = application.timing;
timing.begin("Pruning application...");
DirectMappedDexApplication result;
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 c2b3627..1de617d 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -1918,6 +1918,11 @@
public boolean registerTypeReference(DexType type) {
return checkTypeReference(type);
}
+
+ @Override
+ public boolean registerInstanceOf(DexType type) {
+ return checkTypeReference(type);
+ }
}
protected static class SynthesizedBridgeCode extends AbstractSynthesizedCode {
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 6d85a4e..dfd253d 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -184,7 +184,7 @@
enableClassStaticizer = false;
enableDevirtualization = false;
enableLambdaMerging = false;
- enableHorizontalClassMerging = false;
+ enableStaticClassMerging = false;
enableVerticalClassMerging = false;
enableEnumUnboxing = false;
enableUninstantiatedTypeOptimization = false;
@@ -217,7 +217,8 @@
public boolean enableFieldAssignmentTracker = true;
public boolean enableFieldBitAccessAnalysis =
System.getProperty("com.android.tools.r8.fieldBitAccessAnalysis") != null;
- public boolean enableHorizontalClassMerging = true;
+ public boolean enableStaticClassMerging = true;
+ public boolean enableHorizontalClassMerging = false;
public boolean enableVerticalClassMerging = true;
public boolean enableArgumentRemoval = true;
public boolean enableUnusedInterfaceRemoval = true;
diff --git a/src/main/java/com/android/tools/r8/utils/WorkList.java b/src/main/java/com/android/tools/r8/utils/WorkList.java
index 092a4fc..e9bf6cd 100644
--- a/src/main/java/com/android/tools/r8/utils/WorkList.java
+++ b/src/main/java/com/android/tools/r8/utils/WorkList.java
@@ -30,6 +30,12 @@
return new WorkList<T>(EqualityTest.IDENTITY);
}
+ public static <T> WorkList<T> newIdentityWorkList(T item) {
+ WorkList<T> workList = new WorkList<>(EqualityTest.IDENTITY);
+ workList.addIfNotSeen(item);
+ return workList;
+ }
+
public static <T> WorkList<T> newIdentityWorkList(Iterable<T> items) {
WorkList<T> workList = new WorkList<>(EqualityTest.IDENTITY);
workList.addIfNotSeen(items);
@@ -44,6 +50,10 @@
}
}
+ public void addAll(Iterable<T> items) {
+ items.forEach(workingList::addLast);
+ }
+
public void addIfNotSeen(Iterable<T> items) {
items.forEach(this::addIfNotSeen);
}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
index 4b4eccb..8297a9f 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
@@ -17,18 +17,26 @@
public class LongLivedProgramMethodSetBuilder<T extends ProgramMethodSet> {
private final IntFunction<T> factory;
- private final Set<DexMethod> methods = Sets.newIdentityHashSet();
+ private final Set<DexMethod> methods;
- private LongLivedProgramMethodSetBuilder(IntFunction<T> factory) {
+ private LongLivedProgramMethodSetBuilder(IntFunction<T> factory, Set<DexMethod> methods) {
this.factory = factory;
+ this.methods = methods;
}
- public static LongLivedProgramMethodSetBuilder<?> create() {
- return new LongLivedProgramMethodSetBuilder<>(ProgramMethodSet::create);
+ public static LongLivedProgramMethodSetBuilder<?> createForIdentitySet() {
+ return new LongLivedProgramMethodSetBuilder<>(
+ ProgramMethodSet::create, Sets.newIdentityHashSet());
}
- public static LongLivedProgramMethodSetBuilder<SortedProgramMethodSet> createSorted() {
- return new LongLivedProgramMethodSetBuilder<>(ignore -> SortedProgramMethodSet.create());
+ public static LongLivedProgramMethodSetBuilder<SortedProgramMethodSet> createForSortedSet() {
+ return new LongLivedProgramMethodSetBuilder<>(
+ ignore -> SortedProgramMethodSet.create(), Sets.newIdentityHashSet());
+ }
+
+ public static LongLivedProgramMethodSetBuilder<?> createConcurrentForIdentitySet() {
+ return new LongLivedProgramMethodSetBuilder<>(
+ ignore -> ProgramMethodSet.create(), Sets.newConcurrentHashSet());
}
public void add(ProgramMethod method) {
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java
index 14d938d..92d51ea 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java
@@ -22,7 +22,6 @@
private static final ProgramMethodSet EMPTY = new ProgramMethodSet(ImmutableMap.of());
- private boolean deterministicOrdering = false;
private Map<DexMethod, ProgramMethod> backing;
ProgramMethodSet(Map<DexMethod, ProgramMethod> backing) {
diff --git a/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java b/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
index e30b788..a022ecf 100644
--- a/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
+++ b/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
@@ -7,7 +7,6 @@
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
@@ -43,8 +42,7 @@
options.programConsumer = DexIndexedConsumer.emptyConsumer();
DirectMappedDexApplication application =
new ApplicationReader(input, options, timing).read(executorService).toDirect();
- IRConverter converter =
- new IRConverter(AppView.createForR8(new AppInfoWithClassHierarchy(application)), null);
+ IRConverter converter = new IRConverter(AppView.createForR8(application), null);
converter.optimize();
DexProgramClass clazz = application.classes().iterator().next();
assertEquals(4, clazz.getMethodCollection().numberOfDirectMethods());
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index b764451..545ab13 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -32,7 +32,6 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.SmaliWriter;
import com.android.tools.r8.graph.SubtypingInfo;
@@ -613,8 +612,7 @@
DexItemFactory dexItemFactory = new DexItemFactory();
InternalOptions options = new InternalOptions(keepConfig.apply(dexItemFactory), new Reporter());
DexApplication dexApplication = readApplicationForDexOutput(app, options);
- AppView<AppInfoWithClassHierarchy> appView =
- AppView.createForR8(new AppInfoWithClassHierarchy(dexApplication.toDirect()));
+ AppView<AppInfoWithClassHierarchy> appView = AppView.createForR8(dexApplication.toDirect());
appView.setAppServices(AppServices.builder(appView).build());
return appView;
}
@@ -641,16 +639,15 @@
AppView<AppInfoWithClassHierarchy> appView = computeAppViewWithSubtyping(app, keepConfig);
// Run the tree shaker to compute an instance of AppInfoWithLiveness.
ExecutorService executor = Executors.newSingleThreadExecutor();
- DirectMappedDexApplication application = appView.appInfo().app().asDirect();
- SubtypingInfo subtypingInfo = new SubtypingInfo(application.allClasses(), application);
+ SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
RootSet rootSet =
new RootSetBuilder(
- appView, subtypingInfo, application.options.getProguardConfiguration().getRules())
+ appView, subtypingInfo, appView.options().getProguardConfiguration().getRules())
.run(executor);
appView.setRootSet(rootSet);
AppInfoWithLiveness appInfoWithLiveness =
EnqueuerFactory.createForInitialTreeShaking(appView, subtypingInfo)
- .traceApplication(rootSet, ProguardClassFilter.empty(), executor, application.timing);
+ .traceApplication(rootSet, ProguardClassFilter.empty(), executor, Timing.empty());
// We do not run the tree pruner to ensure that the hierarchy is as designed and not modified
// due to liveness.
return appView.setAppInfo(appInfoWithLiveness);
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/B162969014.java b/src/test/java/com/android/tools/r8/checkdiscarded/B162969014.java
new file mode 100644
index 0000000..6eb081f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/B162969014.java
@@ -0,0 +1,108 @@
+// 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.checkdiscarded;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.AssumeNoSideEffects;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+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 B162969014 extends TestBase {
+
+ private final boolean checkLogIsDiscarded;
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{1}, checkLogIsDiscarded: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
+
+ public B162969014(boolean checkLogIsDiscarded, TestParameters parameters) {
+ this.checkLogIsDiscarded = checkLogIsDiscarded;
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ R8TestCompileResult compileResult;
+ try {
+ compileResult =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(B162969014.class)
+ .addKeepMainRule(TestClass.class)
+ .apply(this::applyCheckDiscardedRule)
+ .enableAssumeNoSideEffectsAnnotations()
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile();
+ } catch (CompilationFailedException e) {
+ assertTrue(checkLogIsDiscarded);
+ assertEquals(e.getCause().getMessage(), "Discard checks failed.");
+ return;
+ }
+
+ compileResult
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("foo");
+ }
+
+ private void applyCheckDiscardedRule(R8FullTestBuilder builder) {
+ if (checkLogIsDiscarded) {
+ builder.addKeepRules(
+ "-checkdiscard class " + Log.class.getTypeName() + "{",
+ " public static void foo();",
+ " public static void bar();",
+ "}");
+ }
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject logClassSubject = inspector.clazz(Log.class);
+ assertThat(logClassSubject, isPresent());
+ assertThat(logClassSubject.uniqueMethodWithName("foo"), isPresent());
+ assertThat(logClassSubject.uniqueMethodWithName("bar"), not(isPresent()));
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ Log.foo();
+ Log.bar();
+ }
+ }
+
+ static class Log {
+
+ @NeverInline
+ public static void foo() {
+ System.out.println("foo");
+ }
+
+ @AssumeNoSideEffects
+ @NeverInline
+ public static void bar() {
+ System.out.println("bar");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/lambdanames/PackageDependentLambdaNamesTest.java b/src/test/java/com/android/tools/r8/desugaring/lambdanames/PackageDependentLambdaNamesTest.java
new file mode 100644
index 0000000..934a15b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/lambdanames/PackageDependentLambdaNamesTest.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.desugaring.lambdanames;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.transformers.ClassFileTransformer;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class PackageDependentLambdaNamesTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("Hello, world");
+
+ private final TestParameters parameters;
+ private final boolean samePackage;
+
+ @Parameterized.Parameters(name = "{0}, same-pkg:{1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimes().withAllApiLevels().build(), BooleanUtils.values());
+ }
+
+ public PackageDependentLambdaNamesTest(TestParameters parameters, boolean samePackage) {
+ this.parameters = parameters;
+ this.samePackage = samePackage;
+ }
+
+ @Test
+ public void test() throws Exception {
+ TestRunResult<?> result =
+ testForRuntime(parameters)
+ .addProgramClasses(StringConsumer.class)
+ .addProgramClassFileData(getTestClass(), getA(), getB())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ if (parameters.isDexRuntime()) {
+ result.inspect(
+ inspector -> {
+ // When in the same package we expect the two System.out::print lambdas to be shared.
+ assertEquals(
+ samePackage ? 2 : 3,
+ inspector.allClasses().stream()
+ .filter(c -> c.isSynthesizedJavaLambdaClass())
+ .count());
+ });
+ }
+ }
+
+ private byte[] getA() throws Exception {
+ ClassFileTransformer transformer = transformer(A.class);
+ if (!samePackage) {
+ transformer.setClassDescriptor("La/A;");
+ }
+ return transformer.transform();
+ }
+
+ private byte[] getB() throws Exception {
+ ClassFileTransformer transformer = transformer(B.class);
+ if (!samePackage) {
+ transformer.setClassDescriptor("Lb/B;");
+ }
+ return transformer.transform();
+ }
+
+ private byte[] getTestClass() throws Exception {
+ ClassFileTransformer transformer = transformer(TestClass.class);
+ if (!samePackage) {
+ transformer
+ .replaceClassDescriptorInMethodInstructions(
+ DescriptorUtils.javaTypeToDescriptor(A.class.getTypeName()), "La/A;")
+ .replaceClassDescriptorInMethodInstructions(
+ DescriptorUtils.javaTypeToDescriptor(B.class.getTypeName()), "Lb/B;");
+ }
+ return transformer.transform();
+ }
+
+ @FunctionalInterface
+ public interface StringConsumer {
+ void accept(String arg);
+ }
+
+ public static class A {
+ public void hello() {
+ TestClass.apply(System.out::print, "Hello, ");
+ }
+ }
+
+ public static class B {
+ public void world() {
+ TestClass.apply(System.out::print, "world");
+ }
+ }
+
+ public static class TestClass {
+
+ public static void apply(StringConsumer consumer, String arg) {
+ consumer.accept(arg);
+ }
+
+ public static void main(String[] args) {
+ new A().hello();
+ new B().world();
+ apply(System.out::println, "");
+ }
+ }
+}
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 83393bf..1a440cb 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
@@ -65,6 +65,7 @@
.addKeepMainRule(AlwaysCorrectProgram.class)
.addKeepMainRule(AlwaysCorrectProgram2.class)
.addKeepRules(enumKeepRules.getKeepRules())
+ .addKeepAttributeLineNumberTable()
.addOptionsModification(
options -> {
options.enableEnumUnboxing = enumUnboxing;
@@ -75,19 +76,26 @@
.allowDiagnosticInfoMessages(enumUnboxing)
.setMinApi(parameters.getApiLevel())
.compile();
- compile
- .run(parameters.getRuntime(), AlwaysCorrectProgram.class)
- .assertSuccessWithOutputLines("0", "1", "2", "0", "1", "2", "0", "1", "2");
+ 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/160667929): Fix toString and enum unboxing.
+ // TODO(b/160939354): Enum unboxing synthesizes a toString() method based on field names.
compile
.run(parameters.getRuntime(), AlwaysCorrectProgram2.class)
.assertFailureWithErrorThatMatches(containsString("IllegalArgumentException"));
- return;
+ } else {
+ compile
+ .run(parameters.getRuntime(), AlwaysCorrectProgram2.class)
+ .assertSuccessWithOutputLines("0", "1", "2", "0", "1", "2");
}
- compile
- .run(parameters.getRuntime(), AlwaysCorrectProgram2.class)
- .assertSuccessWithOutputLines("0", "1", "2", "0", "1", "2");
}
private void assertEnumFieldsMinified(CodeInspector codeInspector) throws Exception {
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..758ed36 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.util.List;
@@ -70,7 +71,11 @@
assertThat(codeInspector.clazz(Companion.class).uniqueMethodWithName("method"), isPresent());
return;
}
- MethodSubject method = codeInspector.clazz(CompanionHost.class).uniqueMethodWithName("method");
+ MethodSubject method =
+ codeInspector
+ .clazz(CompanionHost.class)
+ .uniqueMethodWithName(
+ EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "1$method");
assertThat(method, isPresent());
assertEquals("int", method.getMethod().method.proto.parameters.toString());
}
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 65aee25..02b5a5f 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
@@ -42,6 +42,10 @@
return keepRules;
}
+ public boolean isStudio() {
+ return this == STUDIO;
+ }
+
public boolean isSnap() {
return this == SNAP;
}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/OverloadingEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/OverloadingEnumUnboxingTest.java
new file mode 100644
index 0000000..8e206b8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/OverloadingEnumUnboxingTest.java
@@ -0,0 +1,141 @@
+// 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 OverloadingEnumUnboxingTest 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 OverloadingEnumUnboxingTest(
+ TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
+ this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
+ }
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ Class<?> classToTest = TestClass.class;
+ R8TestRunResult run =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(OverloadingEnumUnboxingTest.class)
+ .addKeepMainRule(classToTest)
+ .addKeepRules(enumKeepRules.getKeepRules())
+ .enableNeverClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .allowDiagnosticInfoMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectDiagnosticMessages(
+ m -> {
+ assertEnumIsUnboxed(MyEnum1.class, MyEnum1.class.getSimpleName(), m);
+ assertEnumIsUnboxed(MyEnum2.class, MyEnum2.class.getSimpleName(), m);
+ })
+ .run(parameters.getRuntime(), classToTest)
+ .assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
+ }
+
+ @SuppressWarnings("SameParameterValue")
+ @NeverClassInline
+ enum MyEnum1 {
+ A,
+ B,
+ C;
+ }
+
+ @NeverClassInline
+ enum MyEnum2 {
+ A,
+ B,
+ C;
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ virtualTest();
+ staticTest();
+ }
+
+ @NeverInline
+ private static void staticTest() {
+ staticMethod(42);
+ System.out.println("42");
+ staticMethod(MyEnum1.A);
+ System.out.println("0");
+ staticMethod(MyEnum1.B);
+ System.out.println("1");
+ staticMethod(MyEnum2.A);
+ System.out.println("0");
+ staticMethod(MyEnum2.B);
+ System.out.println("1");
+ }
+
+ @NeverInline
+ private static void virtualTest() {
+ TestClass testClass = new TestClass();
+ testClass.virtualMethod(42);
+ System.out.println("42");
+ testClass.virtualMethod(MyEnum1.A);
+ System.out.println("0");
+ testClass.virtualMethod(MyEnum1.B);
+ System.out.println("1");
+ testClass.virtualMethod(MyEnum2.A);
+ System.out.println("0");
+ testClass.virtualMethod(MyEnum2.B);
+ System.out.println("1");
+ }
+
+ @NeverInline
+ public void virtualMethod(MyEnum1 e1) {
+ System.out.println(e1.ordinal());
+ }
+
+ @NeverInline
+ public void virtualMethod(MyEnum2 e1) {
+ System.out.println(e1.ordinal());
+ }
+
+ @NeverInline
+ public void virtualMethod(int i) {
+ System.out.println(i);
+ }
+
+ @NeverInline
+ public static void staticMethod(MyEnum1 e1) {
+ System.out.println(e1.ordinal());
+ }
+
+ @NeverInline
+ public static void staticMethod(MyEnum2 e1) {
+ System.out.println(e1.ordinal());
+ }
+
+ @NeverInline
+ public static void staticMethod(int i) {
+ System.out.println(i);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsAccessibilityErrorEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsAccessibilityErrorEnumUnboxingTest.java
new file mode 100644
index 0000000..52d8688
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsAccessibilityErrorEnumUnboxingTest.java
@@ -0,0 +1,101 @@
+// 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 org.hamcrest.CoreMatchers.containsString;
+import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
+import static org.objectweb.asm.Opcodes.ACC_STATIC;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.transformers.ClassTransformer;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.MethodVisitor;
+
+@RunWith(Parameterized.class)
+public class VirtualMethodsAccessibilityErrorEnumUnboxingTest 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 VirtualMethodsAccessibilityErrorEnumUnboxingTest(
+ TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
+ this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
+ }
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ byte[] testClassData =
+ transformer(TestClass.class)
+ .addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public MethodVisitor visitMethod(
+ int access,
+ String name,
+ String descriptor,
+ String signature,
+ String[] exceptions) {
+ if (name.equals("privateMethod")) {
+ return super.visitMethod(
+ ACC_STATIC | ACC_PRIVATE, name, descriptor, signature, exceptions);
+ } else {
+ return super.visitMethod(access, name, descriptor, signature, exceptions);
+ }
+ }
+ })
+ .transform();
+ testForR8(parameters.getBackend())
+ .noMinification()
+ .addProgramClasses(MyEnum.class)
+ .addProgramClassFileData(testClassData)
+ .addKeepMainRule(TestClass.class)
+ .addKeepRules(enumKeepRules.getKeepRules())
+ .enableNeverClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .allowDiagnosticInfoMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertFailureWithErrorThatMatches(containsString("java.lang.IllegalAccessError"));
+ }
+
+ public static class TestClass {
+ // The method privateMethod will be transformed into a private method.
+ @NeverInline
+ protected static int privateMethod() {
+ return System.currentTimeMillis() > 0 ? 4 : 3;
+ }
+
+ public static void main(String[] args) {
+ System.out.println(MyEnum.A.ordinal());
+ MyEnum.print();
+ }
+ }
+
+ @NeverClassInline
+ enum MyEnum {
+ A,
+ B;
+
+ @NeverInline
+ static void print() {
+ // This should raise an accessibility error, weither the enum is unboxed or not.
+ System.out.println(TestClass.privateMethod());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java
index 2e3466f..3a1fbab 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java
@@ -1,11 +1,8 @@
// 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 org.hamcrest.CoreMatchers.containsString;
-
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8TestCompileResult;
@@ -19,7 +16,6 @@
@RunWith(Parameterized.class)
public class VirtualMethodsEnumUnboxingTest extends EnumUnboxingTestBase {
-
private final TestParameters parameters;
private final boolean enumValueOptimization;
private final EnumKeepRules enumKeepRules;
@@ -43,7 +39,6 @@
testForR8(parameters.getBackend())
.addInnerClasses(VirtualMethodsEnumUnboxingTest.class)
.addKeepMainRule(classToTest)
- .addKeepMainRule(VirtualMethodsFail.class)
.addKeepRules(enumKeepRules.getKeepRules())
.enableNeverClassInliningAnnotations()
.enableInliningAnnotations()
@@ -58,13 +53,15 @@
assertEnumIsUnboxed(MyEnumWithCollisions.class, classToTest.getSimpleName(), m);
assertEnumIsUnboxed(
MyEnumWithPackagePrivateCall.class, classToTest.getSimpleName(), m);
+ assertEnumIsUnboxed(
+ MyEnumWithProtectedCall.class, classToTest.getSimpleName(), m);
+ assertEnumIsUnboxed(
+ MyEnumWithPackagePrivateFieldAccess.class, classToTest.getSimpleName(), m);
+ assertEnumIsUnboxed(
+ MyEnumWithPackagePrivateAndPrivateCall.class, classToTest.getSimpleName(), m);
});
R8TestRunResult run = compile.run(parameters.getRuntime(), classToTest).assertSuccess();
assertLines2By2Correct(run.getStdOut());
- // TODO(b/160854837): This test should actually be successful.
- compile
- .run(parameters.getRuntime(), VirtualMethodsFail.class)
- .assertFailureWithErrorThatMatches(containsString("IllegalAccessError"));
}
@SuppressWarnings("SameParameterValue")
@@ -110,7 +107,6 @@
printPrivate();
}
}
-
// Use two enums to test collision.
@NeverClassInline
enum MyEnum2 {
@@ -160,10 +156,30 @@
}
@NeverClassInline
- static class PackagePrivateClass {
+ static class PackagePrivateClassWithPublicMembers {
+ @NeverInline
+ public static void print() {
+ System.out.println("print1");
+ }
+
+ public static int item = System.currentTimeMillis() > 0 ? 42 : 41;
+ }
+
+ @NeverClassInline
+ public static class PublicClassWithPackagePrivateMembers {
@NeverInline
static void print() {
- System.out.println("print");
+ System.out.println("print2");
+ }
+
+ static int item = System.currentTimeMillis() > 0 ? 4 : 1;
+ }
+
+ @NeverClassInline
+ public static class PublicClassWithProtectedMembers {
+ @NeverInline
+ protected static void print() {
+ System.out.println("print3");
}
}
@@ -175,41 +191,75 @@
@NeverInline
public static void callPackagePrivate() {
- PackagePrivateClass.print();
+ PackagePrivateClassWithPublicMembers.print();
+ System.out.println("print1");
+ PublicClassWithPackagePrivateMembers.print();
+ System.out.println("print2");
}
}
- static class VirtualMethodsFail {
- public static void main(String[] args) {
- testCollisions();
- testPackagePrivate();
+ @NeverClassInline
+ enum MyEnumWithPackagePrivateAndPrivateCall {
+ A,
+ B,
+ C;
+
+ @NeverInline
+ public static void privateMethod() {
+ System.out.println("private");
}
@NeverInline
- private static void testPackagePrivate() {
- System.out.println(MyEnumWithPackagePrivateCall.A.ordinal());
- System.out.println(0);
- MyEnumWithPackagePrivateCall.callPackagePrivate();
- System.out.println("print");
+ public static void callPackagePrivateAndPrivate() {
+ // This method has "SAME_CLASS" as compilation state, yet,
+ // it also has a package-private call.
+ privateMethod();
+ System.out.println("private");
+ PackagePrivateClassWithPublicMembers.print();
+ System.out.println("print1");
+ PublicClassWithPackagePrivateMembers.print();
+ System.out.println("print2");
}
+ }
+
+ @NeverClassInline
+ enum MyEnumWithProtectedCall {
+ A,
+ B,
+ C;
@NeverInline
- private static void testCollisions() {
- System.out.println(MyEnumWithCollisions.A.get());
- System.out.println(5);
- System.out.println(MyEnumWithCollisions.B.get());
- System.out.println(2);
- System.out.println(MyEnumWithCollisions.C.get());
- System.out.println(1);
+ public static void callProtected() {
+ PublicClassWithProtectedMembers.print();
+ System.out.println("print3");
+ }
+ }
+
+ @NeverClassInline
+ enum MyEnumWithPackagePrivateFieldAccess {
+ A,
+ B,
+ C;
+
+ @NeverInline
+ public static void accessPackagePrivate() {
+ System.out.println(PackagePrivateClassWithPublicMembers.item);
+ System.out.println("42");
+ System.out.println(PublicClassWithPackagePrivateMembers.item);
+ System.out.println("4");
}
}
static class VirtualMethods {
-
public static void main(String[] args) {
testCustomMethods();
testCustomMethods2();
testNonPublicMethods();
+ testCollisions();
+ testPackagePrivateMethod();
+ testProtectedAccess();
+ testPackagePrivateAndPrivateMethod();
+ testPackagePrivateAccess();
}
@NeverInline
@@ -243,5 +293,43 @@
System.out.println((MyEnum2.A.returnEnum(true).ordinal()));
System.out.println(1);
}
+
+ @NeverInline
+ private static void testPackagePrivateMethod() {
+ System.out.println(MyEnumWithPackagePrivateCall.A.ordinal());
+ System.out.println(0);
+ MyEnumWithPackagePrivateCall.callPackagePrivate();
+ }
+
+ @NeverInline
+ private static void testPackagePrivateAndPrivateMethod() {
+ System.out.println(MyEnumWithPackagePrivateAndPrivateCall.A.ordinal());
+ System.out.println(0);
+ MyEnumWithPackagePrivateAndPrivateCall.callPackagePrivateAndPrivate();
+ }
+
+ @NeverInline
+ private static void testPackagePrivateAccess() {
+ System.out.println(MyEnumWithPackagePrivateFieldAccess.A.ordinal());
+ System.out.println(0);
+ MyEnumWithPackagePrivateFieldAccess.accessPackagePrivate();
+ }
+
+ @NeverInline
+ private static void testProtectedAccess() {
+ System.out.println(MyEnumWithProtectedCall.A.ordinal());
+ System.out.println(0);
+ MyEnumWithProtectedCall.callProtected();
+ }
+
+ @NeverInline
+ private static void testCollisions() {
+ System.out.println(MyEnumWithCollisions.A.get());
+ System.out.println(5);
+ System.out.println(MyEnumWithCollisions.B.get());
+ System.out.println(2);
+ System.out.println(MyEnumWithCollisions.C.get());
+ System.out.println(1);
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/graph/DexTypeTest.java b/src/test/java/com/android/tools/r8/graph/DexTypeTest.java
index 014249b..6b2d73e 100644
--- a/src/test/java/com/android/tools/r8/graph/DexTypeTest.java
+++ b/src/test/java/com/android/tools/r8/graph/DexTypeTest.java
@@ -35,7 +35,7 @@
.read()
.toDirect();
factory = options.itemFactory;
- appInfo = new AppInfoWithClassHierarchy(application);
+ appInfo = AppView.createForR8(application).appInfo();
}
@Test
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
index ca35303..82f21d4 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
@@ -59,10 +59,9 @@
new ApplicationReader(app, new InternalOptions(), timing)
.read(proguardMap, executorService)
.toDirect();
- InternalOptions options = new InternalOptions();
- appView = AppView.createForR8(new AppInfoWithClassHierarchy(program));
+ appView = AppView.createForR8(program);
appView.setAppServices(AppServices.builder(appView).build());
- subtypingInfo = new SubtypingInfo(program.allClasses(), program);
+ subtypingInfo = new SubtypingInfo(appView);
}
private AppInfoWithClassHierarchy appInfo() {
diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java
index 2bd2803..cb48232 100644
--- a/src/test/java/com/android/tools/r8/ir/InlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java
@@ -14,7 +14,6 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
@@ -65,12 +64,10 @@
MethodSubject method,
List<IRCode> additionalCode)
throws ExecutionException {
- DirectMappedDexApplication directApp = application.asDirect();
- AppView<AppInfoWithClassHierarchy> appView =
- AppView.createForR8(new AppInfoWithClassHierarchy(directApp));
+ AppView<AppInfoWithClassHierarchy> appView = AppView.createForR8(application.asDirect());
appView.setAppServices(AppServices.builder(appView).build());
ExecutorService executorService = ThreadUtils.getExecutorService(options);
- SubtypingInfo subtypingInfo = new SubtypingInfo(directApp.allClasses(), directApp);
+ SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
appView.setRootSet(
new RootSetBuilder(
appView,
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 83f64ee..a031cf8 100644
--- a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
@@ -12,7 +12,7 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.ValueNumberGenerator;
+import com.android.tools.r8.ir.code.NumberGenerator;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.shaking.MainDexClasses;
import com.android.tools.r8.smali.SmaliBuilder;
@@ -68,7 +68,7 @@
public final List<IRCode> additionalCode;
public final AndroidAppConsumers consumers;
- public final ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
+ public final NumberGenerator valueNumberGenerator = new NumberGenerator();
public TestApplication(AppView<?> appView, MethodSubject method) {
this(appView, method, null);
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
index 86f4e3d..357fa0e 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
@@ -134,7 +134,7 @@
timing)
.read()
.toDirect();
- return AppView.createForR8(new AppInfoWithClassHierarchy(application));
+ return AppView.createForR8(application);
}
private DexEncodedField uniqueFieldByName(DexProgramClass clazz, String name) {
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
index 06de00a..5cdd0ca 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
@@ -60,7 +60,7 @@
.read()
.toDirect();
factory = options.itemFactory;
- appView = AppView.createForR8(new AppInfoWithClassHierarchy(application));
+ appView = AppView.createForR8(application);
}
private TopTypeElement top() {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/B161735546.java b/src/test/java/com/android/tools/r8/ir/optimize/B161735546.java
new file mode 100644
index 0000000..ce969b7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/B161735546.java
@@ -0,0 +1,66 @@
+// 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;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class B161735546 extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public B161735546(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(B161735546.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("1", "2", "3");
+ }
+
+ static class TestClass {
+ public static void main(String[] args) {
+ new CounterUtils() {}.add(new Counter(), 3);
+ }
+ }
+
+ static class Counter {
+ int i = 0;
+ }
+
+ interface CounterConsumer {
+ void accept(Counter counter);
+ }
+
+ interface CounterUtils {
+ default void add(Counter counter, int i) {
+ if (i == 0) {
+ return;
+ }
+ CounterConsumer continuation =
+ c -> {
+ System.out.println(++counter.i);
+ add(counter, i - 1);
+ };
+ continuation.accept(counter);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
index c139dcf..cdb9264 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
@@ -16,11 +16,11 @@
import com.android.tools.r8.ir.code.IRMetadata;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.Move;
+import com.android.tools.r8.ir.code.NumberGenerator;
import com.android.tools.r8.ir.code.NumericType;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Return;
import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
import com.android.tools.r8.ir.regalloc.LiveIntervals;
import com.android.tools.r8.origin.Origin;
@@ -70,7 +70,7 @@
//
// Then test that peephole optimization realizes that the last const number
// is needed and the value 10 is *not* still in register 0 at that point.
- final ValueNumberGenerator basicBlockNumberGenerator = new ValueNumberGenerator();
+ final NumberGenerator basicBlockNumberGenerator = new NumberGenerator();
BasicBlock block = new BasicBlock();
block.setNumber(basicBlockNumberGenerator.next());
@@ -141,7 +141,7 @@
options,
null,
blocks,
- new ValueNumberGenerator(),
+ new NumberGenerator(),
basicBlockNumberGenerator,
IRMetadata.unknown(),
Origin.unknown());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
index 69394e0..9fc16f8 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
@@ -22,11 +22,11 @@
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.If.Type;
import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.NumberGenerator;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Return;
import com.android.tools.r8.ir.code.Throw;
import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
@@ -62,7 +62,7 @@
// throw v0
// block2:
// return
- final ValueNumberGenerator basicBlockNumberGenerator = new ValueNumberGenerator();
+ final NumberGenerator basicBlockNumberGenerator = new NumberGenerator();
Position position = Position.testingPosition();
BasicBlock block2 = new BasicBlock();
BasicBlock block0 =
@@ -98,7 +98,7 @@
options,
null,
blocks,
- new ValueNumberGenerator(),
+ new NumberGenerator(),
basicBlockNumberGenerator,
IRMetadata.unknown(),
Origin.unknown());
@@ -126,7 +126,7 @@
//
// block3:
// goto block3
- final ValueNumberGenerator basicBlockNumberGenerator = new ValueNumberGenerator();
+ final NumberGenerator basicBlockNumberGenerator = new NumberGenerator();
Position position = Position.testingPosition();
BasicBlock block0 = new BasicBlock();
block0.setNumber(basicBlockNumberGenerator.next());
@@ -185,7 +185,7 @@
options,
null,
blocks,
- new ValueNumberGenerator(),
+ new NumberGenerator(),
basicBlockNumberGenerator,
IRMetadata.unknown(),
Origin.unknown());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/DevirtualizerNonNullRewritingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/DevirtualizerNonNullRewritingTest.java
index 12bce34..61d5cf3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/DevirtualizerNonNullRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/DevirtualizerNonNullRewritingTest.java
@@ -7,7 +7,8 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NeverMerge;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+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;
@@ -16,15 +17,24 @@
@RunWith(Parameterized.class)
public class DevirtualizerNonNullRewritingTest extends TestBase {
- private final Backend backend;
+ private final TestParameters parameters;
+ private static final String EXPECTED_OUTPUT = "Hello!";
- @Parameters(name = "Backend: {0}")
- public static Backend[] data() {
- return ToolHelper.getBackends();
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
- public DevirtualizerNonNullRewritingTest(Backend backend) {
- this.backend = backend;
+ public DevirtualizerNonNullRewritingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(DevirtualizerNonNullRewritingTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
}
/**
@@ -32,17 +42,14 @@
*/
@Test
public void test() throws Exception {
- String expectedOutput = "Hello!";
-
- testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
-
- testForR8(backend)
+ testForR8(parameters.getBackend())
.addInnerClasses(DevirtualizerNonNullRewritingTest.class)
.addKeepMainRule(TestClass.class)
.enableInliningAnnotations()
.enableMergeAnnotations()
- .run(TestClass.class)
- .assertSuccessWithOutput(expectedOutput);
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
index a75cc4c..efb4422 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
@@ -14,7 +14,6 @@
import com.android.tools.r8.ir.optimize.devirtualize.invokeinterface.A1;
import com.android.tools.r8.ir.optimize.devirtualize.invokeinterface.I;
import com.android.tools.r8.ir.optimize.devirtualize.invokeinterface.Main;
-import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -27,10 +26,11 @@
public class InvokeInterfaceToInvokeVirtualTest extends TestBase {
private final TestParameters parameters;
+ private final String EXPECTED_OUTPUT = "0";
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public InvokeInterfaceToInvokeVirtualTest(TestParameters parameters) {
@@ -38,25 +38,24 @@
}
@Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(I.class, A.class, A0.class, A1.class, Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+ }
+
+ @Test
public void listOfInterface() throws Exception {
- String expectedOutput = StringUtils.lines("0");
-
- if (parameters.isCfRuntime()) {
- testForJvm()
- .addTestClasspath()
- .run(parameters.getRuntime(), Main.class)
- .assertSuccessWithOutput(expectedOutput);
- }
-
CodeInspector inspector =
testForR8(parameters.getBackend())
.addProgramClasses(I.class, A.class, A0.class, A1.class, Main.class)
.addKeepMainRule(Main.class)
.addOptionsModification(
options -> options.enableInliningOfInvokesWithNullableReceivers = false)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
- .assertSuccessWithOutput(expectedOutput)
+ .assertSuccessWithOutputLines(EXPECTED_OUTPUT)
.inspector();
ClassSubject clazz = inspector.clazz(Main.class);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/EnumCompanionClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/EnumCompanionClassStaticizerTest.java
new file mode 100644
index 0000000..a865bc2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/EnumCompanionClassStaticizerTest.java
@@ -0,0 +1,81 @@
+// 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.staticizer;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+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 com.android.tools.r8.utils.codeinspector.ClassSubject;
+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 EnumCompanionClassStaticizerTest extends TestBase {
+
+ private final boolean enableEnumUnboxing;
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{1}, enum unboxing: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
+
+ public EnumCompanionClassStaticizerTest(boolean enableEnumUnboxing, TestParameters parameters) {
+ this.enableEnumUnboxing = enableEnumUnboxing;
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class, MyEnum.class, Companion.class)
+ .addKeepMainRule(TestClass.class)
+ .addOptionsModification(options -> options.enableEnumUnboxing = enableEnumUnboxing)
+ .enableNeverClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject companionClassSubject = inspector.clazz(Companion.class);
+ // TODO(b/162798790): Should be absent.
+ assertThat(companionClassSubject, isPresent());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ MyEnum.A.companion.greet();
+ }
+ }
+
+ enum MyEnum {
+ A;
+
+ public static final Companion companion = new Companion();
+ }
+
+ @NeverClassInline
+ static class Companion {
+
+ @NeverInline
+ public void greet() {
+ System.out.println("Hello world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderFullyInDoWhileLoopTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderFullyInDoWhileLoopTest.java
new file mode 100644
index 0000000..0074f01
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderFullyInDoWhileLoopTest.java
@@ -0,0 +1,67 @@
+// 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.string;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class StringBuilderFullyInDoWhileLoopTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public StringBuilderFullyInDoWhileLoopTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(StringBuilderFullyInDoWhileLoopTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("000");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ MethodSubject mainMethodSubject = inspector.clazz(TestClass.class).mainMethod();
+ assertThat(mainMethodSubject, isPresent());
+ assertEquals(
+ parameters.isCfRuntime(),
+ mainMethodSubject
+ .streamInstructions()
+ .anyMatch(instruction -> instruction.isNewInstance("java.lang.StringBuilder")));
+ }
+
+ static class TestClass {
+ public static void main(String[] args) {
+ int i = 0;
+ do {
+ StringBuilder builder = new StringBuilder();
+ builder.append("0");
+ System.out.print(builder.toString());
+ i++;
+ } while (i < 3);
+ System.out.println();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java
index 761856f..9c1a2b1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.ir.analysis.AnalysisTestBase;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.string.StringBuilderOptimizer.BuilderState;
import java.util.HashSet;
@@ -367,11 +368,13 @@
boolean expectToSeeMethodToString) {
boolean seenMethodToString = false;
for (Map.Entry<Instruction, BuilderState> entry : perBuilderState.entrySet()) {
- if (entry.getKey().isInvokeMethod()
- && optimizer.optimizationConfiguration.isToStringMethod(
- entry.getKey().asInvokeMethod().getInvokedMethod())) {
+ InvokeMethod invoke = entry.getKey().asInvokeMethod();
+ if (invoke != null
+ && optimizer.optimizationConfiguration.isToStringMethod(invoke.getInvokedMethod())) {
seenMethodToString = true;
- assertEquals(expectedConstString, optimizer.analysis.toCompileTimeString(entry.getValue()));
+ Value builder = invoke.getArgument(0).getAliasedValue();
+ assertEquals(
+ expectedConstString, optimizer.analysis.toCompileTimeString(builder, entry.getValue()));
}
}
assertEquals(expectToSeeMethodToString, seenMethodToString);
@@ -384,11 +387,12 @@
boolean expectToSeeMethodToString) {
boolean seenMethodToString = false;
for (Map.Entry<Instruction, BuilderState> entry : perBuilderState.entrySet()) {
- if (entry.getKey().isInvokeMethod()
- && optimizer.optimizationConfiguration.isToStringMethod(
- entry.getKey().asInvokeMethod().getInvokedMethod())) {
+ InvokeMethod invoke = entry.getKey().asInvokeMethod();
+ if (invoke != null
+ && optimizer.optimizationConfiguration.isToStringMethod(invoke.getInvokedMethod())) {
seenMethodToString = true;
- String computedString = optimizer.analysis.toCompileTimeString(entry.getValue());
+ Value builder = invoke.getArgument(0).getAliasedValue();
+ String computedString = optimizer.analysis.toCompileTimeString(builder, entry.getValue());
assertNotNull(computedString);
assertTrue(expectedConstStrings.contains(computedString));
expectedConstStrings.remove(computedString);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderPartiallyInDoWhileLoopTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderPartiallyInDoWhileLoopTest.java
new file mode 100644
index 0000000..62becf9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderPartiallyInDoWhileLoopTest.java
@@ -0,0 +1,48 @@
+// 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.string;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class StringBuilderPartiallyInDoWhileLoopTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public StringBuilderPartiallyInDoWhileLoopTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(StringBuilderPartiallyInDoWhileLoopTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("000");
+ }
+
+ static class TestClass {
+ public static void main(String[] args) {
+ StringBuilder builder = new StringBuilder();
+ int i = 0;
+ do {
+ builder.append("0");
+ i++;
+ } while (i < 3);
+ System.out.println(builder.toString());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataPrimitiveTypeRewriteTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataPrimitiveTypeRewriteTest.java
new file mode 100644
index 0000000..76c2894
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataPrimitiveTypeRewriteTest.java
@@ -0,0 +1,130 @@
+// 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.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.JvmTestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataPrimitiveTypeRewriteTest extends KotlinMetadataTestBase {
+
+ private final TestParameters parameters;
+ private final String EXPECTED = "Hello World";
+ private static final String PKG_LIB = PKG + ".primitive_type_rewrite_lib";
+ private static final String PKG_APP = PKG + ".primitive_type_rewrite_app";
+
+ @Parameterized.Parameters(name = "{0} target: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+ }
+
+ public MetadataPrimitiveTypeRewriteTest(
+ TestParameters parameters, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.parameters = parameters;
+ }
+
+ private static Map<KotlinTargetVersion, Path> libJars = new HashMap<>();
+
+ @BeforeClass
+ public static void createLibJar() throws Exception {
+ for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+ Path baseLibJar =
+ kotlinc(KOTLINC, targetVersion)
+ .addSourceFiles(
+ getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_LIB), "lib"))
+ .compile();
+ libJars.put(targetVersion, baseLibJar);
+ }
+ }
+
+ @Test
+ public void smokeTest() throws Exception {
+ Path libJar = libJars.get(targetVersion);
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(
+ getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testRenamingRenameUnit() throws Exception {
+ runTest(false);
+ }
+
+ @Test
+ public void testRenamingKeepUnit() throws Exception {
+ runTest(true);
+ }
+
+ private void runTest(boolean keepUnit) throws Exception {
+ Path libJar =
+ testForR8(parameters.getBackend())
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar(), libJars.get(targetVersion))
+ .addKeepAllClassesRuleWithAllowObfuscation()
+ .addKeepRules("-keep class " + PKG_LIB + ".LibKt { *; }")
+ .addKeepRules("-keep class kotlin.Metadata { *; }")
+ .applyIf(keepUnit, b -> b.addKeepRules("-keep class kotlin.Unit { *; }"))
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .allowDiagnosticWarningMessages()
+ .compile()
+ .inspect(
+ codeInspector -> {
+ final ClassSubject clazz = codeInspector.clazz("kotlin.Unit");
+ assertThat(clazz, isPresentAndRenamed(!keepUnit));
+ })
+ .assertAllWarningMessagesMatch(
+ equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+ .writeToZip();
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(
+ getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+ final JvmTestRunResult runResult =
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), PKG_APP + ".MainKt");
+ if (keepUnit) {
+ runResult.assertSuccessWithOutputLines(EXPECTED);
+ } else {
+ runResult.assertFailureWithErrorThatMatches(
+ containsString(
+ "java.lang.NoSuchMethodError:"
+ + " com.android.tools.r8.kotlin.metadata.primitive_type_rewrite_lib.LibKt.foo()"));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataVersionNumberBumpTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataVersionNumberBumpTest.java
new file mode 100644
index 0000000..e262f66
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataVersionNumberBumpTest.java
@@ -0,0 +1,153 @@
+// 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.kotlin.metadata;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.objectweb.asm.Opcodes.ASM7;
+
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.graph.DexAnnotationElement;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.transformers.ClassTransformer;
+import com.android.tools.r8.utils.StreamUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.AnnotationVisitor;
+
+@RunWith(Parameterized.class)
+public class MetadataVersionNumberBumpTest extends KotlinMetadataTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public MetadataVersionNumberBumpTest(TestParameters parameters) {
+ super(KotlinTargetVersion.JAVA_8);
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testLessThan1_4() throws Exception {
+ final R8FullTestBuilder testBuilder = testForR8(parameters.getBackend());
+ rewriteMetadataVersion(testBuilder::addProgramClassFileData, new int[] {1, 1, 16});
+ testBuilder
+ .setMinApi(parameters.getApiLevel())
+ .addKeepAllClassesRule()
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .compile()
+ .inspect(inspector -> inspectMetadataVersion(inspector, "1.4.0"));
+ }
+
+ @Test
+ public void testEqualTo1_4() throws Exception {
+ final R8FullTestBuilder testBuilder = testForR8(parameters.getBackend());
+ rewriteMetadataVersion(testBuilder::addProgramClassFileData, new int[] {1, 4, 0});
+ testBuilder
+ .setMinApi(parameters.getApiLevel())
+ .addKeepAllClassesRule()
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .compile()
+ .inspect(inspector -> inspectMetadataVersion(inspector, "1.4.0"));
+ }
+
+ @Test
+ public void testGreaterThan1_4() throws Exception {
+ final R8FullTestBuilder testBuilder = testForR8(parameters.getBackend());
+ rewriteMetadataVersion(testBuilder::addProgramClassFileData, new int[] {1, 4, 2});
+ testBuilder
+ .setMinApi(parameters.getApiLevel())
+ .addKeepAllClassesRule()
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .compile()
+ .inspect(inspector -> inspectMetadataVersion(inspector, "1.4.2"));
+ }
+
+ private void rewriteMetadataVersion(Consumer<byte[]> rewrittenBytesConsumer, int[] newVersion)
+ throws IOException {
+ ZipUtils.iter(
+ ToolHelper.getKotlinStdlibJar().toString(),
+ ((entry, input) -> {
+ if (!entry.getName().endsWith(".class")) {
+ return;
+ }
+ final byte[] bytes = StreamUtils.StreamToByteArrayClose(input);
+ final byte[] rewrittenBytes =
+ transformMetadataVersion(
+ entry.getName().substring(0, entry.getName().length() - 6), bytes, newVersion);
+ rewrittenBytesConsumer.accept(rewrittenBytes);
+ }));
+ }
+
+ private byte[] transformMetadataVersion(String descriptor, byte[] bytes, int[] newVersion) {
+ return transformer(bytes, Reference.classFromDescriptor(descriptor))
+ .addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ if (!descriptor.equals("Lkotlin/Metadata;")) {
+ return super.visitAnnotation(descriptor, visible);
+ } else {
+ return new AnnotationVisitor(ASM7, super.visitAnnotation(descriptor, visible)) {
+ @Override
+ public void visit(String name, Object value) {
+ if (name.equals("mv")) {
+ super.visit(name, newVersion);
+ } else {
+ super.visit(name, value);
+ }
+ }
+ };
+ }
+ }
+ })
+ .transform();
+ }
+
+ private void inspectMetadataVersion(CodeInspector inspector, String expectedVersion) {
+ for (FoundClassSubject clazz : inspector.allClasses()) {
+ verifyExpectedVersionForClass(clazz, expectedVersion);
+ }
+ }
+
+ private void verifyExpectedVersionForClass(FoundClassSubject clazz, String expectedVersion) {
+ final AnnotationSubject annotationSubject = clazz.annotation("kotlin.Metadata");
+ // TODO(b/164418977): All classes should have an annotation?
+ if (!annotationSubject.isPresent()) {
+ return;
+ }
+ final DexAnnotationElement[] elements = annotationSubject.getAnnotation().elements;
+ for (DexAnnotationElement element : elements) {
+ if (!element.name.toString().equals("mv")) {
+ continue;
+ }
+ final String version =
+ Arrays.stream(element.value.asDexValueArray().getValues())
+ .map(val -> val.asDexValueInt().value + "")
+ .collect(Collectors.joining("."));
+ assertEquals(expectedVersion, version);
+ return;
+ }
+ fail("Could not find the mv (metadataVersion) element");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/primitive_type_rewrite_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/primitive_type_rewrite_app/main.kt
new file mode 100644
index 0000000..adcf477
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/primitive_type_rewrite_app/main.kt
@@ -0,0 +1,11 @@
+// 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.kotlin.metadata.primitive_type_rewrite_app
+
+import com.android.tools.r8.kotlin.metadata.primitive_type_rewrite_lib.foo
+
+fun main() {
+ foo()
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/primitive_type_rewrite_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/primitive_type_rewrite_lib/lib.kt
new file mode 100644
index 0000000..ec26d64
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/primitive_type_rewrite_lib/lib.kt
@@ -0,0 +1,9 @@
+// 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.kotlin.metadata.primitive_type_rewrite_lib
+
+fun foo() {
+ println("Hello World")
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 984d813..e4e0b53 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -30,7 +30,6 @@
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ClassAccessFlags;
import com.android.tools.r8.graph.Code;
@@ -808,8 +807,7 @@
options.minApiLevel = minApi;
options.intermediate = intermediate;
DexItemFactory factory = options.itemFactory;
- AppInfo appInfo = new AppInfo(DexApplication.builder(options, timing).build());
- AppView<?> appView = AppView.createForR8(appInfo);
+ AppView<?> appView = AppView.createForR8(DexApplication.builder(options, timing).build());
DexApplication.Builder<?> builder = DexApplication.builder(options, timing);
for (String clazz : classes) {
DexString desc = factory.createString(DescriptorUtils.javaTypeToDescriptor(clazz));
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java
index cc4e99c..36bf030 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java
@@ -111,7 +111,7 @@
options -> {
options.enableInlining = false;
options.enableVerticalClassMerging = false;
- options.enableHorizontalClassMerging = false;
+ options.enableStaticClassMerging = false;
options.enableClassInlining = false;
})
.compile();
diff --git a/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
index 4ad2bed..f13728a 100644
--- a/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+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.DexType;
@@ -19,8 +20,6 @@
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Timing;
-import java.io.IOException;
-import java.util.concurrent.ExecutionException;
import org.junit.Test;
public class ArrayTargetLookupTest extends TestBase {
@@ -28,7 +27,7 @@
public static class Foo {}
@Test
- public void testArrays() throws IOException, ExecutionException {
+ public void testArrays() throws Exception {
Timing timing = Timing.empty();
InternalOptions options = new InternalOptions();
AndroidApp app =
@@ -38,7 +37,7 @@
.build();
DirectMappedDexApplication application =
new ApplicationReader(app, options, timing).read().toDirect();
- AppInfoWithClassHierarchy appInfo = new AppInfoWithClassHierarchy(application);
+ AppInfoWithClassHierarchy appInfo = AppView.createForR8(application).appInfo();
DexItemFactory factory = options.itemFactory;
DexType fooType =
factory.createType(DescriptorUtils.javaTypeToDescriptor(Foo.class.getTypeName()));
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/KeptTargetsIncompleteLookupTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/KeptTargetsIncompleteLookupTest.java
index 4332d86..3566d3a 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/KeptTargetsIncompleteLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/KeptTargetsIncompleteLookupTest.java
@@ -88,7 +88,11 @@
DexMethod method = buildNullaryVoidMethod(initial, "foo", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
DexProgramClass context =
- appView.definitionForProgramType(buildType(Unrelated.class, appInfo.dexItemFactory()));
+ DexProgramClass.asProgramClassOrNull(
+ appView
+ .appInfo()
+ .definitionForWithoutExistenceAssert(
+ buildType(Unrelated.class, appInfo.dexItemFactory())));
LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
assertTrue(lookupResult.isLookupResultSuccess());
LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess();
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
index ec3fcc1..f2dc303 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.Version;
import com.android.tools.r8.retrace.stacktraces.ActualRetraceBotStackTrace;
import com.android.tools.r8.retrace.stacktraces.ActualRetraceBotStackTraceWithInfo;
import com.android.tools.r8.retrace.stacktraces.FoundMethodVerboseStackTrace;
@@ -130,6 +131,13 @@
}
@Test
+ public void testVersion() throws Exception {
+ ProcessResult processResult = runRetraceCommandLine(null, Arrays.asList("--version"));
+ assertEquals(0, processResult.exitCode);
+ assertEquals(StringUtils.lines("Retrace " + Version.getVersionString()), processResult.stdout);
+ }
+
+ @Test
public void testNonAscii() throws IOException {
runTest("", SMILEY_EMOJI, false, SMILEY_EMOJI + StringUtils.LINE_SEPARATOR);
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
index 1bf5f83..ae4947a 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
@@ -20,7 +20,6 @@
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.util.List;
import java.util.Objects;
-import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -75,14 +74,13 @@
if (enableOptimization) {
assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("simple"), 1);
assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("local"), 1);
- // TODO(b/160667929): Re-enable name()/toString() optimizations.
// String concatenation optimization is enabled for DEX output.
// Even replaced ordinal is concatenated (and gone).
- // if (parameters.isDexRuntime()) {
- // assertOrdinalReplacedAndGone(clazz.uniqueMethodWithName("multipleUsages"));
- // } else {
- assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("multipleUsages"), 1);
- // }
+ if (parameters.isDexRuntime()) {
+ assertOrdinalReplacedAndGone(clazz.uniqueMethodWithName("multipleUsages"));
+ } else {
+ assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("multipleUsages"), 1);
+ }
assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("inlined"), 1);
assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("inSwitch"), 11);
assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("differentTypeStaticField"), 1);
@@ -123,7 +121,6 @@
assertTrue(clazz.isPresent());
if (enableOptimization) {
- Assume.assumeTrue("TODO(b/160667929): Re-enable name()/toString() optimizations.", false);
assertNameReplacedWithConst(clazz.uniqueMethodWithName("simple"), "TWO");
assertNameReplacedWithConst(clazz.uniqueMethodWithName("local"), "TWO");
// String concatenation optimization is enabled for DEX output.
@@ -174,7 +171,6 @@
assertToStringWasNotReplaced(clazz.uniqueMethodWithName("valueWithoutToString"));
if (enableOptimization) {
- Assume.assumeTrue("TODO(b/160667929): Re-enable name()/toString() optimizations.", false);
assertToStringReplacedWithConst(clazz.uniqueMethodWithName("noToString"), "TWO");
assertToStringReplacedWithConst(clazz.uniqueMethodWithName("local"), "TWO");
assertToStringReplacedWithConst(clazz.uniqueMethodWithName("multipleUsages"), "TWO");
diff --git a/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java b/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
index 35f67b2..0040143 100644
--- a/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DirectMappedDexApplication;
@@ -31,8 +32,9 @@
public void readApp() throws IOException, ExecutionException {
program = ToolHelper.buildApplication(ImmutableList.of(APP_FILE_NAME));
dexItemFactory = program.dexItemFactory;
- appInfo = new AppInfoWithClassHierarchy(program);
- subtypingInfo = new SubtypingInfo(program.allClasses(), program);
+ AppView<AppInfoWithClassHierarchy> appView = AppView.createForR8(program);
+ appInfo = appView.appInfo();
+ subtypingInfo = new SubtypingInfo(appView);
}
private void validateSubtype(DexType super_type, DexType sub_type) {
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
new file mode 100644
index 0000000..456c4cb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/IdenticalFieldMembersTest.java
@@ -0,0 +1,100 @@
+// 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 com.android.tools.r8.*;
+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 IdenticalFieldMembersTest extends TestBase {
+ private final TestParameters parameters;
+ private final boolean enableHorizontalClassMerging;
+
+ public IdenticalFieldMembersTest(
+ 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(IdenticalFieldMembersTest.class)
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("foo A", "bar B")
+ .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());
+ } else {
+ assertThat(codeInspector.clazz(A.class), isPresent());
+ assertThat(codeInspector.clazz(B.class), isPresent());
+ }
+ });
+ }
+
+ @NeverClassInline
+ public static class A {
+ private String field;
+
+ public A(String v) {
+ this.field = v;
+ }
+
+ @NeverInline
+ public void foo() {
+ System.out.println("foo " + field);
+ }
+ }
+
+ @NeverClassInline
+ public static class B {
+ private String field;
+
+ public B(String v) {
+ this.field = v;
+ }
+
+ @NeverInline
+ public void bar() {
+ System.out.println("bar " + field);
+ }
+ }
+
+ public static class Main {
+ public static void main(String[] args) {
+ A a = new A("A");
+ a.foo();
+ B b = new B("B");
+ b.bar();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/NoFieldMembersTest.java b/src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/NoFieldMembersTest.java
new file mode 100644
index 0000000..8dfe240
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/NoFieldMembersTest.java
@@ -0,0 +1,87 @@
+// 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 com.android.tools.r8.*;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.*;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NoFieldMembersTest extends TestBase {
+ private final TestParameters parameters;
+ private final boolean enableHorizontalClassMerging;
+
+ public NoFieldMembersTest(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(NoFieldMembersTest.class)
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("foo", "bar")
+ .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());
+ } else {
+ assertThat(codeInspector.clazz(A.class), isPresent());
+ assertThat(codeInspector.clazz(B.class), isPresent());
+ }
+ });
+ }
+
+ @NeverClassInline
+ public static class A {
+ @NeverInline
+ public void foo() {
+ System.out.println("foo");
+ }
+ }
+
+ @NeverClassInline
+ public static class B {
+ @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();
+ b.bar();
+ }
+ }
+}
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 2dcfeb6..5b92c70 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -77,6 +77,11 @@
'--min-api',
help='Set min-api (default read from dump properties file)',
default=None)
+ parser.add_argument(
+ '--loop',
+ help='Run the compilation in a loop',
+ default=False,
+ action='store_true')
return parser
def error(msg):
@@ -201,10 +206,10 @@
def is_hash(version):
return len(version) == 40
-def run(args, otherargs):
+def run1(out, args, otherargs):
with utils.TempDir() as temp:
- if args.temp:
- temp = args.temp
+ if out:
+ temp = out
if not os.path.exists(temp):
os.makedirs(temp)
dump = read_dump(args, temp)
@@ -273,6 +278,19 @@
retrace.run(local_map, hash_or_version, stacktrace, is_hash(version))
return 1
+def run(args, otherargs):
+ if (args.loop):
+ count = 1
+ while True:
+ print('Iteration {:03d}'.format(count))
+ out = args.temp
+ if out:
+ out = os.path.join(out, '{:03d}'.format(count))
+ run1(out, args, otherargs)
+ count += 1
+ else:
+ run1(args.temp, args, otherargs)
+
if __name__ == '__main__':
(args, otherargs) = make_parser().parse_known_args(sys.argv[1:])
sys.exit(run(args, otherargs))
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 0612c36..3641f0c 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -210,7 +210,7 @@
git_message = ("""Update D8 and R8 to %s
-Version: master %s
+Version: %s
This build IS NOT suitable for preview or public release.
Built here: go/r8-releases/raw/%s
@@ -249,7 +249,7 @@
return release_maven
def git_message_dev(version):
- return """Update D8 R8 master to %s
+ return """Update D8 R8 to %s
This is a development snapshot, it's fine to use for studio canary build, but
not for BETA or release, for those we would need a release version of R8