Merge commit '77627c90b270921a32af4e2d1c057e4f885e5928' into dev-release
diff --git a/.gitignore b/.gitignore index e64324a..76604f8 100644 --- a/.gitignore +++ b/.gitignore
@@ -133,6 +133,10 @@ third_party/r8.tar.gz third_party/r8mappings third_party/r8mappings.tar.gz +third_party/remapper +third_party/remapper.tar.gz +third_party/retrace_benchmark +third_party/retrace_benchmark.tar.gz third_party/rhino-1.7.10 third_party/rhino-1.7.10.tar.gz third_party/rhino-android-1.1.1
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java index 8323666..80dcf13 100644 --- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java +++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.errors.CompilationError; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.inspector.Inspector; import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration; import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser; import com.android.tools.r8.origin.Origin; @@ -14,10 +15,14 @@ import com.android.tools.r8.utils.FileUtils; import com.android.tools.r8.utils.InternalOptions.DesugarState; import com.android.tools.r8.utils.Reporter; +import com.android.tools.r8.utils.ThreadUtils; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.function.BiPredicate; +import java.util.function.Consumer; import java.util.function.Function; /** @@ -41,6 +46,8 @@ private final boolean optimizeMultidexForLinearAlloc; private final BiPredicate<String, Long> dexClassChecksumFilter; private final List<AssertionsConfiguration> assertionsConfiguration; + private final List<Consumer<Inspector>> outputInspections; + private int threadCount; BaseCompilerCommand(boolean printHelp, boolean printVersion) { super(printHelp, printVersion); @@ -54,6 +61,8 @@ optimizeMultidexForLinearAlloc = false; dexClassChecksumFilter = (name, checksum) -> true; assertionsConfiguration = new ArrayList<>(); + outputInspections = null; + threadCount = ThreadUtils.NOT_SPECIFIED; } BaseCompilerCommand( @@ -67,7 +76,9 @@ boolean optimizeMultidexForLinearAlloc, boolean includeClassesChecksum, BiPredicate<String, Long> dexClassChecksumFilter, - List<AssertionsConfiguration> assertionsConfiguration) { + List<AssertionsConfiguration> assertionsConfiguration, + List<Consumer<Inspector>> outputInspections, + int threadCount) { super(app); assert minApiLevel > 0; assert mode != null; @@ -81,6 +92,8 @@ this.includeClassesChecksum = includeClassesChecksum; this.dexClassChecksumFilter = dexClassChecksumFilter; this.assertionsConfiguration = assertionsConfiguration; + this.outputInspections = outputInspections; + this.threadCount = threadCount; } /** @@ -140,7 +153,16 @@ } public List<AssertionsConfiguration> getAssertionsConfiguration() { - return assertionsConfiguration; + return Collections.unmodifiableList(assertionsConfiguration); + } + + public Collection<Consumer<Inspector>> getOutputInspections() { + return Collections.unmodifiableList(outputInspections); + } + + /** Get the number of threads to use for the compilation. */ + public int getThreadCount() { + return threadCount; } Reporter getReporter() { @@ -166,6 +188,7 @@ private CompilationMode mode; private int minApiLevel = 0; + private int threadCount = ThreadUtils.NOT_SPECIFIED; protected DesugarState desugarState = DesugarState.ON; private List<StringResource> desugaredLibraryConfigurationResources = new ArrayList<>(); private boolean includeClassesChecksum = false; @@ -173,6 +196,7 @@ private boolean optimizeMultidexForLinearAlloc = false; private BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true; private List<AssertionsConfiguration> assertionsConfiguration = new ArrayList<>(); + private List<Consumer<Inspector>> outputInspections = new ArrayList<>(); abstract CompilationMode defaultCompilationMode(); @@ -490,6 +514,20 @@ return self(); } + /** Set the number of threads to use for the compilation */ + B setThreadCount(int threadCount) { + if (threadCount <= 0) { + getReporter().error("Invalid threadCount: " + threadCount); + } else { + this.threadCount = threadCount; + } + return self(); + } + + int getThreadCount() { + return threadCount; + } + /** Encodes the checksums into the dex output. */ public boolean getIncludeClassesChecksum() { return includeClassesChecksum; @@ -552,5 +590,31 @@ } super.validate(); } + + /** + * Add an inspection of the output program. + * + * <p>On a successful compilation the inspection is guaranteed to be called with inspectors that + * combined cover all of the output program. The inspections may be called multiple times with + * inspectors that have overlapping content (eg, classes synthesized based on multiple inputs + * can lead to this). Any overlapping content will be consistent, e.g., the inspection of type T + * will be the same (equality, not identify) as any other inspection of type T. + * + * <p>There is no guarantee of the order inspections are called or on which thread they are + * called. + * + * <p>The validity of an {@code Inspector} and all of its sub-inspectors, eg, + * {@MethodInspector}, is that of the callback. If any inspector object escapes the scope of the + * callback, the behavior of that inspector is undefined. + * + * @param inspection Inspection callback receiving inspectors denoting parts of the output. + */ + public void addOutputInspection(Consumer<Inspector> inspection) { + outputInspections.add(inspection); + } + + List<Consumer<Inspector>> getOutputInspections() { + return outputInspections; + } } }
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java index dbfe6b7..410441b3 100644 --- a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java +++ b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
@@ -12,10 +12,14 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; +import java.util.function.Consumer; public class BaseCompilerCommandParser< C extends BaseCompilerCommand, B extends BaseCompilerCommand.Builder<C, B>> { + protected static final String MIN_API_FLAG = "--min-api"; + protected static final String THREAD_COUNT_FLAG = "--thread-count"; + static final Iterable<String> ASSERTIONS_USAGE_MESSAGE = Arrays.asList( " --force-enable-assertions[:[<class name>|<package name>...]]", @@ -32,19 +36,20 @@ " # is the default handling of javac assertion code when", " # generating class file format."); - void parseMinApi(B builder, String minApiString, Origin origin) { - int minApi; + void parsePositiveIntArgument( + B builder, String flag, String argument, Origin origin, Consumer<Integer> setter) { + int value; try { - minApi = Integer.parseInt(minApiString); + value = Integer.parseInt(argument); } catch (NumberFormatException e) { - builder.error(new StringDiagnostic("Invalid argument to --min-api: " + minApiString, origin)); + builder.error(new StringDiagnostic("Invalid argument to " + flag + ": " + argument, origin)); return; } - if (minApi < 1) { - builder.error(new StringDiagnostic("Invalid argument to --min-api: " + minApiString, origin)); + if (value < 1) { + builder.error(new StringDiagnostic("Invalid argument to " + flag + ": " + argument, origin)); return; } - builder.setMinApiLevel(minApi); + setter.accept(value); } private static String PACKAGE_ASSERTION_POSTFIX = "...";
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java index 1fa0bb0..1ce0024 100644 --- a/src/main/java/com/android/tools/r8/D8.java +++ b/src/main/java/com/android/tools/r8/D8.java
@@ -16,7 +16,9 @@ import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.GraphLense; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis; +import com.android.tools.r8.inspector.internal.InspectorImpl; import com.android.tools.r8.ir.conversion.IRConverter; import com.android.tools.r8.ir.desugar.PrefixRewritingMapper; import com.android.tools.r8.ir.optimize.AssertionsRewriter; @@ -220,6 +222,7 @@ markers.add(marker); } + InspectorImpl.runInspections(options.outputInspections, app); if (options.isGeneratingClassFiles()) { new CfApplicationWriter( app, @@ -237,6 +240,7 @@ options, marker == null ? null : ImmutableList.copyOf(markers), GraphLense.getIdentityLense(), + InitClassLens.getDefault(), PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView), null) .write(executor);
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java index b7c9347..92f194a 100644 --- a/src/main/java/com/android/tools/r8/D8Command.java +++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -6,6 +6,8 @@ import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation; import com.android.tools.r8.errors.DexFileOverflowDiagnostic; import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.inspector.Inspector; +import com.android.tools.r8.inspector.internal.InspectorImpl; import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.AndroidApiLevel; @@ -15,10 +17,12 @@ import com.android.tools.r8.utils.InternalOptions.DesugarState; import com.android.tools.r8.utils.Reporter; import com.android.tools.r8.utils.StringDiagnostic; +import com.android.tools.r8.utils.ThreadUtils; import java.nio.file.Path; import java.util.Collection; import java.util.List; import java.util.function.BiPredicate; +import java.util.function.Consumer; /** * Immutable command structure for an invocation of the {@link D8} compiler. @@ -36,13 +40,6 @@ @Keep public final class D8Command extends BaseCompilerCommand { - private static class ClasspathInputOrigin extends InputFileOrigin { - - public ClasspathInputOrigin(Path file) { - super("classpath input", file); - } - } - private static class DefaultD8DiagnosticsHandler implements DiagnosticsHandler { @Override @@ -229,6 +226,8 @@ desugaredLibraryKeepRuleConsumer, libraryConfiguration, getAssertionsConfiguration(), + getOutputInspections(), + getThreadCount(), factory); } } @@ -297,6 +296,8 @@ StringConsumer desugaredLibraryKeepRuleConsumer, DesugaredLibraryConfiguration libraryConfiguration, List<AssertionsConfiguration> assertionsConfiguration, + List<Consumer<Inspector>> outputInspections, + int threadCount, DexItemFactory factory) { super( inputApp, @@ -309,7 +310,9 @@ optimizeMultidexForLinearAlloc, encodeChecksum, dexClassChecksumFilter, - assertionsConfiguration); + assertionsConfiguration, + outputInspections, + threadCount); this.intermediate = intermediate; this.desugarGraphConsumer = desugarGraphConsumer; this.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer; @@ -379,6 +382,11 @@ new AssertionConfigurationWithDefault( AssertionTransformation.DISABLE, getAssertionsConfiguration()); + internal.outputInspections = InspectorImpl.wrapInspections(getOutputInspections()); + + assert internal.threadCount == ThreadUtils.NOT_SPECIFIED; + internal.threadCount = getThreadCount(); + return internal; } }
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java index a6827d3..e9df325 100644 --- a/src/main/java/com/android/tools/r8/D8CommandParser.java +++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -29,10 +29,11 @@ "--output", "--lib", "--classpath", - "--min-api", + MIN_API_FLAG, "--main-dex-list", "--main-dex-list-output", - "--desugared-lib"); + "--desugared-lib", + THREAD_COUNT_FLAG); private static final String APK_EXTENSION = ".apk"; private static final String JAR_EXTENSION = ".jar"; @@ -121,7 +122,10 @@ " # <file> must be an existing directory or a zip file.", " --lib <file|jdk-home> # Add <file|jdk-home> as a library resource.", " --classpath <file> # Add <file> as a classpath resource.", - " --min-api <number> # Minimum Android API level compatibility, default: " + " " + + MIN_API_FLAG + + " <number> " + + "# Minimum Android API level compatibility, default: " + AndroidApiLevel.getDefault().getLevel() + ".", " --intermediate # Compile an intermediate result intended for later", @@ -243,13 +247,17 @@ builder.setMainDexListOutputPath(Paths.get(nextArg)); } else if (arg.equals("--optimize-multidex-for-linearalloc")) { builder.setOptimizeMultidexForLinearAlloc(true); - } else if (arg.equals("--min-api")) { + } else if (arg.equals(MIN_API_FLAG)) { if (hasDefinedApiLevel) { - builder.error(new StringDiagnostic("Cannot set multiple --min-api options", origin)); + builder.error( + new StringDiagnostic("Cannot set multiple " + MIN_API_FLAG + " options", origin)); } else { - parseMinApi(builder, nextArg, origin); + parsePositiveIntArgument(builder, MIN_API_FLAG, nextArg, origin, builder::setMinApiLevel); hasDefinedApiLevel = true; } + } else if (arg.equals(THREAD_COUNT_FLAG)) { + parsePositiveIntArgument( + builder, THREAD_COUNT_FLAG, nextArg, origin, builder::setThreadCount); } else if (arg.equals("--intermediate")) { builder.setIntermediate(true); } else if (arg.equals("--no-desugaring")) {
diff --git a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java index 2509e12..94ea634 100644 --- a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java +++ b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
@@ -13,6 +13,7 @@ import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.GraphLense; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.ExceptionUtils; @@ -102,6 +103,7 @@ options, markers, GraphLense.getIdentityLense(), + InitClassLens.getDefault(), NamingLens.getIdentityLens(), null); writer.write(executor);
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java index 7ec9143..a9d4644 100644 --- a/src/main/java/com/android/tools/r8/DexSplitterHelper.java +++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
@@ -14,6 +14,7 @@ import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.GraphLense; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.graph.LazyLoadedDexApplication; import com.android.tools.r8.naming.ClassNameMapper; import com.android.tools.r8.naming.NamingLens; @@ -82,8 +83,6 @@ getDistribution(app, featureClassMapping, mapper); for (Entry<String, LazyLoadedDexApplication.Builder> entry : applications.entrySet()) { DexApplication featureApp = entry.getValue().build(); - // We use the same factory, reset sorting. - featureApp.dexItemFactory.resetSortedIndices(); assert !options.hasMethodsFilter(); // Run d8 optimize to ensure jumbo strings are handled. @@ -103,6 +102,7 @@ options, markers, GraphLense.getIdentityLense(), + InitClassLens.getDefault(), NamingLens.getIdentityLens(), null, consumer)
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java index 9db1c33..c32fbb7 100644 --- a/src/main/java/com/android/tools/r8/L8Command.java +++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -7,6 +7,7 @@ import com.android.tools.r8.ProgramResource.Kind; import com.android.tools.r8.errors.DexFileOverflowDiagnostic; import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.inspector.Inspector; import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.AndroidApp; @@ -16,11 +17,13 @@ import com.android.tools.r8.utils.Pair; import com.android.tools.r8.utils.Reporter; import com.android.tools.r8.utils.StringDiagnostic; +import com.android.tools.r8.utils.ThreadUtils; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.function.Consumer; /** Immutable command structure for an invocation of the {@link L8} library compiler. */ @Keep @@ -83,6 +86,8 @@ Reporter diagnosticsHandler, DesugaredLibraryConfiguration libraryConfiguration, List<AssertionsConfiguration> assertionsConfiguration, + List<Consumer<Inspector>> outputInspections, + int threadCount, DexItemFactory factory) { super( inputApp, @@ -95,7 +100,9 @@ false, false, (name, checksum) -> true, - assertionsConfiguration); + assertionsConfiguration, + outputInspections, + threadCount); this.d8Command = d8Command; this.r8Command = r8Command; this.libraryConfiguration = libraryConfiguration; @@ -180,6 +187,9 @@ new AssertionConfigurationWithDefault( AssertionTransformation.DISABLE, getAssertionsConfiguration()); + assert internal.threadCount == ThreadUtils.NOT_SPECIFIED; + internal.threadCount = getThreadCount(); + return internal; } @@ -317,6 +327,8 @@ getReporter(), libraryConfiguration, getAssertionsConfiguration(), + getOutputInspections(), + getThreadCount(), factory); } }
diff --git a/src/main/java/com/android/tools/r8/L8CommandParser.java b/src/main/java/com/android/tools/r8/L8CommandParser.java index bf35b67..976fb4a 100644 --- a/src/main/java/com/android/tools/r8/L8CommandParser.java +++ b/src/main/java/com/android/tools/r8/L8CommandParser.java
@@ -19,7 +19,7 @@ public class L8CommandParser extends BaseCompilerCommandParser<L8Command, L8Command.Builder> { private static final Set<String> OPTIONS_WITH_PARAMETER = - ImmutableSet.of("--output", "--lib", "--min-api", "--desugared-lib"); + ImmutableSet.of("--output", "--lib", MIN_API_FLAG, "--desugared-lib", THREAD_COUNT_FLAG); public static void main(String[] args) throws CompilationFailedException { L8Command command = parse(args, Origin.root()).build(); @@ -43,7 +43,10 @@ " --output <file> # Output result in <outfile>.", " # <file> must be an existing directory or a zip file.", " --lib <file|jdk-home> # Add <file|jdk-home> as a library resource.", - " --min-api <number> # Minimum Android API level compatibility, default: " + " " + + MIN_API_FLAG + + " <number> " + + "# Minimum Android API level compatibility, default: " + AndroidApiLevel.getDefault().getLevel() + ".", " --pg-conf <file> # Proguard configuration <file>.", @@ -131,11 +134,12 @@ continue; } outputPath = Paths.get(nextArg); - } else if (arg.equals("--min-api")) { + } else if (arg.equals(MIN_API_FLAG)) { if (hasDefinedApiLevel) { - builder.error(new StringDiagnostic("Cannot set multiple --min-api options", origin)); + builder.error( + new StringDiagnostic("Cannot set multiple " + MIN_API_FLAG + " options", origin)); } else { - parseMinApi(builder, nextArg, origin); + parsePositiveIntArgument(builder, MIN_API_FLAG, nextArg, origin, builder::setMinApiLevel); hasDefinedApiLevel = true; } } else if (arg.equals("--lib")) { @@ -144,6 +148,9 @@ builder.addProguardConfigurationFiles(Paths.get(nextArg)); } else if (arg.equals("--desugared-lib")) { builder.addDesugaredLibraryConfiguration(StringResource.fromFile(Paths.get(nextArg))); + } else if (arg.equals(THREAD_COUNT_FLAG)) { + parsePositiveIntArgument( + builder, THREAD_COUNT_FLAG, nextArg, origin, builder::setThreadCount); } else if (arg.startsWith("--")) { if (!tryParseAssertionArgument(builder, arg, origin)) { builder.error(new StringDiagnostic("Unknown option: " + arg, origin));
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java index d492e67..57f8395 100644 --- a/src/main/java/com/android/tools/r8/PrintUses.java +++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -91,6 +91,12 @@ } @Override + public boolean registerInitClass(DexType clazz) { + addType(clazz); + return false; + } + + @Override public boolean registerInvokeVirtual(DexMethod method) { ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method); DexEncodedMethod target =
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index 8817968..5223520 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java
@@ -28,8 +28,10 @@ import com.android.tools.r8.graph.EnumValueInfoMapCollection; import com.android.tools.r8.graph.GraphLense; import com.android.tools.r8.graph.GraphLense.NestedGraphLense; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis; import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis; +import com.android.tools.r8.inspector.internal.InspectorImpl; import com.android.tools.r8.ir.analysis.proto.GeneratedExtensionRegistryShrinker; import com.android.tools.r8.ir.conversion.IRConverter; import com.android.tools.r8.ir.desugar.BackportedMethodRewriter; @@ -46,9 +48,7 @@ import com.android.tools.r8.ir.optimize.enums.EnumValueInfoMapCollector; import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple; import com.android.tools.r8.jar.CfApplicationWriter; -import com.android.tools.r8.kotlin.Kotlin; -import com.android.tools.r8.kotlin.KotlinInfo; -import com.android.tools.r8.kotlin.KotlinMemberInfo; +import com.android.tools.r8.kotlin.KotlinInfoCollector; import com.android.tools.r8.logging.Log; import com.android.tools.r8.naming.ClassNameMapper; import com.android.tools.r8.naming.Minifier; @@ -66,6 +66,7 @@ import com.android.tools.r8.shaking.AbstractMethodRemover; import com.android.tools.r8.shaking.AnnotationRemover; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.shaking.ClassInitFieldSynthesizer; import com.android.tools.r8.shaking.DefaultTreePrunerConfiguration; import com.android.tools.r8.shaking.DiscardedChecker; import com.android.tools.r8.shaking.Enqueuer; @@ -91,7 +92,6 @@ import com.android.tools.r8.utils.ExceptionUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.LineNumberOptimizer; -import com.android.tools.r8.utils.Reporter; import com.android.tools.r8.utils.SelfRetraceTest; import com.android.tools.r8.utils.StringDiagnostic; import com.android.tools.r8.utils.ThreadUtils; @@ -155,7 +155,6 @@ System.gc(); } timing = Timing.create("R8", options); - options.itemFactory.resetSortedIndices(); } /** @@ -197,10 +196,12 @@ DexApplication application, AppView<?> appView, GraphLense graphLense, + InitClassLens initClassLens, NamingLens namingLens, InternalOptions options, ProguardMapSupplier proguardMapSupplier) throws ExecutionException { + InspectorImpl.runInspections(options.outputInspections, application); try { Marker marker = options.getMarker(Tool.R8); assert marker != null; @@ -215,6 +216,7 @@ options, Collections.singletonList(marker), graphLense, + initClassLens, namingLens, proguardMapSupplier) .write(executorService); @@ -309,7 +311,8 @@ // Compute kotlin info before setting the roots and before // kotlin metadata annotation is removed. - computeKotlinInfoForProgramClasses(application, appView, executorService); + KotlinInfoCollector.computeKotlinInfoForProgramClasses( + application, appView, executorService); // Add synthesized -assumenosideeffects from min api if relevant. if (options.isGeneratingDex()) { @@ -613,9 +616,7 @@ appView.setAppInfo(new AppInfoWithSubtyping(application)); - if (options.isShrinking() - || options.isMinifying() - || options.getProguardConfiguration().hasApplyMappingFile()) { + if (options.shouldRerunEnqueuer()) { timing.begin("Post optimization code stripping"); try { GraphConsumer keptGraphConsumer = null; @@ -699,6 +700,9 @@ // Remove types that no longer exists from the computed main dex list. mainDexClasses = mainDexClasses.prunedCopy(appView.appInfo().withLiveness()); } + + // Synthesize fields for triggering class initializers. + new ClassInitFieldSynthesizer(appViewWithLiveness).run(executorService); } } finally { timing.end(); @@ -819,6 +823,7 @@ application, appView, appView.graphLense(), + appView.initClassLens(), PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView, namingLens), options, proguardMapSupplier); @@ -913,24 +918,6 @@ } } - private void computeKotlinInfoForProgramClasses( - DexApplication application, AppView<?> appView, ExecutorService executorService) - throws ExecutionException { - if (appView.options().kotlinOptimizationOptions().disableKotlinSpecificOptimizations) { - return; - } - Kotlin kotlin = appView.dexItemFactory().kotlin; - Reporter reporter = options.reporter; - ThreadUtils.processItems( - application.classes(), - programClass -> { - KotlinInfo kotlinInfo = kotlin.getKotlinInfo(programClass, reporter); - programClass.setKotlinInfo(kotlinInfo); - KotlinMemberInfo.markKotlinMemberInfo(programClass, kotlinInfo, reporter); - }, - executorService); - } - private static boolean verifyNoJarApplicationReaders(List<DexProgramClass> classes) { for (DexProgramClass clazz : classes) { for (DexEncodedMethod method : clazz.methods()) {
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java index 4051f9c..611bc7a 100644 --- a/src/main/java/com/android/tools/r8/R8Command.java +++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -9,6 +9,8 @@ import com.android.tools.r8.experimental.graphinfo.GraphConsumer; import com.android.tools.r8.features.FeatureSplitConfiguration; import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.inspector.Inspector; +import com.android.tools.r8.inspector.internal.InspectorImpl; import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.origin.PathOrigin; @@ -29,6 +31,7 @@ import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization; import com.android.tools.r8.utils.Reporter; import com.android.tools.r8.utils.StringDiagnostic; +import com.android.tools.r8.utils.ThreadUtils; import com.google.common.collect.ImmutableList; import java.io.InputStream; import java.nio.file.Path; @@ -567,7 +570,9 @@ desugaredLibraryKeepRuleConsumer, libraryConfiguration, featureSplitConfiguration, - getAssertionsConfiguration()); + getAssertionsConfiguration(), + getOutputInspections(), + getThreadCount()); return command; } @@ -724,7 +729,9 @@ StringConsumer desugaredLibraryKeepRuleConsumer, DesugaredLibraryConfiguration libraryConfiguration, FeatureSplitConfiguration featureSplitConfiguration, - List<AssertionsConfiguration> assertionsConfiguration) { + List<AssertionsConfiguration> assertionsConfiguration, + List<Consumer<Inspector>> outputInspections, + int threadCount) { super( inputApp, mode, @@ -736,7 +743,9 @@ optimizeMultidexForLinearAlloc, encodeChecksum, dexClassChecksumFilter, - assertionsConfiguration); + assertionsConfiguration, + outputInspections, + threadCount); assert proguardConfiguration != null; assert mainDexKeepRules != null; this.mainDexKeepRules = mainDexKeepRules; @@ -874,6 +883,8 @@ internal.syntheticProguardRulesConsumer = syntheticProguardRulesConsumer; + internal.outputInspections = InspectorImpl.wrapInspections(getOutputInspections()); + // Default is to remove all javac generated assertion code when generating dex. assert internal.assertionsConfiguration == null; internal.assertionsConfiguration = @@ -903,6 +914,9 @@ internal.desugaredLibraryConfiguration = libraryConfiguration; internal.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer; + assert internal.threadCount == ThreadUtils.NOT_SPECIFIED; + internal.threadCount = getThreadCount(); + return internal; }
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java index 22ebb5e..84aa83e 100644 --- a/src/main/java/com/android/tools/r8/R8CommandParser.java +++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -21,13 +21,14 @@ "--output", "--lib", "--classpath", - "--min-api", + MIN_API_FLAG, "--main-dex-rules", "--main-dex-list", "--main-dex-list-output", "--pg-conf", "--pg-map-output", - "--desugared-lib"); + "--desugared-lib", + THREAD_COUNT_FLAG); public static void main(String[] args) throws CompilationFailedException { R8Command command = parse(args, Origin.root()).build(); @@ -64,7 +65,10 @@ " # <file> must be an existing directory or a zip file.", " --lib <file|jdk-home> # Add <file|jdk-home> as a library resource.", " --classpath <file> # Add <file> as a classpath resource.", - " --min-api <number> # Minimum Android API level compatibility, default: " + " " + + MIN_API_FLAG + + " <number> " + + "# Minimum Android API level compatibility, default: " + AndroidApiLevel.getDefault().getLevel() + ".", " --pg-conf <file> # Proguard configuration <file>.", @@ -190,13 +194,18 @@ addLibraryArgument(builder, argsOrigin, nextArg); } else if (arg.equals("--classpath")) { builder.addClasspathFiles(Paths.get(nextArg)); - } else if (arg.equals("--min-api")) { + } else if (arg.equals(MIN_API_FLAG)) { if (state.hasDefinedApiLevel) { - builder.error(new StringDiagnostic("Cannot set multiple --min-api options", argsOrigin)); + builder.error( + new StringDiagnostic("Cannot set multiple " + MIN_API_FLAG + " options", argsOrigin)); } else { - parseMinApi(builder, nextArg, argsOrigin); + parsePositiveIntArgument( + builder, MIN_API_FLAG, nextArg, argsOrigin, builder::setMinApiLevel); state.hasDefinedApiLevel = true; } + } else if (arg.equals(THREAD_COUNT_FLAG)) { + parsePositiveIntArgument( + builder, THREAD_COUNT_FLAG, nextArg, argsOrigin, builder::setThreadCount); } else if (arg.equals("--no-tree-shaking")) { builder.setDisableTreeShaking(true); } else if (arg.equals("--no-minification")) {
diff --git a/src/main/java/com/android/tools/r8/bisect/Bisect.java b/src/main/java/com/android/tools/r8/bisect/Bisect.java index 1d09319..0e86dc7 100644 --- a/src/main/java/com/android/tools/r8/bisect/Bisect.java +++ b/src/main/java/com/android/tools/r8/bisect/Bisect.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.errors.CompilationError; import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.AndroidAppConsumers; @@ -87,7 +88,6 @@ return null; } state.setPreviousResult(command.apply(app)); - app.options.itemFactory.resetSortedIndices(); } } @@ -186,7 +186,15 @@ StringConsumer proguardMapConsumer = options.proguardMapConsumer; AndroidAppConsumers compatSink = new AndroidAppConsumers(options); ApplicationWriter writer = - new ApplicationWriter(app, null, options, null, null, NamingLens.getIdentityLens(), null); + new ApplicationWriter( + app, + null, + options, + null, + null, + InitClassLens.getDefault(), + NamingLens.getIdentityLens(), + null); writer.write(executor); options.signalFinishedToConsumers(); compatSink.build().writeToDirectory(output, OutputMode.DexIndexed);
diff --git a/src/main/java/com/android/tools/r8/bisect/BisectState.java b/src/main/java/com/android/tools/r8/bisect/BisectState.java index 5500637..cd3fe0a 100644 --- a/src/main/java/com/android/tools/r8/bisect/BisectState.java +++ b/src/main/java/com/android/tools/r8/bisect/BisectState.java
@@ -21,7 +21,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Comparator; import java.util.List; import java.util.Map; @@ -324,9 +323,7 @@ private static List<DexProgramClass> getSortedClasses(DexApplication app) { List<DexProgramClass> classes = new ArrayList<>(app.classes()); - app.dexItemFactory.sort(NamingLens.getIdentityLens()); - classes.sort(Comparator.comparing(DexProgramClass::getType)); - app.dexItemFactory.resetSortedIndices(); + classes.sort((a, b) -> a.type.slowCompareTo(b.type, NamingLens.getIdentityLens())); return classes; }
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java index 9eedd04..ccd1686 100644 --- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java +++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -23,6 +23,7 @@ import com.android.tools.r8.cf.code.CfIf; import com.android.tools.r8.cf.code.CfIfCmp; import com.android.tools.r8.cf.code.CfIinc; +import com.android.tools.r8.cf.code.CfInitClass; import com.android.tools.r8.cf.code.CfInstanceOf; import com.android.tools.r8.cf.code.CfInstruction; import com.android.tools.r8.cf.code.CfInvoke; @@ -321,6 +322,12 @@ appendType(constClass.getType()); } + public void print(CfInitClass initClass) { + indent(); + builder.append("initclass "); + appendType(initClass.getClassValue()); + } + public void print(CfReturnVoid ret) { print("return"); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java index fc2a080..e9997b0 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.ir.code.NumericType; import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.ir.conversion.CfSourceCode; @@ -128,7 +129,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitInsn(getAsmOpcode()); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java index 53536d2..85c4773 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
@@ -5,6 +5,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; import com.android.tools.r8.ir.conversion.CfState.Slot; @@ -18,7 +19,7 @@ public class CfArrayLength extends CfInstruction { @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitInsn(Opcodes.ARRAYLENGTH); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java index 1f667b4..010bcfd 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.ir.code.MemberType; import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.ir.conversion.CfSourceCode; @@ -55,7 +56,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitInsn(getLoadType()); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java index d0eadef..52df928 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.ir.code.MemberType; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; @@ -53,7 +54,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitInsn(getStoreType()); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java index e89c480..19ca32e 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
@@ -5,6 +5,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.graph.UseRegistry; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; @@ -29,7 +30,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitTypeInsn(Opcodes.CHECKCAST, lens.lookupInternalName(type)); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java index dac3eca..259bfe3 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfCmp.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.ir.code.Cmp; import com.android.tools.r8.ir.code.Cmp.Bias; import com.android.tools.r8.ir.code.NumericType; @@ -78,7 +79,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitInsn(getAsmOpcode()); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java index 7f77ade..a203742 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.graph.UseRegistry; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; @@ -29,7 +30,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitLdcInsn(Type.getObjectType(getInternalName(lens))); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java index a9a1d73..04e569b 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.graph.DexMethodHandle; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.graph.UseRegistry; import com.android.tools.r8.graph.UseRegistry.MethodHandleUse; import com.android.tools.r8.ir.conversion.CfSourceCode; @@ -29,7 +30,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitLdcInsn(handle.toAsmHandle(lens)); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java index 063b6fa..a67c3ed 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.graph.DexProto; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.graph.UseRegistry; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; @@ -29,7 +30,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitLdcInsn(Type.getType(type.toDescriptorString(lens))); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java index 7ad901a..c3be968 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
@@ -5,6 +5,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; @@ -18,7 +19,7 @@ public class CfConstNull extends CfInstruction { @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitInsn(Opcodes.ACONST_NULL); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java index 72e4638..8977303 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; @@ -55,7 +56,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { switch (type) { case INT: {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java index d52d901..08ee865 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; import com.android.tools.r8.ir.conversion.IRBuilder; @@ -41,7 +42,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitLdcInsn(string.toString()); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java index 36ba9b4..fbc4939 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
@@ -7,6 +7,7 @@ import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.DexReference; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.graph.UseRegistry; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; @@ -46,7 +47,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { throw new Unreachable( "CfDexItemBasedConstString instructions should always be rewritten into CfConstString"); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java index a000dcf..5812288 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
@@ -7,6 +7,7 @@ import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.graph.UseRegistry; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; @@ -50,7 +51,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { String owner = lens.lookupInternalName(field.holder); String name = lens.lookupName(declaringField).toString(); String desc = lens.lookupDescriptor(field.type).toString();
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java index e5a3c19..88d5ece 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -9,6 +9,7 @@ import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; import com.android.tools.r8.ir.conversion.IRBuilder; @@ -211,7 +212,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { int stackCount = computeStackCount(); Object[] stackTypes = computeStackTypes(stackCount, lens); int localsCount = computeLocalsCount();
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java index efe67ba..412c97c7 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
@@ -5,6 +5,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; import com.android.tools.r8.ir.conversion.IRBuilder; @@ -43,7 +44,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitJumpInsn(Opcodes.GOTO, target.getLabel()); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIf.java b/src/main/java/com/android/tools/r8/cf/code/CfIf.java index 1ba9c06..ebc6a98 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfIf.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.ir.code.If; import com.android.tools.r8.ir.code.If.Type; import com.android.tools.r8.ir.code.ValueType; @@ -68,7 +69,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitJumpInsn(getOpcode(), target.getLabel()); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java index 28a59d7..c2f34f7 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.ir.code.If; import com.android.tools.r8.ir.code.If.Type; import com.android.tools.r8.ir.code.ValueType; @@ -68,7 +69,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitJumpInsn(getOpcode(), target.getLabel()); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIinc.java b/src/main/java/com/android/tools/r8/cf/code/CfIinc.java index 92f2094..986d11f 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfIinc.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
@@ -5,6 +5,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.ir.code.NumericType; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; @@ -25,7 +26,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitIincInsn(var, increment); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java new file mode 100644 index 0000000..a355f58 --- /dev/null +++ b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
@@ -0,0 +1,72 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.cf.code; + +import com.android.tools.r8.cf.CfPrinter; +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; +import com.android.tools.r8.graph.UseRegistry; +import com.android.tools.r8.ir.conversion.CfSourceCode; +import com.android.tools.r8.ir.conversion.CfState; +import com.android.tools.r8.ir.conversion.IRBuilder; +import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; +import com.android.tools.r8.ir.optimize.InliningConstraints; +import com.android.tools.r8.naming.NamingLens; +import org.objectweb.asm.MethodVisitor; + +public class CfInitClass extends CfInstruction { + + private static final int OPCODE = org.objectweb.asm.Opcodes.GETSTATIC; + + private final DexType clazz; + + public CfInitClass(DexType clazz) { + this.clazz = clazz; + } + + public DexType getClassValue() { + return clazz; + } + + public int getOpcode() { + return OPCODE; + } + + @Override + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { + DexField field = initClassLens.getInitClassField(clazz); + String owner = lens.lookupInternalName(field.holder); + String name = lens.lookupName(field).toString(); + String desc = lens.lookupDescriptor(field.type).toString(); + visitor.visitFieldInsn(OPCODE, owner, name, desc); + } + + @Override + public void print(CfPrinter printer) { + printer.print(this); + } + + @Override + public void registerUse(UseRegistry registry, DexType context) { + registry.registerInitClass(clazz); + } + + @Override + public boolean canThrow() { + return true; + } + + @Override + public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) { + int dest = state.push(builder.appView.dexItemFactory().intType).register; + builder.addInitClass(dest, clazz); + } + + @Override + public ConstraintWithTarget inliningConstraint( + InliningConstraints inliningConstraints, DexType context) { + return inliningConstraints.forInitClass(clazz, context); + } +}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java index d4d2066..5d0997f 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
@@ -5,6 +5,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.graph.UseRegistry; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; @@ -38,7 +39,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitTypeInsn(Opcodes.INSTANCEOF, lens.lookupInternalName(type)); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java index 43d209e..d3cc826 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -5,6 +5,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.graph.UseRegistry; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; @@ -16,7 +17,7 @@ public abstract class CfInstruction { - public abstract void write(MethodVisitor visitor, NamingLens lens); + public abstract void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens); public abstract void print(CfPrinter printer);
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 6311bfd..1a5dd5c 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
@@ -16,6 +16,7 @@ import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.GraphLense; import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.graph.UseRegistry; import com.android.tools.r8.ir.code.Invoke; import com.android.tools.r8.ir.code.Invoke.Type; @@ -69,7 +70,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { String owner = lens.lookupInternalName(method.holder); String name = lens.lookupName(method).toString(); String desc = method.proto.toDescriptorString(lens);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java index 231c115..ef0ec1b 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.graph.DexValue.DexValueMethodType; import com.android.tools.r8.graph.DexValue.DexValueString; import com.android.tools.r8.graph.DexValue.DexValueType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.graph.UseRegistry; import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.ir.conversion.CfSourceCode; @@ -41,7 +42,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { DexMethodHandle bootstrapMethod = callSite.bootstrapMethod; List<DexValue> bootstrapArgs = callSite.bootstrapArgs; Object[] bsmArgs = new Object[bootstrapArgs.size()];
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java b/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java index 55aea68..7e678dc 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.errors.CompilationError; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; import com.android.tools.r8.ir.conversion.IRBuilder; @@ -28,7 +29,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { throw error(); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java index df9d6bd..3901a1a 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
@@ -5,6 +5,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; import com.android.tools.r8.ir.conversion.IRBuilder; @@ -41,7 +42,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitLabel(getLabel()); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java index ae1fd85..80d6a8e 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; @@ -55,7 +56,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitVarInsn(getLoadType(), var); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java index 1d03017..c2775a0 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.ir.code.NumericType; import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.ir.conversion.CfSourceCode; @@ -103,7 +104,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitInsn(getAsmOpcode()); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java index dc117dd..6386b3c 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
@@ -5,6 +5,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.ir.code.Monitor.Type; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; @@ -29,7 +30,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitInsn(type == Type.ENTER ? Opcodes.MONITORENTER : Opcodes.MONITOREXIT); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java index 936f62d..d12d76b 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
@@ -5,6 +5,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.graph.UseRegistry; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; @@ -34,7 +35,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitMultiANewArrayInsn(lens.lookupInternalName(type), dimensions); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNeg.java b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java index 254ee77..02b2a83 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfNeg.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.ir.code.NumericType; import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.ir.conversion.CfSourceCode; @@ -30,7 +31,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitInsn(getAsmOpcode()); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNew.java b/src/main/java/com/android/tools/r8/cf/code/CfNew.java index 46821da..be087af 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfNew.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
@@ -5,6 +5,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.graph.UseRegistry; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; @@ -28,7 +29,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitTypeInsn(Opcodes.NEW, lens.lookupInternalName(type)); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java index 10504d9..1ce6048 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.graph.UseRegistry; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; @@ -63,7 +64,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { if (type.isPrimitiveArrayType()) { visitor.visitIntInsn(Opcodes.NEWARRAY, getPrimitiveTypeCode()); } else {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNop.java b/src/main/java/com/android/tools/r8/cf/code/CfNop.java index e124a0b..be9170c 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfNop.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
@@ -5,6 +5,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; import com.android.tools.r8.ir.conversion.IRBuilder; @@ -17,7 +18,7 @@ public class CfNop extends CfInstruction { @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitInsn(Opcodes.NOP); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java index c1118e5..dbedc8a 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.ir.code.NumericType; import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.ir.conversion.CfSourceCode; @@ -40,7 +41,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitInsn(this.getAsmOpcode()); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java index 035604b..2dac0c6 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
@@ -5,6 +5,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.ir.code.Position; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; @@ -25,7 +26,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitLineNumber(position.line, label.getLabel()); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java index 76475fa..e1b6de1 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; @@ -52,7 +53,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitInsn(getOpcode()); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java b/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java index fd55aa0..0bbe1cd 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
@@ -5,6 +5,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; import com.android.tools.r8.ir.conversion.IRBuilder; @@ -22,7 +23,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitInsn(Opcodes.RETURN); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java index 5886058..5821c3f 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
@@ -7,6 +7,7 @@ import com.android.tools.r8.errors.CompilationError; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; @@ -74,7 +75,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitInsn(opcode.opcode); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStore.java b/src/main/java/com/android/tools/r8/cf/code/CfStore.java index 5f8a05d..d560b82 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfStore.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; @@ -55,7 +56,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitVarInsn(getStoreType(), var); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java index 47aff14..bfbe30f 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
@@ -5,6 +5,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; import com.android.tools.r8.ir.conversion.CfState.Slot; @@ -68,7 +69,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { Label[] labels = new Label[targets.size()]; for (int i = 0; i < targets.size(); i++) { labels[i] = targets.get(i).getLabel();
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java index a7609c0..c0c094d 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
@@ -5,6 +5,7 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.ir.conversion.CfSourceCode; import com.android.tools.r8.ir.conversion.CfState; import com.android.tools.r8.ir.conversion.CfState.Slot; @@ -23,7 +24,7 @@ } @Override - public void write(MethodVisitor visitor, NamingLens lens) { + public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) { visitor.visitInsn(Opcodes.ATHROW); }
diff --git a/src/main/java/com/android/tools/r8/code/DexInitClass.java b/src/main/java/com/android/tools/r8/code/DexInitClass.java new file mode 100644 index 0000000..9df8285 --- /dev/null +++ b/src/main/java/com/android/tools/r8/code/DexInitClass.java
@@ -0,0 +1,131 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.code; + +import com.android.tools.r8.dex.IndexedItemCollection; +import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.ObjectToOffsetMapping; +import com.android.tools.r8.graph.UseRegistry; +import com.android.tools.r8.ir.code.FieldMemberType; +import com.android.tools.r8.ir.conversion.IRBuilder; +import com.android.tools.r8.naming.ClassNameMapper; +import java.nio.ShortBuffer; + +public class DexInitClass extends Base2Format { + + public static final int OPCODE = 0x60; + public static final String NAME = "InitClass"; + public static final String SMALI_NAME = "initclass"; + + private final int dest; + private final DexType clazz; + + public DexInitClass(int dest, DexType clazz) { + assert clazz.isClassType(); + this.dest = dest; + this.clazz = clazz; + } + + @Override + public void buildIR(IRBuilder builder) { + builder.addInitClass(dest, clazz); + } + + @Override + public void collectIndexedItems( + IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) { + DexField field = indexedItems.getInitClassLens().getInitClassField(clazz); + field.collectIndexedItems(indexedItems, method, instructionOffset); + } + + @Override + public boolean canThrow() { + return true; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public String getSmaliName() { + return SMALI_NAME; + } + + @Override + public int getOpcode() { + throw new Unreachable(); + } + + private int getOpcode(DexField field) { + FieldMemberType type = FieldMemberType.fromDexType(field.type); + switch (type) { + case INT: + case FLOAT: + return Sget.OPCODE; + case LONG: + case DOUBLE: + return SgetWide.OPCODE; + case OBJECT: + return SgetObject.OPCODE; + case BOOLEAN: + return SgetBoolean.OPCODE; + case BYTE: + return SgetByte.OPCODE; + case CHAR: + return SgetChar.OPCODE; + case SHORT: + return SgetShort.OPCODE; + default: + throw new Unreachable("Unexpected type: " + type); + } + } + + @Override + public void registerUse(UseRegistry registry) { + registry.registerInitClass(clazz); + } + + @Override + public void write(ShortBuffer buffer, ObjectToOffsetMapping mapping) { + DexField field = mapping.getClinitField(clazz); + writeFirst(dest, buffer, getOpcode(field)); + write16BitReference(field, buffer, mapping); + } + + @Override + public int hashCode() { + return ((clazz.hashCode() << 8) | dest) ^ getClass().hashCode(); + } + + @Override + public boolean equals(Object other) { + if (other == null || getClass() != other.getClass()) { + return false; + } + DexInitClass initClass = (DexInitClass) other; + return dest == initClass.dest && clazz == initClass.clazz; + } + + @Override + public String toSmaliString(ClassNameMapper naming) { + return formatSmaliString("v" + dest + ", " + clazz.toSmaliString()); + } + + @Override + public String toString(ClassNameMapper naming) { + StringBuilder builder = new StringBuilder("v").append(dest).append(", "); + if (naming == null) { + builder.append(clazz.toSourceString()); + } else { + builder.append(naming.originalNameOf(clazz)); + } + return formatString(builder.toString()); + } +}
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java index d549e28..ea056cb 100644 --- a/src/main/java/com/android/tools/r8/code/Instruction.java +++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -82,7 +82,11 @@ } protected void writeFirst(int aa, ShortBuffer dest) { - dest.put((short) (((aa & 0xff) << 8) | (getOpcode() & 0xff))); + writeFirst(aa, dest, getOpcode()); + } + + protected void writeFirst(int aa, ShortBuffer dest, int opcode) { + dest.put((short) (((aa & 0xff) << 8) | (opcode & 0xff))); } protected void writeFirst(int a, int b, ShortBuffer dest) {
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java index 32a4bf7..c4c318c 100644 --- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java +++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -34,6 +34,7 @@ import com.android.tools.r8.graph.DexValue; import com.android.tools.r8.graph.EnclosingMethodAttribute; import com.android.tools.r8.graph.GraphLense; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.graph.InnerClassAttribute; import com.android.tools.r8.graph.ObjectToOffsetMapping; import com.android.tools.r8.graph.ParameterAnnotationsList; @@ -69,6 +70,7 @@ public final DexApplication application; public final AppView<?> appView; public final GraphLense graphLense; + public final InitClassLens initClassLens; public final NamingLens namingLens; public final InternalOptions options; private final CodeToKeep desugaredLibraryCodeToKeep; @@ -80,10 +82,16 @@ private static class SortAnnotations extends MixedSectionCollection { + private final NamingLens namingLens; + + public SortAnnotations(NamingLens namingLens) { + this.namingLens = namingLens; + } + @Override public boolean add(DexAnnotationSet dexAnnotationSet) { // Annotation sets are sorted by annotation types. - dexAnnotationSet.sort(); + dexAnnotationSet.sort(namingLens); return true; } @@ -141,6 +149,7 @@ InternalOptions options, List<Marker> markers, GraphLense graphLense, + InitClassLens initClassLens, NamingLens namingLens, ProguardMapSupplier proguardMapSupplier) { this( @@ -149,6 +158,7 @@ options, markers, graphLense, + initClassLens, namingLens, proguardMapSupplier, null); @@ -160,6 +170,7 @@ InternalOptions options, List<Marker> markers, GraphLense graphLense, + InitClassLens initClassLens, NamingLens namingLens, ProguardMapSupplier proguardMapSupplier, DexIndexedConsumer consumer) { @@ -171,6 +182,7 @@ this.desugaredLibraryCodeToKeep = CodeToKeep.createCodeToKeep(options, namingLens); this.markers = markers; this.graphLense = graphLense; + this.initClassLens = initClassLens; this.namingLens = namingLens; this.proguardMapSupplier = proguardMapSupplier; this.programConsumer = consumer; @@ -238,6 +250,7 @@ } } try { + // TODO(b/151313715): Move this to the writer threads. insertAttributeAnnotations(); // Generate the dex file contents. @@ -246,21 +259,12 @@ if (options.encodeChecksums) { encodeChecksums(virtualFiles); } - // TODO(b/149190785): Only sort the live program! - if (appView != null) { - appView.appInfo().disableDefinitionForAssert(); - } - namingLens.setIsSortingBeforeWriting(true); - application.dexItemFactory.sort(namingLens); - namingLens.setIsSortingBeforeWriting(false); - if (appView != null) { - appView.appInfo().enableDefinitionForAssert(); - } assert markers == null || markers.isEmpty() || application.dexItemFactory.extractMarkers() != null; - SortAnnotations sortAnnotations = new SortAnnotations(); + // TODO(b/151313617): Sorting annotations mutates elements so run single threaded on main. + SortAnnotations sortAnnotations = new SortAnnotations(namingLens); application.classes().forEach((clazz) -> clazz.addDependencies(sortAnnotations)); for (VirtualFile virtualFile : virtualFiles) { @@ -290,7 +294,8 @@ byteBufferProvider = options.getDexIndexedConsumer(); } } - ObjectToOffsetMapping objectMapping = virtualFile.computeMapping(application); + ObjectToOffsetMapping objectMapping = + virtualFile.computeMapping(application, namingLens, initClassLens); MethodToCodeObjectMapping codeMapping = rewriteCodeWithJumboStrings( objectMapping, virtualFile.classes(), application);
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java index 8b0cd00..a7c3ec0 100644 --- a/src/main/java/com/android/tools/r8/dex/FileWriter.java +++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -21,6 +21,7 @@ import com.android.tools.r8.graph.DexCode.TryHandler; import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair; import com.android.tools.r8.graph.DexDebugInfo; +import com.android.tools.r8.graph.DexDebugInfoForWriting; import com.android.tools.r8.graph.DexEncodedAnnotation; import com.android.tools.r8.graph.DexEncodedArray; import com.android.tools.r8.graph.DexEncodedField; @@ -41,7 +42,6 @@ import com.android.tools.r8.graph.IndexedDexItem; import com.android.tools.r8.graph.ObjectToOffsetMapping; import com.android.tools.r8.graph.ParameterAnnotationsList; -import com.android.tools.r8.graph.PresortedComparable; import com.android.tools.r8.graph.ProgramClassVisitor; import com.android.tools.r8.logging.Log; import com.android.tools.r8.naming.ClassNameMapper; @@ -59,10 +59,12 @@ import it.unimi.dsi.fastutil.objects.Reference2IntMap; import java.security.MessageDigest; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; @@ -94,6 +96,7 @@ private final DexOutputBuffer dest; private final MixedSectionOffsets mixedSectionOffsets; private final CodeToKeep desugaredLibraryCodeToKeep; + private final Map<DexProgramClass, DexEncodedArray> staticFieldValues = new IdentityHashMap<>(); public FileWriter( ByteBufferProvider provider, @@ -118,10 +121,11 @@ if (Log.ENABLED) { Log.verbose(FileWriter.class, "Writing encoded annotation @ %08x", dest.position()); } + List<DexAnnotationElement> elements = new ArrayList<>(Arrays.asList(annotation.elements)); + elements.sort((a, b) -> a.name.slowCompareTo(b.name, mapping.getNamingLens())); dest.putUleb128(mapping.getOffsetFor(annotation.type)); - dest.putUleb128(annotation.elements.length); - assert PresortedComparable.isSorted(annotation.elements, (element) -> element.name); - for (DexAnnotationElement element : annotation.elements) { + dest.putUleb128(elements.size()); + for (DexAnnotationElement element : elements) { dest.putUleb128(mapping.getOffsetFor(element.name)); element.value.writeTo(dest, mapping); } @@ -132,8 +136,6 @@ new ProgramClassDependencyCollector(application, mapping.getClasses()) .run(mapping.getClasses()); - // Ensure everything is sorted. - assert mixedSectionOffsets.getClassesWithData().stream().allMatch(DexProgramClass::isSorted); // Add the static values for all fields now that we have committed to their sorting. mixedSectionOffsets.getClassesWithData().forEach(this::addStaticFieldValues); @@ -172,11 +174,22 @@ // Output the debug_info_items first, as they have no dependencies. dest.moveTo(layout.getCodesOffset() + sizeOfCodeItems(codes)); - writeItems(mixedSectionOffsets.getDebugInfos(), layout::setDebugInfosOffset, - this::writeDebugItem); + if (mixedSectionOffsets.getDebugInfos().isEmpty()) { + layout.setDebugInfosOffset(0); + } else { + // Ensure deterministic ordering of debug info by sorting consistent with the code objects. + layout.setDebugInfosOffset(dest.align(1)); + Set<DexDebugInfo> seen = new HashSet<>(mixedSectionOffsets.getDebugInfos().size()); + for (DexCode code : codes) { + DexDebugInfoForWriting info = code.getDebugInfoForWriting(); + if (info != null && seen.add(info)) { + writeDebugItem(info); + } + } + } // Remember the typelist offset for later. - layout.setTypeListsOffset(dest.align(4)); // type_list are aligned. + layout.setTypeListsOffset(dest.align(4)); // type_list are aligned. // Now output the code. dest.moveTo(layout.getCodesOffset()); @@ -465,7 +478,7 @@ dest.putInt(mixedSectionOffsets.getOffsetForAnnotationsDirectory(clazz)); dest.putInt( clazz.hasMethodsOrFields() ? mixedSectionOffsets.getOffsetFor(clazz) : Constants.NO_OFFSET); - dest.putInt(mixedSectionOffsets.getOffsetFor(clazz.getStaticValues())); + dest.putInt(mixedSectionOffsets.getOffsetFor(staticFieldValues.get(clazz))); } private void writeDebugItem(DexDebugInfo debugInfo) { @@ -551,14 +564,14 @@ } private void writeAnnotationSet(DexAnnotationSet set) { - assert PresortedComparable.isSorted(set.annotations, (item) -> item.annotation.type) - : "Unsorted annotation set: " + set.toSourceString(); mixedSectionOffsets.setOffsetFor(set, dest.align(4)); if (Log.ENABLED) { Log.verbose(getClass(), "Writing AnnotationSet @ 0x%08x.", dest.position()); } - dest.putInt(set.annotations.length); - for (DexAnnotation annotation : set.annotations) { + List<DexAnnotation> annotations = new ArrayList<>(Arrays.asList(set.annotations)); + annotations.sort((a, b) -> a.annotation.type.slowCompareTo(b.annotation.type, namingLens)); + dest.putInt(annotations.size()); + for (DexAnnotation annotation : annotations) { dest.putInt(mixedSectionOffsets.getOffsetFor(annotation)); } } @@ -588,9 +601,11 @@ private void writeAnnotationDirectory(DexAnnotationDirectory annotationDirectory) { mixedSectionOffsets.setOffsetForAnnotationsDirectory(annotationDirectory, dest.align(4)); dest.putInt(mixedSectionOffsets.getOffsetFor(annotationDirectory.getClazzAnnotations())); - List<DexEncodedMethod> methodAnnotations = annotationDirectory.getMethodAnnotations(); - List<DexEncodedMethod> parameterAnnotations = annotationDirectory.getParameterAnnotations(); - List<DexEncodedField> fieldAnnotations = annotationDirectory.getFieldAnnotations(); + List<DexEncodedMethod> methodAnnotations = + annotationDirectory.sortMethodAnnotations(namingLens); + List<DexEncodedMethod> parameterAnnotations = + annotationDirectory.sortParameterAnnotations(namingLens); + List<DexEncodedField> fieldAnnotations = annotationDirectory.sortFieldAnnotations(namingLens); dest.putInt(fieldAnnotations.size()); dest.putInt(methodAnnotations.size()); dest.putInt(parameterAnnotations.size()); @@ -602,10 +617,12 @@ item -> mixedSectionOffsets.getOffsetFor(item.parameterAnnotationsList)); } - private void writeEncodedFields(List<DexEncodedField> fields) { - assert PresortedComparable.isSorted(fields); + private void writeEncodedFields(List<DexEncodedField> unsortedFields) { + List<DexEncodedField> fields = new ArrayList<>(unsortedFields); + fields.sort((a, b) -> a.field.slowCompareTo(b.field, namingLens)); int currentOffset = 0; for (DexEncodedField field : fields) { + assert field.validateDexValue(application.dexItemFactory); int nextOffset = mapping.getOffsetFor(field.field); assert nextOffset - currentOffset >= 0; dest.putUleb128(nextOffset - currentOffset); @@ -615,8 +632,10 @@ } } - private void writeEncodedMethods(List<DexEncodedMethod> methods, boolean isSharedSynthetic) { - assert PresortedComparable.isSorted(methods); + private void writeEncodedMethods( + List<DexEncodedMethod> unsortedMethods, boolean isSharedSynthetic) { + List<DexEncodedMethod> methods = new ArrayList<>(unsortedMethods); + methods.sort((a, b) -> a.method.slowCompareTo(b.method, namingLens)); int currentOffset = 0; for (DexEncodedMethod method : methods) { int nextOffset = mapping.getOffsetFor(method.method); @@ -657,12 +676,12 @@ } private void addStaticFieldValues(DexProgramClass clazz) { - clazz.computeStaticValues(); // We have collected the individual components of this array due to the data stored in // DexEncodedField#staticValues. However, we have to collect the DexEncodedArray itself // here. - DexEncodedArray staticValues = clazz.getStaticValues(); + DexEncodedArray staticValues = clazz.computeStaticValuesArray(namingLens); if (staticValues != null) { + staticFieldValues.put(clazz, staticValues); mixedSectionOffsets.add(staticValues); } }
diff --git a/src/main/java/com/android/tools/r8/dex/IndexedItemCollection.java b/src/main/java/com/android/tools/r8/dex/IndexedItemCollection.java index 4da7302..45b8bfe 100644 --- a/src/main/java/com/android/tools/r8/dex/IndexedItemCollection.java +++ b/src/main/java/com/android/tools/r8/dex/IndexedItemCollection.java
@@ -13,6 +13,7 @@ import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.IndexedDexItem; +import com.android.tools.r8.graph.InitClassLens; /** * Common interface for constant pools. @@ -99,6 +100,10 @@ */ boolean addMethodHandle(DexMethodHandle methodHandle); + default InitClassLens getInitClassLens() { + return InitClassLens.getDefault(); + } + default DexString getRenamedName(DexMethod method) { return method.name; }
diff --git a/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java b/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java index 37649d3..e623f5e 100644 --- a/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java +++ b/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java
@@ -10,6 +10,7 @@ import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.utils.ThreadUtils; import com.google.common.collect.Maps; @@ -69,7 +70,7 @@ public void updateNumbersOfIds() { // Use a temporary VirtualFile to evaluate the number of ids in the group. - VirtualFile virtualFile = new VirtualFile(0, namingLens); + VirtualFile virtualFile = new VirtualFile(0, initClassLens, namingLens); // Note: sort not needed. for (DexProgramClass clazz : members) { virtualFile.addClass(clazz); @@ -283,6 +284,7 @@ private final Set<DexProgramClass> classes; private final DexApplication app; private int dexIndexOffset; + private final InitClassLens initClassLens; private final NamingLens namingLens; private final DirectSubClassesInfo directSubClasses; @@ -290,8 +292,8 @@ VirtualFile mainDex, List<VirtualFile> dexes, Set<DexProgramClass> classes, - Map<DexProgramClass, String> originalNames, int dexIndexOffset, + InitClassLens initClassLens, NamingLens namingLens, DexApplication app, ExecutorService executorService) { @@ -299,6 +301,7 @@ this.dexes = dexes; this.classes = classes; this.dexIndexOffset = dexIndexOffset; + this.initClassLens = initClassLens; this.namingLens = namingLens; this.app = app; this.executorService = executorService; @@ -370,7 +373,8 @@ } private Collection<VirtualFile> assignGroup(ClassGroup group, List<VirtualFile> dexBlackList) { - VirtualFileCycler cycler = new VirtualFileCycler(dexes, namingLens, dexIndexOffset); + VirtualFileCycler cycler = + new VirtualFileCycler(dexes, initClassLens, namingLens, dexIndexOffset); if (group.members.isEmpty()) { return Collections.emptyList(); } else if (group.canFitInOneDex()) { @@ -418,7 +422,8 @@ Collections.sort(layers); Collection<VirtualFile> usedDex = new ArrayList<>(); - VirtualFileCycler cycler = new VirtualFileCycler(dexes, namingLens, dexIndexOffset); + VirtualFileCycler cycler = + new VirtualFileCycler(dexes, initClassLens, namingLens, dexIndexOffset); // Don't modify input dexBlackList. Think about modifying the input collection considering this // is private API. Set<VirtualFile> currentBlackList = new HashSet<>(dexBlackList);
diff --git a/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java index 2936d80..e05af85 100644 --- a/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java +++ b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
@@ -284,7 +284,7 @@ instruction.setOffset(orignalOffset + offsetDelta); if (instruction instanceof ConstString) { ConstString string = (ConstString) instruction; - if (string.getString().compareTo(firstJumboString) >= 0) { + if (string.getString().slowCompareTo(firstJumboString) >= 0) { ConstStringJumbo jumboString = new ConstStringJumbo(string.AA, string.getString()); jumboString.setOffset(string.getOffset()); offsetDelta++;
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java index 02a6129..a9ff477 100644 --- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java +++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.graph.DexProto; import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.graph.ObjectToOffsetMapping; import com.android.tools.r8.logging.Log; import com.android.tools.r8.naming.ClassNameMapper; @@ -75,23 +76,29 @@ private final DexProgramClass primaryClass; - VirtualFile(int id, NamingLens namingLens) { - this(id, namingLens, null, null); + VirtualFile(int id, InitClassLens initClassLens, NamingLens namingLens) { + this(id, initClassLens, namingLens, null, null); } - VirtualFile(int id, NamingLens namingLens, FeatureSplit featureSplit) { - this(id, namingLens, null, featureSplit); - } - - private VirtualFile(int id, NamingLens namingLens, DexProgramClass primaryClass) { - this(id, namingLens, primaryClass, null); + VirtualFile( + int id, InitClassLens initClassLens, NamingLens namingLens, FeatureSplit featureSplit) { + this(id, initClassLens, namingLens, null, featureSplit); } private VirtualFile( - int id, NamingLens namingLens, DexProgramClass primaryClass, FeatureSplit featureSplit) { + int id, InitClassLens initClassLens, NamingLens namingLens, DexProgramClass primaryClass) { + this(id, initClassLens, namingLens, primaryClass, null); + } + + private VirtualFile( + int id, + InitClassLens initClassLens, + NamingLens namingLens, + DexProgramClass primaryClass, + FeatureSplit featureSplit) { this.id = id; - this.indexedItems = new VirtualFileIndexedItemCollection(namingLens); - this.transaction = new IndexedItemTransaction(indexedItems, namingLens); + this.indexedItems = new VirtualFileIndexedItemCollection(initClassLens, namingLens); + this.transaction = new IndexedItemTransaction(indexedItems, initClassLens, namingLens); this.primaryClass = primaryClass; this.featureSplit = featureSplit; } @@ -174,10 +181,13 @@ return prefix; } - public ObjectToOffsetMapping computeMapping(DexApplication application) { + public ObjectToOffsetMapping computeMapping( + DexApplication application, NamingLens namingLens, InitClassLens initClassLens) { assert transaction.isEmpty(); return new ObjectToOffsetMapping( application, + namingLens, + initClassLens, indexedItems.classes, indexedItems.protos, indexedItems.types, @@ -273,7 +283,8 @@ // Assign dedicated virtual files for all program classes. for (DexProgramClass clazz : application.classes()) { if (!combineSyntheticClassesWithPrimaryClass || clazz.getSynthesizedFrom().isEmpty()) { - VirtualFile file = new VirtualFile(virtualFiles.size(), writer.namingLens, clazz); + VirtualFile file = + new VirtualFile(virtualFiles.size(), writer.initClassLens, writer.namingLens, clazz); virtualFiles.add(file); file.addClass(clazz); files.put(clazz, file); @@ -306,7 +317,7 @@ this.options = options; // Create the primary dex file. The distribution will add more if needed. - mainDexFile = new VirtualFile(0, writer.namingLens); + mainDexFile = new VirtualFile(0, writer.initClassLens, writer.namingLens); assert virtualFiles.isEmpty(); virtualFiles.add(mainDexFile); addMarkers(mainDexFile); @@ -423,7 +434,8 @@ featureSplitClasses.entrySet()) { // Add a new virtual file, start from index 0 again VirtualFile featureFile = - new VirtualFile(0, writer.namingLens, featureSplitSetEntry.getKey()); + new VirtualFile( + 0, writer.initClassLens, writer.namingLens, featureSplitSetEntry.getKey()); virtualFiles.add(featureFile); addMarkers(featureFile); Set<DexProgramClass> featureClasses = @@ -437,6 +449,7 @@ application.dexItemFactory, fillStrategy, 0, + writer.initClassLens, writer.namingLens) .call(); } @@ -471,7 +484,7 @@ assert !virtualFiles.get(0).isEmpty(); assert virtualFiles.size() == 1; // The main dex file is filtered out, so ensure at least one file for the remaining classes. - virtualFiles.add(new VirtualFile(1, writer.namingLens)); + virtualFiles.add(new VirtualFile(1, writer.initClassLens, writer.namingLens)); filesForDistribution = virtualFiles.subList(1, virtualFiles.size()); fileIndexOffset = 1; } @@ -480,16 +493,29 @@ removeFeatureSplitClassesGetMapping(); if (multidexLegacy && options.enableInheritanceClassInDexDistributor) { - new InheritanceClassInDexDistributor(mainDexFile, filesForDistribution, classes, - originalNames, fileIndexOffset, writer.namingLens, writer.application, executorService) + new InheritanceClassInDexDistributor( + mainDexFile, + filesForDistribution, + classes, + fileIndexOffset, + writer.initClassLens, + writer.namingLens, + writer.application, + executorService) .distribute(); } else { // Sort the remaining classes based on the original names. // This with make classes from the same package be adjacent. classes = sortClassesByPackage(classes, originalNames); new PackageSplitPopulator( - filesForDistribution, classes, originalNames, application.dexItemFactory, - fillStrategy, fileIndexOffset, writer.namingLens) + filesForDistribution, + classes, + originalNames, + application.dexItemFactory, + fillStrategy, + fileIndexOffset, + writer.initClassLens, + writer.namingLens) .call(); } addFeatureSplitFiles(featureSplitClasses, fillStrategy); @@ -526,6 +552,7 @@ private static class VirtualFileIndexedItemCollection implements IndexedItemCollection { + private final InitClassLens initClassLens; private final NamingLens namingLens; private final Set<DexProgramClass> classes = Sets.newIdentityHashSet(); @@ -537,10 +564,9 @@ private final Set<DexCallSite> callSites = Sets.newIdentityHashSet(); private final Set<DexMethodHandle> methodHandles = Sets.newIdentityHashSet(); - public VirtualFileIndexedItemCollection( - NamingLens namingLens) { + public VirtualFileIndexedItemCollection(InitClassLens initClassLens, NamingLens namingLens) { + this.initClassLens = initClassLens; this.namingLens = namingLens; - } @Override @@ -596,6 +622,11 @@ } @Override + public InitClassLens getInitClassLens() { + return initClassLens; + } + + @Override public DexString getRenamedDescriptor(DexType type) { return namingLens.lookupDescriptor(type); } @@ -615,6 +646,7 @@ public static class IndexedItemTransaction implements IndexedItemCollection { private final VirtualFileIndexedItemCollection base; + private final InitClassLens initClassLens; private final NamingLens namingLens; private final Set<DexProgramClass> classes = new LinkedHashSet<>(); @@ -626,9 +658,10 @@ private final Set<DexCallSite> callSites = new LinkedHashSet<>(); private final Set<DexMethodHandle> methodHandles = new LinkedHashSet<>(); - private IndexedItemTransaction(VirtualFileIndexedItemCollection base, - NamingLens namingLens) { + private IndexedItemTransaction( + VirtualFileIndexedItemCollection base, InitClassLens initClassLens, NamingLens namingLens) { this.base = base; + this.initClassLens = initClassLens; this.namingLens = namingLens; } @@ -685,6 +718,11 @@ } @Override + public InitClassLens getInitClassLens() { + return initClassLens; + } + + @Override public DexString getRenamedDescriptor(DexType type) { return namingLens.lookupDescriptor(type); } @@ -760,6 +798,7 @@ static class VirtualFileCycler { private final List<VirtualFile> files; + private final InitClassLens initClassLens; private final NamingLens namingLens; private int nextFileId; @@ -767,8 +806,13 @@ private Iterator<VirtualFile> activeFiles; private FeatureSplit featuresplit; - VirtualFileCycler(List<VirtualFile> files, NamingLens namingLens, int fileIndexOffset) { + VirtualFileCycler( + List<VirtualFile> files, + InitClassLens initClassLens, + NamingLens namingLens, + int fileIndexOffset) { this.files = files; + this.initClassLens = initClassLens; this.namingLens = namingLens; nextFileId = files.size() + fileIndexOffset; @@ -799,7 +843,8 @@ if (hasNext()) { return activeFiles.next(); } else { - VirtualFile newFile = new VirtualFile(nextFileId++, namingLens, featuresplit); + VirtualFile newFile = + new VirtualFile(nextFileId++, initClassLens, namingLens, featuresplit); files.add(newFile); allFilesCyclic = Iterators.cycle(files); return newFile; @@ -830,7 +875,7 @@ } VirtualFile addFile() { - VirtualFile newFile = new VirtualFile(nextFileId++, namingLens, featuresplit); + VirtualFile newFile = new VirtualFile(nextFileId++, initClassLens, namingLens, featuresplit); files.add(newFile); reset(); @@ -875,12 +920,13 @@ DexItemFactory dexItemFactory, FillStrategy fillStrategy, int fileIndexOffset, + InitClassLens initClassLens, NamingLens namingLens) { this.classes = new ArrayList<>(classes); this.originalNames = originalNames; this.dexItemFactory = dexItemFactory; this.fillStrategy = fillStrategy; - this.cycler = new VirtualFileCycler(files, namingLens, fileIndexOffset); + this.cycler = new VirtualFileCycler(files, initClassLens, namingLens, fileIndexOffset); } static boolean coveredByPrefix(String originalName, String currentPrefix) {
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 640b7a1..2ddba3f 100644 --- a/src/main/java/com/android/tools/r8/graph/AppInfo.java +++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -705,10 +705,4 @@ entry.getKey(), entry.getValue()); } - - // TODO(b/149190785): Remove once fixed. - public void enableDefinitionForAssert() {} - - // TODO(b/149190785): Remove once fixed. - public void disableDefinitionForAssert() {} }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java index d1e70ed..5f2b826 100644 --- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java +++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -381,7 +381,7 @@ return false; } assert potentialHolder.isInterface(); - for (DexEncodedMethod virtualMethod : potentialHolder.virtualMethods) { + for (DexEncodedMethod virtualMethod : potentialHolder.virtualMethods()) { if (virtualMethod.method.hasSameProtoAndName(method.method) && virtualMethod.accessFlags.isSameVisibility(method.accessFlags)) { return true;
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 9ed88a6..e504e54 100644 --- a/src/main/java/com/android/tools/r8/graph/AppView.java +++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -45,6 +45,7 @@ private final DexItemFactory dexItemFactory; private final WholeProgramOptimizations wholeProgramOptimizations; private GraphLense graphLense; + private InitClassLens initClassLens; private final InternalOptions options; private RootSet rootSet; private final AbstractValueFactory abstractValueFactory = new AbstractValueFactory(); @@ -90,6 +91,7 @@ this.dexItemFactory = appInfo != null ? appInfo.dexItemFactory() : null; this.wholeProgramOptimizations = wholeProgramOptimizations; this.graphLense = GraphLense.getIdentityLense(); + this.initClassLens = InitClassLens.getDefault(); this.options = options; this.rewritePrefix = mapper; @@ -310,6 +312,18 @@ return false; } + public boolean canUseInitClass() { + return options.shouldRerunEnqueuer() && !initClassLens.isFinal(); + } + + public InitClassLens initClassLens() { + return initClassLens; + } + + public void setInitClassLens(InitClassLens initClassLens) { + this.initClassLens = initClassLens; + } + public void setInitializedClassesInInstanceMethods( InitializedClassesInInstanceMethods initializedClassesInInstanceMethods) { this.initializedClassesInInstanceMethods = initializedClassesInInstanceMethods;
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 4f8fa23..0c4267c 100644 --- a/src/main/java/com/android/tools/r8/graph/CfCode.java +++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -202,11 +202,12 @@ NamingLens namingLens, AppView<?> appView, int classFileVersion) { + InitClassLens initClassLens = appView.initClassLens(); InternalOptions options = appView.options(); CfLabel parameterLabel = null; if (shouldAddParameterNames(method, appView)) { parameterLabel = new CfLabel(); - parameterLabel.write(visitor, namingLens); + parameterLabel.write(visitor, initClassLens, namingLens); } for (CfInstruction instruction : instructions) { if (instruction instanceof CfFrame @@ -214,7 +215,7 @@ || (classFileVersion == 50 && !options.shouldKeepStackMapTable()))) { continue; } - instruction.write(visitor, namingLens); + instruction.write(visitor, initClassLens, namingLens); } visitor.visitEnd(); visitor.visitMaxs(maxStack, maxLocals);
diff --git a/src/main/java/com/android/tools/r8/graph/DefaultInitClassLens.java b/src/main/java/com/android/tools/r8/graph/DefaultInitClassLens.java new file mode 100644 index 0000000..56a1733 --- /dev/null +++ b/src/main/java/com/android/tools/r8/graph/DefaultInitClassLens.java
@@ -0,0 +1,23 @@ +// 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; + +import com.android.tools.r8.errors.Unreachable; + +public class DefaultInitClassLens extends InitClassLens { + + private static final DefaultInitClassLens INSTANCE = new DefaultInitClassLens(); + + private DefaultInitClassLens() {} + + public static DefaultInitClassLens getInstance() { + return INSTANCE; + } + + @Override + public DexField getInitClassField(DexType type) { + throw new Unreachable("Unexpected InitClass instruction for `" + type.toSourceString() + "`"); + } +}
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 bb209b5..7d6b095 100644 --- a/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java +++ b/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java
@@ -11,6 +11,11 @@ } @Override + public boolean registerInitClass(DexType clazz) { + return true; + } + + @Override public boolean registerInvokeVirtual(DexMethod method) { return true; }
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java index 2d0667b..a6b7824 100644 --- a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java +++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
@@ -6,10 +6,9 @@ import com.android.tools.r8.dex.IndexedItemCollection; import com.android.tools.r8.dex.MixedSectionCollection; import com.android.tools.r8.errors.Unreachable; -import com.android.tools.r8.utils.OrderedMergingIterator; +import com.android.tools.r8.naming.NamingLens; import java.util.ArrayList; import java.util.List; -import java.util.function.Function; public class DexAnnotationDirectory extends DexItem { @@ -22,28 +21,21 @@ public DexAnnotationDirectory(DexProgramClass clazz) { this.clazz = clazz; this.classHasOnlyInternalizableAnnotations = clazz.hasOnlyInternalizableAnnotations(); - assert isSorted(clazz.directMethods()); - assert isSorted(clazz.virtualMethods()); - OrderedMergingIterator<DexEncodedMethod, DexMethod> methods = - new OrderedMergingIterator<>(clazz.directMethods(), clazz.virtualMethods()); methodAnnotations = new ArrayList<>(); parameterAnnotations = new ArrayList<>(); - while (methods.hasNext()) { - DexEncodedMethod method = methods.next(); - if (!method.annotations().isEmpty()) { - methodAnnotations.add(method); - } - if (!method.parameterAnnotationsList.isEmpty()) { - parameterAnnotations.add(method); - } - } - assert isSorted(clazz.staticFields()); - assert isSorted(clazz.instanceFields()); - OrderedMergingIterator<DexEncodedField, DexField> fields = - new OrderedMergingIterator<>(clazz.staticFields(), clazz.instanceFields()); fieldAnnotations = new ArrayList<>(); - while (fields.hasNext()) { - DexEncodedField field = fields.next(); + clazz + .getMethodCollection() + .forEachMethod( + method -> { + if (!method.annotations().isEmpty()) { + methodAnnotations.add(method); + } + if (!method.parameterAnnotationsList.isEmpty()) { + parameterAnnotations.add(method); + } + }); + for (DexEncodedField field : clazz.fields()) { if (!field.annotations().isEmpty()) { fieldAnnotations.add(field); } @@ -54,15 +46,18 @@ return clazz.annotations(); } - public List<DexEncodedMethod> getMethodAnnotations() { + public List<DexEncodedMethod> sortMethodAnnotations(NamingLens namingLens) { + methodAnnotations.sort((a, b) -> a.method.slowCompareTo(b.method, namingLens)); return methodAnnotations; } - public List<DexEncodedMethod> getParameterAnnotations() { + public List<DexEncodedMethod> sortParameterAnnotations(NamingLens namingLens) { + parameterAnnotations.sort((a, b) -> a.method.slowCompareTo(b.method, namingLens)); return parameterAnnotations; } - public List<DexEncodedField> getFieldAnnotations() { + public List<DexEncodedField> sortFieldAnnotations(NamingLens namingLens) { + fieldAnnotations.sort((a, b) -> a.field.slowCompareTo(b.field, namingLens)); return fieldAnnotations; } @@ -106,22 +101,4 @@ public void collectMixedSectionItems(MixedSectionCollection collection) { throw new Unreachable(); } - - private static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> boolean isSorted( - List<D> items) { - return isSorted(items, DexEncodedMember::toReference); - } - - private static <S, T extends Comparable<T>> boolean isSorted( - List<S> items, Function<S, T> getter) { - T current = null; - for (S item : items) { - T next = getter.apply(item); - if (current != null && current.compareTo(next) >= 0) { - return false; - } - current = next; - } - return true; - } }
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java index dd24ed6..7371902 100644 --- a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java +++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
@@ -5,10 +5,10 @@ import com.android.tools.r8.dex.IndexedItemCollection; import com.android.tools.r8.dex.MixedSectionCollection; +import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.utils.ArrayUtils; import com.google.common.collect.Sets; import java.util.Arrays; -import java.util.Comparator; import java.util.List; import java.util.Set; import java.util.function.Consumer; @@ -93,12 +93,13 @@ return annotations.length == 0; } - public void sort() { + public void sort(NamingLens namingLens) { if (sorted != UNSORTED) { assert sorted == sortedHashCode(); return; } - Arrays.sort(annotations, Comparator.comparing(a -> a.annotation.type)); + Arrays.sort( + annotations, (a, b) -> a.annotation.type.slowCompareTo(b.annotation.type, namingLens)); for (DexAnnotation annotation : annotations) { annotation.annotation.sort(); }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java index a75fd4e..773a71a 100644 --- a/src/main/java/com/android/tools/r8/graph/DexClass.java +++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -10,7 +10,6 @@ import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.OptionalBool; -import com.android.tools.r8.utils.PredicateUtils; import com.google.common.base.MoreObjects; import com.google.common.base.Predicates; import com.google.common.collect.Iterables; @@ -22,7 +21,6 @@ import java.util.Iterator; import java.util.List; import java.util.ListIterator; -import java.util.Optional; import java.util.Set; import java.util.function.Consumer; import java.util.function.Predicate; @@ -33,12 +31,6 @@ void setField(int index, DexEncodedField field); } - public interface MethodSetter { - void setMethod(int index, DexEncodedMethod method); - } - - private Optional<DexEncodedMethod> cachedClassInitializer = null; - public final Origin origin; public DexType type; public final ClassAccessFlags accessFlags; @@ -55,10 +47,7 @@ protected DexEncodedField[] instanceFields = DexEncodedField.EMPTY_ARRAY; /** Access has to be synchronized during concurrent collection/writing phase. */ - protected DexEncodedMethod[] directMethods = DexEncodedMethod.EMPTY_ARRAY; - - /** Access has to be synchronized during concurrent collection/writing phase. */ - protected DexEncodedMethod[] virtualMethods = DexEncodedMethod.EMPTY_ARRAY; + protected final MethodCollection methodCollection; /** Enclosing context of this class if it is an inner class, null otherwise. */ private EnclosingMethodAttribute enclosingMethod; @@ -96,6 +85,7 @@ this.type = type; setStaticFields(staticFields); setInstanceFields(instanceFields); + this.methodCollection = new MethodCollection(this); setDirectMethods(directMethods); setVirtualMethods(virtualMethods); this.nestHost = nestHost; @@ -133,12 +123,16 @@ return Iterables.concat(fields(), methods()); } - public Iterable<DexEncodedMethod> methods() { - return methods(Predicates.alwaysTrue()); + public MethodCollection getMethodCollection() { + return methodCollection; } - public Iterable<DexEncodedMethod> methods(Predicate<? super DexEncodedMethod> predicate) { - return Iterables.concat(directMethods(predicate), virtualMethods(predicate)); + public Iterable<DexEncodedMethod> methods() { + return methodCollection.methods(); + } + + public Iterable<DexEncodedMethod> methods(Predicate<DexEncodedMethod> predicate) { + return methodCollection.methods(predicate); } @Override @@ -147,145 +141,47 @@ } public List<DexEncodedMethod> directMethods() { - assert directMethods != null; - if (InternalOptions.assertionsEnabled()) { - return Collections.unmodifiableList(Arrays.asList(directMethods)); - } - return Arrays.asList(directMethods); + return methodCollection.directMethods(); } public Iterable<DexEncodedMethod> directMethods(Predicate<? super DexEncodedMethod> predicate) { - return Iterables.filter(Arrays.asList(directMethods), predicate::test); + return Iterables.filter(directMethods(), predicate::test); } public void appendDirectMethod(DexEncodedMethod method) { - cachedClassInitializer = null; - DexEncodedMethod[] newMethods = new DexEncodedMethod[directMethods.length + 1]; - System.arraycopy(directMethods, 0, newMethods, 0, directMethods.length); - newMethods[directMethods.length] = method; - directMethods = newMethods; - assert verifyCorrectnessOfMethodHolder(method); - assert verifyNoDuplicateMethods(); + methodCollection.appendDirectMethod(method); } public void appendDirectMethods(Collection<DexEncodedMethod> methods) { - cachedClassInitializer = null; - DexEncodedMethod[] newMethods = new DexEncodedMethod[directMethods.length + methods.size()]; - System.arraycopy(directMethods, 0, newMethods, 0, directMethods.length); - int i = directMethods.length; - for (DexEncodedMethod method : methods) { - newMethods[i] = method; - i++; - } - directMethods = newMethods; - assert verifyCorrectnessOfMethodHolders(methods); - assert verifyNoDuplicateMethods(); - } - - public void removeDirectMethod(int index) { - cachedClassInitializer = null; - DexEncodedMethod[] newMethods = new DexEncodedMethod[directMethods.length - 1]; - System.arraycopy(directMethods, 0, newMethods, 0, index); - System.arraycopy(directMethods, index + 1, newMethods, index, directMethods.length - index - 1); - directMethods = newMethods; + methodCollection.appendDirectMethods(methods); } public void removeDirectMethod(DexMethod method) { - int index = -1; - for (int i = 0; i < directMethods.length; i++) { - if (method.match(directMethods[i])) { - index = i; - break; - } - } - if (index >= 0) { - removeDirectMethod(index); - } - } - - public void setDirectMethod(int index, DexEncodedMethod method) { - cachedClassInitializer = null; - directMethods[index] = method; - assert verifyCorrectnessOfMethodHolder(method); - assert verifyNoDuplicateMethods(); + methodCollection.removeDirectMethod(method); } public void setDirectMethods(DexEncodedMethod[] methods) { - cachedClassInitializer = null; - directMethods = MoreObjects.firstNonNull(methods, DexEncodedMethod.EMPTY_ARRAY); - assert verifyCorrectnessOfMethodHolders(directMethods()); - assert verifyNoDuplicateMethods(); + methodCollection.setDirectMethods(methods); } public List<DexEncodedMethod> virtualMethods() { - assert virtualMethods != null; - if (InternalOptions.assertionsEnabled()) { - return Collections.unmodifiableList(Arrays.asList(virtualMethods)); - } - return Arrays.asList(virtualMethods); + return methodCollection.virtualMethods(); } public Iterable<DexEncodedMethod> virtualMethods(Predicate<? super DexEncodedMethod> predicate) { - return Iterables.filter(Arrays.asList(virtualMethods), predicate::test); + return Iterables.filter(virtualMethods(), predicate::test); } public void appendVirtualMethod(DexEncodedMethod method) { - DexEncodedMethod[] newMethods = new DexEncodedMethod[virtualMethods.length + 1]; - System.arraycopy(virtualMethods, 0, newMethods, 0, virtualMethods.length); - newMethods[virtualMethods.length] = method; - virtualMethods = newMethods; - assert verifyCorrectnessOfMethodHolder(method); - assert verifyNoDuplicateMethods(); + methodCollection.appendVirtualMethod(method); } public void appendVirtualMethods(Collection<DexEncodedMethod> methods) { - DexEncodedMethod[] newMethods = new DexEncodedMethod[virtualMethods.length + methods.size()]; - System.arraycopy(virtualMethods, 0, newMethods, 0, virtualMethods.length); - int i = virtualMethods.length; - for (DexEncodedMethod method : methods) { - newMethods[i] = method; - i++; - } - virtualMethods = newMethods; - assert verifyCorrectnessOfMethodHolders(methods); - assert verifyNoDuplicateMethods(); - } - - public void removeVirtualMethod(int index) { - DexEncodedMethod[] newMethods = new DexEncodedMethod[virtualMethods.length - 1]; - System.arraycopy(virtualMethods, 0, newMethods, 0, index); - System.arraycopy( - virtualMethods, index + 1, newMethods, index, virtualMethods.length - index - 1); - virtualMethods = newMethods; - } - - public void setVirtualMethod(int index, DexEncodedMethod method) { - virtualMethods[index] = method; - assert verifyCorrectnessOfMethodHolder(method); - assert verifyNoDuplicateMethods(); + methodCollection.appendVirtualMethods(methods); } public void setVirtualMethods(DexEncodedMethod[] methods) { - virtualMethods = MoreObjects.firstNonNull(methods, DexEncodedMethod.EMPTY_ARRAY); - assert verifyCorrectnessOfMethodHolders(virtualMethods()); - assert verifyNoDuplicateMethods(); - } - - private boolean verifyCorrectnessOfMethodHolder(DexEncodedMethod method) { - assert method.method.holder == type - : "Expected method `" - + method.method.toSourceString() - + "` to have holder `" - + type.toSourceString() - + "`"; - return true; - } - - private boolean verifyCorrectnessOfMethodHolders(Iterable<DexEncodedMethod> methods) { - for (DexEncodedMethod method : methods) { - assert verifyCorrectnessOfMethodHolder(method); - } - return true; + methodCollection.setVirtualMethods(methods); } private boolean verifyNoAbstractMethodsOnNonAbstractClasses( @@ -301,75 +197,16 @@ return true; } - private boolean verifyNoDuplicateMethods() { - Set<DexMethod> unique = Sets.newIdentityHashSet(); - for (DexEncodedMethod method : methods()) { - boolean changed = unique.add(method.method); - assert changed : "Duplicate method `" + method.method.toSourceString() + "`"; - } - return true; - } - public void forEachMethod(Consumer<DexEncodedMethod> consumer) { - for (DexEncodedMethod method : directMethods()) { - consumer.accept(method); - } - for (DexEncodedMethod method : virtualMethods()) { - consumer.accept(method); - } + methodCollection.forEachMethod(consumer); } - public DexEncodedMethod[] allMethodsSorted() { - int vLen = virtualMethods.length; - int dLen = directMethods.length; - DexEncodedMethod[] result = new DexEncodedMethod[vLen + dLen]; - System.arraycopy(virtualMethods, 0, result, 0, vLen); - System.arraycopy(directMethods, 0, result, vLen, dLen); - Arrays.sort(result, - (DexEncodedMethod a, DexEncodedMethod b) -> a.method.slowCompareTo(b.method)); - return result; - } - - public DexEncodedMethod[] directMethodsSorted() { - DexEncodedMethod[] result = new DexEncodedMethod[directMethods.length]; - System.arraycopy(directMethods, 0, result, 0, directMethods.length); - Arrays.sort( - result, (DexEncodedMethod a, DexEncodedMethod b) -> a.method.slowCompareTo(b.method)); - return result; - } - - public DexEncodedMethod[] virtualMethodsSorted() { - DexEncodedMethod[] result = new DexEncodedMethod[virtualMethods.length]; - System.arraycopy(virtualMethods, 0, result, 0, virtualMethods.length); - Arrays.sort( - result, (DexEncodedMethod a, DexEncodedMethod b) -> a.method.slowCompareTo(b.method)); - return result; + public List<DexEncodedMethod> allMethodsSorted() { + return methodCollection.allMethodsSorted(); } public void virtualizeMethods(Set<DexEncodedMethod> privateInstanceMethods) { - int vLen = virtualMethods.length; - int dLen = directMethods.length; - int mLen = privateInstanceMethods.size(); - assert mLen <= dLen; - - DexEncodedMethod[] newDirectMethods = new DexEncodedMethod[dLen - mLen]; - int index = 0; - for (int i = 0; i < dLen; i++) { - DexEncodedMethod encodedMethod = directMethods[i]; - if (!privateInstanceMethods.contains(encodedMethod)) { - newDirectMethods[index++] = encodedMethod; - } - } - assert index == dLen - mLen; - setDirectMethods(newDirectMethods); - - DexEncodedMethod[] newVirtualMethods = new DexEncodedMethod[vLen + mLen]; - System.arraycopy(virtualMethods, 0, newVirtualMethods, 0, vLen); - index = vLen; - for (DexEncodedMethod encodedMethod : privateInstanceMethods) { - newVirtualMethods[index++] = encodedMethod; - } - setVirtualMethods(newVirtualMethods); + methodCollection.virtualizeMethods(privateInstanceMethods); } /** @@ -561,28 +398,27 @@ /** Find direct method in this class matching {@param method}. */ public DexEncodedMethod lookupDirectMethod(DexMethod method) { - return lookupTarget(directMethods, method); + return methodCollection.getDirectMethod(method); } /** Find direct method in this class matching {@param predicate}. */ public DexEncodedMethod lookupDirectMethod(Predicate<DexEncodedMethod> predicate) { - return PredicateUtils.findFirst(directMethods, predicate); + return methodCollection.getDirectMethod(predicate); } /** Find virtual method in this class matching {@param method}. */ public DexEncodedMethod lookupVirtualMethod(DexMethod method) { - return lookupTarget(virtualMethods, method); + return methodCollection.getVirtualMethod(method); } /** Find virtual method in this class matching {@param predicate}. */ public DexEncodedMethod lookupVirtualMethod(Predicate<DexEncodedMethod> predicate) { - return PredicateUtils.findFirst(virtualMethods, predicate); + return methodCollection.getVirtualMethod(predicate); } /** Find method in this class matching {@param method}. */ public DexEncodedMethod lookupMethod(DexMethod method) { - DexEncodedMethod result = lookupDirectMethod(method); - return result == null ? lookupVirtualMethod(method) : result; + return methodCollection.getMethod(method); } public DexEncodedMethod lookupSignaturePolymorphicMethod( @@ -592,7 +428,7 @@ } DexEncodedMethod matchingName = null; DexEncodedMethod signaturePolymorphicMethod = null; - for (DexEncodedMethod method : virtualMethods) { + for (DexEncodedMethod method : virtualMethods()) { if (method.method.name == methodName) { if (matchingName != null) { // The jvm spec, section 5.4.3.3 details that there must be exactly one method with the @@ -701,16 +537,7 @@ } public synchronized DexEncodedMethod getClassInitializer() { - if (cachedClassInitializer == null) { - cachedClassInitializer = Optional.empty(); - for (DexEncodedMethod directMethod : directMethods) { - if (directMethod.isClassInitializer()) { - cachedClassInitializer = Optional.of(directMethod); - break; - } - } - } - return cachedClassInitializer.orElse(null); + return methodCollection.getClassInitializer(); } public Origin getOrigin() { @@ -1004,8 +831,7 @@ assert !isInterface() || virtualMethods().stream().noneMatch(DexEncodedMethod::isFinal); assert verifyCorrectnessOfFieldHolders(fields()); assert verifyNoDuplicateFields(); - assert verifyCorrectnessOfMethodHolders(methods()); - assert verifyNoDuplicateMethods(); + assert methodCollection.verify(); return true; }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java index c0c2d6e..f5c5272 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
@@ -63,7 +63,7 @@ assert sorted == sortedHashCode(); return; } - Arrays.sort(elements, (a, b) -> a.name.compareTo(b.name)); + Arrays.sort(elements, (a, b) -> a.name.slowCompareTo(b.name)); for (DexAnnotationElement element : elements) { element.value.sort(); }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java index e8bd9dc..8a21e7b 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -80,6 +80,10 @@ return kotlinMemberInfo.memberKind.isBackingField(); } + public boolean isKotlinBackingFieldForCompanionObject() { + return kotlinMemberInfo.memberKind.isBackingFieldForCompanionObject(); + } + @Override public void collectIndexedItems( IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) { @@ -134,10 +138,18 @@ return accessFlags.isStatic(); } + public boolean isPackagePrivate() { + return accessFlags.isPackagePrivate(); + } + public boolean isPrivate() { return accessFlags.isPrivate(); } + public boolean isProtected() { + return accessFlags.isProtected(); + } + public boolean isPublic() { return accessFlags.isPublic(); } @@ -254,4 +266,19 @@ : DefaultFieldOptimizationInfo.getInstance(); return result; } + + public boolean validateDexValue(DexItemFactory factory) { + if (!accessFlags.isStatic() || staticValue == null) { + return true; + } + if (field.type.isPrimitiveType()) { + assert staticValue.getType(factory) == field.type + : "Static " + field + " has invalid static value " + staticValue + "."; + } + if (staticValue.isDexValueNull()) { + assert field.type.isReferenceType() : "Static " + field + " has invalid null static value."; + } + // TODO(b/150593449): Support non primitive DexValue (String, enum) and add assertions. + return true; + } }
diff --git a/src/main/java/com/android/tools/r8/graph/DexField.java b/src/main/java/com/android/tools/r8/graph/DexField.java index 02cb985..d10a2c8 100644 --- a/src/main/java/com/android/tools/r8/graph/DexField.java +++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -72,11 +72,6 @@ } @Override - public int compareTo(DexField other) { - return sortedCompareTo(other.getSortedIndex()); - } - - @Override public int slowCompareTo(DexField other) { int result = holder.slowCompareTo(other.holder); if (result != 0) { @@ -103,19 +98,6 @@ } @Override - public int layeredCompareTo(DexField other, NamingLens namingLens) { - int result = holder.compareTo(other.holder); - if (result != 0) { - return result; - } - result = namingLens.lookupName(this).compareTo(namingLens.lookupName(other)); - if (result != 0) { - return result; - } - return type.compareTo(other.type); - } - - @Override public boolean match(DexField field) { return field.name == name && field.type == type; }
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 58b768d..bfaf19c 100644 --- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java +++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -27,7 +27,6 @@ import com.android.tools.r8.ir.code.Position; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.kotlin.Kotlin; -import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.utils.ArrayUtils; import com.android.tools.r8.utils.LRUCacheTable; import com.android.tools.r8.utils.Pair; @@ -44,7 +43,6 @@ import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.IdentityHashMap; @@ -572,7 +570,7 @@ createString("makeConcat") ); - public final Set<DexMethod> libraryMethodsReturningReceiver = + public Set<DexMethod> libraryMethodsReturningReceiver = ImmutableSet.<DexMethod>builder() .addAll(stringBufferMethods.appendMethods) .addAll(stringBuilderMethods.appendMethods) @@ -1725,39 +1723,6 @@ ); } - private static <S extends PresortedComparable<S>> void assignSortedIndices(Collection<S> items, - NamingLens namingLens) { - List<S> sorted = new ArrayList<>(items); - sorted.sort((a, b) -> a.layeredCompareTo(b, namingLens)); - int i = 0; - for (S value : sorted) { - value.setSortedIndex(i++); - } - } - - synchronized public void sort(NamingLens namingLens) { - assert !sorted; - assignSortedIndices(strings.values(), namingLens); - assignSortedIndices(types.values(), namingLens); - assignSortedIndices(fields.values(), namingLens); - assignSortedIndices(protos.values(), namingLens); - assignSortedIndices(methods.values(), namingLens); - sorted = true; - } - - synchronized public void resetSortedIndices() { - if (!sorted) { - return; - } - // Only used for asserting that we don't use the sorted index after we build the graph. - strings.values().forEach(IndexedDexItem::resetSortedIndex); - types.values().forEach(IndexedDexItem::resetSortedIndex); - fields.values().forEach(IndexedDexItem::resetSortedIndex); - protos.values().forEach(IndexedDexItem::resetSortedIndex); - methods.values().forEach(IndexedDexItem::resetSortedIndex); - sorted = false; - } - synchronized public void forAllTypes(Consumer<DexType> f) { new ArrayList<>(types.values()).forEach(f); }
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java index a29eb31..80605e5 100644 --- a/src/main/java/com/android/tools/r8/graph/DexMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -6,17 +6,12 @@ import com.android.tools.r8.dex.IndexedItemCollection; import com.android.tools.r8.errors.CompilationError; import com.android.tools.r8.naming.NamingLens; -import com.google.common.collect.Maps; -import java.util.Map; public class DexMethod extends DexMember<DexEncodedMethod, DexMethod> { public final DexProto proto; public final DexString name; - // Caches used during processing. - private Map<DexType, DexEncodedMethod> singleTargetCache; - DexMethod(DexType holder, DexProto proto, DexString name, boolean skipNameValidationForTesting) { super(holder); this.proto = proto; @@ -102,11 +97,6 @@ } @Override - public int compareTo(DexMethod other) { - return sortedCompareTo(other.getSortedIndex()); - } - - @Override public int slowCompareTo(DexMethod other) { int result = holder.slowCompareTo(other.holder); if (result != 0) { @@ -133,19 +123,6 @@ } @Override - public int layeredCompareTo(DexMethod other, NamingLens namingLens) { - int result = holder.compareTo(other.holder); - if (result != 0) { - return result; - } - result = namingLens.lookupName(this).compareTo(namingLens.lookupName(other)); - if (result != 0) { - return result; - } - return proto.compareTo(other.proto); - } - - @Override public boolean match(DexMethod method) { return method.name == name && method.proto == proto; } @@ -192,21 +169,4 @@ return name == dexItemFactory.deserializeLambdaMethodName && proto == dexItemFactory.deserializeLambdaMethodProto; } - - synchronized public void setSingleVirtualMethodCache( - DexType receiverType, DexEncodedMethod method) { - if (singleTargetCache == null) { - singleTargetCache = Maps.newIdentityHashMap(); - } - singleTargetCache.put(receiverType, method); - } - - synchronized public boolean isSingleVirtualMethodCached(DexType receiverType) { - return singleTargetCache != null && singleTargetCache.containsKey(receiverType); - } - - synchronized public DexEncodedMethod getSingleVirtualMethodCache(DexType receiverType) { - assert isSingleVirtualMethodCached(receiverType); - return singleTargetCache.get(receiverType); - } }
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java index fbe8a9f..a4be032 100644 --- a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java +++ b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
@@ -339,25 +339,6 @@ return result; } - @Override - public int layeredCompareTo(DexMethodHandle other, NamingLens namingLens) { - int result = type.getValue() - other.type.getValue(); - if (result == 0) { - if (isFieldHandle()) { - result = asField().layeredCompareTo(other.asField(), namingLens); - } else { - assert isMethodHandle(); - result = asMethod().layeredCompareTo(other.asMethod(), namingLens); - } - } - return result; - } - - @Override - public int compareTo(DexMethodHandle other) { - return slowCompareTo(other); - } - public Handle toAsmHandle(NamingLens lens) { String owner; String name;
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java index 78a59c2..221a19e 100644 --- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java +++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -9,13 +9,13 @@ import com.android.tools.r8.dex.MixedSectionCollection; import com.android.tools.r8.errors.CompilationError; import com.android.tools.r8.kotlin.KotlinInfo; +import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.shaking.AppInfoWithLiveness; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -34,7 +34,6 @@ new DexEncodedArray(DexValue.EMPTY_ARRAY); private final ProgramResource.Kind originKind; - private DexEncodedArray staticValues = SENTINEL_NOT_YET_COMPUTED; private final Collection<DexProgramClass> synthesizedFrom; private int initialClassFileVersion = -1; private KotlinInfo kotlinInfo = null; @@ -160,8 +159,9 @@ } synchronizedCollectAll(indexedItems, staticFields); synchronizedCollectAll(indexedItems, instanceFields); - synchronizedCollectAll(indexedItems, directMethods); - synchronizedCollectAll(indexedItems, virtualMethods); + synchronized (methodCollection) { + methodCollection.forEachMethod(m -> m.collectIndexedItems(indexedItems)); + } } } @@ -192,14 +192,9 @@ // We only have a class data item if there are methods or fields. if (hasMethodsOrFields()) { collector.add(this); - - // The ordering of methods and fields may not be deterministic due to concurrency - // (see b/116027780). - sortMembers(); - synchronizedCollectAll(collector, directMethods); - synchronizedCollectAll(collector, virtualMethods); - synchronizedCollectAll(collector, staticFields); - synchronizedCollectAll(collector, instanceFields); + methodCollection.forEachMethod(m -> m.collectMixedSectionItems(collector)); + collectAll(collector, staticFields); + collectAll(collector, instanceFields); } annotations().collectMixedSectionItems(collector); if (interfaces != null) { @@ -207,13 +202,6 @@ } } - private static <T extends DexItem> void synchronizedCollectAll(MixedSectionCollection collection, - T[] items) { - synchronized (items) { - collectAll(collection, items); - } - } - @Override public String toString() { return type.toString(); @@ -278,7 +266,7 @@ } public boolean hasMethods() { - return directMethods.length + virtualMethods.length > 0; + return methodCollection.size() > 0; } public boolean hasMethodsOrFields() { @@ -287,15 +275,13 @@ public boolean hasAnnotations() { return !annotations().isEmpty() - || hasAnnotations(virtualMethods) - || hasAnnotations(directMethods) + || hasAnnotations(methodCollection) || hasAnnotations(staticFields) || hasAnnotations(instanceFields); } boolean hasOnlyInternalizableAnnotations() { - return !hasAnnotations(virtualMethods) - && !hasAnnotations(directMethods) + return !hasAnnotations(methodCollection) && !hasAnnotations(staticFields) && !hasAnnotations(instanceFields); } @@ -306,9 +292,9 @@ } } - private boolean hasAnnotations(DexEncodedMethod[] methods) { + private boolean hasAnnotations(MethodCollection methods) { synchronized (methods) { - return Arrays.stream(methods).anyMatch(DexEncodedMethod::hasAnnotation); + return methods.hasAnnotations(); } } @@ -320,97 +306,49 @@ } } - public void computeStaticValues() { - // It does not actually hurt to compute this multiple times. So racing on staticValues is OK. - if (staticValues == SENTINEL_NOT_YET_COMPUTED) { - synchronized (staticFields) { - assert PresortedComparable.isSorted(Arrays.asList(staticFields)); - DexEncodedField[] fields = staticFields; - int length = 0; - List<DexValue> values = new ArrayList<>(fields.length); - for (int i = 0; i < fields.length; i++) { - DexEncodedField field = fields[i]; - DexValue staticValue = field.getStaticValue(); - assert staticValue != null; - values.add(staticValue); - if (!staticValue.isDefault(field.field.type)) { - length = i + 1; - } - } - staticValues = - length > 0 - ? new DexEncodedArray(values.subList(0, length).toArray(DexValue.EMPTY_ARRAY)) - : null; - } - } - } - - public boolean isSorted() { - return isSorted(virtualMethods) - && isSorted(directMethods) - && isSorted(staticFields) - && isSorted(instanceFields); - } - - private static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> boolean isSorted( - D[] items) { - synchronized (items) { - D[] sorted = items.clone(); - Arrays.sort(sorted, Comparator.comparing(DexEncodedMember::toReference)); - return Arrays.equals(items, sorted); - } - } - public DexEncodedArray getStaticValues() { - // The sentinel value is left over for classes that actually have no fields. - if (staticValues == SENTINEL_NOT_YET_COMPUTED) { - assert !hasMethodsOrFields(); + public DexEncodedArray computeStaticValuesArray(NamingLens namingLens) { + // Fast path to avoid sorting and collection allocation when no non-default values exist. + if (!hasNonDefaultStaticFieldValues()) { return null; } - return staticValues; + DexEncodedField[] fields = staticFields; + Arrays.sort(fields, (a, b) -> a.field.slowCompareTo(b.field, namingLens)); + int length = 0; + List<DexValue> values = new ArrayList<>(fields.length); + for (int i = 0; i < fields.length; i++) { + DexEncodedField field = fields[i]; + DexValue staticValue = field.getStaticValue(); + assert staticValue != null; + values.add(staticValue); + if (!staticValue.isDefault(field.field.type)) { + length = i + 1; + } + } + return length > 0 + ? new DexEncodedArray(values.subList(0, length).toArray(DexValue.EMPTY_ARRAY)) + : null; + } + + private boolean hasNonDefaultStaticFieldValues() { + for (DexEncodedField field : staticFields) { + DexValue value = field.getStaticValue(); + if (value != null && !value.isDefault(field.field.type)) { + return true; + } + } + return false; } public void addMethod(DexEncodedMethod method) { - if (method.accessFlags.isStatic() - || method.accessFlags.isPrivate() - || method.accessFlags.isConstructor()) { - addDirectMethod(method); - } else { - addVirtualMethod(method); - } + methodCollection.addMethod(method); } public void addVirtualMethod(DexEncodedMethod virtualMethod) { - assert !virtualMethod.accessFlags.isStatic(); - assert !virtualMethod.accessFlags.isPrivate(); - assert !virtualMethod.accessFlags.isConstructor(); - virtualMethods = Arrays.copyOf(virtualMethods, virtualMethods.length + 1); - virtualMethods[virtualMethods.length - 1] = virtualMethod; + methodCollection.addVirtualMethod(virtualMethod); } - public void addDirectMethod(DexEncodedMethod staticMethod) { - assert staticMethod.accessFlags.isStatic() || staticMethod.accessFlags.isPrivate() - || staticMethod.accessFlags.isConstructor(); - directMethods = Arrays.copyOf(directMethods, directMethods.length + 1); - directMethods[directMethods.length - 1] = staticMethod; - } - - public void sortMembers() { - sortEncodedFields(staticFields); - sortEncodedFields(instanceFields); - sortEncodedMethods(directMethods); - sortEncodedMethods(virtualMethods); - } - - private void sortEncodedFields(DexEncodedField[] fields) { - synchronized (fields) { - Arrays.sort(fields, Comparator.comparing(a -> a.field)); - } - } - - private void sortEncodedMethods(DexEncodedMethod[] methods) { - synchronized (methods) { - Arrays.sort(methods, Comparator.comparing(a -> a.method)); - } + public void addDirectMethod(DexEncodedMethod directMethod) { + methodCollection.addDirectMethod(directMethod); } @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexProto.java b/src/main/java/com/android/tools/r8/graph/DexProto.java index 43d6a29..a9a15f3 100644 --- a/src/main/java/com/android/tools/r8/graph/DexProto.java +++ b/src/main/java/com/android/tools/r8/graph/DexProto.java
@@ -63,11 +63,6 @@ } @Override - public int compareTo(DexProto other) { - return sortedCompareTo(other.getSortedIndex()); - } - - @Override public int slowCompareTo(DexProto other) { int result = returnType.slowCompareTo(other.returnType); if (result == 0) { @@ -86,15 +81,6 @@ } @Override - public int layeredCompareTo(DexProto other, NamingLens namingLens) { - int result = returnType.compareTo(other.returnType); - if (result == 0) { - result = parameters.compareTo(other.parameters); - } - return result; - } - - @Override public String toSmaliString() { return toDescriptorString(); }
diff --git a/src/main/java/com/android/tools/r8/graph/DexString.java b/src/main/java/com/android/tools/r8/graph/DexString.java index f3a829c..03303f6 100644 --- a/src/main/java/com/android/tools/r8/graph/DexString.java +++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -251,11 +251,6 @@ } @Override - public int compareTo(DexString other) { - return sortedCompareTo(other.getSortedIndex()); - } - - @Override public int slowCompareTo(DexString other) { // Compare the bytes, as comparing UTF-8 encoded strings as strings of unsigned bytes gives // the same result as comparing the corresponding Unicode strings lexicographically by @@ -294,12 +289,6 @@ return slowCompareTo(other); } - @Override - public int layeredCompareTo(DexString other, NamingLens lens) { - // Strings have no subparts that are already sorted. - return slowCompareTo(other); - } - private static boolean isValidClassDescriptor(String string) { if (string.length() < 3 || string.charAt(0) != 'L'
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java index c046e6a..4b8c6d4 100644 --- a/src/main/java/com/android/tools/r8/graph/DexType.java +++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -175,11 +175,6 @@ } @Override - public int compareTo(DexType other) { - return sortedCompareTo(other.getSortedIndex()); - } - - @Override public int slowCompareTo(DexType other) { return descriptor.slowCompareTo(other.descriptor); } @@ -188,14 +183,7 @@ public int slowCompareTo(DexType other, NamingLens namingLens) { DexString thisDescriptor = namingLens.lookupDescriptor(this); DexString otherDescriptor = namingLens.lookupDescriptor(other); - return thisDescriptor.slowCompareTo(otherDescriptor); - } - - @Override - public int layeredCompareTo(DexType other, NamingLens namingLens) { - DexString thisDescriptor = namingLens.lookupDescriptor(this); - DexString otherDescriptor = namingLens.lookupDescriptor(other); - return thisDescriptor.compareTo(otherDescriptor); + return thisDescriptor.slowCompareTo(otherDescriptor, namingLens); } public boolean isPrimitiveType() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeList.java b/src/main/java/com/android/tools/r8/graph/DexTypeList.java index a124bc4..58b6afd 100644 --- a/src/main/java/com/android/tools/r8/graph/DexTypeList.java +++ b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
@@ -9,7 +9,7 @@ import com.android.tools.r8.naming.NamingLens; import java.util.Arrays; -public class DexTypeList extends DexItem implements Comparable<DexTypeList> { +public class DexTypeList extends DexItem { private static final DexTypeList theEmptyTypeList = new DexTypeList(); @@ -75,23 +75,6 @@ return builder.toString(); } - @Override - public int compareTo(DexTypeList other) { - for (int i = 0; i <= Math.min(values.length, other.values.length); i++) { - if (i == values.length) { - return i == other.values.length ? 0 : -1; - } else if (i == other.values.length) { - return 1; - } else { - int result = values[i].compareTo(other.values[i]); - if (result != 0) { - return result; - } - } - } - throw new Unreachable(); - } - public int slowCompareTo(DexTypeList other) { for (int i = 0; i <= Math.min(values.length, other.values.length); i++) { if (i == values.length) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java index 794f7c8..be0cf37 100644 --- a/src/main/java/com/android/tools/r8/graph/DexValue.java +++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -304,6 +304,8 @@ } } + public abstract DexType getType(DexItemFactory factory); + public abstract Object getBoxedValue(); /** Returns an instruction that can be used to materialize this {@link DexValue} (or null). */ @@ -390,6 +392,11 @@ } @Override + public DexType getType(DexItemFactory factory) { + return factory.byteType; + } + + @Override public long getRawValue() { return value; } @@ -463,6 +470,11 @@ } @Override + public DexType getType(DexItemFactory factory) { + return factory.shortType; + } + + @Override public long getRawValue() { return value; } @@ -535,6 +547,11 @@ } @Override + public DexType getType(DexItemFactory factory) { + return factory.charType; + } + + @Override public long getRawValue() { return value; } @@ -611,6 +628,11 @@ } @Override + public DexType getType(DexItemFactory factory) { + return factory.intType; + } + + @Override public long getRawValue() { return value; } @@ -683,6 +705,11 @@ } @Override + public DexType getType(DexItemFactory factory) { + return factory.longType; + } + + @Override public long getRawValue() { return value; } @@ -755,6 +782,11 @@ } @Override + public DexType getType(DexItemFactory factory) { + return factory.floatType; + } + + @Override public long getRawValue() { return Float.floatToIntBits(value); } @@ -833,6 +865,11 @@ } @Override + public DexType getType(DexItemFactory factory) { + return factory.doubleType; + } + + @Override public long getRawValue() { return Double.doubleToRawLongBits(value); } @@ -902,6 +939,11 @@ protected abstract DexValueKind getValueKind(); + @Override + public DexType getType(DexItemFactory factory) { + throw new Unreachable(); + } + public T getValue() { return value; } @@ -987,6 +1029,11 @@ } @Override + public DexType getType(DexItemFactory factory) { + return factory.stringType; + } + + @Override public ConstInstruction asConstInstruction( AppView<? extends AppInfoWithSubtyping> appView, IRCode code, DebugLocalInfo local) { TypeLatticeElement type = TypeLatticeElement.stringClassType(appView, definitelyNotNull()); @@ -1040,6 +1087,11 @@ } @Override + public DexType getType(DexItemFactory factory) { + return factory.stringType; + } + + @Override public ConstInstruction asConstInstruction( AppView<? extends AppInfoWithSubtyping> appView, IRCode code, DebugLocalInfo local) { TypeLatticeElement type = TypeLatticeElement.stringClassType(appView, definitelyNotNull()); @@ -1195,6 +1247,11 @@ } @Override + public DexType getType(DexItemFactory factory) { + throw new Unreachable(); + } + + @Override public Object getBoxedValue() { throw new Unreachable("No boxed value for DexValueArray"); } @@ -1265,6 +1322,11 @@ } @Override + public DexType getType(DexItemFactory factory) { + throw new Unreachable(); + } + + @Override public Object getBoxedValue() { throw new Unreachable("No boxed value for DexValueAnnotation"); } @@ -1315,6 +1377,11 @@ } @Override + public DexType getType(DexItemFactory factory) { + throw new Unreachable(); + } + + @Override public long getRawValue() { return 0; } @@ -1391,6 +1458,11 @@ } @Override + public DexType getType(DexItemFactory factory) { + return factory.booleanType; + } + + @Override public long getRawValue() { return BooleanUtils.longValue(value); }
diff --git a/src/main/java/com/android/tools/r8/graph/FinalInitClassLens.java b/src/main/java/com/android/tools/r8/graph/FinalInitClassLens.java new file mode 100644 index 0000000..4f0a854 --- /dev/null +++ b/src/main/java/com/android/tools/r8/graph/FinalInitClassLens.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.graph; + +import com.android.tools.r8.errors.Unreachable; +import java.util.Map; + +public class FinalInitClassLens extends InitClassLens { + + private final Map<DexType, DexField> mapping; + + FinalInitClassLens(Map<DexType, DexField> mapping) { + this.mapping = mapping; + } + + @Override + public DexField getInitClassField(DexType type) { + DexField field = mapping.get(type); + if (field != null) { + return field; + } + throw new Unreachable("Unexpected InitClass instruction for `" + type.toSourceString() + "`"); + } + + @Override + public boolean isFinal() { + return true; + } +}
diff --git a/src/main/java/com/android/tools/r8/graph/IndexedDexItem.java b/src/main/java/com/android/tools/r8/graph/IndexedDexItem.java index 68a0e5d..0c115e1 100644 --- a/src/main/java/com/android/tools/r8/graph/IndexedDexItem.java +++ b/src/main/java/com/android/tools/r8/graph/IndexedDexItem.java
@@ -6,13 +6,8 @@ import com.android.tools.r8.dex.IndexedItemCollection; import com.android.tools.r8.dex.MixedSectionCollection; -/** - * Subset of dex items that are referenced by some table index. - */ -public abstract class IndexedDexItem extends CachedHashValueDexItem implements Presorted { - - private static final int SORTED_INDEX_UNKNOWN = -1; - private int sortedIndex = SORTED_INDEX_UNKNOWN; // assigned globally after reading. +/** Subset of dex items that are referenced by some table index. */ +public abstract class IndexedDexItem extends CachedHashValueDexItem { @Override public abstract void collectIndexedItems(IndexedItemCollection indexedItems, @@ -29,32 +24,7 @@ // Partial implementation of PresortedComparable. @Override - final public void setSortedIndex(int sortedIndex) { - assert sortedIndex > SORTED_INDEX_UNKNOWN; - assert this.sortedIndex == SORTED_INDEX_UNKNOWN; - this.sortedIndex = sortedIndex; - } - - @Override - final public int getSortedIndex() { - return sortedIndex; - } - - @Override - final public int sortedCompareTo(int other) { - assert sortedIndex > SORTED_INDEX_UNKNOWN - : "sortedIndex <= SORTED_INDEX_UKNOWN for: " + this.toString(); - assert other > SORTED_INDEX_UNKNOWN : "other < SORTED_INDEX_UKNOWN for: " + this.toString(); - return Integer.compare(sortedIndex, other); - } - - @Override public void flushCachedValues() { super.flushCachedValues(); - resetSortedIndex(); - } - - public void resetSortedIndex() { - sortedIndex = SORTED_INDEX_UNKNOWN; } }
diff --git a/src/main/java/com/android/tools/r8/graph/InitClassLens.java b/src/main/java/com/android/tools/r8/graph/InitClassLens.java new file mode 100644 index 0000000..cac3870 --- /dev/null +++ b/src/main/java/com/android/tools/r8/graph/InitClassLens.java
@@ -0,0 +1,39 @@ +// 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; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public abstract class InitClassLens { + + public static Builder builder() { + return new Builder(); + } + + public static DefaultInitClassLens getDefault() { + return DefaultInitClassLens.getInstance(); + } + + public abstract DexField getInitClassField(DexType clazz); + + public boolean isFinal() { + return false; + } + + public static class Builder { + + private final Map<DexType, DexField> mapping = new ConcurrentHashMap<>(); + + public void map(DexType type, DexField field) { + assert field.holder == type; + mapping.put(type, field); + } + + public FinalInitClassLens build() { + return new FinalInitClassLens(mapping); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java new file mode 100644 index 0000000..7800d81a --- /dev/null +++ b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
@@ -0,0 +1,300 @@ +// 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; + +import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.PredicateUtils; +import com.android.tools.r8.utils.TraversalContinuation; +import com.google.common.base.MoreObjects; +import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; + +public class MethodArrayBacking extends MethodCollectionBacking { + + private DexEncodedMethod[] directMethods = DexEncodedMethod.EMPTY_ARRAY; + private DexEncodedMethod[] virtualMethods = DexEncodedMethod.EMPTY_ARRAY; + + private boolean belongsToDirectPool(DexEncodedMethod method) { + return method.accessFlags.isStatic() + || method.accessFlags.isPrivate() + || method.accessFlags.isConstructor(); + } + + private boolean belongsToVirtualPool(DexEncodedMethod method) { + return !belongsToDirectPool(method); + } + + int size() { + return directMethods.length + virtualMethods.length; + } + + @Override + TraversalContinuation traverse(Function<DexEncodedMethod, TraversalContinuation> fn) { + for (DexEncodedMethod method : directMethods) { + TraversalContinuation stepResult = fn.apply(method); + if (stepResult.shouldBreak()) { + return stepResult; + } + } + for (DexEncodedMethod method : virtualMethods) { + TraversalContinuation stepResult = fn.apply(method); + if (stepResult.shouldBreak()) { + return stepResult; + } + } + return TraversalContinuation.CONTINUE; + } + + public Iterable<DexEncodedMethod> methods() { + return Iterables.concat(Arrays.asList(directMethods), Arrays.asList(virtualMethods)); + } + + List<DexEncodedMethod> directMethods() { + assert directMethods != null; + if (InternalOptions.assertionsEnabled()) { + return Collections.unmodifiableList(Arrays.asList(directMethods)); + } + return Arrays.asList(directMethods); + } + + void appendDirectMethod(DexEncodedMethod method) { + DexEncodedMethod[] newMethods = new DexEncodedMethod[directMethods.length + 1]; + System.arraycopy(directMethods, 0, newMethods, 0, directMethods.length); + newMethods[directMethods.length] = method; + directMethods = newMethods; + assert verifyNoDuplicateMethods(); + } + + void appendDirectMethods(Collection<DexEncodedMethod> methods) { + DexEncodedMethod[] newMethods = new DexEncodedMethod[directMethods.length + methods.size()]; + System.arraycopy(directMethods, 0, newMethods, 0, directMethods.length); + int i = directMethods.length; + for (DexEncodedMethod method : methods) { + newMethods[i] = method; + i++; + } + directMethods = newMethods; + assert verifyNoDuplicateMethods(); + } + + private void removeDirectMethod(int index) { + DexEncodedMethod[] newMethods = new DexEncodedMethod[directMethods.length - 1]; + System.arraycopy(directMethods, 0, newMethods, 0, index); + System.arraycopy(directMethods, index + 1, newMethods, index, directMethods.length - index - 1); + directMethods = newMethods; + } + + void removeDirectMethod(DexMethod method) { + int index = -1; + for (int i = 0; i < directMethods.length; i++) { + if (method.match(directMethods[i])) { + index = i; + break; + } + } + if (index >= 0) { + removeDirectMethod(index); + } + } + + void setDirectMethod(int index, DexEncodedMethod method) { + directMethods[index] = method; + assert verifyNoDuplicateMethods(); + } + + void setDirectMethods(DexEncodedMethod[] methods) { + directMethods = MoreObjects.firstNonNull(methods, DexEncodedMethod.EMPTY_ARRAY); + assert verifyNoDuplicateMethods(); + } + + List<DexEncodedMethod> virtualMethods() { + assert virtualMethods != null; + if (InternalOptions.assertionsEnabled()) { + return Collections.unmodifiableList(Arrays.asList(virtualMethods)); + } + return Arrays.asList(virtualMethods); + } + + void appendVirtualMethod(DexEncodedMethod method) { + DexEncodedMethod[] newMethods = new DexEncodedMethod[virtualMethods.length + 1]; + System.arraycopy(virtualMethods, 0, newMethods, 0, virtualMethods.length); + newMethods[virtualMethods.length] = method; + virtualMethods = newMethods; + assert verifyNoDuplicateMethods(); + } + + void appendVirtualMethods(Collection<DexEncodedMethod> methods) { + DexEncodedMethod[] newMethods = new DexEncodedMethod[virtualMethods.length + methods.size()]; + System.arraycopy(virtualMethods, 0, newMethods, 0, virtualMethods.length); + int i = virtualMethods.length; + for (DexEncodedMethod method : methods) { + newMethods[i] = method; + i++; + } + virtualMethods = newMethods; + assert verifyNoDuplicateMethods(); + } + + void setVirtualMethods(DexEncodedMethod[] methods) { + virtualMethods = MoreObjects.firstNonNull(methods, DexEncodedMethod.EMPTY_ARRAY); + assert verifyNoDuplicateMethods(); + } + + void virtualizeMethods(Set<DexEncodedMethod> privateInstanceMethods) { + int vLen = virtualMethods.length; + int dLen = directMethods.length; + int mLen = privateInstanceMethods.size(); + assert mLen <= dLen; + + DexEncodedMethod[] newDirectMethods = new DexEncodedMethod[dLen - mLen]; + int index = 0; + for (int i = 0; i < dLen; i++) { + DexEncodedMethod encodedMethod = directMethods[i]; + if (!privateInstanceMethods.contains(encodedMethod)) { + newDirectMethods[index++] = encodedMethod; + } + } + assert index == dLen - mLen; + setDirectMethods(newDirectMethods); + + DexEncodedMethod[] newVirtualMethods = new DexEncodedMethod[vLen + mLen]; + System.arraycopy(virtualMethods, 0, newVirtualMethods, 0, vLen); + index = vLen; + for (DexEncodedMethod encodedMethod : privateInstanceMethods) { + newVirtualMethods[index++] = encodedMethod; + } + setVirtualMethods(newVirtualMethods); + } + + DexEncodedMethod getDirectMethod(DexMethod method) { + for (DexEncodedMethod directMethod : directMethods) { + if (method.match(directMethod)) { + return directMethod; + } + } + return null; + } + + DexEncodedMethod getDirectMethod(Predicate<DexEncodedMethod> predicate) { + return PredicateUtils.findFirst(directMethods, predicate); + } + + DexEncodedMethod getVirtualMethod(DexMethod method) { + for (DexEncodedMethod virtualMethod : virtualMethods) { + if (method.match(virtualMethod)) { + return virtualMethod; + } + } + return null; + } + + DexEncodedMethod getVirtualMethod(Predicate<DexEncodedMethod> predicate) { + return PredicateUtils.findFirst(virtualMethods, predicate); + } + + DexEncodedMethod getMethod(DexMethod method) { + DexEncodedMethod result = getDirectMethod(method); + return result == null ? getVirtualMethod(method) : result; + } + + void addMethod(DexEncodedMethod method) { + if (method.accessFlags.isStatic() + || method.accessFlags.isPrivate() + || method.accessFlags.isConstructor()) { + addDirectMethod(method); + } else { + addVirtualMethod(method); + } + } + + void addVirtualMethod(DexEncodedMethod virtualMethod) { + assert !virtualMethod.accessFlags.isStatic(); + assert !virtualMethod.accessFlags.isPrivate(); + assert !virtualMethod.accessFlags.isConstructor(); + virtualMethods = Arrays.copyOf(virtualMethods, virtualMethods.length + 1); + virtualMethods[virtualMethods.length - 1] = virtualMethod; + } + + void addDirectMethod(DexEncodedMethod staticMethod) { + assert staticMethod.accessFlags.isStatic() + || staticMethod.accessFlags.isPrivate() + || staticMethod.accessFlags.isConstructor(); + directMethods = Arrays.copyOf(directMethods, directMethods.length + 1); + directMethods[directMethods.length - 1] = staticMethod; + } + + boolean verifyNoDuplicateMethods() { + Set<DexMethod> unique = Sets.newIdentityHashSet(); + forEachMethod( + method -> { + boolean changed = unique.add(method.method); + assert changed : "Duplicate method `" + method.method.toSourceString() + "`"; + }); + return true; + } + + public DexEncodedMethod replaceDirectMethod( + DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) { + for (int i = 0; i < directMethods.length; i++) { + DexEncodedMethod directMethod = directMethods[i]; + if (method.match(directMethod)) { + DexEncodedMethod newMethod = replacement.apply(directMethod); + assert belongsToDirectPool(newMethod); + directMethods[i] = newMethod; + return newMethod; + } + } + return null; + } + + public DexEncodedMethod replaceDirectMethodWithVirtualMethod( + DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) { + for (int i = 0; i < directMethods.length; i++) { + DexEncodedMethod directMethod = directMethods[i]; + if (method.match(directMethod)) { + DexEncodedMethod newMethod = replacement.apply(directMethod); + assert belongsToVirtualPool(newMethod); + removeDirectMethod(i); + appendVirtualMethod(newMethod); + return newMethod; + } + } + return null; + } + + public void replaceMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) { + replaceDirectMethods(replacement); + replaceVirtualMethods(replacement); + } + + public void replaceDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) { + for (int i = 0; i < directMethods.length; i++) { + DexEncodedMethod method = directMethods[i]; + DexEncodedMethod newMethod = replacement.apply(method); + assert newMethod != null; + if (method != newMethod) { + assert belongsToDirectPool(newMethod); + directMethods[i] = newMethod; + } + } + } + + public void replaceVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) { + for (int i = 0; i < virtualMethods.length; i++) { + DexEncodedMethod method = virtualMethods[i]; + DexEncodedMethod newMethod = replacement.apply(method); + if (method != newMethod) { + assert belongsToVirtualPool(newMethod); + virtualMethods[i] = newMethod; + } + } + } +}
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollection.java b/src/main/java/com/android/tools/r8/graph/MethodCollection.java new file mode 100644 index 0000000..a4f3d5a --- /dev/null +++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -0,0 +1,221 @@ +package com.android.tools.r8.graph; + +import com.android.tools.r8.utils.IterableUtils; +import com.android.tools.r8.utils.TraversalContinuation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; + +public class MethodCollection { + + private final DexClass holder; + private final MethodArrayBacking backing = new MethodArrayBacking(); + private Optional<DexEncodedMethod> cachedClassInitializer = null; + + public MethodCollection(DexClass holder) { + this.holder = holder; + } + + public int size() { + return backing.size(); + } + + public TraversalContinuation traverse(Function<DexEncodedMethod, TraversalContinuation> fn) { + return backing.traverse(fn); + } + + public void forEachMethod(Consumer<DexEncodedMethod> consumer) { + backing.forEachMethod(consumer); + } + + public Iterable<DexEncodedMethod> methods() { + return backing.methods(); + } + + public Iterable<DexEncodedMethod> methods(Predicate<DexEncodedMethod> predicate) { + return IterableUtils.filter(methods(), predicate); + } + + public List<DexEncodedMethod> allMethodsSorted() { + List<DexEncodedMethod> sorted = new ArrayList<>(size()); + forEachMethod(sorted::add); + sorted.sort((a, b) -> a.method.slowCompareTo(b.method)); + return sorted; + } + + public List<DexEncodedMethod> directMethods() { + return backing.directMethods(); + } + + public List<DexEncodedMethod> virtualMethods() { + return backing.virtualMethods(); + } + + public DexEncodedMethod getMethod(DexMethod method) { + return backing.getMethod(method); + } + + public DexEncodedMethod getDirectMethod(DexMethod method) { + return backing.getDirectMethod(method); + } + + public DexEncodedMethod getDirectMethod(Predicate<DexEncodedMethod> predicate) { + return backing.getDirectMethod(predicate); + } + + public DexEncodedMethod getVirtualMethod(DexMethod method) { + return backing.getVirtualMethod(method); + } + + public DexEncodedMethod getVirtualMethod(Predicate<DexEncodedMethod> predicate) { + return backing.getVirtualMethod(predicate); + } + + public DexEncodedMethod getClassInitializer() { + if (cachedClassInitializer == null) { + cachedClassInitializer = Optional.empty(); + for (DexEncodedMethod directMethod : directMethods()) { + if (directMethod.isClassInitializer()) { + cachedClassInitializer = Optional.of(directMethod); + break; + } + } + } + return cachedClassInitializer.orElse(null); + } + + public void addMethod(DexEncodedMethod method) { + backing.addMethod(method); + } + + public void addVirtualMethod(DexEncodedMethod virtualMethod) { + backing.addVirtualMethod(virtualMethod); + } + + public void addDirectMethod(DexEncodedMethod directMethod) { + cachedClassInitializer = null; + backing.addDirectMethod(directMethod); + } + + public DexEncodedMethod replaceDirectMethod( + DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) { + cachedClassInitializer = null; + return backing.replaceDirectMethod(method, replacement); + } + + public void replaceMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) { + backing.replaceMethods(replacement); + } + + public void replaceVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) { + backing.replaceVirtualMethods(replacement); + } + + public void replaceDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) { + cachedClassInitializer = null; + backing.replaceDirectMethods(replacement); + } + + /** + * Replace a direct method, if found, by a computed virtual method using the replacement function. + * + * @param method Direct method to replace if present. + * @param replacement Replacement function computing the virtual replacement. + * @return Returns the replacement if found, null otherwise. + */ + public DexEncodedMethod replaceDirectMethodWithVirtualMethod( + DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) { + // The class initializer can never by converted to a virtual. + return backing.replaceDirectMethodWithVirtualMethod(method, replacement); + } + + public void appendDirectMethod(DexEncodedMethod method) { + assert verifyCorrectnessOfMethodHolder(method); + cachedClassInitializer = null; + backing.appendDirectMethod(method); + } + + public void appendDirectMethods(Collection<DexEncodedMethod> methods) { + assert verifyCorrectnessOfMethodHolders(methods); + cachedClassInitializer = null; + backing.appendDirectMethods(methods); + } + + public void removeDirectMethod(DexMethod method) { + cachedClassInitializer = null; + backing.removeDirectMethod(method); + } + + public void setDirectMethods(DexEncodedMethod[] methods) { + assert verifyCorrectnessOfMethodHolders(methods); + cachedClassInitializer = null; + backing.setDirectMethods(methods); + } + + public void appendVirtualMethod(DexEncodedMethod method) { + assert verifyCorrectnessOfMethodHolder(method); + backing.appendVirtualMethod(method); + } + + public void appendVirtualMethods(Collection<DexEncodedMethod> methods) { + assert verifyCorrectnessOfMethodHolders(methods); + backing.appendVirtualMethods(methods); + } + + public void setVirtualMethods(DexEncodedMethod[] methods) { + assert verifyCorrectnessOfMethodHolders(methods); + backing.setVirtualMethods(methods); + } + + public void virtualizeMethods(Set<DexEncodedMethod> privateInstanceMethods) { + backing.virtualizeMethods(privateInstanceMethods); + } + + public boolean hasAnnotations() { + return traverse( + method -> + method.hasAnnotation() + ? TraversalContinuation.BREAK + : TraversalContinuation.CONTINUE) + .shouldBreak(); + } + + public boolean verify() { + forEachMethod( + method -> { + assert verifyCorrectnessOfMethodHolder(method); + }); + assert backing.verifyNoDuplicateMethods(); + return true; + } + + private boolean verifyCorrectnessOfMethodHolder(DexEncodedMethod method) { + assert method.method.holder == holder.type + : "Expected method `" + + method.method.toSourceString() + + "` to have holder `" + + holder.type.toSourceString() + + "`"; + return true; + } + + private boolean verifyCorrectnessOfMethodHolders(DexEncodedMethod[] methods) { + if (methods == null) { + return true; + } + return verifyCorrectnessOfMethodHolders(Arrays.asList(methods)); + } + + private boolean verifyCorrectnessOfMethodHolders(Iterable<DexEncodedMethod> methods) { + for (DexEncodedMethod method : methods) { + assert verifyCorrectnessOfMethodHolder(method); + } + return true; + } +}
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java new file mode 100644 index 0000000..b50adf6 --- /dev/null +++ b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
@@ -0,0 +1,21 @@ +// 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; + +import com.android.tools.r8.utils.TraversalContinuation; +import java.util.function.Consumer; +import java.util.function.Function; + +public abstract class MethodCollectionBacking { + + abstract TraversalContinuation traverse(Function<DexEncodedMethod, TraversalContinuation> fn); + + void forEachMethod(Consumer<DexEncodedMethod> fn) { + traverse( + method -> { + fn.accept(method); + return TraversalContinuation.CONTINUE; + }); + } +}
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java index a29ae60..33f3b90 100644 --- a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java +++ b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
@@ -5,14 +5,16 @@ import com.android.tools.r8.dex.Constants; import com.android.tools.r8.errors.CompilationError; +import com.android.tools.r8.naming.NamingLens; import it.unimi.dsi.fastutil.objects.Reference2IntLinkedOpenHashMap; import it.unimi.dsi.fastutil.objects.Reference2IntMap; import it.unimi.dsi.fastutil.objects.Reference2IntMap.Entry; import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.List; -import java.util.Map; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -21,18 +23,25 @@ private final static int NOT_FOUND = -1; private final static int NOT_SET = -2; + private final NamingLens namingLens; + private final InitClassLens initClassLens; + + // Sorted collection of objects mapped to their offsets. private final DexProgramClass[] classes; - private final Reference2IntMap<DexProto> protos; - private final Reference2IntMap<DexType> types; - private final Reference2IntMap<DexMethod> methods; - private final Reference2IntMap<DexField> fields; - private final Reference2IntMap<DexString> strings; - private final Reference2IntMap<DexCallSite> callSites; - private final Reference2IntMap<DexMethodHandle> methodHandles; + private final Reference2IntLinkedOpenHashMap<DexProto> protos; + private final Reference2IntLinkedOpenHashMap<DexType> types; + private final Reference2IntLinkedOpenHashMap<DexMethod> methods; + private final Reference2IntLinkedOpenHashMap<DexField> fields; + private final Reference2IntLinkedOpenHashMap<DexString> strings; + private final Reference2IntLinkedOpenHashMap<DexCallSite> callSites; + private final Reference2IntLinkedOpenHashMap<DexMethodHandle> methodHandles; + private DexString firstJumboString; public ObjectToOffsetMapping( DexApplication application, + NamingLens namingLens, + InitClassLens initClassLens, Collection<DexProgramClass> classes, Collection<DexProto> protos, Collection<DexType> types, @@ -50,15 +59,21 @@ assert strings != null; assert callSites != null; assert methodHandles != null; + assert initClassLens != null; + this.namingLens = namingLens; + this.initClassLens = initClassLens; + this.classes = sortClasses(application, classes, namingLens); + this.protos = createSortedMap(protos, compare(namingLens), this::failOnOverflow); + this.types = createSortedMap(types, compare(namingLens), this::failOnOverflow); + this.methods = createSortedMap(methods, compare(namingLens), this::failOnOverflow); + this.fields = createSortedMap(fields, compare(namingLens), this::failOnOverflow); + this.strings = createSortedMap(strings, compare(namingLens), this::setFirstJumboString); + this.callSites = createSortedMap(callSites, DexCallSite::compareTo, this::failOnOverflow); + this.methodHandles = createSortedMap(methodHandles, compare(namingLens), this::failOnOverflow); + } - this.classes = sortClasses(application, classes); - this.protos = createMap(protos, this::failOnOverflow); - this.types = createMap(types, this::failOnOverflow); - this.methods = createMap(methods, this::failOnOverflow); - this.fields = createMap(fields, this::failOnOverflow); - this.strings = createMap(strings, this::setFirstJumboString); - this.callSites = createMap(callSites, this::failOnOverflow); - this.methodHandles = createMap(methodHandles, this::failOnOverflow); + private static <T extends PresortedComparable<T>> Comparator<T> compare(NamingLens namingLens) { + return (a, b) -> a.slowCompareTo(b, namingLens); } private void setFirstJumboString(DexString string) { @@ -70,14 +85,16 @@ throw new CompilationError("Index overflow for " + item.getClass()); } - private <T extends IndexedDexItem> Reference2IntMap<T> createMap(Collection<T> items, - Consumer<T> onUInt16Overflow) { + private <T> Reference2IntLinkedOpenHashMap<T> createSortedMap( + Collection<T> items, Comparator<T> comparator, Consumer<T> onUInt16Overflow) { if (items.isEmpty()) { return null; } - Reference2IntMap<T> map = new Reference2IntLinkedOpenHashMap<>(items.size()); + // Sort items and compute the offset mapping for each in sorted order. + ArrayList<T> sorted = new ArrayList<>(items); + sorted.sort(comparator); + Reference2IntLinkedOpenHashMap<T> map = new Reference2IntLinkedOpenHashMap<>(items.size()); map.defaultReturnValue(NOT_FOUND); - Collection<T> sorted = items.stream().sorted().collect(Collectors.toList()); int index = 0; for (T item : sorted) { if (index == Constants.U16BIT_MAX + 1) { @@ -135,26 +152,30 @@ } private static DexProgramClass[] sortClasses( - DexApplication application, Collection<DexProgramClass> classes) { + DexApplication application, Collection<DexProgramClass> classes, NamingLens namingLens) { // Collect classes in subtyping order, based on a sorted list of classes to start with. ProgramClassDepthsMemoized classDepths = new ProgramClassDepthsMemoized(application); List<DexProgramClass> sortedClasses = - classes - .stream() + classes.stream() .sorted( (x, y) -> { int dx = classDepths.getDepth(x); int dy = classDepths.getDepth(y); - return dx != dy ? dx - dy : x.type.compareTo(y.type); + return dx != dy ? dx - dy : x.type.slowCompareTo(y.type, namingLens); }) .collect(Collectors.toList()); return sortedClasses.toArray(DexProgramClass.EMPTY_ARRAY); } - private static <T> Collection<T> keysOrEmpty(Map<T, ?> map) { + private static <T> Collection<T> keysOrEmpty(Reference2IntLinkedOpenHashMap<T> map) { + // The key-set is deterministic (linked) and inserted in sorted order. return map == null ? Collections.emptyList() : map.keySet(); } + public NamingLens getNamingLens() { + return namingLens; + } + public Collection<DexMethod> getMethods() { return keysOrEmpty(methods); } @@ -238,4 +259,8 @@ public int getOffsetFor(DexMethodHandle methodHandle) { return getOffsetFor(methodHandle, methodHandles); } + + public DexField getClinitField(DexType type) { + return initClassLens.getInitClassField(type); + } }
diff --git a/src/main/java/com/android/tools/r8/graph/Presorted.java b/src/main/java/com/android/tools/r8/graph/Presorted.java deleted file mode 100644 index 475da13..0000000 --- a/src/main/java/com/android/tools/r8/graph/Presorted.java +++ /dev/null
@@ -1,16 +0,0 @@ -// Copyright (c) 2016, 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; - -/** - * Interface for capturing presorted behavior for Dex items. - */ -public interface Presorted { - - void setSortedIndex(int sortedIndex); - - int getSortedIndex(); - - int sortedCompareTo(int other); -}
diff --git a/src/main/java/com/android/tools/r8/graph/PresortedComparable.java b/src/main/java/com/android/tools/r8/graph/PresortedComparable.java index 075c187..e4eb4b7 100644 --- a/src/main/java/com/android/tools/r8/graph/PresortedComparable.java +++ b/src/main/java/com/android/tools/r8/graph/PresortedComparable.java
@@ -4,41 +4,11 @@ package com.android.tools.r8.graph; import com.android.tools.r8.naming.NamingLens; -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; -public interface PresortedComparable<T> extends Presorted, Comparable<T> { +public interface PresortedComparable<T> { - static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> boolean isSorted( - List<? extends DexEncodedMember<D, R>> items) { - return isSorted(items, DexEncodedMember::toReference); - } - - static <S, T extends Comparable<T>> boolean isSorted(S[] items, Function<S, T> getter) { - return isSorted(Arrays.asList(items), getter); - } - - static <S, T extends Comparable<T>> boolean isSorted( - List<? extends S> items, Function<S, T> getter) { - T current = null; - for (S item : items) { - T next = getter.apply(item); - if (current != null && current.compareTo(next) >= 0) { - return false; - } - current = next; - } - return true; - } - - // Slow comparison methods that make no use of indices for comparisons. These are used - // for sorting operations when reading dex files. int slowCompareTo(T other); int slowCompareTo(T other, NamingLens namingLens); - // Layered comparison methods that make use of indices for subpart comparisons. These rely - // on subparts already being sorted and having indices assigned. - int layeredCompareTo(T other, NamingLens namingLens); static <T extends PresortedComparable<T>> int slowCompare(T a, T b) { return a.slowCompareTo(b);
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java index f9cea40..5c1611d 100644 --- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java +++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -410,14 +410,11 @@ // TODO(b/148769279): Remove the check for hasInstantiatedLambdas. Box<Boolean> hasInstantiatedLambdas = new Box<>(false); InstantiatedSubTypeInfo instantiatedSubTypeInfo = - refinedReceiverLowerBound == null - ? instantiatedSubTypeInfoWithoutLowerBound( - appInfo, refinedReceiverUpperBound, hasInstantiatedLambdas) - : instantiatedSubTypeInfoWithLowerBound( - appInfo, - refinedReceiverUpperBound, - refinedReceiverLowerBound, - hasInstantiatedLambdas); + instantiatedSubTypeInfoForInstantiatedType( + appInfo, + refinedReceiverUpperBound, + refinedReceiverLowerBound, + hasInstantiatedLambdas); LookupResult lookupResult = lookupVirtualDispatchTargets( context, @@ -430,37 +427,28 @@ return lookupResult; } - private InstantiatedSubTypeInfo instantiatedSubTypeInfoWithoutLowerBound( - AppInfoWithLiveness appInfo, - DexProgramClass refinedReceiverUpperBound, - Box<Boolean> hasInstantiatedLambdas) { - return (type, subTypeConsumer, callSiteConsumer) -> { - appInfo.forEachInstantiatedSubType( - refinedReceiverUpperBound.type, - subType -> { - if (appInfo.hasAnyInstantiatedLambdas(subType)) { - hasInstantiatedLambdas.set(true); - } - subTypeConsumer.accept(subType); - }, - callSiteConsumer); - }; - } - - private InstantiatedSubTypeInfo instantiatedSubTypeInfoWithLowerBound( + private InstantiatedSubTypeInfo instantiatedSubTypeInfoForInstantiatedType( AppInfoWithLiveness appInfo, DexProgramClass refinedReceiverUpperBound, DexProgramClass refinedReceiverLowerBound, Box<Boolean> hasInstantiatedLambdas) { - return (type, subTypeConsumer, callSiteConsumer) -> { - List<DexProgramClass> subTypes = - appInfo.computeProgramClassRelationChain( - refinedReceiverLowerBound, refinedReceiverUpperBound); - for (DexProgramClass subType : subTypes) { - if (appInfo.hasAnyInstantiatedLambdas(subType)) { - hasInstantiatedLambdas.set(true); - } - subTypeConsumer.accept(subType); + return (ignored, subTypeConsumer, callSiteConsumer) -> { + Consumer<DexProgramClass> lambdaInstantiatedConsumer = + subType -> { + subTypeConsumer.accept(subType); + if (appInfo.hasAnyInstantiatedLambdas(subType)) { + hasInstantiatedLambdas.set(true); + } + }; + if (refinedReceiverLowerBound == null) { + appInfo.forEachInstantiatedSubType( + refinedReceiverUpperBound.type, lambdaInstantiatedConsumer, callSiteConsumer); + } else { + appInfo.forEachInstantiatedSubTypeInChain( + refinedReceiverUpperBound, + refinedReceiverLowerBound, + lambdaInstantiatedConsumer, + callSiteConsumer); } }; }
diff --git a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java index 663524d..dd2cde4 100644 --- a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java +++ b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
@@ -4,11 +4,9 @@ package com.android.tools.r8.graph; -import com.android.tools.r8.ir.analysis.type.TypeLatticeElement; import com.android.tools.r8.ir.code.ConstInstruction; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.Position; -import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.BooleanUtils; import com.google.common.collect.Ordering; @@ -150,28 +148,6 @@ return newType == appView.dexItemFactory().voidType; } - public boolean defaultValueHasChanged() { - if (newType.isPrimitiveType()) { - if (oldType.isPrimitiveType()) { - return ValueType.fromDexType(newType) != ValueType.fromDexType(oldType); - } - return true; - } else if (oldType.isPrimitiveType()) { - return true; - } - // All reference types uses null as default value. - assert newType.isReferenceType(); - assert oldType.isReferenceType(); - return false; - } - - public TypeLatticeElement defaultValueLatticeElement(AppView<?> appView) { - if (newType.isPrimitiveType()) { - return TypeLatticeElement.fromDexType(newType, null, appView); - } - return TypeLatticeElement.getNull(); - } - @Override public boolean isRewrittenTypeInfo() { return true;
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 f452556..2c27763 100644 --- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java +++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -25,6 +25,8 @@ this.factory = factory; } + public abstract boolean registerInitClass(DexType type); + public abstract boolean registerInvokeVirtual(DexMethod method); public abstract boolean registerInvokeDirect(DexMethod method);
diff --git a/src/main/java/com/android/tools/r8/inspector/BooleanValueInspector.java b/src/main/java/com/android/tools/r8/inspector/BooleanValueInspector.java new file mode 100644 index 0000000..1fba365 --- /dev/null +++ b/src/main/java/com/android/tools/r8/inspector/BooleanValueInspector.java
@@ -0,0 +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.inspector; + +public interface BooleanValueInspector extends ValueInspector { + boolean getBooleanValue(); +}
diff --git a/src/main/java/com/android/tools/r8/inspector/ByteValueInspector.java b/src/main/java/com/android/tools/r8/inspector/ByteValueInspector.java new file mode 100644 index 0000000..718183e --- /dev/null +++ b/src/main/java/com/android/tools/r8/inspector/ByteValueInspector.java
@@ -0,0 +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.inspector; + +public interface ByteValueInspector extends ValueInspector { + byte getByteValue(); +}
diff --git a/src/main/java/com/android/tools/r8/inspector/CharValueInspector.java b/src/main/java/com/android/tools/r8/inspector/CharValueInspector.java new file mode 100644 index 0000000..d79ee9c --- /dev/null +++ b/src/main/java/com/android/tools/r8/inspector/CharValueInspector.java
@@ -0,0 +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.inspector; + +public interface CharValueInspector extends ValueInspector { + char getCharValue(); +}
diff --git a/src/main/java/com/android/tools/r8/inspector/ClassInspector.java b/src/main/java/com/android/tools/r8/inspector/ClassInspector.java new file mode 100644 index 0000000..b18dd26 --- /dev/null +++ b/src/main/java/com/android/tools/r8/inspector/ClassInspector.java
@@ -0,0 +1,22 @@ +// 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.inspector; + +import com.android.tools.r8.Keep; +import com.android.tools.r8.references.ClassReference; +import java.util.function.Consumer; + +/** Inspector for a class or interface definition. */ +@Keep +public interface ClassInspector { + + /** Get the class reference for the class of this inspector. */ + ClassReference getClassReference(); + + /** Iterate all fields declared in the class/interface (unspecified order). */ + void forEachField(Consumer<FieldInspector> inspection); + + /** Iterate all methods declared in the class/interface (unspecified order). */ + void forEachMethod(Consumer<MethodInspector> inspection); +}
diff --git a/src/main/java/com/android/tools/r8/inspector/DoubleValueInspector.java b/src/main/java/com/android/tools/r8/inspector/DoubleValueInspector.java new file mode 100644 index 0000000..334f7ac --- /dev/null +++ b/src/main/java/com/android/tools/r8/inspector/DoubleValueInspector.java
@@ -0,0 +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.inspector; + +public interface DoubleValueInspector extends ValueInspector { + double getDoubleValue(); +}
diff --git a/src/main/java/com/android/tools/r8/inspector/FieldInspector.java b/src/main/java/com/android/tools/r8/inspector/FieldInspector.java new file mode 100644 index 0000000..5d2e656 --- /dev/null +++ b/src/main/java/com/android/tools/r8/inspector/FieldInspector.java
@@ -0,0 +1,30 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.inspector; + +import com.android.tools.r8.Keep; +import com.android.tools.r8.references.FieldReference; +import java.util.Optional; + +/** Inspector for a field definition. */ +@Keep +public interface FieldInspector { + + /** Get the field reference for the field of this inspector. */ + FieldReference getFieldReference(); + + /** True if the field is declared static. */ + boolean isStatic(); + + /** True if the field is declared final. */ + boolean isFinal(); + + /** + * Returns an inspector for the initial value if it is known by the compiler. + * + * <p>Note that the determination of the value is best effort, often the value will simply be the + * default value for the given type. + */ + Optional<ValueInspector> getInitialValue(); +}
diff --git a/src/main/java/com/android/tools/r8/inspector/FloatValueInspector.java b/src/main/java/com/android/tools/r8/inspector/FloatValueInspector.java new file mode 100644 index 0000000..cb4c12d --- /dev/null +++ b/src/main/java/com/android/tools/r8/inspector/FloatValueInspector.java
@@ -0,0 +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.inspector; + +public interface FloatValueInspector extends ValueInspector { + float getFloatValue(); +}
diff --git a/src/main/java/com/android/tools/r8/inspector/Inspector.java b/src/main/java/com/android/tools/r8/inspector/Inspector.java new file mode 100644 index 0000000..880b450 --- /dev/null +++ b/src/main/java/com/android/tools/r8/inspector/Inspector.java
@@ -0,0 +1,15 @@ +// 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.inspector; + +import com.android.tools.r8.Keep; +import java.util.function.Consumer; + +/** Inspector providing access to various parts of an application. */ +@Keep +public interface Inspector { + + /** Iterate all classes and interfaces defined by the program (order unspecified). */ + void forEachClass(Consumer<ClassInspector> inspection); +}
diff --git a/src/main/java/com/android/tools/r8/inspector/IntValueInspector.java b/src/main/java/com/android/tools/r8/inspector/IntValueInspector.java new file mode 100644 index 0000000..38376c2 --- /dev/null +++ b/src/main/java/com/android/tools/r8/inspector/IntValueInspector.java
@@ -0,0 +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.inspector; + +public interface IntValueInspector extends ValueInspector { + int getIntValue(); +}
diff --git a/src/main/java/com/android/tools/r8/inspector/LongValueInspector.java b/src/main/java/com/android/tools/r8/inspector/LongValueInspector.java new file mode 100644 index 0000000..177a885 --- /dev/null +++ b/src/main/java/com/android/tools/r8/inspector/LongValueInspector.java
@@ -0,0 +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.inspector; + +public interface LongValueInspector extends ValueInspector { + long getLongValue(); +}
diff --git a/src/main/java/com/android/tools/r8/inspector/MethodInspector.java b/src/main/java/com/android/tools/r8/inspector/MethodInspector.java new file mode 100644 index 0000000..41cc8de --- /dev/null +++ b/src/main/java/com/android/tools/r8/inspector/MethodInspector.java
@@ -0,0 +1,15 @@ +// 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.inspector; + +import com.android.tools.r8.Keep; +import com.android.tools.r8.references.MethodReference; + +/** Inspector for a method definition. */ +@Keep +public interface MethodInspector { + + /** Get the method reference for the method of this inspector. */ + MethodReference getMethodReference(); +}
diff --git a/src/main/java/com/android/tools/r8/inspector/ShortValueInspector.java b/src/main/java/com/android/tools/r8/inspector/ShortValueInspector.java new file mode 100644 index 0000000..fb1d823 --- /dev/null +++ b/src/main/java/com/android/tools/r8/inspector/ShortValueInspector.java
@@ -0,0 +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.inspector; + +public interface ShortValueInspector extends ValueInspector { + short getShortValue(); +}
diff --git a/src/main/java/com/android/tools/r8/inspector/StringValueInspector.java b/src/main/java/com/android/tools/r8/inspector/StringValueInspector.java new file mode 100644 index 0000000..c0b78c4 --- /dev/null +++ b/src/main/java/com/android/tools/r8/inspector/StringValueInspector.java
@@ -0,0 +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.inspector; + +public interface StringValueInspector extends ValueInspector { + String getStringValue(); +}
diff --git a/src/main/java/com/android/tools/r8/inspector/ValueInspector.java b/src/main/java/com/android/tools/r8/inspector/ValueInspector.java new file mode 100644 index 0000000..84f1fe0 --- /dev/null +++ b/src/main/java/com/android/tools/r8/inspector/ValueInspector.java
@@ -0,0 +1,53 @@ +// 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.inspector; + +import com.android.tools.r8.Keep; +import com.android.tools.r8.references.TypeReference; + +/** Inspector for a JVM representable value. */ +@Keep +public interface ValueInspector { + + /** Get the type reference describing the type of the value. */ + TypeReference getTypeReference(); + + boolean isPrimitive(); + + boolean isBooleanValue(); + + boolean isByteValue(); + + boolean isCharValue(); + + boolean isShortValue(); + + boolean isIntValue(); + + boolean isLongValue(); + + boolean isFloatValue(); + + boolean isDoubleValue(); + + boolean isStringValue(); + + BooleanValueInspector asBooleanValue(); + + ByteValueInspector asByteValue(); + + CharValueInspector asCharValue(); + + ShortValueInspector asShortValue(); + + IntValueInspector asIntValue(); + + LongValueInspector asLongValue(); + + FloatValueInspector asFloatValue(); + + DoubleValueInspector asDoubleValue(); + + StringValueInspector asStringValue(); +}
diff --git a/src/main/java/com/android/tools/r8/inspector/internal/ClassInspectorImpl.java b/src/main/java/com/android/tools/r8/inspector/internal/ClassInspectorImpl.java new file mode 100644 index 0000000..c2ebc8e --- /dev/null +++ b/src/main/java/com/android/tools/r8/inspector/internal/ClassInspectorImpl.java
@@ -0,0 +1,40 @@ +// 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.inspector.internal; + +import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.inspector.ClassInspector; +import com.android.tools.r8.inspector.FieldInspector; +import com.android.tools.r8.inspector.MethodInspector; +import com.android.tools.r8.references.ClassReference; +import com.android.tools.r8.references.Reference; +import java.util.function.Consumer; + +public class ClassInspectorImpl implements ClassInspector { + + private final DexClass clazz; + private ClassReference reference = null; + + ClassInspectorImpl(DexClass clazz) { + this.clazz = clazz; + } + + @Override + public ClassReference getClassReference() { + if (reference == null) { + reference = Reference.classFromDescriptor(clazz.type.toDescriptorString()); + } + return reference; + } + + @Override + public void forEachField(Consumer<FieldInspector> inspection) { + clazz.forEachField(field -> inspection.accept(new FieldInspectorImpl(this, field))); + } + + @Override + public void forEachMethod(Consumer<MethodInspector> inspection) { + clazz.forEachMethod(method -> inspection.accept(new MethodInspectorImpl(this, method))); + } +}
diff --git a/src/main/java/com/android/tools/r8/inspector/internal/FieldInspectorImpl.java b/src/main/java/com/android/tools/r8/inspector/internal/FieldInspectorImpl.java new file mode 100644 index 0000000..056d652e --- /dev/null +++ b/src/main/java/com/android/tools/r8/inspector/internal/FieldInspectorImpl.java
@@ -0,0 +1,52 @@ +// 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.inspector.internal; + +import com.android.tools.r8.graph.DexEncodedField; +import com.android.tools.r8.inspector.FieldInspector; +import com.android.tools.r8.inspector.ValueInspector; +import com.android.tools.r8.references.FieldReference; +import com.android.tools.r8.references.Reference; +import java.util.Optional; + +public class FieldInspectorImpl implements FieldInspector { + private final ClassInspectorImpl parent; + private final DexEncodedField field; + private FieldReference reference = null; + + public FieldInspectorImpl(ClassInspectorImpl parent, DexEncodedField field) { + this.parent = parent; + this.field = field; + } + + @Override + public FieldReference getFieldReference() { + if (reference == null) { + reference = + Reference.field( + parent.getClassReference(), + field.field.name.toString(), + Reference.typeFromDescriptor(field.field.type.toDescriptorString())); + } + return reference; + } + + @Override + public boolean isStatic() { + return field.accessFlags.isStatic(); + } + + @Override + public boolean isFinal() { + return field.accessFlags.isFinal(); + } + + @Override + public Optional<ValueInspector> getInitialValue() { + if (field.isStatic() && field.getStaticValue() != null) { + return Optional.of(new ValueInspectorImpl(field.getStaticValue(), field.field.type)); + } + return Optional.empty(); + } +}
diff --git a/src/main/java/com/android/tools/r8/inspector/internal/InspectorImpl.java b/src/main/java/com/android/tools/r8/inspector/internal/InspectorImpl.java new file mode 100644 index 0000000..c3d8a64 --- /dev/null +++ b/src/main/java/com/android/tools/r8/inspector/internal/InspectorImpl.java
@@ -0,0 +1,54 @@ +// 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.inspector.internal; + +import com.android.tools.r8.graph.DexApplication; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.inspector.ClassInspector; +import com.android.tools.r8.inspector.Inspector; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; + +public class InspectorImpl implements Inspector { + + // This wrapping appears odd, but allows hooking in inspections on the impl type from tests. + public static List<Consumer<InspectorImpl>> wrapInspections( + Collection<Consumer<Inspector>> inspections) { + if (inspections == null || inspections.isEmpty()) { + return Collections.emptyList(); + } + List<Consumer<InspectorImpl>> wrapped = new ArrayList<>(inspections.size()); + for (Consumer<Inspector> inspection : inspections) { + wrapped.add(inspection::accept); + } + return wrapped; + } + + public static void runInspections( + List<Consumer<InspectorImpl>> inspections, DexApplication application) { + if (inspections == null || inspections.isEmpty()) { + return; + } + InspectorImpl inspector = new InspectorImpl(application); + for (Consumer<InspectorImpl> inspection : inspections) { + inspection.accept(inspector); + } + } + + private final DexApplication application; + + public InspectorImpl(DexApplication application) { + this.application = application; + } + + @Override + public void forEachClass(Consumer<ClassInspector> inspection) { + for (DexProgramClass clazz : application.classes()) { + inspection.accept(new ClassInspectorImpl(clazz)); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/inspector/internal/MethodInspectorImpl.java b/src/main/java/com/android/tools/r8/inspector/internal/MethodInspectorImpl.java new file mode 100644 index 0000000..1d868be --- /dev/null +++ b/src/main/java/com/android/tools/r8/inspector/internal/MethodInspectorImpl.java
@@ -0,0 +1,41 @@ +// 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.inspector.internal; + +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.inspector.MethodInspector; +import com.android.tools.r8.references.MethodReference; +import com.android.tools.r8.references.Reference; +import com.android.tools.r8.utils.ListUtils; +import java.util.Arrays; + +public class MethodInspectorImpl implements MethodInspector { + + private final ClassInspectorImpl parent; + private final DexEncodedMethod method; + private MethodReference reference; + + public MethodInspectorImpl(ClassInspectorImpl parent, DexEncodedMethod method) { + this.parent = parent; + this.method = method; + } + + @Override + public MethodReference getMethodReference() { + if (reference == null) { + reference = + Reference.method( + parent.getClassReference(), + method.method.name.toString(), + ListUtils.map( + Arrays.asList(method.method.proto.parameters.values), + param -> Reference.typeFromDescriptor(param.toDescriptorString())), + method.method.proto.returnType.isVoidType() + ? null + : Reference.typeFromDescriptor( + method.method.proto.returnType.toDescriptorString())); + } + return reference; + } +}
diff --git a/src/main/java/com/android/tools/r8/inspector/internal/ValueInspectorImpl.java b/src/main/java/com/android/tools/r8/inspector/internal/ValueInspectorImpl.java new file mode 100644 index 0000000..8f269d7 --- /dev/null +++ b/src/main/java/com/android/tools/r8/inspector/internal/ValueInspectorImpl.java
@@ -0,0 +1,198 @@ +// 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.inspector.internal; + +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.DexValue; +import com.android.tools.r8.inspector.BooleanValueInspector; +import com.android.tools.r8.inspector.ByteValueInspector; +import com.android.tools.r8.inspector.CharValueInspector; +import com.android.tools.r8.inspector.DoubleValueInspector; +import com.android.tools.r8.inspector.FloatValueInspector; +import com.android.tools.r8.inspector.IntValueInspector; +import com.android.tools.r8.inspector.LongValueInspector; +import com.android.tools.r8.inspector.ShortValueInspector; +import com.android.tools.r8.inspector.StringValueInspector; +import com.android.tools.r8.references.Reference; +import com.android.tools.r8.references.TypeReference; + +public class ValueInspectorImpl + implements BooleanValueInspector, + ByteValueInspector, + CharValueInspector, + ShortValueInspector, + IntValueInspector, + LongValueInspector, + FloatValueInspector, + DoubleValueInspector, + StringValueInspector { + + private final DexValue value; + private final DexType type; + + public ValueInspectorImpl(DexValue value, DexType type) { + this.value = value; + this.type = type; + } + + @Override + public TypeReference getTypeReference() { + return Reference.typeFromDescriptor(type.toDescriptorString()); + } + + @Override + public boolean isPrimitive() { + return type.isPrimitiveType(); + } + + @Override + public boolean isBooleanValue() { + return type.isBooleanType(); + } + + @Override + public BooleanValueInspector asBooleanValue() { + return isBooleanValue() ? this : null; + } + + @Override + public boolean getBooleanValue() { + guard(isBooleanValue()); + return value.asDexValueBoolean().getValue(); + } + + @Override + public boolean isByteValue() { + return type.isByteType(); + } + + @Override + public ByteValueInspector asByteValue() { + return isByteValue() ? this : null; + } + + @Override + public byte getByteValue() { + guard(isByteValue()); + return value.asDexValueByte().getValue(); + } + + @Override + public boolean isCharValue() { + return type.isCharType(); + } + + @Override + public CharValueInspector asCharValue() { + return isCharValue() ? this : null; + } + + @Override + public char getCharValue() { + guard(isCharValue()); + return value.asDexValueChar().getValue(); + } + + @Override + public boolean isShortValue() { + return type.isShortType(); + } + + @Override + public ShortValueInspector asShortValue() { + return isShortValue() ? this : null; + } + + @Override + public short getShortValue() { + guard(isShortValue()); + return value.asDexValueShort().getValue(); + } + + @Override + public boolean isIntValue() { + return type.isIntType(); + } + + @Override + public IntValueInspector asIntValue() { + return isIntValue() ? this : null; + } + + @Override + public int getIntValue() { + guard(isIntValue()); + return value.asDexValueInt().value; + } + + @Override + public boolean isLongValue() { + return type.isLongType(); + } + + @Override + public LongValueInspector asLongValue() { + return isLongValue() ? this : null; + } + + @Override + public long getLongValue() { + guard(isLongValue()); + return value.asDexValueLong().getValue(); + } + + @Override + public boolean isFloatValue() { + return type.isFloatType(); + } + + @Override + public FloatValueInspector asFloatValue() { + return isFloatValue() ? this : null; + } + + @Override + public float getFloatValue() { + guard(isFloatValue()); + return value.asDexValueFloat().getValue(); + } + + @Override + public boolean isDoubleValue() { + return type.isDoubleType(); + } + + @Override + public DoubleValueInspector asDoubleValue() { + return isDoubleValue() ? this : null; + } + + @Override + public double getDoubleValue() { + guard(isDoubleValue()); + return value.asDexValueDouble().getValue(); + } + + @Override + public boolean isStringValue() { + return type.isClassType() && value.isDexValueString(); + } + + @Override + public StringValueInspector asStringValue() { + return isStringValue() ? this : null; + } + + @Override + public String getStringValue() { + guard(isStringValue()); + return value.asDexValueString().getValue().toString(); + } + + private static void guard(boolean precondition) { + if (!precondition) { + throw new IllegalStateException("Invalid call on ValueInspector"); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java index 80c81e9..7270d42 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
@@ -22,6 +22,7 @@ import com.android.tools.r8.ir.code.DominatorTree.Inclusive; import com.android.tools.r8.ir.code.FieldInstruction; import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.InitClass; import com.android.tools.r8.ir.code.InstanceGet; import com.android.tools.r8.ir.code.InstancePut; import com.android.tools.r8.ir.code.Instruction; @@ -233,6 +234,20 @@ public static class InstructionUtils { + public static boolean forInitClass( + InitClass instruction, + DexType type, + AppView<?> appView, + Query mode, + AnalysisAssumption assumption) { + if (assumption == AnalysisAssumption.NONE) { + // Class initialization may fail with ExceptionInInitializerError. + return false; + } + DexClass clazz = appView.definitionFor(instruction.getClassValue()); + return clazz != null && isTypeInitializedBy(instruction, type, clazz, appView, mode); + } + public static boolean forInstanceGet( InstanceGet instruction, DexType type,
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/DeterminismAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/DeterminismAnalysis.java index b8e6950..0fc8bd0 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/DeterminismAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/DeterminismAnalysis.java
@@ -56,16 +56,17 @@ } // isArrayPut and isFieldPut are missed as they don't have out value. assert instr.isArgument() - || instr.isAssume() - || instr.isBinop() - || instr.isUnop() - || instr.isMonitor() - || instr.isMove() - || instr.isCheckCast() - || instr.isInstanceOf() - || instr.isConstInstruction() - || instr.isJumpInstruction() - || instr.isDebugInstruction() + || instr.isAssume() + || instr.isBinop() + || instr.isInitClass() + || instr.isUnop() + || instr.isMonitor() + || instr.isMove() + || instr.isCheckCast() + || instr.isInstanceOf() + || instr.isConstInstruction() + || instr.isJumpInstruction() + || instr.isDebugInstruction() : "Instruction that impacts determinism: " + instr; } return true;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java index 72ac893..0bb8f21 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
@@ -117,7 +117,7 @@ block.deduplicatePhis(); } - code.removeAllTrivialPhis(); + code.removeAllDeadAndTrivialPhis(); } private LatticeElement getLatticeElement(Value value) {
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 b06a015..5ddacd6 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
@@ -5,17 +5,13 @@ package com.android.tools.r8.ir.analysis.fieldaccess; -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.DexEncodedMethod; import com.android.tools.r8.graph.DexField; -import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexType; -import com.android.tools.r8.graph.FieldAccessFlags; import com.android.tools.r8.graph.FieldAccessInfo; import com.android.tools.r8.graph.FieldAccessInfoCollection; import com.android.tools.r8.graph.UseRegistry; @@ -26,7 +22,6 @@ import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed; import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple; import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.android.tools.r8.utils.OptionalBool; import com.android.tools.r8.utils.ThreadUtils; import com.android.tools.r8.utils.Timing; import com.google.common.collect.Sets; @@ -61,7 +56,7 @@ assert feedback.noUpdatesLeft(); timing.begin("Compute fields of interest"); - computeFieldsOfInterest(appInfo); + computeFieldsOfInterest(); timing.end(); // Compute fields of interest if (fieldsOfInterest.isEmpty()) { @@ -81,40 +76,18 @@ fieldsOfInterest.forEach(OptimizationFeedbackSimple.getInstance()::markFieldAsDead); } - private void computeFieldsOfInterest(AppInfoWithLiveness appInfo) { - DexItemFactory dexItemFactory = appView.dexItemFactory(); - for (DexProgramClass clazz : appInfo.classes()) { + private void computeFieldsOfInterest() { + for (DexProgramClass clazz : appView.appInfo().classes()) { for (DexEncodedField field : clazz.instanceFields()) { if (canOptimizeField(field, appView)) { fieldsOfInterest.add(field); } } - OptionalBool mayRequireClinitField = OptionalBool.unknown(); - for (DexEncodedField field : clazz.staticFields()) { - if (canOptimizeField(field, appView)) { - if (mayRequireClinitField.isUnknown()) { - mayRequireClinitField = - OptionalBool.of(clazz.classInitializationMayHaveSideEffects(appView)); + if (appView.canUseInitClass() || !clazz.classInitializationMayHaveSideEffects(appView)) { + for (DexEncodedField field : clazz.staticFields()) { + if (canOptimizeField(field, appView)) { + fieldsOfInterest.add(field); } - fieldsOfInterest.add(field); - } - } - if (mayRequireClinitField.isTrue()) { - DexField clinitField = dexItemFactory.objectMembers.clinitField; - if (clazz.lookupStaticField(dexItemFactory.objectMembers.clinitField) == null) { - FieldAccessFlags accessFlags = - FieldAccessFlags.fromSharedAccessFlags( - Constants.ACC_SYNTHETIC - | Constants.ACC_FINAL - | Constants.ACC_PUBLIC - | Constants.ACC_STATIC); - clazz.appendStaticField( - new DexEncodedField( - dexItemFactory.createField(clazz.type, clinitField.type, clinitField.name), - accessFlags, - DexAnnotationSet.empty(), - null)); - appView.appInfo().invalidateTypeCacheFor(clazz.type); } } } @@ -224,6 +197,11 @@ } @Override + public boolean registerInitClass(DexType clazz) { + return false; + } + + @Override public boolean registerInvokeVirtual(DexMethod method) { return false; }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java index dffd9e0..5479737 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
@@ -111,7 +111,7 @@ public SingleValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLense lens) { DexField rewrittenField = lens.lookupField(field); assert !appView.unboxedEnums().containsEnum(field.holder) - || !appView.definitionFor(rewrittenField).accessFlags.isEnum(); + || !appView.appInfo().resolveField(rewrittenField).accessFlags.isEnum(); return appView.abstractValueFactory().createSingleFieldValue(rewrittenField); } }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java index 12ff943..da92bf3 100644 --- a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java +++ b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
@@ -83,7 +83,8 @@ } @Override - public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) { + public boolean instructionMayHaveSideEffects( + AppView<?> appView, DexType context, SideEffectAssumption assumption) { return instructionInstanceCanThrow(appView, context).isThrowing(); }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java index 8ab298a..589710f 100644 --- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java +++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -144,7 +144,8 @@ } @Override - public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) { + public boolean instructionMayHaveSideEffects( + AppView<?> appView, DexType context, SideEffectAssumption assumption) { // In debug mode, ArrayPut has a side-effect on the locals. if (appView.options().debug) { return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java index c28bc4a..a084a0f 100644 --- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java +++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -95,7 +95,8 @@ } @Override - public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) { + public boolean instructionMayHaveSideEffects( + AppView<?> appView, DexType context, SideEffectAssumption assumption) { return instructionInstanceCanThrow(appView, context).isThrowing(); }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java index 1574275..a9089f8 100644 --- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java +++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -135,7 +135,8 @@ } @Override - public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) { + public boolean instructionMayHaveSideEffects( + AppView<?> appView, DexType context, SideEffectAssumption assumption) { return instructionInstanceCanThrow(appView, context).isThrowing(); }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java b/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java index f2cdc93..fdfbe61 100644 --- a/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java +++ b/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java
@@ -160,6 +160,11 @@ } @Override + public T visit(InitClass instruction) { + return null; + } + + @Override public T visit(InstanceGet instruction) { return handleFieldInstruction(instruction); }
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java index 366f0cf..507ae25 100644 --- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java +++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -28,20 +28,6 @@ public abstract class FieldInstruction extends Instruction { - public enum Assumption { - NONE, - CLASS_ALREADY_INITIALIZED, - RECEIVER_NOT_NULL; - - boolean canAssumeClassIsAlreadyInitialized() { - return this == CLASS_ALREADY_INITIALIZED; - } - - boolean canAssumeReceiverIsNotNull() { - return this == RECEIVER_NOT_NULL; - } - } - private final DexField field; protected FieldInstruction(DexField field, Value dest, Value value) { @@ -76,11 +62,11 @@ @Override public AbstractError instructionInstanceCanThrow(AppView<?> appView, DexType context) { - return instructionInstanceCanThrow(appView, context, Assumption.NONE); + return instructionInstanceCanThrow(appView, context, SideEffectAssumption.NONE); } public AbstractError instructionInstanceCanThrow( - AppView<?> appView, DexType context, Assumption assumption) { + AppView<?> appView, DexType context, SideEffectAssumption assumption) { DexEncodedField resolvedField; if (appView.enableWholeProgramOptimizations()) { // TODO(b/123857022): Should be possible to use definitionFor().
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 952bd5c..883d49f 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
@@ -1126,27 +1126,32 @@ return true; } - public boolean removeAllTrivialPhis() { - return removeAllTrivialPhis(null, null); + public boolean removeAllDeadAndTrivialPhis() { + return removeAllDeadAndTrivialPhis(null, null); } - public boolean removeAllTrivialPhis(IRBuilder builder) { - return removeAllTrivialPhis(builder, null); + public boolean removeAllDeadAndTrivialPhis(IRBuilder builder) { + return removeAllDeadAndTrivialPhis(builder, null); } - public boolean removeAllTrivialPhis(Set<Value> affectedValues) { - return removeAllTrivialPhis(null, affectedValues); + public boolean removeAllDeadAndTrivialPhis(Set<Value> affectedValues) { + return removeAllDeadAndTrivialPhis(null, affectedValues); } - public boolean removeAllTrivialPhis(IRBuilder builder, Set<Value> affectedValues) { - boolean anyTrivialPhisRemoved = false; + public boolean removeAllDeadAndTrivialPhis(IRBuilder builder, Set<Value> affectedValues) { + boolean anyPhisRemoved = false; for (BasicBlock block : blocks) { List<Phi> phis = new ArrayList<>(block.getPhis()); for (Phi phi : phis) { - anyTrivialPhisRemoved |= phi.removeTrivialPhi(builder, affectedValues); + if (phi.hasAnyUsers()) { + anyPhisRemoved |= phi.removeTrivialPhi(builder, affectedValues); + } else { + phi.removeDeadPhi(); + anyPhisRemoved = true; + } } } - return anyTrivialPhisRemoved; + return anyPhisRemoved; } public int reserveMarkingColor() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InitClass.java b/src/main/java/com/android/tools/r8/ir/code/InitClass.java new file mode 100644 index 0000000..8dd260c --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
@@ -0,0 +1,167 @@ +// 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.code; + +import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isTypeVisibleFromContext; + +import com.android.tools.r8.cf.LoadStoreHelper; +import com.android.tools.r8.cf.code.CfInitClass; +import com.android.tools.r8.code.DexInitClass; +import com.android.tools.r8.dex.Constants; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.ir.analysis.AbstractError; +import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis; +import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption; +import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query; +import com.android.tools.r8.ir.analysis.type.TypeLatticeElement; +import com.android.tools.r8.ir.conversion.CfBuilder; +import com.android.tools.r8.ir.conversion.DexBuilder; +import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; +import com.android.tools.r8.ir.optimize.InliningConstraints; +import com.google.common.collect.Sets; + +public class InitClass extends Instruction { + + private final DexType clazz; + + public InitClass(Value outValue, DexType clazz) { + super(outValue); + assert hasOutValue(); + assert outValue.getTypeLattice().isInt(); + assert clazz.isClassType(); + this.clazz = clazz; + } + + public DexType getClassValue() { + return clazz; + } + + @Override + public boolean isInitClass() { + return true; + } + + @Override + public InitClass asInitClass() { + return this; + } + + @Override + public TypeLatticeElement evaluate(AppView<?> appView) { + return TypeLatticeElement.getInt(); + } + + @Override + public int opcode() { + return Opcodes.INIT_CLASS; + } + + @Override + public <T> T accept(InstructionVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public void buildDex(DexBuilder builder) { + int dest = builder.allocatedRegister(outValue(), getNumber()); + builder.add(this, new DexInitClass(dest, clazz)); + } + + @Override + public void buildCf(CfBuilder builder) { + builder.add(new CfInitClass(clazz)); + } + + @Override + public boolean definitelyTriggersClassInitialization( + DexType clazz, + DexType context, + AppView<?> appView, + Query mode, + AnalysisAssumption assumption) { + return ClassInitializationAnalysis.InstructionUtils.forInitClass( + this, clazz, appView, mode, assumption); + } + + @Override + public boolean identicalNonValueNonPositionParts(Instruction other) { + return other.isInitClass() && clazz == other.asInitClass().clazz; + } + + @Override + public AbstractError instructionInstanceCanThrow(AppView<?> appView, DexType context) { + if (!isTypeVisibleFromContext(appView, context, clazz)) { + return AbstractError.top(); + } + if (clazz.classInitializationMayHaveSideEffects( + appView, + // Types that are a super type of `context` are guaranteed to be initialized already. + type -> appView.isSubtype(context, type).isTrue(), + Sets.newIdentityHashSet())) { + return AbstractError.top(); + } + return AbstractError.bottom(); + } + + @Override + public boolean instructionMayHaveSideEffects( + AppView<?> appView, DexType context, SideEffectAssumption assumption) { + return instructionInstanceCanThrow(appView, context).isThrowing(); + } + + @Override + public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, DexType context) { + if (appView.enableWholeProgramOptimizations()) { + // In R8, check if the class initialization of `clazz` or any of its ancestor types may have + // side effects. + return clazz.classInitializationMayHaveSideEffects( + appView, + // Types that are a super type of `context` are guaranteed to be initialized already. + type -> appView.isSubtype(context, type).isTrue(), + Sets.newIdentityHashSet()); + } else { + // In D8, this instruction may trigger class initialization if `clazz` is different from the + // current context. + return clazz != context; + } + } + + @Override + public boolean instructionTypeCanThrow() { + return true; + } + + @Override + public int maxInValueRegister() { + return Constants.U8BIT_MAX; + } + + @Override + public int maxOutValueRegister() { + return Constants.U8BIT_MAX; + } + + @Override + public ConstraintWithTarget inliningConstraint( + InliningConstraints inliningConstraints, DexType invocationContext) { + return inliningConstraints.forInitClass(clazz, invocationContext); + } + + @Override + public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) { + helper.storeOutValue(this, it); + } + + @Override + public boolean hasInvariantOutType() { + return true; + } + + @Override + public String toString() { + return super.toString() + "; " + clazz.toSourceString(); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java index 94bfdaf..e78d3a7 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java +++ b/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java
@@ -6,7 +6,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexType; -import com.android.tools.r8.ir.code.FieldInstruction.Assumption; +import com.android.tools.r8.ir.code.Instruction.SideEffectAssumption; public interface InstanceFieldInstruction { @@ -16,10 +16,13 @@ Value object(); - boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context, Assumption assumption); + boolean instructionMayHaveSideEffects( + AppView<?> appView, DexType context, SideEffectAssumption assumption); FieldInstruction asFieldInstruction(); + boolean isInstanceFieldInstruction(); + boolean isInstanceGet(); InstanceGet asInstanceGet();
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java index b070660..6d9b8e9 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java +++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -115,13 +115,8 @@ } @Override - public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) { - return instructionMayHaveSideEffects(appView, context, Assumption.NONE); - } - - @Override public boolean instructionMayHaveSideEffects( - AppView<?> appView, DexType context, Assumption assumption) { + AppView<?> appView, DexType context, SideEffectAssumption assumption) { return instructionInstanceCanThrow(appView, context, assumption).isThrowing(); } @@ -161,6 +156,16 @@ } @Override + public boolean isInstanceFieldInstruction() { + return true; + } + + @Override + public InstanceFieldInstruction asInstanceFieldInstruction() { + return this; + } + + @Override public boolean isInstanceGet() { return true; }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java index 00a5f30..5680260 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java +++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -114,13 +114,8 @@ } @Override - public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) { - return instructionMayHaveSideEffects(appView, context, Assumption.NONE); - } - - @Override public boolean instructionMayHaveSideEffects( - AppView<?> appView, DexType context, Assumption assumption) { + AppView<?> appView, DexType context, SideEffectAssumption assumption) { if (appView.appInfo().hasLiveness()) { AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness(); AppInfoWithLiveness appInfoWithLiveness = appViewWithLiveness.appInfo(); @@ -200,6 +195,16 @@ } @Override + public boolean isInstanceFieldInstruction() { + return true; + } + + @Override + public InstanceFieldInstruction asInstanceFieldInstruction() { + return this; + } + + @Override public boolean isInstancePut() { return true; }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java index b1459be..a56f0a2 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java +++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -571,6 +571,11 @@ } public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) { + return instructionMayHaveSideEffects(appView, context, SideEffectAssumption.NONE); + } + + public boolean instructionMayHaveSideEffects( + AppView<?> appView, DexType context, SideEffectAssumption assumption) { return instructionInstanceCanThrow(); } @@ -888,6 +893,14 @@ return null; } + public boolean isInstanceFieldInstruction() { + return false; + } + + public InstanceFieldInstruction asInstanceFieldInstruction() { + return null; + } + public boolean isInstanceGet() { return false; } @@ -1032,6 +1045,14 @@ return null; } + public boolean isInitClass() { + return false; + } + + public InitClass asInitClass() { + return null; + } + public boolean isAdd() { return false; } @@ -1452,4 +1473,18 @@ public boolean outTypeKnownToBeBoolean(Set<Phi> seen) { return false; } + + public enum SideEffectAssumption { + NONE, + CLASS_ALREADY_INITIALIZED, + RECEIVER_NOT_NULL; + + boolean canAssumeClassIsAlreadyInitialized() { + return this == CLASS_ALREADY_INITIALIZED; + } + + boolean canAssumeReceiverIsNotNull() { + return this == RECEIVER_NOT_NULL; + } + } }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java b/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java index d7b3a83..4bb29bb 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java +++ b/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
@@ -64,6 +64,8 @@ T visit(Inc instruction); + T visit(InitClass instruction); + T visit(InstanceGet instruction); T visit(InstanceOf instruction);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java index ec0c68f..b4531c3 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java +++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -158,14 +158,15 @@ } @Override - public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) { + public boolean instructionMayHaveSideEffects( + AppView<?> appView, DexType context, SideEffectAssumption assumption) { if (appView.options().debug) { return true; } // Check if it could throw a NullPointerException as a result of the receiver being null. Value receiver = getReceiver(); - if (receiver.getTypeLattice().isNullable()) { + if (!assumption.canAssumeReceiverIsNotNull() && receiver.getTypeLattice().isNullable()) { return true; }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java index 831c65f..02e45ef 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java +++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
@@ -171,7 +171,8 @@ } @Override - public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) { + public boolean instructionMayHaveSideEffects( + AppView<?> appView, DexType context, SideEffectAssumption assumption) { // Check if the instruction has a side effect on the locals environment. if (hasOutValue() && outValue().hasLocalInfo()) { assert appView.options().debug;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java index 8378a5d..ce75c7c 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java +++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -184,7 +184,8 @@ } @Override - public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) { + public boolean instructionMayHaveSideEffects( + AppView<?> appView, DexType context, SideEffectAssumption assumption) { // Check if the instruction has a side effect on the locals environment. if (hasOutValue() && outValue().hasLocalInfo()) { assert appView.options().debug;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java index da1c8fd..2d29972 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java +++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -156,7 +156,8 @@ } @Override - public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) { + public boolean instructionMayHaveSideEffects( + AppView<?> appView, DexType context, SideEffectAssumption assumption) { if (!appView.enableWholeProgramOptimizations()) { return true; } @@ -187,22 +188,24 @@ } // Verify that the target method does not have side-effects. - boolean targetMayHaveSideEffects; if (appViewWithLiveness.appInfo().noSideEffects.containsKey(target.method)) { - targetMayHaveSideEffects = false; - } else { - targetMayHaveSideEffects = - target.getOptimizationInfo().mayHaveSideEffects() - // Verify that calling the target method won't lead to class initialization. - || target.method.holder.classInitializationMayHaveSideEffects( - appView, - // Types that are a super type of `context` are guaranteed to be initialized - // already. - type -> appView.isSubtype(context, type).isTrue(), - Sets.newIdentityHashSet()); + return false; } - return targetMayHaveSideEffects; + if (target.getOptimizationInfo().mayHaveSideEffects()) { + return true; + } + + if (assumption.canAssumeClassIsAlreadyInitialized()) { + return false; + } + + return target.method.holder.classInitializationMayHaveSideEffects( + appView, + // Types that are a super type of `context` are guaranteed to be initialized + // already. + type -> appView.isSubtype(context, type).isTrue(), + Sets.newIdentityHashSet()); } return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java index 1ec8873..1218f08 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java +++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -142,7 +142,8 @@ } @Override - public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) { + public boolean instructionMayHaveSideEffects( + AppView<?> appView, DexType context, SideEffectAssumption assumption) { if (!appView.enableWholeProgramOptimizations()) { return true; } @@ -153,7 +154,7 @@ // Check if it could throw a NullPointerException as a result of the receiver being null. Value receiver = getReceiver(); - if (receiver.getTypeLattice().isNullable()) { + if (!assumption.canAssumeReceiverIsNotNull() && receiver.getTypeLattice().isNullable()) { return true; }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java index 226b771..afd87ff 100644 --- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java +++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -129,7 +129,8 @@ } @Override - public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) { + public boolean instructionMayHaveSideEffects( + AppView<?> appView, DexType context, SideEffectAssumption assumption) { // Treat the instruction as possibly having side-effects if it may throw or the array is used. if (instructionInstanceCanThrow(appView, context).isThrowing() || src().numberOfAllUsers() > 1) {
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 23ac981..384a27a 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
@@ -141,7 +141,8 @@ } @Override - public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) { + public boolean instructionMayHaveSideEffects( + AppView<?> appView, DexType context, SideEffectAssumption assumption) { DexItemFactory dexItemFactory = appView.dexItemFactory(); if (!appView.enableWholeProgramOptimizations()) { return !(dexItemFactory.libraryTypesAssumedToBePresent.contains(clazz)
diff --git a/src/main/java/com/android/tools/r8/ir/code/Opcodes.java b/src/main/java/com/android/tools/r8/ir/code/Opcodes.java index 65a09d6..a3dc92b 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Opcodes.java +++ b/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
@@ -33,43 +33,44 @@ int GOTO = 24; int IF = 25; int INC = 26; - int INSTANCE_GET = 27; - int INSTANCE_OF = 28; - int INSTANCE_PUT = 29; - int INT_SWITCH = 30; - int INVOKE_CUSTOM = 31; - int INVOKE_DIRECT = 32; - int INVOKE_INTERFACE = 33; - int INVOKE_MULTI_NEW_ARRAY = 34; - int INVOKE_NEW_ARRAY = 35; - int INVOKE_POLYMORPHIC = 36; - int INVOKE_STATIC = 37; - int INVOKE_SUPER = 38; - int INVOKE_VIRTUAL = 39; - int LOAD = 40; - int MONITOR = 41; - int MOVE = 42; - int MOVE_EXCEPTION = 43; - int MUL = 44; - int NEG = 45; - int NEW_ARRAY_EMPTY = 46; - int NEW_ARRAY_FILLED_DATA = 47; - int NEW_INSTANCE = 48; - int NOT = 49; - int NUMBER_CONVERSION = 50; - int OR = 51; - int POP = 52; - int REM = 53; - int RETURN = 54; - int SHL = 55; - int SHR = 56; - int STATIC_GET = 57; - int STATIC_PUT = 58; - int STORE = 59; - int STRING_SWITCH = 60; - int SUB = 61; - int SWAP = 62; - int THROW = 63; - int USHR = 64; - int XOR = 65; + int INIT_CLASS = 27; + int INSTANCE_GET = 28; + int INSTANCE_OF = 29; + int INSTANCE_PUT = 30; + int INT_SWITCH = 31; + int INVOKE_CUSTOM = 32; + int INVOKE_DIRECT = 33; + int INVOKE_INTERFACE = 34; + int INVOKE_MULTI_NEW_ARRAY = 35; + int INVOKE_NEW_ARRAY = 36; + int INVOKE_POLYMORPHIC = 37; + int INVOKE_STATIC = 38; + int INVOKE_SUPER = 39; + int INVOKE_VIRTUAL = 40; + int LOAD = 41; + int MONITOR = 42; + int MOVE = 43; + int MOVE_EXCEPTION = 44; + int MUL = 45; + int NEG = 46; + int NEW_ARRAY_EMPTY = 47; + int NEW_ARRAY_FILLED_DATA = 48; + int NEW_INSTANCE = 49; + int NOT = 50; + int NUMBER_CONVERSION = 51; + int OR = 52; + int POP = 53; + int REM = 54; + int RETURN = 55; + int SHL = 56; + int SHR = 57; + int STATIC_GET = 58; + int STATIC_PUT = 59; + int STORE = 60; + int STRING_SWITCH = 61; + int SUB = 62; + int SWAP = 63; + int THROW = 64; + int USHR = 65; + int XOR = 66; }
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticFieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/StaticFieldInstruction.java index 9c5a123..286b470 100644 --- a/src/main/java/com/android/tools/r8/ir/code/StaticFieldInstruction.java +++ b/src/main/java/com/android/tools/r8/ir/code/StaticFieldInstruction.java
@@ -6,7 +6,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexType; -import com.android.tools.r8.ir.code.FieldInstruction.Assumption; +import com.android.tools.r8.ir.code.Instruction.SideEffectAssumption; public interface StaticFieldInstruction { @@ -14,7 +14,10 @@ Value outValue(); - boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context, Assumption assumption); + boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context); + + boolean instructionMayHaveSideEffects( + AppView<?> appView, DexType context, SideEffectAssumption assumption); FieldInstruction asFieldInstruction();
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java index e7c2dee..a3c202b 100644 --- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java +++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -137,12 +137,12 @@ @Override public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) { - return instructionMayHaveSideEffects(appView, context, Assumption.NONE); + return instructionMayHaveSideEffects(appView, context, SideEffectAssumption.NONE); } @Override public boolean instructionMayHaveSideEffects( - AppView<?> appView, DexType context, Assumption assumption) { + AppView<?> appView, DexType context, SideEffectAssumption assumption) { return instructionInstanceCanThrow(appView, context, assumption).isThrowing(); }
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java index 9d25691..e7d2bdc 100644 --- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java +++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -94,13 +94,8 @@ } @Override - public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) { - return instructionMayHaveSideEffects(appView, context, Assumption.NONE); - } - - @Override public boolean instructionMayHaveSideEffects( - AppView<?> appView, DexType context, Assumption assumption) { + AppView<?> appView, DexType context, SideEffectAssumption assumption) { if (appView.appInfo().hasLiveness()) { AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness(); AppInfoWithLiveness appInfoWithLiveness = appViewWithLiveness.appInfo();
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 25ddb6a..6ff7e2f 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
@@ -282,6 +282,21 @@ } } + private void processInitClass(DexType type) { + DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type)); + if (clazz == null) { + assert false; + return; + } + addClassInitializerTarget(clazz); + } + + @Override + public boolean registerInitClass(DexType clazz) { + processInitClass(clazz); + return false; + } + @Override public boolean registerInvokeVirtual(DexMethod method) { processInvoke(Invoke.Type.VIRTUAL, method);
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 3c33035..e87f619 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
@@ -71,6 +71,7 @@ import com.android.tools.r8.ir.code.IRMetadata; import com.android.tools.r8.ir.code.If; import com.android.tools.r8.ir.code.ImpreciseMemberTypeInstruction; +import com.android.tools.r8.ir.code.InitClass; import com.android.tools.r8.ir.code.InstanceGet; import com.android.tools.r8.ir.code.InstanceOf; import com.android.tools.r8.ir.code.InstancePut; @@ -687,7 +688,7 @@ block.deduplicatePhis(); } - ir.removeAllTrivialPhis(this); + ir.removeAllDeadAndTrivialPhis(this); ir.removeUnreachableBlocks(); // Compute precise types for all values. @@ -1795,6 +1796,13 @@ closeCurrentBlock(ret); } + public void addInitClass(int dest, DexType clazz) { + Value out = writeRegister(dest, TypeLatticeElement.getInt(), ThrowingInfo.CAN_THROW); + InitClass instruction = new InitClass(out, clazz); + assert instruction.instructionTypeCanThrow(); + addInstruction(instruction); + } + public void addStaticGet(int dest, DexField field) { Value out = writeRegister(
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 f4e315b..d968d31 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
@@ -395,11 +395,10 @@ throw new Unreachable(); } - private boolean removeLambdaDeserializationMethods() { + private void removeLambdaDeserializationMethods() { if (lambdaRewriter != null) { - return lambdaRewriter.removeLambdaDeserializationMethods(appView.appInfo().classes()); + lambdaRewriter.removeLambdaDeserializationMethods(appView.appInfo().classes()); } - return false; } private void desugarNestBasedAccess(Builder<?> builder, ExecutorService executorService)
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java index d7cfc6b..06c4bdf 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -42,6 +42,7 @@ import com.android.tools.r8.ir.code.ConstInstruction; import com.android.tools.r8.ir.code.ConstMethodHandle; import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.InitClass; import com.android.tools.r8.ir.code.InstanceGet; import com.android.tools.r8.ir.code.InstanceOf; import com.android.tools.r8.ir.code.InstancePut; @@ -61,6 +62,7 @@ import com.android.tools.r8.ir.code.StaticGet; import com.android.tools.r8.ir.code.StaticPut; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.logging.Log; import com.google.common.collect.Sets; import java.util.ArrayList; @@ -138,6 +140,11 @@ affectedPhis.addAll(newOutValue.uniquePhiUsers()); } } + } else if (current.isInitClass()) { + InitClass initClass = current.asInitClass(); + new InstructionReplacer(code, current, iterator, affectedPhis) + .replaceInstructionIfTypeChanged( + initClass.getClassValue(), (t, v) -> new InitClass(v, t)); } else if (current.isInvokeMethod()) { InvokeMethod invoke = current.asInvokeMethod(); DexMethod invokedMethod = invoke.getInvokedMethod(); @@ -195,22 +202,14 @@ ArgumentInfo argumentInfo = argumentInfoCollection.getArgumentInfo(i); if (argumentInfo.isRewrittenTypeInfo()) { RewrittenTypeInfo argInfo = argumentInfo.asRewrittenTypeInfo(); - Value value = invoke.inValues().get(i); - // When converting types the default value may change (for example default value - // of a reference type is null while default value of int is 0). - if (argInfo.defaultValueHasChanged() - && value.isConstNumber() - && value.definition.asConstNumber().isZero()) { - iterator.previous(); - // TODO(b/150188380): Add API to insert a const instruction with a type lattice. - Value rewrittenNull = - iterator.insertConstIntInstruction(code, appView.options(), 0); - iterator.next(); - rewrittenNull.setTypeLattice(argInfo.defaultValueLatticeElement(appView)); - newInValues.add(rewrittenNull); - } else { - newInValues.add(invoke.inValues().get(i)); - } + Value rewrittenValue = + rewriteValueIfDefault( + code, + iterator, + argInfo.getOldType(), + argInfo.getNewType(), + invoke.inValues().get(i)); + newInValues.add(rewrittenValue); } else if (!argumentInfo.isRemovedArgumentInfo()) { newInValues.add(invoke.inValues().get(i)); } @@ -311,9 +310,12 @@ iterator.replaceCurrentInstruction( new InvokeStatic(replacementMethod, null, current.inValues())); } else if (actualField != field) { + Value rewrittenValue = + rewriteValueIfDefault( + code, iterator, field.type, actualField.type, instancePut.value()); InstancePut newInstancePut = InstancePut.createPotentiallyInvalid( - actualField, instancePut.object(), instancePut.value()); + actualField, instancePut.object(), rewrittenValue); iterator.replaceCurrentInstruction(newInstancePut); } } else if (current.isStaticGet()) { @@ -348,7 +350,10 @@ iterator.replaceCurrentInstruction( new InvokeStatic(replacementMethod, current.outValue(), current.inValues())); } else if (actualField != field) { - StaticPut newStaticPut = new StaticPut(staticPut.value(), actualField); + Value rewrittenValue = + rewriteValueIfDefault( + code, iterator, field.type, actualField.type, staticPut.value()); + StaticPut newStaticPut = new StaticPut(rewrittenValue, actualField); iterator.replaceCurrentInstruction(newStaticPut); } } else if (current.isCheckCast()) { @@ -414,6 +419,49 @@ assert code.hasNoVerticallyMergedClasses(appView); } + // If the initialValue is a default value and its type is rewritten from a reference type to a + // primitive type, then the default value type lattice needs to be changed. + private Value rewriteValueIfDefault( + IRCode code, + InstructionListIterator iterator, + DexType oldType, + DexType newType, + Value initialValue) { + if (initialValue.isConstNumber() + && initialValue.definition.asConstNumber().isZero() + && defaultValueHasChanged(oldType, newType)) { + iterator.previous(); + // TODO(b/150188380): Add API to insert a const instruction with a type lattice. + Value rewrittenDefaultValue = iterator.insertConstIntInstruction(code, appView.options(), 0); + iterator.next(); + rewrittenDefaultValue.setTypeLattice(defaultValueLatticeElement(newType)); + return rewrittenDefaultValue; + } + return initialValue; + } + + private boolean defaultValueHasChanged(DexType oldType, DexType newType) { + if (newType.isPrimitiveType()) { + if (oldType.isPrimitiveType()) { + return ValueType.fromDexType(newType) != ValueType.fromDexType(oldType); + } + return true; + } else if (oldType.isPrimitiveType()) { + return true; + } + // All reference types uses null as default value. + assert newType.isReferenceType(); + assert oldType.isReferenceType(); + return false; + } + + private TypeLatticeElement defaultValueLatticeElement(DexType type) { + if (type.isPrimitiveType()) { + return TypeLatticeElement.fromDexType(type, null, appView); + } + return TypeLatticeElement.getNull(); + } + public DexCallSite rewriteCallSite(DexCallSite callSite, DexEncodedMethod context) { DexItemFactory dexItemFactory = appView.dexItemFactory(); DexProto newMethodProto = @@ -643,6 +691,7 @@ } else { assert current.hasInvariantOutType(); assert current.isConstClass() + || current.isInitClass() || current.isInstanceOf() || (current.isInvokeVirtual() && current.asInvokeVirtual().getInvokedMethod().holder.isArrayType());
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java index 41d1363..20ab05c 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java
@@ -113,7 +113,7 @@ } } if (changed) { - code.removeAllTrivialPhis(); + code.removeAllDeadAndTrivialPhis(); code.removeUnreachableBlocks(); } }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java index e4af36c..7b53a35 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
@@ -84,7 +84,6 @@ new InvokeStatic(bridge, invokeMethod.outValue(), invokeMethod.arguments())); } } - } else if (instruction.isFieldInstruction()) { DexEncodedField encodedField = appView.definitionFor(instruction.asFieldInstruction().getField());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java index 0831e6d..ec1b05f 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -615,39 +615,41 @@ DexMethod implMethod = descriptor.implHandle.asMethod(); DexClass implMethodHolder = definitionFor(implMethod.holder); - List<DexEncodedMethod> directMethods = implMethodHolder.directMethods(); - for (int i = 0; i < directMethods.size(); i++) { - DexEncodedMethod encodedMethod = directMethods.get(i); - if (implMethod.match(encodedMethod)) { - // We need to create a new static method with the same code to be able to safely - // relax its accessibility without making it virtual. - MethodAccessFlags newAccessFlags = encodedMethod.accessFlags.copy(); - newAccessFlags.setStatic(); - newAccessFlags.unsetPrivate(); - // Always make the method public to provide access when r8 minification is allowed to move - // the lambda class accessing this method to another package (-allowaccessmodification). - newAccessFlags.setPublic(); - DexEncodedMethod newMethod = - new DexEncodedMethod( - callTarget, - newAccessFlags, - encodedMethod.annotations(), - encodedMethod.parameterAnnotationsList, - encodedMethod.getCode(), - true); - newMethod.copyMetadata(encodedMethod); - rewriter.originalMethodSignatures.put(callTarget, encodedMethod.method); + DexEncodedMethod replacement = + implMethodHolder + .getMethodCollection() + .replaceDirectMethod( + implMethod, + encodedMethod -> { + // We need to create a new static method with the same code to be able to safely + // relax its accessibility without making it virtual. + MethodAccessFlags newAccessFlags = encodedMethod.accessFlags.copy(); + newAccessFlags.setStatic(); + newAccessFlags.unsetPrivate(); + // Always make the method public to provide access when r8 minification is + // allowed to move the lambda class accessing this method to another package + // (-allowaccessmodification). + newAccessFlags.setPublic(); + DexEncodedMethod newMethod = + new DexEncodedMethod( + callTarget, + newAccessFlags, + encodedMethod.annotations(), + encodedMethod.parameterAnnotationsList, + encodedMethod.getCode(), + true); + newMethod.copyMetadata(encodedMethod); + rewriter.originalMethodSignatures.put(callTarget, encodedMethod.method); - DexEncodedMethod.setDebugInfoWithFakeThisParameter( - newMethod.getCode(), callTarget.getArity(), rewriter.getAppView()); - implMethodHolder.setDirectMethod(i, newMethod); + DexEncodedMethod.setDebugInfoWithFakeThisParameter( + newMethod.getCode(), callTarget.getArity(), rewriter.getAppView()); + return newMethod; + }); - return newMethod; - } - } - assert false + assert replacement != null : "Unexpected failure to find direct lambda target for: " + implMethod.qualifiedName(); - return null; + + return replacement; } } @@ -673,33 +675,29 @@ private DexEncodedMethod modifyLambdaImplementationMethod( DexMethod implMethod, DexClass implMethodHolder) { - List<DexEncodedMethod> oldDirectMethods = implMethodHolder.directMethods(); - for (int i = 0; i < oldDirectMethods.size(); i++) { - DexEncodedMethod encodedMethod = oldDirectMethods.get(i); - if (implMethod.match(encodedMethod)) { - // We need to create a new method with the same code to be able to safely relax its - // accessibility and make it virtual. - MethodAccessFlags newAccessFlags = encodedMethod.accessFlags.copy(); - newAccessFlags.unsetPrivate(); - newAccessFlags.setPublic(); - DexEncodedMethod newMethod = - new DexEncodedMethod( - callTarget, - newAccessFlags, - encodedMethod.annotations(), - encodedMethod.parameterAnnotationsList, - encodedMethod.getCode(), - true); - newMethod.copyMetadata(encodedMethod); - rewriter.originalMethodSignatures.put(callTarget, encodedMethod.method); - // Move the method from the direct methods to the virtual methods set. - implMethodHolder.removeDirectMethod(i); - implMethodHolder.appendVirtualMethod(newMethod); - - return newMethod; - } - } - return null; + return implMethodHolder + .getMethodCollection() + .replaceDirectMethodWithVirtualMethod( + implMethod, + encodedMethod -> { + assert encodedMethod.isDirectMethod(); + // We need to create a new method with the same code to be able to safely relax its + // accessibility and make it virtual. + MethodAccessFlags newAccessFlags = encodedMethod.accessFlags.copy(); + newAccessFlags.unsetPrivate(); + newAccessFlags.setPublic(); + DexEncodedMethod newMethod = + new DexEncodedMethod( + callTarget, + newAccessFlags, + encodedMethod.annotations(), + encodedMethod.parameterAnnotationsList, + encodedMethod.getCode(), + true); + newMethod.copyMetadata(encodedMethod); + rewriter.originalMethodSignatures.put(callTarget, encodedMethod.method); + return newMethod; + }); } private DexEncodedMethod createSyntheticAccessor(
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 007f170..770f170 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
@@ -172,29 +172,10 @@ } /** Remove lambda deserialization methods. */ - public boolean removeLambdaDeserializationMethods(Iterable<DexProgramClass> classes) { - boolean anyRemoved = false; + public void removeLambdaDeserializationMethods(Iterable<DexProgramClass> classes) { for (DexProgramClass clazz : classes) { - // Search for a lambda deserialization method and remove it if found. - List<DexEncodedMethod> directMethods = clazz.directMethods(); - if (directMethods != null) { - int methodCount = directMethods.size(); - for (int i = 0; i < methodCount; i++) { - DexEncodedMethod encoded = directMethods.get(i); - DexMethod method = encoded.method; - if (method.isLambdaDeserializeMethod(getFactory())) { - assert encoded.accessFlags.isStatic(); - assert encoded.accessFlags.isSynthetic(); - clazz.removeDirectMethod(i); - anyRemoved = true; - - // We assume there is only one such method in the class. - break; - } - } - } + clazz.removeDirectMethod(getFactory().deserializeLambdaMethod); } - return anyRemoved; } /** Generates lambda classes and adds them to the builder. */
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 4c2a36d..b78757d 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
@@ -375,6 +375,12 @@ } @Override + public boolean registerInitClass(DexType clazz) { + // Nothing to do since we always use a public field for initializing the class. + return true; + } + + @Override public boolean registerInvokeVirtual(DexMethod method) { // Calls to class nest mate private methods are targeted by invokeVirtual in jdk11. // The spec recommends to do so, but do not enforce it, hence invokeDirect is also registered.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java index 0ebe028..6bb9b03 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
@@ -55,9 +55,6 @@ public void markUsersForRemoval(Value value) { for (Instruction user : value.aliasedUsers()) { if (user.isAssumeDynamicType()) { - assert value.numberOfAllUsers() == 1 - : "Expected value flowing into Assume<DynamicTypeAssumption> instruction to have a " - + "unique user."; markForRemoval(user.asAssumeDynamicType()); } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java index 60813ab..f7755d4 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -26,7 +26,6 @@ import com.android.tools.r8.ir.analysis.equivalence.BasicBlockBehavioralSubsumption; import com.android.tools.r8.ir.analysis.type.TypeAnalysis; import com.android.tools.r8.ir.analysis.type.TypeLatticeElement; -import com.android.tools.r8.ir.analysis.type.TypeUtils; import com.android.tools.r8.ir.analysis.value.AbstractValue; import com.android.tools.r8.ir.code.AlwaysMaterializingNop; import com.android.tools.r8.ir.code.ArrayLength; @@ -208,7 +207,7 @@ // Therefore, Assume elimination may result in a trivial phi: // z <- phi(x, x) if (needToCheckTrivialPhis) { - code.removeAllTrivialPhis(valuesThatRequireWidening); + code.removeAllDeadAndTrivialPhis(valuesThatRequireWidening); } if (!valuesThatRequireWidening.isEmpty()) { @@ -273,9 +272,34 @@ } Throw throwInstruction = valueIsNullTarget.exit().asThrow(); - Value exceptionValue = throwInstruction.exception(); - if (!exceptionValue.isConstZero() - && !TypeUtils.isNullPointerException(exceptionValue.getTypeLattice(), appView)) { + Value exceptionValue = throwInstruction.exception().getAliasedValue(); + Value message; + if (exceptionValue.isConstZero()) { + message = null; + } else if (exceptionValue.isDefinedByInstructionSatisfying(Instruction::isNewInstance)) { + NewInstance newInstance = exceptionValue.definition.asNewInstance(); + if (newInstance.clazz != dexItemFactory.npeType) { + continue; + } + if (newInstance.outValue().numberOfAllUsers() != 2) { + continue; // Could be mutated before it is thrown. + } + InvokeDirect constructorCall = newInstance.getUniqueConstructorInvoke(dexItemFactory); + if (constructorCall == null) { + continue; + } + DexMethod invokedMethod = constructorCall.getInvokedMethod(); + if (invokedMethod == dexItemFactory.npeMethods.init) { + message = null; + } else if (invokedMethod == dexItemFactory.npeMethods.initWithMessage) { + if (!appView.options().canUseRequireNonNull()) { + continue; + } + message = constructorCall.getArgument(1); + } else { + continue; + } + } else { continue; } @@ -290,12 +314,26 @@ continue; } + if (message != null) { + Instruction definition = message.definition; + if (message.definition.getBlock() == valueIsNullTarget) { + it.previous(); + Instruction entry; + do { + entry = valueIsNullTarget.getInstructions().removeFirst(); + it.add(entry); + } while (entry != definition); + it.next(); + } + } + rewriteIfToRequireNonNull( block, it, ifInstruction, ifInstruction.targetFromCondition(1), valueIsNullTarget, + message, throwInstruction.getPosition()); shouldRemoveUnreachableBlocks = true; } @@ -1221,10 +1259,10 @@ assumeDynamicTypeRemover.removeMarkedInstructions(blocksToBeRemoved).finish(); if (!blocksToBeRemoved.isEmpty()) { code.removeBlocks(blocksToBeRemoved); - code.removeAllTrivialPhis(affectedValues); + code.removeAllDeadAndTrivialPhis(affectedValues); assert code.getUnreachableBlocks().isEmpty(); } else if (mayHaveRemovedTrivialPhi || assumeDynamicTypeRemover.mayHaveIntroducedTrivialPhi()) { - code.removeAllTrivialPhis(affectedValues); + code.removeAllDeadAndTrivialPhis(affectedValues); } if (!affectedValues.isEmpty()) { new TypeAnalysis(appView).narrowing(affectedValues); @@ -1299,7 +1337,7 @@ // Removing check-cast may result in a trivial phi: // v3 <- phi(v1, v1) if (needToRemoveTrivialPhis) { - code.removeAllTrivialPhis(affectedValues); + code.removeAllDeadAndTrivialPhis(affectedValues); if (!affectedValues.isEmpty()) { typeAnalysis.narrowing(affectedValues); } @@ -2593,7 +2631,7 @@ } if (changed) { - code.removeAllTrivialPhis(); + code.removeAllDeadAndTrivialPhis(); } assert code.isConsistentSSA(); } @@ -2922,15 +2960,24 @@ If theIf, BasicBlock target, BasicBlock deadTarget, + Value message, Position position) { deadTarget.unlinkSinglePredecessorSiblingsAllowed(); assert theIf == block.exit(); iterator.previous(); Instruction instruction; if (appView.options().canUseRequireNonNull()) { - DexMethod requireNonNullMethod = appView.dexItemFactory().objectsMethods.requireNonNull; - instruction = new InvokeStatic(requireNonNullMethod, null, ImmutableList.of(theIf.lhs())); + if (message != null) { + DexMethod requireNonNullMethod = + appView.dexItemFactory().objectsMethods.requireNonNullWithMessage; + instruction = + new InvokeStatic(requireNonNullMethod, null, ImmutableList.of(theIf.lhs(), message)); + } else { + DexMethod requireNonNullMethod = appView.dexItemFactory().objectsMethods.requireNonNull; + instruction = new InvokeStatic(requireNonNullMethod, null, ImmutableList.of(theIf.lhs())); + } } else { + assert message == null; DexMethod getClassMethod = appView.dexItemFactory().objectMembers.getClass; instruction = new InvokeVirtual(getClassMethod, null, ImmutableList.of(theIf.lhs())); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java index 5160e6c..2dff525 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -257,7 +257,7 @@ shouldSimplifyIfs |= newConst.outValue().hasUserThatMatches(Instruction::isIf); } while (iterator.hasNext()); - shouldSimplifyIfs |= code.removeAllTrivialPhis(); + shouldSimplifyIfs |= code.removeAllDeadAndTrivialPhis(); if (shouldSimplifyIfs) { codeRewriter.simplifyIf(code);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java index f1c5dc9..463bfce 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -128,62 +128,6 @@ return false; } - private boolean canInlineStaticInvoke( - InvokeStatic invoke, - DexEncodedMethod method, - DexEncodedMethod target, - ClassInitializationAnalysis classInitializationAnalysis, - WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) { - // Only proceed with inlining a static invoke if: - // - the holder for the target is a subtype of the holder for the method, - // - the target method always triggers class initialization of its holder before any other side - // effect (hence preserving class initialization semantics), - // - the current method has already triggered the holder for the target method to be - // initialized, or - // - there is no non-trivial class initializer. - DexType targetHolder = target.method.holder; - if (appView.appInfo().isSubtype(method.method.holder, targetHolder)) { - return true; - } - DexClass clazz = appView.definitionFor(targetHolder); - assert clazz != null; - if (target.getOptimizationInfo().triggersClassInitBeforeAnySideEffect()) { - return true; - } - if (!method.isStatic()) { - boolean targetIsGuaranteedToBeInitialized = - appView.withInitializedClassesInInstanceMethods( - analysis -> - analysis.isClassDefinitelyLoadedInInstanceMethodsOn( - target.method.holder, method.method.holder), - false); - if (targetIsGuaranteedToBeInitialized) { - return true; - } - } - if (classInitializationAnalysis.isClassDefinitelyLoadedBeforeInstruction( - target.method.holder, invoke)) { - return true; - } - // Check for class initializer side effects when loading this class, as inlining might remove - // the load operation. - // - // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-5.html#jvms-5.5. - // - // For simplicity, we are conservative and consider all interfaces, not only the ones with - // default methods. - if (!clazz.classInitializationMayHaveSideEffects(appView)) { - return true; - } - - if (appView.rootSet().bypassClinitForInlining.contains(target.method)) { - return true; - } - - whyAreYouNotInliningReporter.reportMustTriggerClassInitialization(); - return false; - } - @Override public boolean passesInliningConstraints( InvokeMethod invoke, @@ -391,12 +335,71 @@ Reason reason, ClassInitializationAnalysis classInitializationAnalysis, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) { - // Abort inlining attempt if we can not guarantee class for static target has been initialized. - if (!canInlineStaticInvoke( - invoke, method, singleTarget, classInitializationAnalysis, whyAreYouNotInliningReporter)) { - return null; + InlineAction action = new InlineAction(singleTarget, invoke, reason); + if (isTargetClassInitialized(invoke, method, singleTarget, classInitializationAnalysis)) { + return action; } - return new InlineAction(singleTarget, invoke, reason); + if (appView.canUseInitClass() + && appView.options().enableInliningOfInvokesWithClassInitializationSideEffects) { + action.setShouldSynthesizeInitClass(); + return action; + } + whyAreYouNotInliningReporter.reportMustTriggerClassInitialization(); + return null; + } + + private boolean isTargetClassInitialized( + InvokeStatic invoke, + DexEncodedMethod method, + DexEncodedMethod target, + ClassInitializationAnalysis classInitializationAnalysis) { + // Only proceed with inlining a static invoke if: + // - the holder for the target is a subtype of the holder for the method, + // - the target method always triggers class initialization of its holder before any other side + // effect (hence preserving class initialization semantics), + // - the current method has already triggered the holder for the target method to be + // initialized, or + // - there is no non-trivial class initializer. + DexType targetHolder = target.method.holder; + if (appView.appInfo().isSubtype(method.method.holder, targetHolder)) { + return true; + } + DexClass clazz = appView.definitionFor(targetHolder); + assert clazz != null; + if (target.getOptimizationInfo().triggersClassInitBeforeAnySideEffect()) { + return true; + } + if (!method.isStatic()) { + boolean targetIsGuaranteedToBeInitialized = + appView.withInitializedClassesInInstanceMethods( + analysis -> + analysis.isClassDefinitelyLoadedInInstanceMethodsOn( + target.method.holder, method.method.holder), + false); + if (targetIsGuaranteedToBeInitialized) { + return true; + } + } + if (classInitializationAnalysis.isClassDefinitelyLoadedBeforeInstruction( + target.method.holder, invoke)) { + return true; + } + // Check for class initializer side effects when loading this class, as inlining might remove + // the load operation. + // + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-5.html#jvms-5.5. + // + // For simplicity, we are conservative and consider all interfaces, not only the ones with + // default methods. + if (!clazz.classInitializationMayHaveSideEffects(appView)) { + return true; + } + + if (appView.rootSet().bypassClinitForInlining.contains(target.method)) { + return true; + } + + return false; } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java index 9cbc694..ebf6e57 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
@@ -248,7 +248,7 @@ } } - code.removeAllTrivialPhis(); + code.removeAllDeadAndTrivialPhis(); assert code.isConsistentSSA(); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java index c9bf058..d45bbb0 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -26,6 +26,7 @@ import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler; import com.android.tools.r8.ir.code.ConstClass; import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.InitClass; import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.InstructionIterator; import com.android.tools.r8.ir.code.InstructionListIterator; @@ -573,6 +574,7 @@ public final Invoke invoke; final Reason reason; + private boolean shouldSynthesizeInitClass; private boolean shouldSynthesizeNullCheckForReceiver; InlineAction(DexEncodedMethod target, Invoke invoke, Reason reason) { @@ -581,7 +583,13 @@ this.reason = reason; } + void setShouldSynthesizeInitClass() { + assert !shouldSynthesizeNullCheckForReceiver; + shouldSynthesizeInitClass = true; + } + void setShouldSynthesizeNullCheckForReceiver() { + assert !shouldSynthesizeInitClass; shouldSynthesizeNullCheckForReceiver = true; } @@ -599,6 +607,12 @@ // Build the IR for a yet not processed method, and perform minimal IR processing. IRCode code = inliningIRProvider.getInliningIR(invoke, target); + // Insert a init class instruction if this is needed to preserve class initialization + // semantics. + if (shouldSynthesizeInitClass) { + synthesizeInitClass(code); + } + // Insert a null check if this is needed to preserve the implicit null check for the receiver. // This is only needed if we do not also insert a monitor-enter instruction, since that will // throw a NPE if the receiver is null. @@ -737,6 +751,21 @@ return new InlineeWithReason(code, reason); } + private void synthesizeInitClass(IRCode code) { + List<Value> arguments = code.collectArguments(); + BasicBlock entryBlock = code.entryBlock(); + + // Insert a new block between the last argument instruction and the first actual instruction + // of the method. + BasicBlock initClassBlock = + entryBlock.listIterator(code, arguments.size()).split(code, 0, null); + assert !initClassBlock.hasCatchHandlers(); + + InstructionListIterator iterator = initClassBlock.listIterator(code); + iterator.setInsertionPosition(entryBlock.exit().getPosition()); + iterator.add(new InitClass(code.createValue(TypeLatticeElement.getInt()), target.holder())); + } + private void synthesizeNullCheckForReceiver(AppView<?> appView, IRCode code) { List<Value> arguments = code.collectArguments(); if (!arguments.isEmpty()) { @@ -1058,7 +1087,7 @@ assumeDynamicTypeRemover.finish(); classInitializationAnalysis.finish(); code.removeBlocks(blocksToRemove); - code.removeAllTrivialPhis(); + code.removeAllDeadAndTrivialPhis(); assert code.isConsistentSSA(); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java index e10b121..a1307a0 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -126,6 +126,10 @@ return ConstraintWithTarget.ALWAYS; } + public ConstraintWithTarget forInitClass(DexType clazz, DexType context) { + return ConstraintWithTarget.classIsVisible(context, clazz, appView); + } + public ConstraintWithTarget forInstanceGet(DexField field, DexType invocationContext) { DexField lookup = graphLense.lookupField(field); return forFieldInstruction(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java index 783ed8a..7e25c8a 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -23,7 +23,7 @@ import com.android.tools.r8.ir.code.FieldInstruction; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.IRMetadata; -import com.android.tools.r8.ir.code.InstanceFieldInstruction; +import com.android.tools.r8.ir.code.InitClass; import com.android.tools.r8.ir.code.InstancePut; import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.InstructionListIterator; @@ -31,7 +31,6 @@ import com.android.tools.r8.ir.code.InvokeStatic; import com.android.tools.r8.ir.code.InvokeVirtual; import com.android.tools.r8.ir.code.Position; -import com.android.tools.r8.ir.code.StaticFieldInstruction; import com.android.tools.r8.ir.code.StaticGet; import com.android.tools.r8.ir.code.StaticPut; import com.android.tools.r8.ir.code.Value; @@ -211,7 +210,7 @@ } if (current.isStaticGet()) { StaticGet staticGet = current.asStaticGet(); - replaceStaticFieldInstructionByClinitAccessIfPossible( + replaceInstructionByInitClassIfPossible( staticGet, staticGet.getField().holder, code, iterator, code.method.holder()); } replacement.setPosition(position); @@ -226,7 +225,7 @@ private void rewriteInvokeMethodWithConstantValues( IRCode code, - DexType callingContext, + DexType context, Set<Value> affectedValues, ListIterator<BasicBlock> blocks, InstructionListIterator iterator, @@ -236,7 +235,7 @@ if (!invokedHolder.isClassType()) { return; } - DexEncodedMethod target = current.lookupSingleTarget(appView, callingContext); + DexEncodedMethod target = current.lookupSingleTarget(appView, context); if (target != null && target.isInstanceInitializer()) { // Member value propagation does not apply to constructors. Removing a call to a constructor // that is marked as having no side effects could lead to verification errors, due to @@ -291,15 +290,27 @@ AbstractValue abstractReturnValue = target.getOptimizationInfo().getAbstractReturnValue(); if (abstractReturnValue.isSingleValue()) { SingleValue singleReturnValue = abstractReturnValue.asSingleValue(); - if (singleReturnValue.isMaterializableInContext(appView, callingContext)) { + if (singleReturnValue.isMaterializableInContext(appView, context)) { + BasicBlock block = current.getBlock(); + Position position = current.getPosition(); + Instruction replacement = singleReturnValue.createMaterializingInstruction(appView, code, current); affectedValues.addAll(current.outValue().affectedValues()); + current.moveDebugValues(replacement); current.outValue().replaceUsers(replacement.outValue()); current.setOutValue(null); - replacement.setPosition(current.getPosition()); - current.moveDebugValues(replacement); - if (current.getBlock().hasCatchHandlers()) { + + if (current.isInvokeMethodWithReceiver()) { + replaceInstructionByNullCheckIfPossible(current, iterator, context); + } else if (current.isInvokeStatic()) { + replaceInstructionByInitClassIfPossible( + current, target.holder(), code, iterator, context); + } + + // Insert the definition of the replacement. + replacement.setPosition(position); + if (block.hasCatchHandlers()) { iterator.split(code, blocks).listIterator(code).add(replacement); } else { iterator.add(replacement); @@ -354,47 +365,45 @@ Instruction replacement = target.valueAsConstInstruction(code, current.outValue().getLocalInfo(), appView); if (replacement != null) { + BasicBlock block = current.getBlock(); + DexType context = code.method.holder(); + Position position = current.getPosition(); + + // All usages are replaced by the replacement value. affectedValues.addAll(current.outValue().affectedValues()); - DexType context = code.method.method.holder; - if (current.instructionMayHaveSideEffects(appView, context)) { - BasicBlock block = current.getBlock(); - Position position = current.getPosition(); + current.outValue().replaceUsers(replacement.outValue()); - // All usages are replaced by the replacement value. - current.outValue().replaceUsers(replacement.outValue()); - - // To preserve side effects, original field-get is replaced by an explicit null-check, if - // the field-get instruction may only fail with an NPE, or the field-get remains as-is. - if (current.isInstanceGet()) { - replaceInstanceFieldInstructionByNullCheckIfPossible( - current.asInstanceGet(), iterator, context); - } else { - replaceStaticFieldInstructionByClinitAccessIfPossible( - current.asStaticGet(), target.holder(), code, iterator, context); - } - - // Insert the definition of the replacement. - replacement.setPosition(position); - if (block.hasCatchHandlers()) { - iterator.split(code, blocks).listIterator(code).add(replacement); - } else { - iterator.add(replacement); - } + // To preserve side effects, original field-get is replaced by an explicit null-check, if + // the field-get instruction may only fail with an NPE, or the field-get remains as-is. + if (current.isInstanceGet()) { + replaceInstructionByNullCheckIfPossible(current, iterator, context); } else { - iterator.replaceCurrentInstruction(replacement); + replaceInstructionByInitClassIfPossible(current, target.holder(), code, iterator, context); + } + + // Insert the definition of the replacement. + replacement.setPosition(position); + if (block.hasCatchHandlers()) { + iterator.split(code, blocks).listIterator(code).add(replacement); + } else { + iterator.add(replacement); } feedback.markFieldAsPropagated(target); } } - private void replaceInstanceFieldInstructionByNullCheckIfPossible( - InstanceFieldInstruction instruction, InstructionListIterator iterator, DexType context) { + private void replaceInstructionByNullCheckIfPossible( + Instruction instruction, InstructionListIterator iterator, DexType context) { + assert instruction.isInstanceFieldInstruction() || instruction.isInvokeMethodWithReceiver(); assert !instruction.hasOutValue() || !instruction.outValue().hasAnyUsers(); if (instruction.instructionMayHaveSideEffects( - appView, context, FieldInstruction.Assumption.RECEIVER_NOT_NULL)) { + appView, context, Instruction.SideEffectAssumption.RECEIVER_NOT_NULL)) { return; } - Value receiver = instruction.object(); + Value receiver = + instruction.isInstanceFieldInstruction() + ? instruction.asInstanceFieldInstruction().object() + : instruction.asInvokeMethodWithReceiver().getReceiver(); if (receiver.isNeverNull()) { iterator.removeOrReplaceByDebugLocalRead(); return; @@ -410,6 +419,38 @@ iterator.replaceCurrentInstruction(replacement); } + private void replaceInstructionByInitClassIfPossible( + Instruction instruction, + DexType holder, + IRCode code, + InstructionListIterator iterator, + DexType context) { + assert instruction.isStaticFieldInstruction() || instruction.isInvokeStatic(); + if (instruction.instructionMayHaveSideEffects( + appView, context, Instruction.SideEffectAssumption.CLASS_ALREADY_INITIALIZED)) { + return; + } + boolean classInitializationMayHaveSideEffects = + holder.classInitializationMayHaveSideEffects( + appView, + // Types that are a super type of `context` are guaranteed to be initialized + // already. + type -> appView.isSubtype(context, type).isTrue(), + Sets.newIdentityHashSet()); + if (!classInitializationMayHaveSideEffects) { + iterator.removeOrReplaceByDebugLocalRead(); + return; + } + if (!appView.canUseInitClass()) { + return; + } + DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(holder)); + if (clazz != null) { + Value dest = code.createValue(TypeLatticeElement.getInt()); + iterator.replaceCurrentInstruction(new InitClass(dest, clazz.type)); + } + } + private void replaceInstancePutByNullCheckIfNeverRead( IRCode code, InstructionListIterator iterator, InstancePut current) { DexEncodedField target = appView.appInfo().resolveField(current.getField()); @@ -421,10 +462,10 @@ return; } - replaceInstanceFieldInstructionByNullCheckIfPossible(current, iterator, code.method.holder()); + replaceInstructionByNullCheckIfPossible(current, iterator, code.method.holder()); } - private void replaceStaticPutByClinitAccessIfNeverRead( + private void replaceStaticPutByInitClassIfNeverRead( IRCode code, InstructionListIterator iterator, StaticPut current) { DexEncodedField field = appView.appInfo().resolveField(current.getField()); if (field == null || appView.appInfo().isFieldRead(field)) { @@ -435,32 +476,10 @@ return; } - replaceStaticFieldInstructionByClinitAccessIfPossible( + replaceInstructionByInitClassIfPossible( current, field.holder(), code, iterator, code.method.holder()); } - private void replaceStaticFieldInstructionByClinitAccessIfPossible( - StaticFieldInstruction instruction, - DexType holder, - IRCode code, - InstructionListIterator iterator, - DexType context) { - if (instruction.instructionMayHaveSideEffects( - appView, context, FieldInstruction.Assumption.CLASS_ALREADY_INITIALIZED)) { - return; - } - DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(holder)); - if (clazz != null) { - DexEncodedField clinitField = - clazz.lookupStaticField(appView.dexItemFactory().objectMembers.clinitField); - if (clinitField != null) { - Value dest = code.createValue(TypeLatticeElement.getInt()); - StaticGet replacement = new StaticGet(dest, clinitField.field); - iterator.replaceCurrentInstruction(replacement); - } - } - } - /** * Replace invoke targets and field accesses with constant values where possible. * @@ -488,7 +507,7 @@ } else if (current.isInstancePut()) { replaceInstancePutByNullCheckIfNeverRead(code, iterator, current.asInstancePut()); } else if (current.isStaticPut()) { - replaceStaticPutByClinitAccessIfNeverRead(code, iterator, current.asStaticPut()); + replaceStaticPutByInitClassIfNeverRead(code, iterator, current.asStaticPut()); } } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java index 698818f..de9d4cc 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -16,6 +16,7 @@ import com.android.tools.r8.ir.code.DominatorTree; import com.android.tools.r8.ir.code.FieldInstruction; import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.InitClass; import com.android.tools.r8.ir.code.InstanceGet; import com.android.tools.r8.ir.code.InstancePut; import com.android.tools.r8.ir.code.Instruction; @@ -28,6 +29,7 @@ import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection; import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo; +import com.android.tools.r8.utils.SetUtils; import com.google.common.collect.Sets; import java.util.ArrayList; import java.util.HashMap; @@ -54,6 +56,8 @@ private final Set<Value> affectedValues = Sets.newIdentityHashSet(); // Maps keeping track of fields that have an already loaded value at basic block entry. + private final Map<BasicBlock, Set<DexType>> activeInitializedClassesAtEntry = + new IdentityHashMap<>(); private final Map<BasicBlock, Map<FieldAndObject, FieldValue>> activeInstanceFieldsAtEntry = new IdentityHashMap<>(); private final Map<BasicBlock, Map<DexField, FieldValue>> activeStaticFieldsAtEntry = @@ -61,6 +65,7 @@ // Maps keeping track of fields with already loaded values for the current block during // elimination. + private Set<DexType> activeInitializedClasses; private Map<FieldAndObject, FieldValue> activeInstanceFieldValues; private Map<DexField, FieldValue> activeStaticFieldValues; @@ -156,6 +161,10 @@ public void run() { DexType context = method.method.holder; for (BasicBlock block : dominatorTree.getSortedBlocks()) { + activeInitializedClasses = + activeInitializedClassesAtEntry.containsKey(block) + ? activeInitializedClassesAtEntry.get(block) + : Sets.newIdentityHashSet(); activeInstanceFieldValues = activeInstanceFieldsAtEntry.containsKey(block) ? activeInstanceFieldsAtEntry.get(block) @@ -217,6 +226,12 @@ killActiveFields(staticPut); activeStaticFieldValues.put(field, new ExistingValue(staticPut.value())); } + } else if (instruction.isInitClass()) { + InitClass initClass = instruction.asInitClass(); + assert !initClass.outValue().hasAnyUsers(); + if (activeInitializedClasses.contains(initClass.getClassValue())) { + it.removeOrReplaceByDebugLocalRead(); + } } else if (instruction.isMonitor()) { if (instruction.asMonitor().isEnter()) { killAllActiveFields(); @@ -271,7 +286,7 @@ : "Unexpected instruction of type " + instruction.getClass().getTypeName(); } } - propagateActiveFieldsFrom(block); + propagateActiveStateFrom(block); } if (!affectedValues.isEmpty()) { new TypeAnalysis(appView).narrowing(affectedValues); @@ -324,17 +339,24 @@ }); } - private void propagateActiveFieldsFrom(BasicBlock block) { + private void propagateActiveStateFrom(BasicBlock block) { for (BasicBlock successor : block.getSuccessors()) { // Allow propagation across exceptional edges, just be careful not to propagate if the // throwing instruction is a field instruction. if (successor.getPredecessors().size() == 1) { if (block.hasCatchSuccessor(successor)) { Instruction exceptionalExit = block.exceptionalExit(); - if (exceptionalExit != null && exceptionalExit.isFieldInstruction()) { - killActiveFieldsForExceptionalExit(exceptionalExit.asFieldInstruction()); + if (exceptionalExit != null) { + if (exceptionalExit.isFieldInstruction()) { + killActiveFieldsForExceptionalExit(exceptionalExit.asFieldInstruction()); + } else if (exceptionalExit.isInitClass()) { + killActiveInitializedClassesForExceptionalExit(exceptionalExit.asInitClass()); + } } } + assert !activeInitializedClassesAtEntry.containsKey(successor); + activeInitializedClassesAtEntry.put( + successor, SetUtils.newIdentityHashSet(activeInitializedClasses)); assert !activeInstanceFieldsAtEntry.containsKey(successor); activeInstanceFieldsAtEntry.put(successor, new HashMap<>(activeInstanceFieldValues)); assert !activeStaticFieldsAtEntry.containsKey(successor); @@ -393,4 +415,8 @@ activeStaticFieldValues.remove(field); } } + + private void killActiveInitializedClassesForExceptionalExit(InitClass instruction) { + activeInitializedClasses.remove(instruction.getClassValue()); + } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java index b8ca4bc..8e0858a 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -47,7 +47,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; -import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Set; @@ -193,33 +192,34 @@ } // Change the return type of direct methods that return an uninstantiated type to void. - List<DexEncodedMethod> directMethods = clazz.directMethods(); - for (int i = 0; i < directMethods.size(); ++i) { - DexEncodedMethod encodedMethod = directMethods.get(i); - DexMethod method = encodedMethod.method; - RewrittenPrototypeDescription prototypeChanges = - prototypeChangesPerMethod.getOrDefault( - encodedMethod, RewrittenPrototypeDescription.none()); - ArgumentInfoCollection removedArgumentsInfo = prototypeChanges.getArgumentInfoCollection(); - DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges); - if (newMethod != method) { - Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod); + clazz + .getMethodCollection() + .replaceDirectMethods( + encodedMethod -> { + DexMethod method = encodedMethod.method; + RewrittenPrototypeDescription prototypeChanges = + prototypeChangesPerMethod.getOrDefault( + encodedMethod, RewrittenPrototypeDescription.none()); + ArgumentInfoCollection removedArgumentsInfo = + prototypeChanges.getArgumentInfoCollection(); + DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges); + if (newMethod != method) { + Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod); - // TODO(b/110806787): Can be extended to handle collisions by renaming the given - // method. - if (usedSignatures.add(wrapper)) { - clazz.setDirectMethod( - i, - encodedMethod.toTypeSubstitutedMethod( - newMethod, - removedArgumentsInfo.createParameterAnnotationsRemover(encodedMethod))); - methodMapping.put(method, newMethod); - if (removedArgumentsInfo.hasRemovedArguments()) { - removedArgumentsInfoPerMethod.put(newMethod, removedArgumentsInfo); - } - } - } - } + // TODO(b/110806787): Can be extended to handle collisions by renaming the given + // method. + if (usedSignatures.add(wrapper)) { + methodMapping.put(method, newMethod); + if (removedArgumentsInfo.hasRemovedArguments()) { + removedArgumentsInfoPerMethod.put(newMethod, removedArgumentsInfo); + } + return encodedMethod.toTypeSubstitutedMethod( + newMethod, + removedArgumentsInfo.createParameterAnnotationsRemover(encodedMethod)); + } + } + return encodedMethod; + }); // Change the return type of virtual methods that return an uninstantiated type to void. // This is done in two steps. First we change the return type of all methods that override @@ -227,66 +227,74 @@ // all supertypes of the current class are always visited prior to the current class. // This is important to ensure that a method that used to override a method in its super // class will continue to do so after this optimization. - List<DexEncodedMethod> virtualMethods = clazz.virtualMethods(); - for (int i = 0; i < virtualMethods.size(); ++i) { - DexEncodedMethod encodedMethod = virtualMethods.get(i); - DexMethod method = encodedMethod.method; - RewrittenPrototypeDescription prototypeChanges = - getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL); - ArgumentInfoCollection removedArgumentsInfo = prototypeChanges.getArgumentInfoCollection(); - DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges); - if (newMethod != method) { - Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod); + clazz + .getMethodCollection() + .replaceVirtualMethods( + encodedMethod -> { + DexMethod method = encodedMethod.method; + RewrittenPrototypeDescription prototypeChanges = + getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL); + ArgumentInfoCollection removedArgumentsInfo = + prototypeChanges.getArgumentInfoCollection(); + DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges); + if (newMethod != method) { + Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod); - boolean isOverrideOfPreviouslyChangedMethodInSuperClass = - changedVirtualMethods.getOrDefault(equivalence.wrap(method), ImmutableSet.of()).stream() - .anyMatch(other -> appView.appInfo().isSubtype(clazz.type, other)); - if (isOverrideOfPreviouslyChangedMethodInSuperClass) { - assert methodPool.hasSeen(wrapper); + boolean isOverrideOfPreviouslyChangedMethodInSuperClass = + changedVirtualMethods + .getOrDefault(equivalence.wrap(method), ImmutableSet.of()) + .stream() + .anyMatch(other -> appView.appInfo().isSubtype(clazz.type, other)); + if (isOverrideOfPreviouslyChangedMethodInSuperClass) { + assert methodPool.hasSeen(wrapper); - boolean signatureIsAvailable = usedSignatures.add(wrapper); - assert signatureIsAvailable; + boolean signatureIsAvailable = usedSignatures.add(wrapper); + assert signatureIsAvailable; - clazz.setVirtualMethod( - i, - encodedMethod.toTypeSubstitutedMethod( - newMethod, - removedArgumentsInfo.createParameterAnnotationsRemover(encodedMethod))); - methodMapping.put(method, newMethod); - } - } - } - for (int i = 0; i < virtualMethods.size(); ++i) { - DexEncodedMethod encodedMethod = virtualMethods.get(i); - DexMethod method = encodedMethod.method; - RewrittenPrototypeDescription prototypeChanges = - getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL); - ArgumentInfoCollection removedArgumentsInfo = prototypeChanges.getArgumentInfoCollection(); - DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges); - if (newMethod != method) { - Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod); + methodMapping.put(method, newMethod); + return encodedMethod.toTypeSubstitutedMethod( + newMethod, + removedArgumentsInfo.createParameterAnnotationsRemover(encodedMethod)); + } + } + return encodedMethod; + }); + clazz + .getMethodCollection() + .replaceVirtualMethods( + encodedMethod -> { + DexMethod method = encodedMethod.method; + RewrittenPrototypeDescription prototypeChanges = + getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL); + ArgumentInfoCollection removedArgumentsInfo = + prototypeChanges.getArgumentInfoCollection(); + DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges); + if (newMethod != method) { + Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod); - // TODO(b/110806787): Can be extended to handle collisions by renaming the given - // method. Note that this also requires renaming all of the methods that override this - // method, though. - if (!methodPool.hasSeen(wrapper) && usedSignatures.add(wrapper)) { - methodPool.seen(wrapper); + // TODO(b/110806787): Can be extended to handle collisions by renaming the given + // method. Note that this also requires renaming all of the methods that override + // this + // method, though. + if (!methodPool.hasSeen(wrapper) && usedSignatures.add(wrapper)) { + methodPool.seen(wrapper); - clazz.setVirtualMethod( - i, - encodedMethod.toTypeSubstitutedMethod( - newMethod, - removedArgumentsInfo.createParameterAnnotationsRemover(encodedMethod))); - methodMapping.put(method, newMethod); + methodMapping.put(method, newMethod); - boolean added = - changedVirtualMethods - .computeIfAbsent(equivalence.wrap(method), key -> Sets.newIdentityHashSet()) - .add(clazz.type); - assert added; - } - } - } + boolean added = + changedVirtualMethods + .computeIfAbsent( + equivalence.wrap(method), key -> Sets.newIdentityHashSet()) + .add(clazz.type); + assert added; + + return encodedMethod.toTypeSubstitutedMethod( + newMethod, + removedArgumentsInfo.createParameterAnnotationsRemover(encodedMethod)); + } + } + return encodedMethod; + }); } private RewrittenPrototypeDescription getPrototypeChanges( @@ -385,7 +393,7 @@ } assumeDynamicTypeRemover.removeMarkedInstructions(blocksToBeRemoved).finish(); code.removeBlocks(blocksToBeRemoved); - code.removeAllTrivialPhis(valuesToNarrow); + code.removeAllDeadAndTrivialPhis(valuesToNarrow); code.removeUnreachableBlocks(); if (!valuesToNarrow.isEmpty()) { new TypeAnalysis(appView).narrowing(valuesToNarrow);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java index 7c71bd9..db393ed 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -35,7 +35,6 @@ import java.util.BitSet; import java.util.HashSet; import java.util.IdentityHashMap; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutionException; @@ -224,58 +223,64 @@ signatures.markSignatureAsUsed(method.method); } - List<DexEncodedMethod> directMethods = clazz.directMethods(); - for (int i = 0; i < directMethods.size(); i++) { - DexEncodedMethod method = directMethods.get(i); + clazz + .getMethodCollection() + .replaceDirectMethods( + method -> { - // If this is a method with known resolution issues, then don't remove any unused arguments. - if (appView.appInfo().failedResolutionTargets.contains(method.method)) { - continue; - } + // If this is a method with known resolution issues, then don't remove any unused + // arguments. + if (appView.appInfo().failedResolutionTargets.contains(method.method)) { + return method; + } - ArgumentInfoCollection unused = collectUnusedArguments(method); - if (unused != null && unused.hasRemovedArguments()) { - DexProto newProto = createProtoWithRemovedArguments(method, unused); - DexMethod newSignature = signatures.getNewSignature(method, newProto); - if (newSignature == null) { - assert appView.dexItemFactory().isConstructor(method.method); - continue; - } - DexEncodedMethod newMethod = signatures.removeArguments(method, newSignature, unused); - clazz.setDirectMethod(i, newMethod); - synchronized (this) { - methodMapping.put(method.method, newMethod.method); - removedArguments.put(newMethod.method, unused); - } - } - } + ArgumentInfoCollection unused = collectUnusedArguments(method); + if (unused != null && unused.hasRemovedArguments()) { + DexProto newProto = createProtoWithRemovedArguments(method, unused); + DexMethod newSignature = signatures.getNewSignature(method, newProto); + if (newSignature == null) { + assert appView.dexItemFactory().isConstructor(method.method); + return method; + } + DexEncodedMethod newMethod = + signatures.removeArguments(method, newSignature, unused); + synchronized (this) { + methodMapping.put(method.method, newMethod.method); + removedArguments.put(newMethod.method, unused); + } + return newMethod; + } + return method; + }); } private void processVirtualMethods(DexProgramClass clazz) { MemberPool<DexMethod> methodPool = methodPoolCollection.get(clazz); GloballyUsedSignatures signatures = new GloballyUsedSignatures(methodPool); - List<DexEncodedMethod> virtualMethods = clazz.virtualMethods(); - for (int i = 0; i < virtualMethods.size(); i++) { - DexEncodedMethod method = virtualMethods.get(i); - ArgumentInfoCollection unused = collectUnusedArguments(method, methodPool); - if (unused != null && unused.hasRemovedArguments()) { - DexProto newProto = createProtoWithRemovedArguments(method, unused); - DexMethod newSignature = signatures.getNewSignature(method, newProto); + clazz + .getMethodCollection() + .replaceVirtualMethods( + method -> { + ArgumentInfoCollection unused = collectUnusedArguments(method, methodPool); + if (unused != null && unused.hasRemovedArguments()) { + DexProto newProto = createProtoWithRemovedArguments(method, unused); + DexMethod newSignature = signatures.getNewSignature(method, newProto); - // Double-check that the new method signature is in fact available. - assert !methodPool.hasSeenStrictlyAbove(equivalence.wrap(newSignature)); - assert !methodPool.hasSeenStrictlyBelow(equivalence.wrap(newSignature)); + // Double-check that the new method signature is in fact available. + assert !methodPool.hasSeenStrictlyAbove(equivalence.wrap(newSignature)); + assert !methodPool.hasSeenStrictlyBelow(equivalence.wrap(newSignature)); - DexEncodedMethod newMethod = - signatures.removeArguments( - method, signatures.getNewSignature(method, newProto), unused); - clazz.setVirtualMethod(i, newMethod); + DexEncodedMethod newMethod = + signatures.removeArguments( + method, signatures.getNewSignature(method, newProto), unused); - methodMapping.put(method.method, newMethod.method); - removedArguments.put(newMethod.method, unused); - } - } + methodMapping.put(method.method, newMethod.method); + removedArguments.put(newMethod.method, unused); + return newMethod; + } + return method; + }); } private ArgumentInfoCollection collectUnusedArguments(DexEncodedMethod method) {
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 6707fe1..9b443bc 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
@@ -270,7 +270,7 @@ // Restore normality. Set<Value> affectedValues = Sets.newIdentityHashSet(); - code.removeAllTrivialPhis(affectedValues); + code.removeAllDeadAndTrivialPhis(affectedValues); if (!affectedValues.isEmpty()) { new TypeAnalysis(appView).narrowing(affectedValues); }
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 e96b211..5e8b552 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
@@ -8,7 +8,6 @@ import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexClass.FieldSetter; -import com.android.tools.r8.graph.DexClass.MethodSetter; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexField; @@ -17,6 +16,8 @@ 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.DexValue.DexValueInt; +import com.android.tools.r8.graph.DexValue.DexValueNull; import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap; import com.android.tools.r8.graph.GraphLense; import com.android.tools.r8.graph.GraphLense.NestedGraphLense; @@ -559,8 +560,7 @@ assert clazz.instanceFields().size() == 0; clearEnumtoUnboxMethods(clazz); } else { - fixupMethods(clazz.directMethods(), clazz::setDirectMethod); - fixupMethods(clazz.virtualMethods(), clazz::setVirtualMethod); + clazz.getMethodCollection().replaceMethods(this::fixupMethod); fixupFields(clazz.staticFields(), clazz::setStaticField); fixupFields(clazz.instanceFields(), clazz::setInstanceField); } @@ -586,19 +586,13 @@ } } - private void fixupMethods(List<DexEncodedMethod> methods, MethodSetter setter) { - if (methods == null) { - return; + private DexEncodedMethod fixupMethod(DexEncodedMethod encodedMethod) { + DexMethod newMethod = fixupMethod(encodedMethod.method); + if (newMethod != encodedMethod.method) { + lensBuilder.move(encodedMethod.method, newMethod, encodedMethod.isStatic()); + return encodedMethod.toTypeSubstitutedMethod(newMethod); } - for (int i = 0; i < methods.size(); i++) { - DexEncodedMethod encodedMethod = methods.get(i); - DexMethod method = encodedMethod.method; - DexMethod newMethod = fixupMethod(method); - if (newMethod != method) { - lensBuilder.move(method, newMethod, encodedMethod.isStatic()); - setter.setMethod(i, encodedMethod.toTypeSubstitutedMethod(newMethod)); - } - } + return encodedMethod; } private void fixupFields(List<DexEncodedField> fields, FieldSetter setter) { @@ -612,7 +606,13 @@ if (newType != field.type) { DexField newField = factory.createField(field.holder, newType, field.name); lensBuilder.move(field, newField); - setter.setField(i, encodedField.toTypeSubstitutedField(newField)); + DexEncodedField newEncodedField = encodedField.toTypeSubstitutedField(newField); + setter.setField(i, newEncodedField); + if (encodedField.isStatic() && encodedField.hasExplicitStaticValue()) { + assert encodedField.getStaticValue() == DexValueNull.NULL; + newEncodedField.setStaticValue(DexValueInt.DEFAULT); + // TODO(b/150593449): Support conversion from DexValueEnum to DexValueInt. + } } } }
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 84e4fdc..bbf2f22 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
@@ -19,6 +19,7 @@ import static com.android.tools.r8.ir.code.Opcodes.DIV; import static com.android.tools.r8.ir.code.Opcodes.GOTO; import static com.android.tools.r8.ir.code.Opcodes.IF; +import static com.android.tools.r8.ir.code.Opcodes.INIT_CLASS; import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET; import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_OF; import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT; @@ -94,6 +95,7 @@ import com.android.tools.r8.utils.ListUtils; import com.android.tools.r8.utils.MethodSignatureEquivalence; import com.android.tools.r8.utils.Pair; +import com.android.tools.r8.utils.WorkList; import com.google.common.base.Equivalence.Wrapper; import com.google.common.collect.Sets; import java.util.ArrayDeque; @@ -286,9 +288,6 @@ List<Value> values = code.collectArguments(); for (int i = 0; i < values.size(); i++) { Value value = values.get(i); - if (value.numberOfPhiUsers() > 0) { - continue; - } ParameterUsage usage = collectParameterUsages(i, value); if (usage != null) { usages.add(usage); @@ -301,12 +300,23 @@ : new ParameterUsagesInfo(usages)); } - private ParameterUsage collectParameterUsages(int i, Value value) { - ParameterUsageBuilder builder = new ParameterUsageBuilder(value, i, dexItemFactory); - for (Instruction user : value.aliasedUsers()) { - if (!builder.note(user)) { + private ParameterUsage collectParameterUsages(int i, Value root) { + ParameterUsageBuilder builder = new ParameterUsageBuilder(root, i, dexItemFactory); + WorkList<Value> worklist = WorkList.newIdentityWorkList(); + worklist.addIfNotSeen(root); + while (worklist.hasNext()) { + Value value = worklist.next(); + if (value.hasPhiUsers()) { return null; } + for (Instruction user : value.uniqueUsers()) { + if (!builder.note(user)) { + return null; + } + if (user.isAssume()) { + worklist.addIfNotSeen(user.outValue()); + } + } } return builder.build(); } @@ -442,6 +452,7 @@ case CONST_STRING: case DEX_ITEM_BASED_CONST_STRING: case DIV: + case INIT_CLASS: case INSTANCE_OF: case MUL: case NEW_ARRAY_EMPTY:
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java index f10c3fe..1648958 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.ir.code.ConstMethodType; import com.android.tools.r8.ir.code.DefaultInstructionVisitor; import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.InitClass; import com.android.tools.r8.ir.code.InstanceGet; import com.android.tools.r8.ir.code.InstancePut; import com.android.tools.r8.ir.code.InstructionListIterator; @@ -62,6 +63,8 @@ boolean isValidNewInstance(CodeProcessor context, NewInstance invoke); + boolean isValidInitClass(CodeProcessor context, DexType clazz); + void patch(ApplyStrategy context, NewInstance newInstance); void patch(ApplyStrategy context, InvokeMethod invoke); @@ -69,6 +72,8 @@ void patch(ApplyStrategy context, InstanceGet instanceGet); void patch(ApplyStrategy context, StaticGet staticGet); + + void patch(ApplyStrategy context, InitClass initClass); } // No-op strategy. @@ -110,6 +115,11 @@ } @Override + public boolean isValidInitClass(CodeProcessor context, DexType clazz) { + return false; + } + + @Override public void patch(ApplyStrategy context, NewInstance newInstance) { throw new Unreachable(); } @@ -128,6 +138,11 @@ public void patch(ApplyStrategy context, StaticGet staticGet) { throw new Unreachable(); } + + @Override + public void patch(ApplyStrategy context, InitClass initClass) { + throw new Unreachable(); + } }; public final AppView<AppInfoWithLiveness> appView; @@ -353,6 +368,21 @@ return null; } + @Override + public Void visit(InitClass initClass) { + DexType clazz = initClass.getClassValue(); + Strategy strategy = strategyProvider.apply(clazz); + if (strategy.isValidInitClass(this, clazz)) { + if (shouldRewrite(clazz)) { + // Only rewrite references to lambda classes if we are outside the class. + process(strategy, initClass); + } + } else { + lambdaChecker.accept(clazz); + } + return null; + } + abstract void process(Strategy strategy, InvokeMethod invokeMethod); abstract void process(Strategy strategy, NewInstance newInstance); @@ -364,4 +394,6 @@ abstract void process(Strategy strategy, StaticPut staticPut); abstract void process(Strategy strategy, StaticGet staticGet); + + abstract void process(Strategy strategy, InitClass initClass); }
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 176db10..510c122 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
@@ -20,6 +20,7 @@ import com.android.tools.r8.graph.classmerging.HorizontallyMergedLambdaClasses; import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater; import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.InitClass; import com.android.tools.r8.ir.code.InstanceGet; import com.android.tools.r8.ir.code.InstancePut; import com.android.tools.r8.ir.code.Instruction; @@ -529,6 +530,11 @@ void process(Strategy strategy, StaticGet staticGet) { queueForProcessing(method); } + + @Override + void process(Strategy strategy, InitClass initClass) { + queueForProcessing(method); + } } public final class ApplyStrategy extends CodeProcessor { @@ -644,6 +650,11 @@ void process(Strategy strategy, StaticGet staticGet) { strategy.patch(this, staticGet); } + + @Override + void process(Strategy strategy, InitClass initClass) { + strategy.patch(this, initClass); + } } private final class LambdaMergerOptimizationInfoFixer
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java index e54d241..81480d6 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
@@ -15,6 +15,7 @@ import com.android.tools.r8.ir.analysis.type.TypeLatticeElement; import com.android.tools.r8.ir.code.CheckCast; import com.android.tools.r8.ir.code.ConstNumber; +import com.android.tools.r8.ir.code.InitClass; import com.android.tools.r8.ir.code.InstanceGet; import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.InvokeDirect; @@ -50,10 +51,10 @@ assert group.containsLambda(lambda); // Only support writes to singleton static field named 'INSTANCE' from lambda // static class initializer. - return field.name == context.kotlin.functional.kotlinStyleLambdaInstanceName && - lambda == field.type && - context.factory.isClassConstructor(context.method.method) && - context.method.method.holder == lambda; + return field.name == context.kotlin.functional.kotlinStyleLambdaInstanceName + && lambda == field.type + && context.factory.isClassConstructor(context.method.method) + && context.method.method.holder == lambda; } @Override @@ -61,8 +62,8 @@ DexType lambda = field.holder; assert group.containsLambda(lambda); // Support all reads of singleton static field named 'INSTANCE'. - return field.name == context.kotlin.functional.kotlinStyleLambdaInstanceName && - lambda == field.type; + return field.name == context.kotlin.functional.kotlinStyleLambdaInstanceName + && lambda == field.type; } @Override @@ -112,6 +113,13 @@ } @Override + public boolean isValidInitClass(CodeProcessor context, DexType clazz) { + assert group.containsLambda(clazz); + // Support all init class instructions. + return true; + } + + @Override public void patch(ApplyStrategy context, NewInstance newInstance) { DexType oldType = newInstance.clazz; DexType newType = group.getGroupClassType(); @@ -202,6 +210,14 @@ context.recordTypeHasChanged(patchedStaticGet.outValue()); } + @Override + public void patch(ApplyStrategy context, InitClass initClass) { + InitClass pachedInitClass = + new InitClass( + context.code.createValue(TypeLatticeElement.getInt()), group.getGroupClassType()); + context.instructions().replaceCurrentInstruction(pachedInitClass); + } + private void patchInitializer(CodeProcessor context, InvokeDirect invoke) { // Patching includes: // - change of methods
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 8978657..3c99a56 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
@@ -659,6 +659,11 @@ } @Override + public boolean registerInitClass(DexType clazz) { + return registerTypeReference(clazz); + } + + @Override public boolean registerInvokeVirtual(DexMethod method) { return registerMethod(method); }
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 8e8d2da..6c714c9 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
@@ -828,6 +828,9 @@ public boolean isSupportedAppendMethod(InvokeMethod invoke) { DexMethod invokedMethod = invoke.getInvokedMethod(); assert isAppendMethod(invokedMethod); + if (invoke.hasOutValue()) { + return false; + } // Any methods other than append(arg) are not trivial since they may change the builder // state not monotonically. if (invoke.inValues().size() > 2) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java index 4a65b7c..a37a42b 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -13,7 +13,10 @@ 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.DexProgramClass; import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.InnerClassAttribute; @@ -30,6 +33,9 @@ KmClass kmClass; + DexField companionObject = null; + DexProgramClass hostClass = null; + static KotlinClass fromKotlinClassMetadata( KotlinClassMetadata kotlinClassMetadata, DexClass clazz) { assert kotlinClassMetadata instanceof KotlinClassMetadata.Class; @@ -41,6 +47,28 @@ super(metadata, clazz); } + void foundCompanionObject(DexEncodedField companionObject) { + // Companion cannot be nested. If this class is a host (and about to store a field that holds + // a companion object), it should not have a host class. + assert hostClass == null; + this.companionObject = companionObject.field; + } + + boolean hasCompanionObject() { + return companionObject != null; + } + + DexType getCompanionObjectType() { + return hasCompanionObject() ? companionObject.type : null; + } + + void linkHostClass(DexProgramClass hostClass) { + // Companion cannot be nested. If this class is a companion object (and about to link to its + // host class), it should not have a companion object. + assert companionObject == null; + this.hostClass = hostClass; + } + @Override void processMetadata(KotlinClassMetadata.Class metadata) { kmClass = metadata.toKmClass(); @@ -78,10 +106,19 @@ superTypes.add(toKmType(addKotlinPrefix("Any;"))); } - // Rewriting downward hierarchies: nested. + // Rewriting downward hierarchies: nested, including companion class. + // Note that `kotlinc` uses these nested classes to determine which classes to look up when + // resolving declarations in the companion object, e.g., Host.Companion.prop and Host.prop. + // Thus, users (in particular, library developers) should keep InnerClasses and EnclosingMethod + // attributes if declarations in the companion need to be exposed. List<String> nestedClasses = kmClass.getNestedClasses(); nestedClasses.clear(); for (InnerClassAttribute innerClassAttribute : clazz.getInnerClasses()) { + // Skip InnerClass attribute for itself. + // Otherwise, an inner class would have itself as a nested class. + if (clazz.getInnerClassAttributeForThisClass() == innerClassAttribute) { + continue; + } DexString renamedInnerName = lens.lookupInnerName(innerClassAttribute, appView.options()); if (renamedInnerName != null) { nestedClasses.add(renamedInnerName.toString()); @@ -103,6 +140,8 @@ if (!appView.options().enableKotlinMetadataRewritingForMembers) { return; } + + // Rewriting constructors. List<KmConstructor> constructors = kmClass.getConstructors(); constructors.clear(); for (DexEncodedMethod method : clazz.directMethods()) { @@ -115,9 +154,14 @@ } } - // TODO(b/70169921): enum entries + // Rewriting companion object if any. + if (kmClass.getCompanionObject() != null && hasCompanionObject()) { + kmClass.setCompanionObject(lens.lookupName(companionObject).toString()); + } - rewriteDeclarationContainer(kmClass, appView, lens); + // TODO(b/151193864): enum entries + + rewriteDeclarationContainer(appView, lens); } @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java index 4433017..cc1cd14 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
@@ -21,7 +21,7 @@ public final class KotlinClassFacade extends KotlinInfo<KotlinClassMetadata.MultiFileClassFacade> { - // TODO(b/70169921): is it better to maintain List<DexType>? + // TODO(b/151194869): is it better to maintain List<DexType>? List<String> partClassNames; static KotlinClassFacade fromKotlinClassMetadata(
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java index f108252..16223bb 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
@@ -19,7 +19,7 @@ public final class KotlinClassPart extends KotlinInfo<KotlinClassMetadata.MultiFileClassPart> { KmPackage kmPackage; - // TODO(b/70169921): is it better to maintain DexType? + // TODO(b/151194869): is it better to maintain DexType? String facadeClassName; static KotlinClassPart fromKotlinClassMetadata( @@ -48,7 +48,7 @@ if (!appView.options().enableKotlinMetadataRewritingForMembers) { return; } - rewriteDeclarationContainer(kmPackage, appView, lens); + rewriteDeclarationContainer(appView, lens); } @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java index bb4dc47..27c9230 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
@@ -38,7 +38,7 @@ if (!appView.options().enableKotlinMetadataRewritingForMembers) { return; } - rewriteDeclarationContainer(kmPackage, appView, lens); + rewriteDeclarationContainer(appView, lens); } @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java index e9c1eb6..7c53cba 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
@@ -5,6 +5,7 @@ package com.android.tools.r8.kotlin; import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmFunction; +import static kotlinx.metadata.Flag.Class.IS_COMPANION_OBJECT; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; @@ -17,6 +18,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Predicate; import kotlinx.metadata.KmDeclarationContainer; import kotlinx.metadata.KmFunction; import kotlinx.metadata.KmProperty; @@ -93,15 +95,49 @@ return isClass() || isFile() || isClassPart(); } + KmDeclarationContainer getDeclarations() { + if (isClass()) { + return asClass().kmClass; + } else if (isFile()) { + return asFile().kmPackage; + } else if (isClassPart()) { + return asClassPart().kmPackage; + } else { + throw new Unreachable("Unexpected KotlinInfo: " + this); + } + } + // {@link KmClass} and {@link KmPackage} are inherited from {@link KmDeclarationContainer} that // abstract functions and properties. Rewriting of those portions can be unified here. - void rewriteDeclarationContainer( - KmDeclarationContainer kmDeclarationContainer, - AppView<AppInfoWithLiveness> appView, - NamingLens lens) { + void rewriteDeclarationContainer(AppView<AppInfoWithLiveness> appView, NamingLens lens) { assert clazz != null; + KmDeclarationContainer kmDeclarationContainer = getDeclarations(); Map<String, KmPropertyGroup.Builder> propertyGroupBuilderMap = new HashMap<>(); + + // Backing fields for a companion object are declared in its host class. + Iterable<DexEncodedField> fields = clazz.fields(); + Predicate<DexEncodedField> backingFieldTester = DexEncodedField::isKotlinBackingField; + if (isClass()) { + KotlinClass ktClass = asClass(); + if (IS_COMPANION_OBJECT.invoke(ktClass.kmClass.getFlags()) && ktClass.hostClass != null) { + fields = ktClass.hostClass.fields(); + backingFieldTester = DexEncodedField::isKotlinBackingFieldForCompanionObject; + } + } + + for (DexEncodedField field : fields) { + if (backingFieldTester.test(field)) { + String name = field.getKotlinMemberInfo().propertyName; + assert name != null; + KmPropertyGroup.Builder builder = + propertyGroupBuilderMap.computeIfAbsent( + name, + k -> KmPropertyGroup.builder(field.getKotlinMemberInfo().propertyFlags, name)); + builder.foundBackingField(field); + } + } + List<KmFunction> functions = kmDeclarationContainer.getFunctions(); functions.clear(); for (DexEncodedMethod method : clazz.methods()) { @@ -121,19 +157,22 @@ assert name != null; KmPropertyGroup.Builder builder = propertyGroupBuilderMap.computeIfAbsent( - name, k -> KmPropertyGroup.builder(method.getKotlinMemberInfo().flag, name)); + name, + // Hitting here (creating a property builder) after visiting all fields means that + // this property doesn't have a backing field. Don't use members' flags. + k -> KmPropertyGroup.builder(method.getKotlinMemberInfo().propertyFlags, name)); switch (method.getKotlinMemberInfo().memberKind) { case EXTENSION_PROPERTY_GETTER: builder.isExtensionGetter(); // fallthrough; case PROPERTY_GETTER: - builder.foundGetter(method); + builder.foundGetter(method, method.getKotlinMemberInfo().flags); break; case EXTENSION_PROPERTY_SETTER: builder.isExtensionSetter(); // fallthrough; case PROPERTY_SETTER: - builder.foundSetter(method); + builder.foundSetter(method, method.getKotlinMemberInfo().flags); break; case EXTENSION_PROPERTY_ANNOTATIONS: builder.isExtensionAnnotations(); @@ -147,18 +186,7 @@ continue; } - // TODO(b/70169921): What should we do for methods that fall into this category---no mark? - } - - for (DexEncodedField field : clazz.fields()) { - if (field.isKotlinBackingField()) { - String name = field.getKotlinMemberInfo().propertyName; - assert name != null; - KmPropertyGroup.Builder builder = - propertyGroupBuilderMap.computeIfAbsent( - name, k -> KmPropertyGroup.builder(field.getKotlinMemberInfo().flag, name)); - builder.foundBackingField(field); - } + // TODO(b/151194869): What should we do for methods that fall into this category---no mark? } List<KmProperty> properties = kmDeclarationContainer.getProperties();
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInfoCollector.java b/src/main/java/com/android/tools/r8/kotlin/KotlinInfoCollector.java new file mode 100644 index 0000000..9d89660 --- /dev/null +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfoCollector.java
@@ -0,0 +1,62 @@ +// 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; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexApplication; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.utils.Reporter; +import com.android.tools.r8.utils.ThreadUtils; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; + +public class KotlinInfoCollector { + public static void computeKotlinInfoForProgramClasses( + DexApplication application, AppView<?> appView, ExecutorService executorService) + throws ExecutionException { + if (appView.options().kotlinOptimizationOptions().disableKotlinSpecificOptimizations) { + return; + } + Kotlin kotlin = appView.dexItemFactory().kotlin; + Reporter reporter = appView.options().reporter; + Map<DexProgramClass, DexProgramClass> companionToHostMap = new ConcurrentHashMap<>(); + ThreadUtils.processItems( + application.classes(), + programClass -> { + KotlinInfo kotlinInfo = kotlin.getKotlinInfo(programClass, reporter); + programClass.setKotlinInfo(kotlinInfo); + KotlinMemberInfo.markKotlinMemberInfo(programClass, kotlinInfo, reporter); + // Store a companion type to revisit. + if (kotlinInfo != null + && kotlinInfo.isClass() + && kotlinInfo.asClass().hasCompanionObject()) { + DexType companionType = kotlinInfo.asClass().getCompanionObjectType(); + DexProgramClass companionClass = appView.definitionForProgramType(companionType); + if (companionClass != null) { + companionToHostMap.put(companionClass, programClass); + } + } + }, + executorService); + // TODO(b/151194869): if we can guarantee that Companion classes are visited ahead and their + // KotlinInfo is created before processing host classes, below could be hoisted to 1st pass. + // Maybe name-based filtering? E.g., classes whose name ends with "$Companion" v.s. not? + ThreadUtils.processItems( + companionToHostMap.keySet(), + companionClass -> { + KotlinInfo kotlinInfo = companionClass.getKotlinInfo(); + if (kotlinInfo != null && kotlinInfo.isClass()) { + DexProgramClass hostClass = companionToHostMap.get(companionClass); + assert hostClass != null; + kotlinInfo.asClass().linkHostClass(hostClass); + // Revisit host class's members with declarations in the companion object. + KotlinMemberInfo.markKotlinMemberInfo(hostClass, kotlinInfo, reporter); + } + }, + executorService); + } +}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java index 50058ec..dfdb28b 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
@@ -7,7 +7,6 @@ import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmMethodSignature; import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.isExtension; -import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; @@ -37,8 +36,12 @@ } public final MemberKind memberKind; - // Original member flag. May be necessary to keep Kotlin-specific flag, e.g., suspend function. - final int flag; + // Original member flags. May be necessary to keep Kotlin-specific flag, e.g., suspend function. + final int flags; + // TODO(b/151194869): better to split into FunctionInfo v.s. PropertyInfo ? + // Original property flags. E.g., for property getter, getter flags are stored to `flags`, while + // the property's flags are stored here, in case of properties without a backing field. + final int propertyFlags; // Original property name for (extension) property. Otherwise, null. final String propertyName; // Information from original KmValueParameter(s) if available. Otherwise, null. @@ -46,32 +49,40 @@ // Constructor for KmFunction private KotlinMemberInfo( - MemberKind memberKind, int flag, List<KmValueParameter> kmValueParameters) { - this(memberKind, flag, null, kmValueParameters); + MemberKind memberKind, int flags, List<KmValueParameter> kmValueParameters) { + this(memberKind, flags, 0, null, kmValueParameters); } // Constructor for a backing field and a getter in KmProperty - private KotlinMemberInfo(MemberKind memberKind, int flag, String propertyName) { - this(memberKind, flag, propertyName, EMPTY_PARAM); + private KotlinMemberInfo( + MemberKind memberKind, int flags, int propertyFlags, String propertyName) { + this(memberKind, flags, propertyFlags, propertyName, EMPTY_PARAM); } // Constructor for a setter in KmProperty private KotlinMemberInfo( MemberKind memberKind, - int flag, + int flags, + int propertyFlags, String propertyName, KmValueParameter kmValueParameter) { - this(memberKind, flag, propertyName, + this( + memberKind, + flags, + propertyFlags, + propertyName, kmValueParameter != null ? ImmutableList.of(kmValueParameter) : EMPTY_PARAM); } private KotlinMemberInfo( MemberKind memberKind, - int flag, + int flags, + int propertyFlags, String propertyName, List<KmValueParameter> kmValueParameters) { this.memberKind = memberKind; - this.flag = flag; + this.flags = flags; + this.propertyFlags = propertyFlags; this.propertyName = propertyName; assert kmValueParameters != null; if (kmValueParameters.isEmpty()) { @@ -100,6 +111,7 @@ FUNCTION, EXTENSION_FUNCTION, + COMPANION_OBJECT_BACKING_FIELD, PROPERTY_BACKING_FIELD, PROPERTY_GETTER, PROPERTY_SETTER, @@ -110,8 +122,6 @@ EXTENSION_PROPERTY_SETTER, EXTENSION_PROPERTY_ANNOTATIONS; - // TODO(b/70169921): companion - public boolean isFunction() { return this == FUNCTION || isExtensionFunction(); } @@ -124,8 +134,13 @@ return this == PROPERTY_BACKING_FIELD; } + public boolean isBackingFieldForCompanionObject() { + return this == COMPANION_OBJECT_BACKING_FIELD; + } + public boolean isProperty() { return isBackingField() + || isBackingFieldForCompanionObject() || this == PROPERTY_GETTER || this == PROPERTY_SETTER || this == PROPERTY_ANNOTATIONS @@ -139,24 +154,17 @@ } } - public static void markKotlinMemberInfo( - DexClass clazz, KotlinInfo kotlinInfo, Reporter reporter) { + static void markKotlinMemberInfo(DexClass clazz, KotlinInfo kotlinInfo, Reporter reporter) { if (kotlinInfo == null || !kotlinInfo.hasDeclarations()) { return; } - if (kotlinInfo.isClass()) { - markKotlinMemberInfo(clazz, kotlinInfo.asClass().kmClass, reporter); - } else if (kotlinInfo.isFile()) { - markKotlinMemberInfo(clazz, kotlinInfo.asFile().kmPackage, reporter); - } else if (kotlinInfo.isClassPart()) { - markKotlinMemberInfo(clazz, kotlinInfo.asClassPart().kmPackage, reporter); - } else { - throw new Unreachable("Unexpected KotlinInfo: " + kotlinInfo); - } - } - private static void markKotlinMemberInfo( - DexClass clazz, KmDeclarationContainer kmDeclarationContainer, Reporter reporter) { + KmDeclarationContainer kmDeclarationContainer = kotlinInfo.getDeclarations(); + String companionObject = null; + if (kotlinInfo.isClass()) { + companionObject = kotlinInfo.asClass().kmClass.getCompanionObject(); + } + Map<String, KmFunction> kmFunctionMap = new HashMap<>(); Map<String, KmProperty> kmPropertyFieldMap = new HashMap<>(); Map<String, KmProperty> kmPropertyGetterMap = new HashMap<>(); @@ -179,16 +187,26 @@ if (propertyProcessor.setterSignature() != null) { kmPropertySetterMap.put(propertyProcessor.setterSignature().asString(), kmProperty); } - // TODO(b/70169921): property annotations + // TODO(b/151194869): property annotations }); for (DexEncodedField field : clazz.fields()) { + if (companionObject != null && companionObject.equals(field.field.name.toString())) { + assert kotlinInfo.isClass(); + kotlinInfo.asClass().foundCompanionObject(field); + continue; + } String key = toJvmFieldSignature(field.field).asString(); if (kmPropertyFieldMap.containsKey(key)) { KmProperty kmProperty = kmPropertyFieldMap.get(key); field.setKotlinMemberInfo( new KotlinMemberInfo( - MemberKind.PROPERTY_BACKING_FIELD, kmProperty.getFlags(), kmProperty.getName())); + clazz == kotlinInfo.clazz + ? MemberKind.PROPERTY_BACKING_FIELD + : MemberKind.COMPANION_OBJECT_BACKING_FIELD, + kmProperty.getFlags(), + kmProperty.getFlags(), + kmProperty.getName())); } } @@ -218,12 +236,16 @@ method.setKotlinMemberInfo( new KotlinMemberInfo( MemberKind.EXTENSION_PROPERTY_GETTER, + kmProperty.getGetterFlags(), kmProperty.getFlags(), kmProperty.getName())); } else { method.setKotlinMemberInfo( new KotlinMemberInfo( - MemberKind.PROPERTY_GETTER, kmProperty.getFlags(), kmProperty.getName())); + MemberKind.PROPERTY_GETTER, + kmProperty.getGetterFlags(), + kmProperty.getFlags(), + kmProperty.getName())); } } else if (kmPropertySetterMap.containsKey(key)) { KmProperty kmProperty = kmPropertySetterMap.get(key); @@ -231,6 +253,7 @@ method.setKotlinMemberInfo( new KotlinMemberInfo( MemberKind.EXTENSION_PROPERTY_SETTER, + kmProperty.getSetterFlags(), kmProperty.getFlags(), kmProperty.getName(), kmProperty.getSetterParameter())); @@ -238,6 +261,7 @@ method.setKotlinMemberInfo( new KotlinMemberInfo( MemberKind.PROPERTY_SETTER, + kmProperty.getSetterFlags(), kmProperty.getFlags(), kmProperty.getName(), kmProperty.getSetterParameter()));
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java index fadf9cb..474c123 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java
@@ -87,6 +87,7 @@ i -> addKotlinPrefix("jvm/functions/Function" + i + ";")))) .build(); + // TODO(b/151195430): remove backward type conversions. private static String remapKotlinType(String type) { if (knownTypeConversion.containsKey(type)) { return knownTypeConversion.get(type); @@ -94,6 +95,7 @@ return type; } + // TODO(b/151195430): remove backward type conversions. // Kotlin @Metadata deserialization has plain "kotlin", which will be relocated in r8lib. // See b/70169921#comment57 for more details. // E.g., desc: (Labc/xyz/C;Lkotlin/Function1;)kotlin/Unit
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 9507b32..5d1777f 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -69,7 +69,7 @@ // If @Metadata is still associated, this class should not be renamed // (by {@link ClassNameMinifier} of course). // Or, we start maintaining @Metadata for renamed classes. - // TODO(b/70169921): if this option is settled down, this assertion is meaningless. + // TODO(b/151194540): if this option is settled down, this assertion is meaningless. assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type || appView.options().enableKotlinMetadataRewritingForRenamedClasses : clazz.toSourceString()
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java index ce685b2..43b5cf2 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -9,6 +9,7 @@ import static com.android.tools.r8.utils.DescriptorUtils.descriptorToKotlinClassifier; import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromDescriptor; import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKmType; +import static kotlinx.metadata.Flag.Property.IS_VAR; import static kotlinx.metadata.FlagsKt.flagsOf; import com.android.tools.r8.graph.AppView; @@ -115,11 +116,11 @@ if (classifier == null) { return null; } - // TODO(b/70169921): Mysterious, why attempts to properly set flags bothers kotlinc? + // TODO(b/151194869): Mysterious, why attempts to properly set flags bothers kotlinc? // and/or why wiping out flags works for KmType but not KmFunction?! KmType kmType = new KmType(flagsOf()); kmType.visitClass(classifier); - // TODO(b/70169921): Can be generalized too, like ArrayTypeSignature.Converter ? + // TODO(b/151194164): Can be generalized too, like ArrayTypeSignature.Converter ? // E.g., java.lang.String[] -> KmType(kotlin/Array, KmTypeProjection(OUT, kotlin/String)) if (type.isArrayType() && !type.isPrimitiveArrayType()) { DexType elementType = type.toArrayElementType(appView.dexItemFactory()); @@ -176,7 +177,7 @@ KmType argumentType = typeArgument.asClassTypeSignature().convert(this); result.getArguments().add(new KmTypeProjection(KmVariance.INVARIANT, argumentType)); } - // TODO(b/70169921): for TypeVariableSignature, there is KmType::visitTypeParameter. + // TODO(b/151194164): for TypeVariableSignature, there is KmType::visitTypeParameter. return result; } @@ -234,11 +235,11 @@ // For a library method override, we should not have renamed it. assert !method.isLibraryMethodOverride().isTrue() || renamedMethod.name == method.method.name : method.toSourceString() + " -> " + renamedMethod.toSourceString(); - // TODO(b/70169921): Should we keep kotlin-specific flags only while synthesizing the base + // TODO(b/151194869): Should we keep kotlin-specific flags only while synthesizing the base // value from general JVM flags? int flag = appView.appInfo().isPinned(method.method) && method.getKotlinMemberInfo() != null - ? method.getKotlinMemberInfo().flag + ? method.getKotlinMemberInfo().flags : method.accessFlags.getAsKotlinFlags(); KmFunction kmFunction = new KmFunction(flag, renamedMethod.name.toString()); JvmExtensionsKt.setSignature(kmFunction, toJvmMethodSignature(renamedMethod)); @@ -369,49 +370,57 @@ * getter, and so on. */ static class KmPropertyGroup { - final int flag; + final int flags; final String name; final DexEncodedField field; final DexEncodedMethod getter; + final int getterFlags; final DexEncodedMethod setter; + final int setterFlags; final DexEncodedMethod annotations; final boolean isExtension; private KmPropertyGroup( - int flag, + int flags, String name, DexEncodedField field, DexEncodedMethod getter, + int getterFlags, DexEncodedMethod setter, + int setterFlags, DexEncodedMethod annotations, boolean isExtension) { - this.flag = flag; + this.flags = flags; this.name = name; this.field = field; this.getter = getter; + this.getterFlags = getterFlags; this.setter = setter; + this.setterFlags = setterFlags; this.annotations = annotations; this.isExtension = isExtension; } - static Builder builder(int flag, String name) { - return new Builder(flag, name); + static Builder builder(int flags, String name) { + return new Builder(flags, name); } static class Builder { - private final int flag; + private final int flags; private final String name; private DexEncodedField field; private DexEncodedMethod getter; + private int getterFlags; private DexEncodedMethod setter; + private int setterFlags; private DexEncodedMethod annotations; private boolean isExtensionGetter; private boolean isExtensionSetter; private boolean isExtensionAnnotations; - private Builder(int flag, String name) { - this.flag = flag; + private Builder(int flags, String name) { + this.flags = flags; this.name = name; } @@ -420,13 +429,15 @@ return this; } - Builder foundGetter(DexEncodedMethod getter) { + Builder foundGetter(DexEncodedMethod getter, int flags) { this.getter = getter; + this.getterFlags = flags; return this; } - Builder foundSetter(DexEncodedMethod setter) { + Builder foundSetter(DexEncodedMethod setter, int flags) { this.setter = setter; + this.setterFlags = flags; return this; } @@ -464,12 +475,13 @@ return null; } } - return new KmPropertyGroup(flag, name, field, getter, setter, annotations, isExtension); + return new KmPropertyGroup( + flags, name, field, getter, getterFlags, setter, setterFlags, annotations, isExtension); } } KmProperty toRenamedKmProperty(AppView<AppInfoWithLiveness> appView, NamingLens lens) { - KmProperty kmProperty = new KmProperty(flag, name, flagsOf(), flagsOf()); + KmProperty kmProperty = new KmProperty(flags, name, flagsOf(), flagsOf()); KmType kmPropertyType = null; KmType kmReceiverType = null; @@ -540,8 +552,13 @@ && renamedPropertyName.equals(name)) { renamedPropertyName = renamedGetter.name.toString(); } - kmProperty.setGetterFlags(getter.accessFlags.getAsKotlinFlags()); + kmProperty.setGetterFlags(getterFlags); JvmExtensionsKt.setGetterSignature(kmProperty, toJvmMethodSignature(renamedGetter)); + } else if (field != null) { + // Property without getter. + // Even though a getter does not exist, `kotlinc` still set getter flags and use them to + // determine when to direct field access v.s. getter calls for property resolution. + kmProperty.setGetterFlags(field.accessFlags.getAsKotlinFlags()); } criteria = checkSetterCriteria(); @@ -610,8 +627,13 @@ && renamedPropertyName.equals(name)) { renamedPropertyName = renamedSetter.name.toString(); } - kmProperty.setSetterFlags(setter.accessFlags.getAsKotlinFlags()); + kmProperty.setSetterFlags(setterFlags); JvmExtensionsKt.setSetterSignature(kmProperty, toJvmMethodSignature(renamedSetter)); + } else if (field != null && IS_VAR.invoke(flags)) { + // Editable property without setter. + // Even though a setter does not exist, `kotlinc` still set setter flags and use them to + // determine when to direct field access v.s. setter calls for property resolution. + kmProperty.setGetterFlags(field.accessFlags.getAsKotlinFlags()); } // If the property type remains null at the end, bail out to synthesize this property.
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java index 5cc7d2e..1bb17ea 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
@@ -12,7 +12,7 @@ import kotlinx.metadata.jvm.KotlinClassMetadata; public final class KotlinSyntheticClass extends KotlinInfo<KotlinClassMetadata.SyntheticClass> { - // TODO(b/70169921): Once converted to internal data structure, this can be gone. + // TODO(b/151194794): Once converted to internal data structure, this can be gone. private KotlinClassMetadata.SyntheticClass metadata; public enum Flavour { @@ -47,13 +47,13 @@ void processMetadata(KotlinClassMetadata.SyntheticClass metadata) { this.metadata = metadata; if (metadata.isLambda()) { - // TODO(b/70169921): Use #toKmLambda to store a mutable model if needed. + // TODO(b/151194794): Use #toKmLambda to store a mutable model if needed. } } @Override void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) { - // TODO(b/70169921): no idea yet! + // TODO(b/151194794): no idea yet! assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type || appView.options().enableKotlinMetadataRewritingForRenamedClasses : toString(); @@ -61,7 +61,7 @@ @Override KotlinClassHeader createHeader() { - // TODO(b/70169921): may need to update if `rewrite` is implemented. + // TODO(b/151194794): may need to update if `rewrite` is implemented. return metadata.getHeader(); }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java index 0fa9f39..3134b8a 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java
@@ -12,14 +12,14 @@ // Provides access to Kotlin information about value parameter. class KotlinValueParameterInfo { - // TODO(b/70169921): When to use original param name v.s. when to *not* use? + // TODO(b/151193860): When to use original param name v.s. when to *not* use? // Original parameter name. final String name; // Original parameter flag, e.g., has default value. final int flag; // Indicates whether the formal parameter is originally `vararg`. final boolean isVararg; - // TODO(b/70169921): Should we treat them as normal annotations? E.g., shrinking and renaming? + // TODO(b/151194869): Should we treat them as normal annotations? E.g., shrinking and renaming? // Annotations on the type of value parameter. final List<KmAnnotation> annotations;
diff --git a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java index 8f4330c..a5ead29 100644 --- a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java +++ b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
@@ -29,17 +29,26 @@ public void run() { ProguardConfiguration proguardConfiguration = appView.options().getProguardConfiguration(); String renameSourceFile = proguardConfiguration.getRenameSourceFileAttribute(); + boolean hasRenameSourceFileAttribute = renameSourceFile != null; // Return early if a user wants to keep the current source file attribute as-is. - if (renameSourceFile == null && proguardConfiguration.getKeepAttributes().sourceFile) { + if (!hasRenameSourceFileAttribute + && proguardConfiguration.getKeepAttributes().sourceFile + && appView.options().forceProguardCompatibility) { return; } - // Now, the user wants either to remove source file attribute or to rename it. - DexString dexRenameSourceFile = - renameSourceFile == null - ? appView.dexItemFactory().createString("") - : appView.dexItemFactory().createString(renameSourceFile); + // Now, the user wants either to remove source file attribute or to rename it for non-kept + // classes. + DexString defaultRenaming = getSourceFileRenaming(proguardConfiguration); for (DexClass clazz : appView.appInfo().classes()) { - clazz.sourceFile = dexRenameSourceFile; + // We only parse sourceFile if -keepattributes SourceFile, but for compat we should add + // a source file name, otherwise line positions will not be printed on the JVM or old version + // of ART. + if (!hasRenameSourceFileAttribute + && proguardConfiguration.getKeepAttributes().sourceFile + && appView.rootSet().mayNotBeMinified(clazz.type, appView)) { + continue; + } + clazz.sourceFile = defaultRenaming; clazz.forEachMethod(encodedMethod -> { // Abstract methods do not have code_item. if (encodedMethod.shouldNotHaveCode()) { @@ -66,4 +75,24 @@ }); } } + + private DexString getSourceFileRenaming(ProguardConfiguration proguardConfiguration) { + // If we should not be keeping the source file, null it out. + if (!appView.options().forceProguardCompatibility + && !proguardConfiguration.getKeepAttributes().sourceFile) { + return null; + } + + String renamedSourceFileAttribute = proguardConfiguration.getRenameSourceFileAttribute(); + if (renamedSourceFileAttribute != null) { + return appView.dexItemFactory().createString(renamedSourceFileAttribute); + } + + // Otherwise, take the smallest size depending on platform. We cannot use NULL since the jvm + // and art will write at foo.bar.baz(Unknown Source) without a line-number. Newer version of ART + // will report the DEX PC. + return appView + .dexItemFactory() + .createString(appView.options().isGeneratingClassFiles() ? "SourceFile" : ""); + } }
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java index b319c6d..4816759 100644 --- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java +++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -142,9 +142,7 @@ lenseBuilder.add(encodedMethod.method); accessFlags.promoteToFinal(); accessFlags.promoteToPublic(); - // Although the current method became public, it surely has the single virtual target. - encodedMethod.method.setSingleVirtualMethodCache( - encodedMethod.method.holder, encodedMethod); + // The method just became public and is therefore not a library override. encodedMethod.setLibraryMethodOverride(OptionalBool.FALSE); return true; }
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 8a65562..292f616 100644 --- a/src/main/java/com/android/tools/r8/optimize/InvokeSingleTargetExtractor.java +++ b/src/main/java/com/android/tools/r8/optimize/InvokeSingleTargetExtractor.java
@@ -43,6 +43,11 @@ } @Override + public boolean registerInitClass(DexType clazz) { + return invalid(); + } + + @Override public boolean registerInvokeVirtual(DexMethod method) { return setTarget(method, InvokeKind.VIRTUAL); }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java index 89e7c9d..c63a707 100644 --- a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java +++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
@@ -421,29 +421,29 @@ List<StackTraceLine> lines, String classLoaderName) { ClassReference classReference = Reference.classFromTypeName(clazz); - retraceBase - .retrace(classReference) - .lookupMethod(method) - .narrowByLine(linePosition) - .forEach( - methodElement -> { - MethodReference methodReference = methodElement.getMethodReference(); - lines.add( - new AtLine( - startingWhitespace, - at, - classLoaderName, - moduleName, - methodReference.getHolderClass().getTypeName(), - methodReference.getMethodName(), - methodDescriptionFromMethodReference(methodReference, verbose), - retraceBase.retraceSourceFile( - classReference, fileName, methodReference.getHolderClass(), true), - hasLinePosition() - ? methodElement.getOriginalLineNumber(linePosition) - : linePosition, - methodElement.getRetraceMethodResult().isAmbiguous())); - }); + RetraceMethodResult retraceResult = retraceBase.retrace(classReference).lookupMethod(method); + if (linePosition != NO_POSITION && linePosition != INVALID_POSITION) { + retraceResult = retraceResult.narrowByLine(linePosition); + } + retraceResult.forEach( + methodElement -> { + MethodReference methodReference = methodElement.getMethodReference(); + lines.add( + new AtLine( + startingWhitespace, + at, + classLoaderName, + moduleName, + methodReference.getHolderClass().getTypeName(), + methodReference.getMethodName(), + methodDescriptionFromMethodReference(methodReference, verbose), + retraceBase.retraceSourceFile( + classReference, fileName, methodReference.getHolderClass(), true), + hasLinePosition() + ? methodElement.getOriginalLineNumber(linePosition) + : linePosition, + methodElement.getRetraceMethodResult().isAmbiguous())); + }); } @Override
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 44367e0..7df2fbc 100644 --- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java +++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -44,6 +44,7 @@ import com.android.tools.r8.utils.ListUtils; import com.android.tools.r8.utils.PredicateSet; import com.android.tools.r8.utils.SetUtils; +import com.android.tools.r8.utils.Visibility; import com.android.tools.r8.utils.WorkList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; @@ -165,6 +166,11 @@ /** Set of const-class references. */ public final Set<DexType> constClassReferences; /** + * A map from seen init-class references to the minimum required visibility of the corresponding + * static field. + */ + public final Map<DexType, Visibility> initClassReferences; + /** * All methods and fields whose value *must* never be propagated due to a configuration directive. * (testing only). */ @@ -184,6 +190,9 @@ final Set<DexType> instantiatedLambdas; + /* A cache to improve the lookup performance of lookupSingleVirtualTarget */ + private final SingleTargetLookupCache singleTargetLookupCache = new SingleTargetLookupCache(); + // TODO(zerny): Clean up the constructors so we have just one. AppInfoWithLiveness( DirectMappedDexApplication application, @@ -225,7 +234,8 @@ Map<DexField, Int2ReferenceMap<DexField>> switchMaps, EnumValueInfoMapCollection enumValueInfoMaps, Set<DexType> instantiatedLambdas, - Set<DexType> constClassReferences) { + Set<DexType> constClassReferences, + Map<DexType, Visibility> initClassReferences) { super(application); this.missingTypes = missingTypes; this.liveTypes = liveTypes; @@ -266,6 +276,7 @@ this.enumValueInfoMaps = enumValueInfoMaps; this.instantiatedLambdas = instantiatedLambdas; this.constClassReferences = constClassReferences; + this.initClassReferences = initClassReferences; } public AppInfoWithLiveness( @@ -308,7 +319,8 @@ Map<DexField, Int2ReferenceMap<DexField>> switchMaps, EnumValueInfoMapCollection enumValueInfoMaps, Set<DexType> instantiatedLambdas, - Set<DexType> constClassReferences) { + Set<DexType> constClassReferences, + Map<DexType, Visibility> initClassReferences) { super(appInfoWithSubtyping); this.missingTypes = missingTypes; this.liveTypes = liveTypes; @@ -349,6 +361,7 @@ this.enumValueInfoMaps = enumValueInfoMaps; this.instantiatedLambdas = instantiatedLambdas; this.constClassReferences = constClassReferences; + this.initClassReferences = initClassReferences; } private AppInfoWithLiveness(AppInfoWithLiveness previous) { @@ -392,7 +405,8 @@ previous.switchMaps, previous.enumValueInfoMaps, previous.instantiatedLambdas, - previous.constClassReferences); + previous.constClassReferences, + previous.initClassReferences); copyMetadataFromPrevious(previous); } @@ -445,7 +459,8 @@ previous.switchMaps, previous.enumValueInfoMaps, previous.instantiatedLambdas, - previous.constClassReferences); + previous.constClassReferences, + previous.initClassReferences); copyMetadataFromPrevious(previous); assert removedClasses == null || assertNoItemRemoved(previous.pinnedItems, removedClasses); } @@ -494,9 +509,11 @@ this.switchMaps = switchMaps; this.enumValueInfoMaps = enumValueInfoMaps; this.constClassReferences = previous.constClassReferences; + this.initClassReferences = previous.initClassReferences; previous.markObsolete(); } + // TODO(b/150736225): Don't disable this assert. private boolean dontAssertDefinitionFor = true; public static AppInfoWithLivenessModifier modifier() { @@ -504,16 +521,6 @@ } @Override - public void enableDefinitionForAssert() { - dontAssertDefinitionFor = false; - } - - @Override - public void disableDefinitionForAssert() { - dontAssertDefinitionFor = true; - } - - @Override public DexClass definitionFor(DexType type) { DexClass definition = super.definitionFor(type); assert dontAssertDefinitionFor @@ -732,6 +739,10 @@ return objectAllocationInfoCollection; } + void removeFromSingleTargetLookupCache(DexClass clazz) { + singleTargetLookupCache.removeInstantiatedType(clazz.type, this); + } + private boolean assertNoItemRemoved(Collection<DexReference> items, Collection<DexType> types) { Set<DexType> typeSet = ImmutableSet.copyOf(types); for (DexReference item : items) { @@ -1069,7 +1080,8 @@ rewriteReferenceKeys(switchMaps, lens::lookupField), enumValueInfoMaps.rewrittenWithLens(lens), rewriteItems(instantiatedLambdas, lens::lookupType), - constClassReferences); + rewriteItems(constClassReferences, lens::lookupType), + rewriteReferenceKeys(initClassReferences, lens::lookupType)); } /** @@ -1112,31 +1124,6 @@ } } - private DexEncodedMethod validateSingleVirtualTarget( - DexEncodedMethod singleTarget, DexEncodedMethod resolutionResult) { - assert resolutionResult.isVirtualMethod(); - - if (singleTarget == null || singleTarget == DexEncodedMethod.SENTINEL) { - return null; - } - - // Art978_virtual_interfaceTest correctly expects an IncompatibleClassChangeError exception - // at runtime. - if (isInvalidSingleVirtualTarget(singleTarget, resolutionResult)) { - return null; - } - - return singleTarget; - } - - private boolean isInvalidSingleVirtualTarget( - DexEncodedMethod singleTarget, DexEncodedMethod resolutionResult) { - assert resolutionResult.isVirtualMethod(); - // Art978_virtual_interfaceTest correctly expects an IncompatibleClassChangeError exception - // at runtime. - return !singleTarget.accessFlags.isAtLeastAsVisibleAs(resolutionResult.accessFlags); - } - /** For mapping invoke virtual instruction to single target method. */ public DexEncodedMethod lookupSingleVirtualTarget( DexMethod method, DexType invocationContext, boolean isInterface) { @@ -1174,25 +1161,87 @@ // (it is either primitive or array). return null; } + DexClass initialResolutionHolder = definitionFor(method.holder); + if (initialResolutionHolder == null || initialResolutionHolder.isInterface() != isInterface) { + return null; + } DexClass refinedReceiverClass = definitionFor(refinedReceiverType); if (refinedReceiverClass == null) { // The refined receiver is not defined in the program and we cannot determine the target. return null; } + if (receiverLowerBoundType == null + && singleTargetLookupCache.hasCachedItem(refinedReceiverType, method)) { + DexEncodedMethod cachedItem = + singleTargetLookupCache.getCachedItem(refinedReceiverType, method); + return cachedItem; + } SingleResolutionResult resolution = - resolveMethod(method.holder, method, isInterface).asSingleResolution(); + resolveMethod(initialResolutionHolder, method).asSingleResolution(); if (resolution == null || !resolution.isAccessibleForVirtualDispatchFrom(invocationClass, this)) { return null; } // If the method is modeled, return the resolution. + DexEncodedMethod resolvedMethod = resolution.getResolvedMethod(); if (modeledPredicate.isModeled(resolution.getResolvedHolder().type)) { if (resolution.getResolvedHolder().isFinal() - || (resolution.getResolvedMethod().isFinal() - && resolution.getResolvedMethod().accessFlags.isPublic())) { - return resolution.getResolvedMethod(); + || (resolvedMethod.isFinal() && resolvedMethod.accessFlags.isPublic())) { + singleTargetLookupCache.addToCache(refinedReceiverType, method, resolvedMethod); + return resolvedMethod; } } + DexEncodedMethod exactTarget = + getMethodTargetFromExactRuntimeInformation( + refinedReceiverType, receiverLowerBoundType, resolution, refinedReceiverClass); + if (exactTarget != null) { + // We are not caching single targets here because the cache does not include the + // lower bound dimension. + return exactTarget == DexEncodedMethod.SENTINEL ? null : exactTarget; + } + if (refinedReceiverClass.isNotProgramClass()) { + // The refined receiver is not defined in the program and we cannot determine the target. + singleTargetLookupCache.addToCache(refinedReceiverType, method, null); + return null; + } + DexClass resolvedHolder = resolution.getResolvedHolder(); + // TODO(b/148769279): Disable lookup single target on lambda's for now. + if (resolvedHolder.isInterface() + && resolvedHolder.isProgramClass() + && hasAnyInstantiatedLambdas(resolvedHolder.asProgramClass())) { + singleTargetLookupCache.addToCache(refinedReceiverType, method, null); + return null; + } + DexEncodedMethod singleMethodTarget = null; + DexProgramClass refinedLowerBound = null; + if (receiverLowerBoundType != null) { + DexClass refinedLowerBoundClass = definitionFor(receiverLowerBoundType.getClassType()); + if (refinedLowerBoundClass != null) { + refinedLowerBound = refinedLowerBoundClass.asProgramClass(); + } + } + LookupResultSuccess lookupResult = + resolution + .lookupVirtualDispatchTargets( + invocationClass, this, refinedReceiverClass.asProgramClass(), refinedLowerBound) + .asLookupResultSuccess(); + if (lookupResult != null && !lookupResult.isIncomplete()) { + LookupTarget singleTarget = lookupResult.getSingleLookupTarget(); + if (singleTarget != null && singleTarget.isMethodTarget()) { + singleMethodTarget = singleTarget.asMethodTarget().getMethod(); + } + } + if (receiverLowerBoundType == null) { + singleTargetLookupCache.addToCache(refinedReceiverType, method, singleMethodTarget); + } + return singleMethodTarget; + } + + private DexEncodedMethod getMethodTargetFromExactRuntimeInformation( + DexType refinedReceiverType, + ClassTypeLatticeElement receiverLowerBoundType, + SingleResolutionResult resolution, + DexClass refinedReceiverClass) { // If the lower-bound on the receiver type is the same as the upper-bound, then we have exact // runtime type information. In this case, the invoke will dispatch to the resolution result // from the runtime type of the receiver. @@ -1203,7 +1252,7 @@ resolution.lookupVirtualDispatchTarget(refinedReceiverClass.asProgramClass(), this); if (clazzAndMethod == null || isPinned(clazzAndMethod.getMethod().method)) { // TODO(b/150640456): We should maybe only consider program methods. - return null; + return DexEncodedMethod.SENTINEL; } return clazzAndMethod.getMethod(); } else { @@ -1211,56 +1260,16 @@ // If we resolved to a method on the refined receiver in the library, then we report the // method as a single target as well. This is a bit iffy since the library could change // implementation, but we use this for library modelling. - DexEncodedMethod targetOnReceiver = refinedReceiverClass.lookupVirtualMethod(method); - if (targetOnReceiver != null - && isOverriding(resolution.getResolvedMethod(), targetOnReceiver)) { + DexEncodedMethod resolvedMethod = resolution.getResolvedMethod(); + DexEncodedMethod targetOnReceiver = + refinedReceiverClass.lookupVirtualMethod(resolvedMethod.method); + if (targetOnReceiver != null && isOverriding(resolvedMethod, targetOnReceiver)) { return targetOnReceiver; } - return null; + return DexEncodedMethod.SENTINEL; } } - if (refinedReceiverClass.isNotProgramClass()) { - // The refined receiver is not defined in the program and we cannot determine the target. - return null; - } - DexClass resolvedHolder = resolution.getResolvedHolder(); - // TODO(b/148769279): Disable lookup single target on lambda's for now. - if (resolvedHolder.isInterface() - && resolvedHolder.isProgramClass() - && hasAnyInstantiatedLambdas(resolvedHolder.asProgramClass())) { - return null; - } - - if (method.isSingleVirtualMethodCached(refinedReceiverType)) { - return method.getSingleVirtualMethodCache(refinedReceiverType); - } - - DexProgramClass refinedLowerBound = null; - if (receiverLowerBoundType != null) { - assert receiverLowerBoundType.isClassType(); - DexClass refinedLowerBoundClass = definitionFor(receiverLowerBoundType.getClassType()); - if (refinedLowerBoundClass != null) { - refinedLowerBound = refinedLowerBoundClass.asProgramClass(); - } - } - - LookupResultSuccess lookupResult = - resolution - .lookupVirtualDispatchTargets( - invocationClass, this, refinedReceiverClass.asProgramClass(), refinedLowerBound) - .asLookupResultSuccess(); - - if (lookupResult == null || lookupResult.isIncomplete()) { - return null; - } - - LookupTarget singleTarget = lookupResult.getSingleLookupTarget(); - DexEncodedMethod singleMethodTarget = null; - if (singleTarget != null && singleTarget.isMethodTarget()) { - singleMethodTarget = singleTarget.asMethodTarget().getMethod(); - } - method.setSingleVirtualMethodCache(refinedReceiverType, singleMethodTarget); - return singleMethodTarget; + return null; } public AppInfoWithLiveness withSwitchMaps(Map<DexField, Int2ReferenceMap<DexField>> switchMaps) { @@ -1275,12 +1284,6 @@ return new AppInfoWithLiveness(this, switchMaps, enumValueInfoMaps); } - public void forEachLiveProgramClass(Consumer<DexProgramClass> fn) { - for (DexType type : liveTypes) { - fn.accept(definitionFor(type).asProgramClass()); - } - } - /** * Visit all class definitions of classpath classes that are referenced in the compilation unit. * @@ -1363,14 +1366,32 @@ if (clazz == null) { continue; } - if (isInstantiatedDirectly(clazz) - || isPinned(clazz.type) - || hasAnyInstantiatedLambdas(clazz)) { + if (isInstantiatedOrPinned(clazz)) { subTypeConsumer.accept(clazz); } } } + public void forEachInstantiatedSubTypeInChain( + DexProgramClass refinedReceiverUpperBound, + DexProgramClass refinedReceiverLowerBound, + Consumer<DexProgramClass> subTypeConsumer, + Consumer<LambdaDescriptor> callSiteConsumer) { + List<DexProgramClass> subTypes = + computeProgramClassRelationChain(refinedReceiverLowerBound, refinedReceiverUpperBound); + for (DexProgramClass subType : subTypes) { + if (isInstantiatedOrPinned(subType)) { + subTypeConsumer.accept(subType); + } + } + } + + private boolean isInstantiatedOrPinned(DexProgramClass clazz) { + return isInstantiatedDirectly(clazz) + || isPinned(clazz.type) + || hasAnyInstantiatedLambdas(clazz); + } + public boolean isPinnedNotProgramOrLibraryOverride(DexReference reference) { if (isPinned(reference)) { return true;
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java index 75c27fd..99f2d84 100644 --- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java +++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java
@@ -36,8 +36,11 @@ // Instantiated classes. ObjectAllocationInfoCollectionImpl objectAllocationInfoCollection = appInfo.getMutableObjectAllocationInfoCollection(); - noLongerInstantiatedClasses.forEach(objectAllocationInfoCollection::markNoLongerInstantiated); - + noLongerInstantiatedClasses.forEach( + clazz -> { + objectAllocationInfoCollection.markNoLongerInstantiated(clazz); + appInfo.removeFromSingleTargetLookupCache(clazz); + }); // Written fields. FieldAccessInfoCollectionImpl fieldAccessInfoCollection = appInfo.getMutableFieldAccessInfoCollection();
diff --git a/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java b/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java new file mode 100644 index 0000000..60bb9d7 --- /dev/null +++ b/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
@@ -0,0 +1,96 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.shaking; + +import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; + +import com.android.tools.r8.dex.Constants; +import com.android.tools.r8.errors.Unreachable; +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.DexField; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.FieldAccessFlags; +import com.android.tools.r8.graph.InitClassLens; +import com.android.tools.r8.utils.ThreadUtils; +import com.android.tools.r8.utils.Visibility; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; + +public class ClassInitFieldSynthesizer { + + private final AppView<AppInfoWithLiveness> appView; + private final DexField clinitField; + private final InitClassLens.Builder lensBuilder = InitClassLens.builder(); + + public ClassInitFieldSynthesizer(AppView<AppInfoWithLiveness> appView) { + this.appView = appView; + this.clinitField = appView.dexItemFactory().objectMembers.clinitField; + } + + public void run(ExecutorService executorService) throws ExecutionException { + ThreadUtils.processItems( + appView.appInfo().initClassReferences, this::synthesizeClassInitField, executorService); + appView.setInitClassLens(lensBuilder.build()); + } + + private void synthesizeClassInitField(DexType type, Visibility minimumRequiredVisibility) { + DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type)); + if (clazz == null) { + assert false; + return; + } + // Use an existing static field if there is one. + DexEncodedField encodedClinitField = null; + for (DexEncodedField staticField : clazz.staticFields()) { + // We need to field to be accessible from the contexts in which it is accessed. + if (!isMinimumRequiredVisibility(staticField, minimumRequiredVisibility)) { + continue; + } + // When compiling for dex, we can't use wide fields since we've only allocated a single + // register for the out-value of each ClassInit instruction + if (staticField.field.type.isWideType()) { + continue; + } + encodedClinitField = staticField; + break; + } + if (encodedClinitField == null) { + FieldAccessFlags accessFlags = + FieldAccessFlags.fromSharedAccessFlags( + Constants.ACC_SYNTHETIC + | Constants.ACC_FINAL + | Constants.ACC_PUBLIC + | Constants.ACC_STATIC); + encodedClinitField = + new DexEncodedField( + appView.dexItemFactory().createField(clazz.type, clinitField.type, clinitField.name), + accessFlags, + DexAnnotationSet.empty(), + null); + clazz.appendStaticField(encodedClinitField); + } + lensBuilder.map(type, encodedClinitField.field); + } + + private boolean isMinimumRequiredVisibility( + DexEncodedField field, Visibility minimumRequiredVisibility) { + if (field.isPublic()) { + return true; + } + switch (minimumRequiredVisibility) { + case PROTECTED: + return field.isProtected(); + case PACKAGE_PRIVATE: + return field.isPackagePrivate() || field.isProtected(); + case PUBLIC: + return false; + default: + throw new Unreachable(); + } + } +}
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 f35fb94..db955c9 100644 --- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java +++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -40,6 +40,11 @@ } @Override + public boolean registerInitClass(DexType clazz) { + return enqueuer.traceInitClass(clazz, context); + } + + @Override public boolean registerInvokeVirtual(DexMethod invokedMethod) { return enqueuer.traceInvokeVirtual(invokedMethod, 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 200562d..2bc230c 100644 --- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java +++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -97,6 +97,7 @@ import com.android.tools.r8.utils.SetUtils; import com.android.tools.r8.utils.StringDiagnostic; import com.android.tools.r8.utils.Timing; +import com.android.tools.r8.utils.Visibility; import com.android.tools.r8.utils.WorkList; import com.google.common.base.Equivalence.Wrapper; import com.google.common.collect.ImmutableSet; @@ -300,6 +301,12 @@ private final Set<DexType> constClassReferences = Sets.newIdentityHashSet(); /** + * A map from seen init-class references to the minimum required visibility of the corresponding + * static field. + */ + private final Map<DexType, Visibility> initClassReferences = new IdentityHashMap<>(); + + /** * A map from annotation classes to annotations that need to be processed should the classes ever * become live. */ @@ -412,6 +419,9 @@ reportMissingClass(type); return null; } + if (clazz.isProgramClass()) { + return clazz; + } if (liveNonProgramTypes.add(clazz) && clazz.isLibraryClass()) { // TODO(b/149201735): This likely needs to apply to classpath too. ensureMethodsContinueToWidenAccess(clazz); @@ -727,6 +737,65 @@ return false; } + boolean traceInitClass(DexType type, ProgramMethod currentMethod) { + assert type.isClassType(); + + Visibility oldMinimumRequiredVisibility = initClassReferences.get(type); + if (oldMinimumRequiredVisibility == null) { + DexProgramClass clazz = getProgramClassOrNull(type); + if (clazz == null) { + assert false; + return false; + } + + initClassReferences.put( + type, computeMinimumRequiredVisibilityForInitClassField(type, currentMethod.getHolder())); + + markTypeAsLive(type, classReferencedFromReporter(currentMethod.getMethod())); + markDirectAndIndirectClassInitializersAsLive(clazz); + return true; + } + + if (oldMinimumRequiredVisibility.isPublic()) { + return false; + } + + Visibility minimumRequiredVisibilityForCurrentMethod = + computeMinimumRequiredVisibilityForInitClassField(type, currentMethod.getHolder()); + + // There should never be a need to have an InitClass instruction for the enclosing class. + assert !minimumRequiredVisibilityForCurrentMethod.isPrivate(); + + if (minimumRequiredVisibilityForCurrentMethod.isPublic()) { + initClassReferences.put(type, minimumRequiredVisibilityForCurrentMethod); + return true; + } + + if (oldMinimumRequiredVisibility.isProtected()) { + return false; + } + + if (minimumRequiredVisibilityForCurrentMethod.isProtected()) { + initClassReferences.put(type, minimumRequiredVisibilityForCurrentMethod); + return true; + } + + assert oldMinimumRequiredVisibility.isPackagePrivate(); + assert minimumRequiredVisibilityForCurrentMethod.isPackagePrivate(); + return false; + } + + private Visibility computeMinimumRequiredVisibilityForInitClassField( + DexType clazz, DexProgramClass context) { + if (clazz.isSamePackage(context.type)) { + return Visibility.PACKAGE_PRIVATE; + } + if (appInfo.isStrictSubtypeOf(context.type, clazz)) { + return Visibility.PROTECTED; + } + return Visibility.PUBLIC; + } + void traceMethodHandle( DexMethodHandle methodHandle, MethodHandleUse use, DexEncodedMethod currentMethod) { // If a method handle is not an argument to a lambda metafactory it could flow to a @@ -2555,7 +2624,8 @@ SetUtils.mapIdentityHashSet( objectAllocationInfoCollection.unknownInstantiatedInterfaceTypes, DexProgramClass::getType), - constClassReferences); + constClassReferences, + initClassReferences); appInfo.markObsolete(); return appInfoWithLiveness; }
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 3a6fcae..15d4e4b 100644 --- a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java +++ b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
@@ -100,6 +100,12 @@ } @Override + public boolean registerInitClass(DexType clazz) { + consumer.accept(clazz); + return true; + } + + @Override public boolean registerInvokeVirtual(DexMethod method) { return registerInvoke(method); }
diff --git a/src/main/java/com/android/tools/r8/shaking/SingleTargetLookupCache.java b/src/main/java/com/android/tools/r8/shaking/SingleTargetLookupCache.java new file mode 100644 index 0000000..1691758 --- /dev/null +++ b/src/main/java/com/android/tools/r8/shaking/SingleTargetLookupCache.java
@@ -0,0 +1,58 @@ +// 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; + +import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexType; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class SingleTargetLookupCache { + + private Map<DexType, Map<DexMethod, DexEncodedMethod>> cache = new ConcurrentHashMap<>(); + + public void addToCache(DexType refinedReceiverType, DexMethod method, DexEncodedMethod target) { + assert target != DexEncodedMethod.SENTINEL; + Map<DexMethod, DexEncodedMethod> methodCache = + cache.computeIfAbsent(refinedReceiverType, ignored -> new ConcurrentHashMap<>()); + target = target == null ? DexEncodedMethod.SENTINEL : target; + assert methodCache.getOrDefault(method, target) == target; + methodCache.putIfAbsent(method, target); + } + + public void removeInstantiatedType(DexType instantiatedType, AppInfoWithLiveness appInfo) { + // Remove all types in the hierarchy related to this type. + cache.remove(instantiatedType); + DexClass clazz = appInfo.definitionFor(instantiatedType); + if (clazz == null) { + return; + } + appInfo.forEachSuperType(clazz, (type, ignore) -> cache.remove(type)); + appInfo.subtypes(instantiatedType).forEach(cache::remove); + } + + public DexEncodedMethod getCachedItem(DexType receiverType, DexMethod method) { + Map<DexMethod, DexEncodedMethod> cachedMethods = cache.get(receiverType); + if (cachedMethods == null) { + return null; + } + DexEncodedMethod target = cachedMethods.get(method); + return target == DexEncodedMethod.SENTINEL ? null : target; + } + + public boolean hasCachedItem(DexType receiverType, DexMethod method) { + Map<DexMethod, DexEncodedMethod> cachedMethods = cache.get(receiverType); + if (cachedMethods == null) { + return false; + } + return cachedMethods.containsKey(method); + } + + public void clear() { + cache = new ConcurrentHashMap<>(); + } +}
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 43110c2..ef0ce63 100644 --- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java +++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -13,7 +13,6 @@ import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexClass.FieldSetter; -import com.android.tools.r8.graph.DexClass.MethodSetter; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMember; import com.android.tools.r8.graph.DexEncodedMethod; @@ -1452,8 +1451,7 @@ private VerticalClassMergerGraphLense fixupTypeReferences() { // Globally substitute merged class types in protos and holders. for (DexProgramClass clazz : appInfo.classes()) { - fixupMethods(clazz.directMethods(), clazz::setDirectMethod); - fixupMethods(clazz.virtualMethods(), clazz::setVirtualMethod); + clazz.getMethodCollection().replaceMethods(this::fixupMethod); fixupFields(clazz.staticFields(), clazz::setStaticField); fixupFields(clazz.instanceFields(), clazz::setInstanceField); } @@ -1467,21 +1465,16 @@ return lens; } - private void fixupMethods(List<DexEncodedMethod> methods, MethodSetter setter) { - if (methods == null) { - return; - } - for (int i = 0; i < methods.size(); i++) { - DexEncodedMethod encodedMethod = methods.get(i); - DexMethod method = encodedMethod.method; - DexMethod newMethod = fixupMethod(method); - if (newMethod != method) { - if (!lensBuilder.hasOriginalSignatureMappingFor(newMethod)) { - lensBuilder.map(method, newMethod).recordMove(method, newMethod); - } - setter.setMethod(i, encodedMethod.toTypeSubstitutedMethod(newMethod)); + private DexEncodedMethod fixupMethod(DexEncodedMethod encodedMethod) { + DexMethod method = encodedMethod.method; + DexMethod newMethod = fixupMethod(method); + if (newMethod != method) { + if (!lensBuilder.hasOriginalSignatureMappingFor(newMethod)) { + lensBuilder.map(method, newMethod).recordMove(method, newMethod); } + return encodedMethod.toTypeSubstitutedMethod(newMethod); } + return encodedMethod; } private void fixupFields(List<DexEncodedField> fields, FieldSetter setter) { @@ -1837,6 +1830,11 @@ } @Override + public boolean registerInitClass(DexType clazz) { + return checkTypeReference(clazz); + } + + @Override public boolean registerInvokeVirtual(DexMethod method) { assert context != null; GraphLenseLookupResult lookup =
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java index 51ab446..6aca533 100644 --- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java +++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -371,6 +371,7 @@ return 'L' + className.replace(JAVA_PACKAGE_SEPARATOR, INNER_CLASS_SEPARATOR) + ';'; } + // TODO(b/151195430): Remove once a new version of kotlinx-metadata is released. // Kotlin @Metadata deserialization has plain "kotlin", which will be relocated in r8lib. // See b/70169921#comment25 for more details. private static String backwardRelocatedName(String name) { @@ -390,7 +391,7 @@ kmType.accept(new KmTypeVisitor() { @Override public void visitClass(String name) { - // TODO(b/70169921): Remove this if metadata lib is resilient to namespace relocation. + // TODO(b/151195430): Remove this if metadata lib is resilient to namespace relocation. // See b/70169921#comment25 for more details. assert descriptor.get() == null : descriptor.get(); descriptor.set(getDescriptorFromKotlinClassifier(backwardRelocatedName(name))); @@ -398,7 +399,7 @@ @Override public void visitTypeAlias(String name) { - // TODO(b/70169921): Remove this if metadata lib is resilient to namespace relocation. + // TODO(b/151195430): Remove this if metadata lib is resilient to namespace relocation. // See b/70169921#comment25 for more details. assert descriptor.get() == null : descriptor.get(); descriptor.set(getDescriptorFromKotlinClassifier(backwardRelocatedName(name)));
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 c3df10f..37b38e9 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -32,6 +32,7 @@ import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.inspector.internal.InspectorImpl; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration; import com.android.tools.r8.ir.optimize.Inliner; @@ -58,6 +59,7 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Comparator; import java.util.Deque; import java.util.HashMap; @@ -113,6 +115,8 @@ public DataResourceConsumer dataResourceConsumer; public FeatureSplitConfiguration featureSplitConfiguration; + public List<Consumer<InspectorImpl>> outputInspections = Collections.emptyList(); + // Constructor for testing and/or other utilities. public InternalOptions() { reporter = new Reporter(); @@ -227,6 +231,7 @@ public boolean applyInliningToInlinee = System.getProperty("com.android.tools.r8.applyInliningToInlinee") != null; public int applyInliningToInlineeMaxDepth = 0; + public boolean enableInliningOfInvokesWithClassInitializationSideEffects = true; public boolean enableInliningOfInvokesWithNullableReceivers = true; public boolean disableInliningOfLibraryMethodOverrides = true; public boolean enableClassInlining = true; @@ -283,7 +288,7 @@ public boolean enablePcDebugInfoOutput = false; // Number of threads to use while processing the dex files. - public int numberOfThreads = DETERMINISTIC_DEBUGGING ? 1 : ThreadUtils.NOT_SPECIFIED; + public int threadCount = DETERMINISTIC_DEBUGGING ? 1 : ThreadUtils.NOT_SPECIFIED; // Print smali disassembly. public boolean useSmaliSyntax = false; // Verbose output. @@ -360,6 +365,10 @@ || getProguardConfiguration().getKeepAttributes().stackMapTable; } + public boolean shouldRerunEnqueuer() { + return isShrinking() || isMinifying() || getProguardConfiguration().hasApplyMappingFile(); + } + public boolean isGeneratingDex() { return isGeneratingDexIndexed() || isGeneratingDexFilePerClassFile(); } @@ -1220,7 +1229,7 @@ } public boolean canUseRequireNonNull() { - return isGeneratingClassFiles() || hasMinApi(AndroidApiLevel.K); + return isGeneratingDex() && hasMinApi(AndroidApiLevel.K); } public boolean canUseSuppressedExceptions() {
diff --git a/src/main/java/com/android/tools/r8/utils/IterableUtils.java b/src/main/java/com/android/tools/r8/utils/IterableUtils.java index 64c44b5..ea35dfb 100644 --- a/src/main/java/com/android/tools/r8/utils/IterableUtils.java +++ b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
@@ -18,4 +18,8 @@ } return -1; } + + public static <T> Iterable<T> filter(Iterable<T> methods, Predicate<T> predicate) { + return () -> IteratorUtils.filter(methods.iterator(), predicate); + } }
diff --git a/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java b/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java deleted file mode 100644 index 2458a64..0000000 --- a/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java +++ /dev/null
@@ -1,55 +0,0 @@ -// Copyright (c) 2016, 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.utils; - -import com.android.tools.r8.errors.InternalCompilerError; -import com.android.tools.r8.graph.DexEncodedMember; -import com.android.tools.r8.graph.DexMember; -import java.util.Iterator; -import java.util.List; -import java.util.NoSuchElementException; - -public class OrderedMergingIterator<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> - implements Iterator<D> { - - private final List<D> one; - private final List<D> other; - private int oneIndex = 0; - private int otherIndex = 0; - - public OrderedMergingIterator(List<D> one, List<D> other) { - this.one = one; - this.other = other; - } - - private D getNextChecked(List<D> list, int position) { - if (position >= list.size()) { - throw new NoSuchElementException(); - } - return list.get(position); - } - - @Override - public boolean hasNext() { - return oneIndex < one.size() || otherIndex < other.size(); - } - - @Override - public D next() { - if (oneIndex >= one.size()) { - return getNextChecked(other, otherIndex++); - } - if (otherIndex >= other.size()) { - return getNextChecked(one, oneIndex++); - } - int comparison = one.get(oneIndex).toReference().compareTo(other.get(otherIndex).toReference()); - if (comparison < 0) { - return one.get(oneIndex++); - } - if (comparison == 0) { - throw new InternalCompilerError("Source arrays are not disjoint."); - } - return other.get(otherIndex++); - } -}
diff --git a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java index 40232ca..84aa0d0 100644 --- a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java +++ b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
@@ -7,6 +7,7 @@ import java.util.Collection; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -39,6 +40,18 @@ executorService); } + public static <T, U, E extends Exception> void processItems( + Map<T, U> items, ThrowingBiConsumer<T, U, E> consumer, ExecutorService executorService) + throws ExecutionException { + processItemsWithResults( + items.entrySet(), + arg -> { + consumer.accept(arg.getKey(), arg.getValue()); + return null; + }, + executorService); + } + public static void awaitFutures(Iterable<? extends Future<?>> futures) throws ExecutionException { Iterator<? extends Future<?>> futureIterator = futures.iterator(); @@ -90,18 +103,23 @@ static ExecutorService getExecutorServiceForProcessors(int processors) { // This heuristic is based on measurements on a 32 core (hyper-threaded) machine. int threads = processors <= 2 ? processors : (int) Math.ceil(Integer.min(processors, 16) / 2.0); + return getExecutorServiceForThreads(threads); + } + + static ExecutorService getExecutorServiceForThreads(int threads) { + // Note Executors.newSingleThreadExecutor() is not used when just one thread is used. See + // b/67338394. return Executors.newWorkStealingPool(threads); } public static ExecutorService getExecutorService(int threads) { - // Don't use Executors.newSingleThreadExecutor() when threads == 1, see b/67338394. return threads == NOT_SPECIFIED ? getExecutorServiceForProcessors(Runtime.getRuntime().availableProcessors()) - : Executors.newWorkStealingPool(threads); + : getExecutorServiceForThreads(threads); } public static ExecutorService getExecutorService(InternalOptions options) { - return getExecutorService(options.numberOfThreads); + return getExecutorService(options.threadCount); } public static int getNumberOfThreads(ExecutorService service) {
diff --git a/src/main/java/com/android/tools/r8/utils/Visibility.java b/src/main/java/com/android/tools/r8/utils/Visibility.java new file mode 100644 index 0000000..32ec7ba --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/Visibility.java
@@ -0,0 +1,50 @@ +// 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.utils; + +import com.android.tools.r8.errors.Unreachable; + +public enum Visibility { + PUBLIC, + PROTECTED, + PRIVATE, + PACKAGE_PRIVATE; + + public boolean isPackagePrivate() { + return this == PACKAGE_PRIVATE; + } + + public boolean isPrivate() { + return this == PRIVATE; + } + + public boolean isProtected() { + return this == PROTECTED; + } + + public boolean isPublic() { + return this == PUBLIC; + } + + @Override + public String toString() { + switch (this) { + case PUBLIC: + return "public"; + + case PROTECTED: + return "protected"; + + case PRIVATE: + return "private"; + + case PACKAGE_PRIVATE: + return "package-private"; + + default: + throw new Unreachable("Unexpected visibility"); + } + } +}
diff --git a/src/main/keep.txt b/src/main/keep.txt index b0c58c9..2450f6b 100644 --- a/src/main/keep.txt +++ b/src/main/keep.txt
@@ -19,7 +19,7 @@ -keep public class com.android.tools.r8.Version { public static java.lang.String getPreReleaseString(); } -keep public class com.android.tools.r8.Version { public static boolean isDevelopmentVersion(); } --keepattributes LineNumberTable, InnerClasses, EnclosingMethod, Exceptions, Signature +-keepattributes SourceFile, LineNumberTable, InnerClasses, EnclosingMethod, Exceptions, Signature -keeppackagenames com.android.tools.r8
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java index 57fc2ec..8d43d07 100644 --- a/src/test/java/com/android/tools/r8/D8CommandTest.java +++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -11,6 +11,7 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import com.android.sdklib.AndroidVersion; import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation; @@ -24,6 +25,7 @@ import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.FileUtils; +import com.android.tools.r8.utils.ThreadUtils; import com.android.tools.r8.utils.ZipUtils; import com.google.common.collect.ImmutableList; import java.io.IOException; @@ -592,6 +594,32 @@ d8Command.getInternalOptions().desugaredLibraryConfiguration.getRewritePrefix().isEmpty()); } + @Test + public void numThreadsOption() throws Exception { + assertEquals(ThreadUtils.NOT_SPECIFIED, parse().getThreadCount()); + assertEquals(1, parse("--thread-count", "1").getThreadCount()); + assertEquals(2, parse("--thread-count", "2").getThreadCount()); + assertEquals(10, parse("--thread-count", "10").getThreadCount()); + } + + private void numThreadsOptionInvalid(String value) throws Exception { + final String expectedErrorContains = "Invalid argument to --thread-count"; + try { + DiagnosticsChecker.checkErrorsContains( + expectedErrorContains, handler -> parse(handler, "--thread-count", value)); + fail("Expected failure"); + } catch (CompilationFailedException e) { + // Expected. + } + } + + @Test + public void numThreadsOptionInvalid() throws Exception { + numThreadsOptionInvalid("0"); + numThreadsOptionInvalid("-1"); + numThreadsOptionInvalid("two"); + } + private D8Command parse(String... args) throws CompilationFailedException { return D8Command.parse(args, EmbeddedOrigin.INSTANCE).build(); }
diff --git a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java index 049cdb7..b62416f 100644 --- a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java +++ b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
@@ -6,6 +6,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.origin.PathOrigin; @@ -60,6 +61,7 @@ DiagnosticsChecker handler = new DiagnosticsChecker(); try { runner.run(handler); + fail("Failure expected"); } catch (CompilationFailedException e) { checkContains(snippet, handler.errors); throw e;
diff --git a/src/test/java/com/android/tools/r8/L8CommandTest.java b/src/test/java/com/android/tools/r8/L8CommandTest.java index 060bdbb..705ecff 100644 --- a/src/test/java/com/android/tools/r8/L8CommandTest.java +++ b/src/test/java/com/android/tools/r8/L8CommandTest.java
@@ -7,6 +7,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation; import com.android.tools.r8.AssertionsConfiguration.AssertionTransformationScope; @@ -14,6 +15,7 @@ import com.android.tools.r8.origin.EmbeddedOrigin; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.ThreadUtils; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -257,7 +259,69 @@ AssertionTransformation.PASSTHROUGH); } + @Test + public void numThreadsOption() throws Exception { + assertEquals( + ThreadUtils.NOT_SPECIFIED, + parse("--desugared-lib", ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING.toString()) + .getThreadCount()); + assertEquals( + 1, + parse( + "--thread-count", + "1", + "--desugared-lib", + ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING.toString()) + .getThreadCount()); + assertEquals( + 2, + parse( + "--thread-count", + "2", + "--desugared-lib", + ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING.toString()) + .getThreadCount()); + assertEquals( + 10, + parse( + "--thread-count", + "10", + "--desugared-lib", + ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING.toString()) + .getThreadCount()); + } + + private void numThreadsOptionInvalid(String value) throws Exception { + final String expectedErrorContains = "Invalid argument to --thread-count"; + try { + DiagnosticsChecker.checkErrorsContains( + expectedErrorContains, + handler -> + parse( + handler, + "--thread-count", + value, + "--desugared-lib", + ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING.toString())); + fail("Expected failure"); + } catch (CompilationFailedException e) { + // Expected. + } + } + + @Test + public void numThreadsOptionInvalid() throws Exception { + numThreadsOptionInvalid("0"); + numThreadsOptionInvalid("-1"); + numThreadsOptionInvalid("two"); + } + private L8Command parse(String... args) throws CompilationFailedException { return L8Command.parse(args, EmbeddedOrigin.INSTANCE).build(); } + + private L8Command parse(DiagnosticsHandler handler, String... args) + throws CompilationFailedException { + return L8Command.parse(args, EmbeddedOrigin.INSTANCE, handler).build(); + } }
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java index 4a703d7..29a9e22 100644 --- a/src/test/java/com/android/tools/r8/R8CommandTest.java +++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -9,6 +9,7 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation; import com.android.tools.r8.AssertionsConfiguration.AssertionTransformationScope; @@ -20,6 +21,7 @@ import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.FileUtils; +import com.android.tools.r8.utils.ThreadUtils; import com.android.tools.r8.utils.ZipUtils; import com.google.common.collect.ImmutableList; import java.io.IOException; @@ -709,6 +711,32 @@ r8Command.getInternalOptions().desugaredLibraryConfiguration.getRewritePrefix().isEmpty()); } + @Test + public void numThreadsOption() throws Exception { + assertEquals(ThreadUtils.NOT_SPECIFIED, parse().getThreadCount()); + assertEquals(1, parse("--thread-count", "1").getThreadCount()); + assertEquals(2, parse("--thread-count", "2").getThreadCount()); + assertEquals(10, parse("--thread-count", "10").getThreadCount()); + } + + private void numThreadsOptionInvalid(String value) throws Exception { + final String expectedErrorContains = "Invalid argument to --thread-count"; + try { + DiagnosticsChecker.checkErrorsContains( + expectedErrorContains, handler -> parse(handler, "--thread-count", value)); + fail("Expected failure"); + } catch (CompilationFailedException e) { + // Expected. + } + } + + @Test + public void numThreadsOptionInvalid() throws Exception { + numThreadsOptionInvalid("0"); + numThreadsOptionInvalid("-1"); + numThreadsOptionInvalid("two"); + } + private R8Command parse(String... args) throws CompilationFailedException { return R8Command.parse(args, EmbeddedOrigin.INSTANCE).build(); }
diff --git a/src/test/java/com/android/tools/r8/R8TestRunResult.java b/src/test/java/com/android/tools/r8/R8TestRunResult.java index 2ec28b8..9fe91d1 100644 --- a/src/test/java/com/android/tools/r8/R8TestRunResult.java +++ b/src/test/java/com/android/tools/r8/R8TestRunResult.java
@@ -59,6 +59,16 @@ return new CodeInspector(app, proguardMap); } + @Override + public <E extends Throwable> R8TestRunResult inspectFailure( + ThrowingConsumer<CodeInspector, E> consumer) throws IOException, ExecutionException, E { + assertFailure(); + assertNotNull(app); + CodeInspector codeInspector = new CodeInspector(app, proguardMap); + consumer.accept(codeInspector); + return self(); + } + public <E extends Throwable> R8TestRunResult inspectOriginalStackTrace( ThrowingConsumer<StackTrace, E> consumer) throws E { consumer.accept(getOriginalStackTrace());
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java index 46bf0a1..bf3bb9d 100644 --- a/src/test/java/com/android/tools/r8/TestRunResult.java +++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -142,6 +142,15 @@ return self(); } + public <E extends Throwable> RR inspectFailure(ThrowingConsumer<CodeInspector, E> consumer) + throws IOException, ExecutionException, E { + assertFailure(); + assertNotNull(app); + CodeInspector inspector = new CodeInspector(app); + consumer.accept(inspector); + return self(); + } + public <E extends Throwable> RR inspectStackTrace(ThrowingConsumer<StackTrace, E> consumer) throws E { consumer.accept(getStackTrace());
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java index 9c144c8..e184d97 100644 --- a/src/test/java/com/android/tools/r8/ToolHelper.java +++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -21,6 +21,7 @@ import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DirectMappedDexApplication; import com.android.tools.r8.graph.GraphLense; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.position.Position; @@ -248,6 +249,10 @@ this.shortName = shortName; } + public boolean isDalvik() { + return isOlderThanOrEqual(Version.V4_4_4); + } + public boolean isLatest() { return this == DEFAULT; } @@ -2075,6 +2080,7 @@ application, null, GraphLense.getIdentityLense(), + InitClassLens.getDefault(), NamingLens.getIdentityLens(), options, null);
diff --git a/src/test/java/com/android/tools/r8/classmerging/InliningAfterStaticClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/InliningAfterStaticClassMergerTest.java index d59f954..e2ee52f 100644 --- a/src/test/java/com/android/tools/r8/classmerging/InliningAfterStaticClassMergerTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/InliningAfterStaticClassMergerTest.java
@@ -8,6 +8,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; +import com.android.tools.r8.NeverPropagateValue; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; @@ -35,6 +36,7 @@ // Cannot be inlined into TestClass.main() because the static initialization of this class could // have side-effects; in order for R8 to be conservative, library classes are treated as if // their static initialization could have side-effects. + @NeverPropagateValue public static String m() { return "StaticMergeCandidateA.m()"; } @@ -44,6 +46,7 @@ // Can be inlined into TestClass.main() because the static initialization of this class has no // side-effects. + @NeverPropagateValue public static String m() { return "StaticMergeCandidateB.m()"; } @@ -65,7 +68,7 @@ @Parameters(name = "{0}") public static TestParametersCollection data() { - return getTestParameters().withAllRuntimes().build(); + return getTestParameters().withAllRuntimesAndApiLevels().build(); } @Test @@ -82,8 +85,9 @@ .addKeepMainRule(TestClass.class) .addOptionsModification( options -> options.libraryInterfacesMayHaveStaticInitialization = true) + .enableMemberValuePropagationAnnotations() .noMinification() - .setMinApi(parameters.getRuntime()) + .setMinApi(parameters.getApiLevel()) .run(parameters.getRuntime(), TestClass.class) .assertSuccessWithOutput(expected) .inspector(); @@ -94,11 +98,16 @@ .filter(clazz -> clazz.getOriginalName().contains("StaticMergeCandidate")) .collect(Collectors.toList()); assertEquals(1, classes.size()); - assertEquals(StaticMergeCandidateA.class.getTypeName(), classes.get(0).getOriginalName()); - // Check that StaticMergeCandidateB.m() has not been moved into StaticMergeCandidateA, because - // that would disable inlining of it. - assertEquals(1, classes.get(0).allMethods().size()); + FoundClassSubject clazz = classes.get(0); + assertEquals(StaticMergeCandidateA.class.getTypeName(), clazz.getOriginalName()); + + // Check that StaticMergeCandidateB.m() has been inlined. + assertEquals(0, clazz.allMethods().size()); + + // Check that a static field has been synthesized in order to trigger class initialization. + assertEquals(1, clazz.allStaticFields().size()); + assertEquals("$r8$clinit", clazz.allStaticFields().get(0).getOriginalName()); // Check that the test class only has a main method. ClassSubject classSubject = inspector.clazz(TestClass.class);
diff --git a/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java b/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java index 6f2e729..005df04 100644 --- a/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java
@@ -44,11 +44,12 @@ private AppView<AppInfoWithLiveness> computeAppViewWithLiveness( Class<?> methodToBeKept, Class<?> classToBeKept) throws Exception { return computeAppViewWithLiveness( - buildClasses(I.class, J.class, K.class, L.class, A.class).build(), + buildClasses(I.class, J.class, K.class, L.class, A.class, Main.class).build(), factory -> { List<ProguardConfigurationRule> rules = new ArrayList<>(); rules.addAll(buildKeepRuleForClassAndMethods(methodToBeKept, factory)); rules.addAll(buildKeepRuleForClass(classToBeKept, factory)); + rules.addAll(buildKeepRuleForClassAndMethods(Main.class, factory)); return rules; }); } @@ -265,4 +266,11 @@ public interface L extends I, K {} public static class A implements L {} + + public static class Main { + + public static void main(String[] args) { + new A(); + } + } }
diff --git a/src/test/java/com/android/tools/r8/debug/MinificationTest.java b/src/test/java/com/android/tools/r8/debug/MinificationTest.java index d1c3ac1..b42d7fa 100644 --- a/src/test/java/com/android/tools/r8/debug/MinificationTest.java +++ b/src/test/java/com/android/tools/r8/debug/MinificationTest.java
@@ -153,7 +153,7 @@ checkLine(SOURCE_FILE, 15), stepInto(INTELLIJ_FILTER), checkMethod(innerClassName, innerMethodName, innerSignature), - checkLine(SOURCE_FILE, 8), + checkLine(8), run()); } @@ -175,7 +175,7 @@ breakpoint(innerClassName, innerMethodName, innerSignature), run(), checkMethod(innerClassName, innerMethodName, innerSignature), - checkLine(SOURCE_FILE, 8), + checkLine(8), run()); }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java index b344944..11dff7b 100644 --- a/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java +++ b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
@@ -23,12 +23,13 @@ import com.android.tools.r8.ToolHelper.DexVm.Version; import com.android.tools.r8.references.Reference; import com.android.tools.r8.retrace.RetraceMethodResult; +import com.android.tools.r8.shaking.ProguardKeepAttributes; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.FoundMethodSubject; import com.android.tools.r8.utils.codeinspector.InstructionSubject; -import com.android.tools.r8.utils.codeinspector.Matchers.InlinePosition; +import com.android.tools.r8.utils.codeinspector.Matchers.LinePosition; import com.android.tools.r8.utils.codeinspector.MethodSubject; import com.google.common.collect.ImmutableList; import java.io.IOException; @@ -41,6 +42,7 @@ @RunWith(Parameterized.class) public class DexPcWithDebugInfoForOverloadedMethodsTestRunner extends TestBase { + private static final String FILENAME_INLINE = "InlineFunction.kt"; private static final Class<?> MAIN = DexPcWithDebugInfoForOverloadedMethodsTest.class; private static final int MINIFIED_LINE_POSITION = 6; private static final String EXPECTED = "java.lang.RuntimeException: overloaded(String)42"; @@ -66,6 +68,7 @@ .addKeepMainRule(MAIN) .addKeepMethodRules(MAIN, "void overloaded(...)") .addKeepAttributeLineNumberTable() + .addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE) .setMinApi(parameters.getApiLevel()) .addOptionsModification( internalOptions -> { @@ -86,18 +89,20 @@ MethodSubject throwingSubject = codeInspector.clazz(MAIN).method("void", "overloaded", "java.lang.String"); assertThat(throwingSubject, isPresent()); - InlinePosition inlineStack = - InlinePosition.stack( - InlinePosition.create( + LinePosition inlineStack = + LinePosition.stack( + LinePosition.create( Reference.methodFromMethod( MAIN.getDeclaredMethod("inlinee", String.class)), MINIFIED_LINE_POSITION, - 11), - InlinePosition.create( + 11, + FILENAME_INLINE), + LinePosition.create( Reference.methodFromMethod( MAIN.getDeclaredMethod("overloaded", String.class)), MINIFIED_LINE_POSITION, - 20)); + 20, + FILENAME_INLINE)); RetraceMethodResult retraceResult = throwingSubject .streamInstructions()
diff --git a/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java index c47f16f..ae533a4 100644 --- a/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java +++ b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
@@ -22,7 +22,7 @@ import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.InstructionSubject; -import com.android.tools.r8.utils.codeinspector.Matchers.InlinePosition; +import com.android.tools.r8.utils.codeinspector.Matchers.LinePosition; import com.android.tools.r8.utils.codeinspector.MethodSubject; import com.google.common.collect.ImmutableList; import java.io.IOException; @@ -35,6 +35,7 @@ @RunWith(Parameterized.class) public class EnsureNoDebugInfoEmittedForPcOnlyTestRunner extends TestBase { + private static final String FILENAME_MAIN = "EnsureNoDebugInfoEmittedForPcOnlyTest.java"; private static final Class<?> MAIN = EnsureNoDebugInfoEmittedForPcOnlyTest.class; private static final int INLINED_DEX_PC = 32; @@ -75,20 +76,23 @@ .inspectStackTrace( (stackTrace, codeInspector) -> { MethodSubject mainSubject = codeInspector.clazz(MAIN).uniqueMethodWithName("main"); - InlinePosition inlineStack = - InlinePosition.stack( - InlinePosition.create( + LinePosition inlineStack = + LinePosition.stack( + LinePosition.create( Reference.methodFromMethod(MAIN.getDeclaredMethod("a")), INLINED_DEX_PC, - 11), - InlinePosition.create( + 11, + FILENAME_MAIN), + LinePosition.create( Reference.methodFromMethod(MAIN.getDeclaredMethod("b")), INLINED_DEX_PC, - 18), - InlinePosition.create( + 18, + FILENAME_MAIN), + LinePosition.create( mainSubject.asFoundMethodSubject().asMethodReference(), INLINED_DEX_PC, - 23)); + 23, + FILENAME_MAIN)); RetraceMethodResult retraceResult = mainSubject .streamInstructions()
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java index 02623a8..b1a6811 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java
@@ -5,7 +5,7 @@ package com.android.tools.r8.desugar.desugaredlibrary.r8ondex; import static junit.framework.TestCase.assertEquals; -import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertTrue; import com.android.tools.r8.R8; import com.android.tools.r8.TestParameters;
diff --git a/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java b/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java index 75ffea7..eeedbab 100644 --- a/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java +++ b/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
@@ -8,7 +8,9 @@ import com.android.tools.r8.graph.DexDebugInfo; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexString; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.graph.ObjectToOffsetMapping; +import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.Reporter; import java.util.Collections; @@ -21,6 +23,8 @@ return new ObjectToOffsetMapping( DexApplication.builder(new InternalOptions(new DexItemFactory(), new Reporter()), null) .build(), + NamingLens.getIdentityLens(), + InitClassLens.getDefault(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(),
diff --git a/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java b/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java index 666cb87..945f3c2 100644 --- a/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java +++ b/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
@@ -25,7 +25,6 @@ import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.MethodAccessFlags; import com.android.tools.r8.graph.ParameterAnnotationsList; -import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.codeinspector.CodeInspector; @@ -43,7 +42,6 @@ public void branching() { DexItemFactory factory = new DexItemFactory(); DexString string = factory.createString("turn into jumbo"); - factory.sort(NamingLens.getIdentityLens()); Instruction[] instructions = buildInstructions(string, false); DexCode code = jumboStringProcess(factory, string, instructions); Instruction[] rewrittenInstructions = code.instructions; @@ -60,7 +58,6 @@ public void branching2() { DexItemFactory factory = new DexItemFactory(); DexString string = factory.createString("turn into jumbo"); - factory.sort(NamingLens.getIdentityLens()); Instruction[] instructions = buildInstructions(string, true); DexCode code = jumboStringProcess(factory, string, instructions); Instruction[] rewrittenInstructions = code.instructions; @@ -142,7 +139,6 @@ DexItemFactory factory = inspector.getFactory(); DexString string = factory.createString("view must have a tag"); - factory.sort(NamingLens.getIdentityLens()); DexCode code = jumboStringProcess(factory, string, instructions); Instruction[] rewrittenInstructions = code.instructions; assertEquals(289, countJumboStrings(rewrittenInstructions));
diff --git a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java index 8542a76..817abcd 100644 --- a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java +++ b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
@@ -24,6 +24,7 @@ import com.android.tools.r8.graph.DexTypeList; import com.android.tools.r8.graph.DirectMappedDexApplication; import com.android.tools.r8.graph.GraphLense; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.graph.MethodAccessFlags; import com.android.tools.r8.graph.ParameterAnnotationsList; import com.android.tools.r8.naming.NamingLens; @@ -155,6 +156,7 @@ options, null, GraphLense.getIdentityLense(), + InitClassLens.getDefault(), NamingLens.getIdentityLens(), null); ExecutorService executorService = ThreadUtils.getExecutorService(options);
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingArrayTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingArrayTest.java new file mode 100644 index 0000000..7d6f2ac --- /dev/null +++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingArrayTest.java
@@ -0,0 +1,174 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.enumunboxing; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.R8TestCompileResult; +import com.android.tools.r8.R8TestRunResult; +import com.android.tools.r8.TestParameters; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class EnumUnboxingArrayTest extends EnumUnboxingTestBase { + + private static final Class<?>[] FAILURES = { + EnumVarArgs.class, + EnumArrayReadWriteNoEscape.class, + EnumArrayReadWrite.class, + Enum2DimArrayReadWrite.class + }; + + private final TestParameters parameters; + private final boolean enumValueOptimization; + private final KeepRule enumKeepRules; + + @Parameters(name = "{0} valueOpt: {1} keep: {2}") + public static List<Object[]> data() { + return enumUnboxingTestParameters(); + } + + public EnumUnboxingArrayTest( + TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) { + this.parameters = parameters; + this.enumValueOptimization = enumValueOptimization; + this.enumKeepRules = enumKeepRules; + } + + @Test + public void testEnumUnboxingFailure() throws Exception { + R8TestCompileResult compile = + testForR8(parameters.getBackend()) + .addInnerClasses(EnumUnboxingArrayTest.class) + .addKeepMainRules(FAILURES) + .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() + .addKeepRules(enumKeepRules.getKeepRule()) + .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization)) + .allowDiagnosticInfoMessages() + .setMinApi(parameters.getApiLevel()) + .compile(); + for (Class<?> failure : FAILURES) { + R8TestRunResult run = + compile + .inspectDiagnosticMessages( + m -> + assertEnumIsBoxed( + failure.getDeclaredClasses()[0], failure.getSimpleName(), m)) + .run(parameters.getRuntime(), failure) + .assertSuccess(); + assertLines2By2Correct(run.getStdOut()); + } + } + + static class EnumVarArgs { + + public static void main(String[] args) { + System.out.println(sum(MyEnum.A)); + System.out.println(1); + System.out.println(sum(MyEnum.B, MyEnum.C)); + System.out.println(2); + } + + @NeverInline + public static int sum(MyEnum... args) { + return args.length; + } + + @NeverClassInline + enum MyEnum { + A, + B, + C; + } + } + + static class EnumArrayReadWriteNoEscape { + + public static void main(String[] args) { + MyEnum[] myEnums = new MyEnum[2]; + myEnums[1] = MyEnum.C; + System.out.println(myEnums[1].ordinal()); + System.out.println(2); + System.out.println(myEnums[0]); + System.out.println("null"); + myEnums[0] = MyEnum.B; + System.out.println(myEnums.length); + System.out.println(2); + } + + @NeverClassInline + enum MyEnum { + A, + B, + C; + } + } + + static class EnumArrayReadWrite { + + public static void main(String[] args) { + MyEnum[] myEnums = getArray(); + System.out.println(myEnums[1].ordinal()); + System.out.println(2); + System.out.println(myEnums[0]); + System.out.println("null"); + myEnums[0] = MyEnum.B; + System.out.println(sum(myEnums)); + System.out.println(2); + } + + @NeverInline + public static MyEnum[] getArray() { + MyEnum[] myEnums = new MyEnum[2]; + myEnums[1] = MyEnum.C; + return myEnums; + } + + @NeverInline + public static int sum(MyEnum[] args) { + return args.length; + } + + @NeverClassInline + enum MyEnum { + A, + B, + C; + } + } + + static class Enum2DimArrayReadWrite { + + public static void main(String[] args) { + MyEnum[][] myEnums = getArray(); + System.out.println(myEnums[1][1].ordinal()); + System.out.println(2); + System.out.println(myEnums[0][0].ordinal()); + System.out.println(1); + } + + @NeverInline + public static MyEnum[][] getArray() { + MyEnum[][] myEnums = new MyEnum[2][2]; + myEnums[1][1] = MyEnum.C; + myEnums[1][0] = MyEnum.A; + myEnums[0][0] = MyEnum.B; + myEnums[0][1] = MyEnum.A; + return myEnums; + } + + @NeverClassInline + enum MyEnum { + A, + B, + C; + } + } +}
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 1b56b1b..e03d352 100644 --- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java +++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
@@ -73,6 +73,7 @@ } void assertEnumIsUnboxed(Class<?> enumClass, String testName, TestDiagnosticMessages m) { + assertTrue(enumClass.isEnum()); Diagnostic diagnostic = m.getInfos().get(0); assertTrue(diagnostic.getDiagnosticMessage().startsWith("Unboxed enums")); assertTrue( @@ -81,6 +82,7 @@ } void assertEnumIsBoxed(Class<?> enumClass, String testName, TestDiagnosticMessages m) { + assertTrue(enumClass.isEnum()); Diagnostic diagnostic = m.getInfos().get(1); assertTrue(diagnostic.getDiagnosticMessage().startsWith("Boxed enums")); assertTrue(
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java index f2ed08b..eedc8f1 100644 --- a/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java +++ b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java
@@ -81,10 +81,12 @@ C } - MyEnum e; + MyEnum e = null; public static void main(String[] args) { InstanceFieldPut fieldPut = new InstanceFieldPut(); + System.out.println(fieldPut.e == null); + System.out.println("true"); fieldPut.setA(); System.out.println(fieldPut.e.ordinal()); System.out.println(0); @@ -113,9 +115,11 @@ C } - static MyEnum e; + static MyEnum e = null; public static void main(String[] args) { + System.out.println(StaticFieldPut.e == null); + System.out.println("true"); setA(); System.out.println(StaticFieldPut.e.ordinal()); System.out.println(0);
diff --git a/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java b/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java index 775f030..33321eb 100644 --- a/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java +++ b/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
@@ -37,26 +37,30 @@ AndroidApp app = testForD8() .debug() - .addProgramClassesAndInnerClasses(A.class, B.class, CY.class, CYY.class) + .addProgramClassesAndInnerClasses( + GenericSignatureTestClassA.class, + GenericSignatureTestClassB.class, + GenericSignatureTestClassCY.class, + GenericSignatureTestClassCYY.class) .compile() .app; AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(app); DexItemFactory factory = appView.dexItemFactory(); CodeInspector inspector = new CodeInspector(appView.appInfo().app()); - ClassSubject a = inspector.clazz(A.class); + ClassSubject a = inspector.clazz(GenericSignatureTestClassA.class); assertThat(a, isPresent()); - ClassSubject y = inspector.clazz(A.Y.class); + ClassSubject y = inspector.clazz(GenericSignatureTestClassA.Y.class); assertThat(y, isPresent()); - ClassSubject yy = inspector.clazz(A.Y.YY.class); + ClassSubject yy = inspector.clazz(GenericSignatureTestClassA.Y.YY.class); assertThat(yy, isPresent()); - ClassSubject zz = inspector.clazz(A.Y.ZZ.class); + ClassSubject zz = inspector.clazz(GenericSignatureTestClassA.Y.ZZ.class); assertThat(zz, isPresent()); - ClassSubject b = inspector.clazz(B.class); + ClassSubject b = inspector.clazz(GenericSignatureTestClassB.class); assertThat(b, isPresent()); - ClassSubject cy = inspector.clazz(CY.class); + ClassSubject cy = inspector.clazz(GenericSignatureTestClassCY.class); assertThat(cy, isPresent()); - ClassSubject cyy = inspector.clazz(CYY.class); + ClassSubject cyy = inspector.clazz(GenericSignatureTestClassCYY.class); assertThat(cyy, isPresent()); DexEncodedMethod method; @@ -252,7 +256,7 @@ // and then extended a bit to explore more details, e.g., MethodTypeSignature. // -class A<T> { +class GenericSignatureTestClassA<T> { class Y { class YY {} @@ -260,7 +264,7 @@ class ZZ<TT> extends YY { public YY yy; - YY newYY(B... bs) { + YY newYY(GenericSignatureTestClassB... bs) { return new YY(); } @@ -301,8 +305,9 @@ } } -class B<T extends A<T>> {} +class GenericSignatureTestClassB<T extends GenericSignatureTestClassA<T>> {} -class CY<T extends A<T>.Y> {} +class GenericSignatureTestClassCY<T extends GenericSignatureTestClassA<T>.Y> {} -class CYY<T extends A<T>.Y> extends CY<T> {} +class GenericSignatureTestClassCYY<T extends GenericSignatureTestClassA<T>.Y> + extends GenericSignatureTestClassCY<T> {}
diff --git a/src/test/java/com/android/tools/r8/graph/initializedclasses/InitializedClassesInInstanceMethodsTest.java b/src/test/java/com/android/tools/r8/graph/initializedclasses/InitializedClassesInInstanceMethodsTest.java index 43015bf..9251ab8 100644 --- a/src/test/java/com/android/tools/r8/graph/initializedclasses/InitializedClassesInInstanceMethodsTest.java +++ b/src/test/java/com/android/tools/r8/graph/initializedclasses/InitializedClassesInInstanceMethodsTest.java
@@ -30,7 +30,8 @@ @Parameters(name = "{1}, enabled: {0}") public static List<Object[]> data() { - return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build()); + return buildParameters( + BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build()); } public InitializedClassesInInstanceMethodsTest( @@ -53,7 +54,7 @@ .allowAccessModification() .enableNeverClassInliningAnnotations() .enableInliningAnnotations() - .setMinApi(parameters.getRuntime()) + .setMinApi(parameters.getApiLevel()) .compile() .inspect(this::verifyOutput) .run(parameters.getRuntime(), TestClass.class) @@ -70,13 +71,15 @@ assertThat(outerClassSubject.uniqueMethodWithName("world"), not(isPresent())); assertThat(outerClassSubject.uniqueMethodWithName("exclamationMark"), not(isPresent())); - int numberOfExpectedAccessibilityBridges = - enableInitializedClassesInInstanceMethodsAnalysis ? 0 : 3; + int numberOfExpectedAccessibilityBridges = 0; assertEquals( numberOfExpectedAccessibilityBridges, outerClassSubject .allMethods(method -> method.getOriginalName().contains("access$")) .size()); + assertEquals( + !enableInitializedClassesInInstanceMethodsAnalysis, + outerClassSubject.uniqueFieldWithName("$r8$clinit").isPresent()); ClassSubject aClassSubject = inspector.clazz(Outer.A.class); assertThat(aClassSubject, isPresent());
diff --git a/src/test/java/com/android/tools/r8/inspection/FieldFlagsAndValueInspectionTest.java b/src/test/java/com/android/tools/r8/inspection/FieldFlagsAndValueInspectionTest.java new file mode 100644 index 0000000..d64be27 --- /dev/null +++ b/src/test/java/com/android/tools/r8/inspection/FieldFlagsAndValueInspectionTest.java
@@ -0,0 +1,112 @@ +// 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.inspection; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.inspector.Inspector; +import com.android.tools.r8.inspector.ValueInspector; +import com.android.tools.r8.references.Reference; +import com.android.tools.r8.utils.StringUtils; +import java.util.Optional; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class FieldFlagsAndValueInspectionTest extends TestBase { + + static final String EXPECTED = StringUtils.lines("30"); + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public FieldFlagsAndValueInspectionTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testD8() throws Exception { + if (parameters.isCfRuntime()) { + testForJvm() + .addProgramClasses(TestClass.class) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED); + return; + } + testForD8() + .addProgramClasses(TestClass.class) + .setMinApi(parameters.getApiLevel()) + .apply(b -> b.getBuilder().addOutputInspection(inspector -> inspection(inspector, false))) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED); + assertFound(); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(TestClass.class) + .addKeepClassAndMembersRules(TestClass.class) + .setMinApi(parameters.getApiLevel()) + .apply(b -> b.getBuilder().addOutputInspection(inspector -> inspection(inspector, true))) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED); + assertFound(); + } + + private int foundFields = 0; + + private void inspection(Inspector inspector, boolean isR8) { + inspector.forEachClass( + classInspector -> { + classInspector.forEachField( + fieldInspector -> { + foundFields++; + String name = fieldInspector.getFieldReference().getFieldName(); + assertEquals(name.contains("s"), fieldInspector.isStatic()); + assertEquals(name.contains("f"), fieldInspector.isFinal()); + assertEquals(Reference.INT, fieldInspector.getFieldReference().getFieldType()); + Optional<ValueInspector> value = fieldInspector.getInitialValue(); + if (fieldInspector.isStatic() && fieldInspector.isFinal()) { + // The static final 'sfi' is static initialized to 2. + assertTrue(value.isPresent()); + assertEquals(2, value.get().asIntValue().getIntValue()); + } else if (fieldInspector.isStatic()) { + // The static 'si' is default initialized to 0 and clinit sets it to 4. + // R8 optimizes that to directly set 4. + assertTrue(value.isPresent()); + assertEquals(isR8 ? 4 : 0, value.get().asIntValue().getIntValue()); + } else { + assertFalse(value.isPresent()); + } + }); + }); + } + + private void assertFound() { + assertEquals(4, foundFields); + } + + static class TestClass { + public static final int sfi = 2; + public static int si = 4; + public final int fi = 8; + public int i = 16; + + public static void main(String[] args) { + TestClass obj = new TestClass(); + System.out.println(obj.sfi + obj.si + obj.fi + obj.i); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/inspection/FieldValueTypesInspectionTest.java b/src/test/java/com/android/tools/r8/inspection/FieldValueTypesInspectionTest.java new file mode 100644 index 0000000..0031885 --- /dev/null +++ b/src/test/java/com/android/tools/r8/inspection/FieldValueTypesInspectionTest.java
@@ -0,0 +1,196 @@ +// 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.inspection; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.inspector.Inspector; +import com.android.tools.r8.inspector.ValueInspector; +import com.android.tools.r8.references.FieldReference; +import com.android.tools.r8.references.Reference; +import com.android.tools.r8.utils.StringUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class FieldValueTypesInspectionTest extends TestBase { + + static final String EXPECTED = + StringUtils.lines( + "" + TestClass.z, + "" + TestClass.b, + "" + TestClass.c, + "" + TestClass.s, + "" + TestClass.i, + "" + TestClass.j, + "" + TestClass.f, + "" + TestClass.d, + "foo"); + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public FieldValueTypesInspectionTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testD8() throws Exception { + if (parameters.isCfRuntime()) { + testForJvm() + .addProgramClasses(TestClass.class) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED); + return; + } + testForD8() + .addProgramClasses(TestClass.class) + .setMinApi(parameters.getApiLevel()) + .apply(b -> b.getBuilder().addOutputInspection(this::inspection)) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED); + assertFound(); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(TestClass.class) + .addKeepClassAndMembersRules(TestClass.class) + .setMinApi(parameters.getApiLevel()) + .apply(b -> b.getBuilder().addOutputInspection(this::inspection)) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED); + assertFound(); + } + + private int foundFields = 0; + + private void inspection(Inspector inspector) { + inspector.forEachClass( + classInspector -> { + classInspector.forEachField( + fieldInspector -> { + foundFields++; + FieldReference reference = fieldInspector.getFieldReference(); + ValueInspector value = fieldInspector.getInitialValue().get(); + assertEquals(reference.getFieldType(), value.getTypeReference()); + String name = reference.getFieldName(); + boolean isBoolean = name.equals("z"); + boolean isByte = name.equals("b"); + boolean isChar = name.equals("c"); + boolean isShort = name.equals("s"); + boolean isInt = name.equals("i"); + boolean isLong = name.equals("j"); + boolean isFloat = name.equals("f"); + boolean isDouble = name.equals("d"); + boolean isString = name.equals("str"); + assertEquals(isBoolean, value.isBooleanValue()); + assertEquals(isByte, value.isByteValue()); + assertEquals(isChar, value.isCharValue()); + assertEquals(isShort, value.isShortValue()); + assertEquals(isInt, value.isIntValue()); + assertEquals(isLong, value.isLongValue()); + assertEquals(isFloat, value.isFloatValue()); + assertEquals(isDouble, value.isDoubleValue()); + assertEquals(isString, value.isStringValue()); + if (isBoolean) { + assertEquals(Reference.BOOL, reference.getFieldType()); + assertEquals(TestClass.z, value.asBooleanValue().getBooleanValue()); + } else { + assertNull(value.asBooleanValue()); + } + if (isByte) { + assertEquals(Reference.BYTE, reference.getFieldType()); + assertEquals(TestClass.b, value.asByteValue().getByteValue()); + } else { + assertNull(value.asByteValue()); + } + if (isChar) { + assertEquals(Reference.CHAR, reference.getFieldType()); + assertEquals(TestClass.c, value.asCharValue().getCharValue()); + } else { + assertNull(value.asCharValue()); + } + if (isShort) { + assertEquals(Reference.SHORT, reference.getFieldType()); + assertEquals(TestClass.s, value.asShortValue().getShortValue()); + } else { + assertNull(value.asShortValue()); + } + if (isInt) { + assertEquals(Reference.INT, reference.getFieldType()); + assertEquals(TestClass.i, value.asIntValue().getIntValue()); + } else { + assertNull(value.asIntValue()); + } + if (isLong) { + assertEquals(Reference.LONG, reference.getFieldType()); + assertEquals(TestClass.j, value.asLongValue().getLongValue()); + } else { + assertNull(value.asLongValue()); + } + if (isFloat) { + assertEquals(Reference.FLOAT, reference.getFieldType()); + assertEquals( + Float.floatToRawIntBits(TestClass.f), + Float.floatToRawIntBits(value.asFloatValue().getFloatValue())); + } else { + assertNull(value.asFloatValue()); + } + if (isDouble) { + assertEquals(Reference.DOUBLE, reference.getFieldType()); + assertEquals( + Double.doubleToRawLongBits(TestClass.d), + Double.doubleToRawLongBits(value.asDoubleValue().getDoubleValue())); + } else { + assertNull(value.asDoubleValue()); + } + if (isString) { + assertEquals(Reference.classFromClass(String.class), reference.getFieldType()); + assertEquals(TestClass.str, value.asStringValue().getStringValue()); + } else { + assertNull(value.asStringValue()); + } + }); + }); + } + + private void assertFound() { + assertEquals(9, foundFields); + } + + static class TestClass { + public static final boolean z = true; + public static final byte b = 2; + public static final char c = 4; + public static final short s = 8; + public static final int i = 16; + public static final long j = 32L; + public static final float f = 64.1F; + public static final double d = 128.1D; + public static final String str = "foo"; + + public static void main(String[] args) { + System.out.println(z); + System.out.println(b); + System.out.println(c); + System.out.println(s); + System.out.println(i); + System.out.println(j); + System.out.println(f); + System.out.println(d); + System.out.println(str); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/inspection/InspectionApiTest.java b/src/test/java/com/android/tools/r8/inspection/InspectionApiTest.java new file mode 100644 index 0000000..5c5a6e3 --- /dev/null +++ b/src/test/java/com/android/tools/r8/inspection/InspectionApiTest.java
@@ -0,0 +1,104 @@ +// 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.inspection; + +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.inspector.Inspector; +import com.android.tools.r8.references.ClassReference; +import com.android.tools.r8.references.FieldReference; +import com.android.tools.r8.references.MethodReference; +import com.android.tools.r8.references.Reference; +import com.android.tools.r8.utils.StringUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class InspectionApiTest extends TestBase { + + static final String EXPECTED = StringUtils.lines("Hello, world"); + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public InspectionApiTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testD8() throws Exception { + if (parameters.isCfRuntime()) { + testForJvm() + .addProgramClasses(TestClass.class) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED); + return; + } + testForD8() + .addProgramClasses(TestClass.class) + .setMinApi(parameters.getApiLevel()) + .apply(b -> b.getBuilder().addOutputInspection(this::inspection)) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED); + assertFound(); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(TestClass.class) + .addKeepClassAndMembersRules(TestClass.class) + .setMinApi(parameters.getApiLevel()) + .apply(b -> b.getBuilder().addOutputInspection(this::inspection)) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED); + assertFound(); + } + + ClassReference foundClass = null; + FieldReference foundField = null; + MethodReference foundMethod = null; + + private void inspection(Inspector inspector) { + inspector.forEachClass( + classInspector -> { + foundClass = classInspector.getClassReference(); + classInspector.forEachField( + fieldInspector -> { + foundField = fieldInspector.getFieldReference(); + }); + classInspector.forEachMethod( + methodInspector -> { + // Ignore clinit (which is removed in R8). + if (!methodInspector.getMethodReference().getMethodName().equals("<clinit>")) { + foundMethod = methodInspector.getMethodReference(); + } + }); + }); + } + + private void assertFound() throws Exception { + assertEquals(Reference.classFromClass(TestClass.class), foundClass); + assertEquals(Reference.fieldFromField(TestClass.class.getDeclaredField("foo")), foundField); + assertEquals( + Reference.methodFromMethod(TestClass.class.getDeclaredMethod("main", String[].class)), + foundMethod); + } + + static class TestClass { + public static int foo = 42; + + public static void main(String[] args) { + System.out.println("Hello, world"); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java index a16962d..cba792d 100644 --- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java +++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
@@ -41,7 +41,7 @@ // For this test just do random shuffle. options.testing.irOrdering = NondeterministicIROrdering.getInstance(); // Only use one thread to process to process in the order decided by the callback. - options.numberOfThreads = 1; + options.threadCount = 1; // Ignore the missing classes. options.ignoreMissingClasses = true; // Store the generated Proguard map.
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java index 48acfdb..97b4db0 100644 --- a/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java +++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java
@@ -5,6 +5,7 @@ import static com.android.tools.r8.ToolHelper.isLocalDevelopment; import static com.android.tools.r8.ToolHelper.shouldRunSlowTests; +import static org.hamcrest.CoreMatchers.containsString; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; @@ -13,6 +14,7 @@ import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.ToolHelper; import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.analysis.ProtoApplicationStats; import org.junit.Test; @@ -29,7 +31,7 @@ @Parameters(name = "{0}") public static TestParametersCollection data() { - return getTestParameters().withDexRuntimes().build(); + return getTestParameters().withNoneRuntime().build(); } public YouTubeV1419TreeShakeJarVerificationTest(TestParameters parameters) { @@ -43,11 +45,21 @@ assumeTrue(isLocalDevelopment()); assumeTrue(shouldRunSlowTests()); + LibrarySanitizer librarySanitizer = + new LibrarySanitizer(temp).addProguardConfigurationFiles(getKeepRuleFiles()).sanitize(); + R8TestCompileResult compileResult = - testForR8(parameters.getBackend()) - .addKeepRuleFiles(getKeepRuleFiles()) + testForR8(Backend.DEX) + .addKeepRuleFiles(librarySanitizer.getSanitizedProguardConfiguration()) + .addLibraryFiles(librarySanitizer.getSanitizedLibrary()) + .addMainDexRuleFiles(getMainDexRuleFiles()) + .allowDiagnosticMessages() .allowUnusedProguardConfigurationRules() - .compile(); + .setMinApi(AndroidApiLevel.H_MR2) + .compile() + .assertAllInfoMessagesMatch( + containsString("Proguard configuration rule does not match anything")) + .assertAllWarningMessagesMatch(containsString("Ignoring option:")); if (ToolHelper.isLocalDevelopment()) { DexItemFactory dexItemFactory = new DexItemFactory();
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 8d73fad..3b3473a 100644 --- a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java +++ b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
@@ -44,7 +44,6 @@ protected DexApplication buildApplication(AndroidApp input, InternalOptions options) { try { - options.itemFactory.resetSortedIndices(); return new ApplicationReader(input, options, Timing.empty()).read(); } catch (IOException | ExecutionException e) { throw new RuntimeException(e);
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCannotBePostponedTest.java b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCannotBePostponedTest.java index d29753b..7bc227a 100644 --- a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCannotBePostponedTest.java +++ b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCannotBePostponedTest.java
@@ -5,6 +5,7 @@ package com.android.tools.r8.ir.analysis.sideeffect; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import com.android.tools.r8.TestBase; @@ -47,9 +48,9 @@ ClassSubject classSubject = inspector.clazz(A.class); assertThat(classSubject, isPresent()); - // A.inlineable() cannot be inlined because it should trigger the class initialization of A, - // which should trigger the class initialization of B, which will print "Hello". - assertThat(classSubject.uniqueMethodWithName("inlineable"), isPresent()); + // The field A.INSTANCE has been accessed to allow inlining of A.inlineable(). + assertThat(classSubject.uniqueFieldWithName("INSTANCE"), isPresent()); + assertThat(classSubject.uniqueMethodWithName("inlineable"), not(isPresent())); } static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java index a9385a6..3b77ead 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -15,7 +15,6 @@ import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.origin.Origin; -import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.FileUtils; import com.android.tools.r8.utils.ZipUtils; @@ -50,7 +49,8 @@ @Parameters(name = "{1}, allow access modification: {0}") public static Collection<Object[]> data() { - return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build()); + return buildParameters( + BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build()); } private final boolean allowAccessModification; @@ -105,7 +105,7 @@ commandBuilder.setProguardMapOutputPath(mapFile); } if (parameters.isDexRuntime()) { - commandBuilder.setMinApiLevel(AndroidApiLevel.M.getLevel()); + commandBuilder.setMinApiLevel(parameters.getApiLevel().getLevel()); } if (allowAccessModification) { commandBuilder.addProguardConfiguration( @@ -136,10 +136,19 @@ if (parameters.isDexRuntime()) { output = ToolHelper.runArtNoVerificationErrors( - outputDir.resolve(DEFAULT_DEX_FILENAME).toString(), "inlining.Inlining"); + Collections.singletonList(outputDir.resolve(DEFAULT_DEX_FILENAME).toString()), + "inlining.Inlining", + builder -> {}, + parameters.getRuntime().asDex().getVm()); } else { assert parameters.isCfRuntime(); - output = ToolHelper.runJavaNoVerify(outputDir, "inlining.Inlining").stdout; + output = + ToolHelper.runJava( + parameters.getRuntime().asCf(), + Collections.singletonList("-noverify"), + Collections.singletonList(outputDir), + "inlining.Inlining") + .stdout; } // Compare result with Java to make sure we have the same behavior.
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/UnusedReturnValueTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/UnusedReturnValueTest.java new file mode 100644 index 0000000..2a2bb9e --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/UnusedReturnValueTest.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.classinliner; + +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class UnusedReturnValueTest extends TestBase { + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public UnusedReturnValueTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(UnusedReturnValueTest.class) + .addKeepMainRule(TestClass.class) + .enableInliningAnnotations() + .setMinApi(parameters.getApiLevel()) + .compile() + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("Hello world!"); + } + + static class TestClass { + + public static void main(String[] args) { + Builder builder = new Builder(); + helper(builder); + System.out.println(builder.build()); + } + + @NeverInline + static Builder helper(Builder builder) { + builder.message = "Hello world!"; + if (System.currentTimeMillis() > 0) { + return builder; + } + return null; + } + } + + static class Builder { + + String message; + + String build() { + return message; + } + } +}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ifs/IfThrowNullPointerExceptionTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ifs/IfThrowNullPointerExceptionTest.java index 2264a29..e679e80 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/ifs/IfThrowNullPointerExceptionTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/ifs/IfThrowNullPointerExceptionTest.java
@@ -7,6 +7,7 @@ import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; @@ -17,10 +18,11 @@ import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.BooleanUtils; +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.MethodSubject; -import com.google.common.collect.ImmutableList; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -41,6 +43,15 @@ } @Test + public void testJVM() throws Exception { + assumeTrue(parameters.isCfRuntime()); + testForJvm() + .addTestClasspath() + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(getExpectedStdout()); + } + + @Test public void testD8() throws Exception { assumeTrue(parameters.isDexRuntime()); testForD8() @@ -50,7 +61,7 @@ .compile() .inspect(this::inspect) .run(parameters.getRuntime(), TestClass.class) - .assertSuccessWithOutputLines("Caught NPE", "Caught NPE"); + .assertSuccessWithOutput(getExpectedStdout()); } @Test @@ -62,55 +73,104 @@ .compile() .inspect(this::inspect) .run(parameters.getRuntime(), TestClass.class) - .assertSuccessWithOutputLines("Caught NPE", "Caught NPE"); + .assertSuccessWithOutput(getExpectedStdout()); } private void inspect(CodeInspector inspector) { ClassSubject classSubject = inspector.clazz(TestClass.class); assertThat(classSubject, isPresent()); + inspectMethod(inspector, classSubject, "testThrowNPE", false, true); + inspectMethod(inspector, classSubject, "testThrowNPEWithMessage", true, canUseRequireNonNull()); + inspectMethod(inspector, classSubject, "testThrowNull", false, true); + } - for (String methodName : ImmutableList.of("testThrowNPE", "testThrowNull")) { - MethodSubject methodSubject = classSubject.uniqueMethodWithName(methodName); - assertThat(methodSubject, isPresent()); + private void inspectMethod( + CodeInspector inspector, + ClassSubject classSubject, + String methodName, + boolean isNPEWithMessage, + boolean shouldBeOptimized) { + MethodSubject methodSubject = classSubject.uniqueMethodWithName(methodName); + assertThat(methodSubject, isPresent()); - IRCode code = methodSubject.buildIR(); + IRCode code = methodSubject.buildIR(); + if (shouldBeOptimized) { assertEquals(1, code.blocks.size()); BasicBlock entryBlock = code.entryBlock(); - assertEquals(3, entryBlock.getInstructions().size()); + assertEquals( + 3 + BooleanUtils.intValue(isNPEWithMessage), entryBlock.getInstructions().size()); assertTrue(entryBlock.getInstructions().getFirst().isArgument()); assertTrue(entryBlock.getInstructions().getLast().isReturn()); - Instruction nullCheckInstruction = entryBlock.getInstructions().get(1); - if (parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.K)) { + Instruction nullCheckInstruction = + entryBlock.getInstructions().get(1 + BooleanUtils.intValue(isNPEWithMessage)); + if (canUseRequireNonNull()) { + assertTrue(nullCheckInstruction.isInvokeStatic()); + if (isNPEWithMessage) { + assertEquals( + inspector.getFactory().objectsMethods.requireNonNullWithMessage, + nullCheckInstruction.asInvokeStatic().getInvokedMethod()); + } else { + assertEquals( + inspector.getFactory().objectsMethods.requireNonNull, + nullCheckInstruction.asInvokeStatic().getInvokedMethod()); + } + } else { + assertFalse(isNPEWithMessage); assertTrue(nullCheckInstruction.isInvokeVirtual()); assertEquals( - "java.lang.Class java.lang.Object.getClass()", - nullCheckInstruction.asInvokeVirtual().getInvokedMethod().toSourceString()); - } else { - assertTrue(nullCheckInstruction.isInvokeStatic()); - assertEquals( - "java.lang.Object java.util.Objects.requireNonNull(java.lang.Object)", - nullCheckInstruction.asInvokeStatic().getInvokedMethod().toSourceString()); + inspector.getFactory().objectMembers.getClass, + nullCheckInstruction.asInvokeVirtual().getInvokedMethod()); } + } else { + assertEquals(3, code.blocks.size()); } } + private String getExpectedStdout() { + if (parameters.isCfRuntime() || canUseRequireNonNull() || isDalvik()) { + return StringUtils.lines("Caught NPE: null", "Caught NPE: x was null", "Caught NPE: null"); + } + return StringUtils.lines( + "Caught NPE: Attempt to invoke virtual method 'java.lang.Class java.lang.Object.getClass()'" + + " on a null object reference", + "Caught NPE: x was null", + "Caught NPE: Attempt to invoke virtual method 'java.lang.Class java.lang.Object.getClass()'" + + " on a null object reference"); + } + + private boolean canUseRequireNonNull() { + return parameters.isDexRuntime() + && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K); + } + + private boolean isDalvik() { + return parameters.isDexRuntime() + && parameters.getRuntime().asDex().getVm().getVersion().isDalvik(); + } + static class TestClass { public static void main(String[] args) { testThrowNPE(new Object()); + testThrowNPEWithMessage(new Object()); testThrowNull(new Object()); try { testThrowNPE(null); } catch (NullPointerException e) { - System.out.println("Caught NPE"); + System.out.println("Caught NPE: " + e.getMessage()); + } + try { + testThrowNPEWithMessage(null); + } catch (NullPointerException e) { + System.out.println("Caught NPE: " + e.getMessage()); } try { testThrowNull(null); } catch (NullPointerException e) { - System.out.println("Caught NPE"); + System.out.println("Caught NPE: " + e.getMessage()); } } @@ -120,6 +180,12 @@ } } + static void testThrowNPEWithMessage(Object x) { + if (x == null) { + throw new NullPointerException("x was null"); + } + } + static void testThrowNull(Object x) { if (x == null) { throw null;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineInvokeWithNullableReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineInvokeWithNullableReceiverTest.java index d8b26ba..dce7b2d 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineInvokeWithNullableReceiverTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineInvokeWithNullableReceiverTest.java
@@ -68,8 +68,7 @@ assertThat(methodSubject, isPresent()); // A `throw` instruction should have been synthesized into main(). - if (parameters.isCfRuntime() - || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K)) { + if (canUseRequireNonNull()) { assertTrue(methodSubject.streamInstructions().anyMatch(InstructionSubject::isInvokeStatic)); } else { assertTrue( @@ -92,6 +91,11 @@ assertThat(otherClassSubject.uniqueMethodWithName("m"), not(isPresent())); } + private boolean canUseRequireNonNull() { + return parameters.isDexRuntime() + && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K); + } + static class TestClass { public static void main(String[] args) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningAfterClassInitializationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningAfterClassInitializationTest.java index 24ffad1..f9081c2 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningAfterClassInitializationTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningAfterClassInitializationTest.java
@@ -221,6 +221,8 @@ .addInnerClasses(InliningAfterClassInitializationTest.class) .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P)) .addKeepMainRule(mainClass) + .addOptionsModification( + options -> options.enableInliningOfInvokesWithClassInitializationSideEffects = false) .enableConstantArgumentAnnotations() .enableInliningAnnotations() .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java index 239b944..a7aa579 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java
@@ -4,7 +4,7 @@ package com.android.tools.r8.ir.optimize.inliner; -import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod; +import static com.android.tools.r8.utils.codeinspector.CodeMatchers.accessesField; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.IsNot.not; @@ -12,14 +12,30 @@ import com.android.tools.r8.NeverInline; import com.android.tools.r8.NeverMerge; import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; 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.MethodSubject; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +@RunWith(Parameterized.class) public class InliningFromCurrentClassTest extends TestBase { + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public InliningFromCurrentClassTest(TestParameters parameters) { + this.parameters = parameters; + } + @Test public void test() throws Exception { String expectedOutput = @@ -29,17 +45,18 @@ "In A.inlineable1()", "In B.inlineable2()", "In C.<clinit>()", - "In C.notInlineable()"); + "In C.inlineableWithInitClass()"); testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput); CodeInspector inspector = - testForR8(Backend.DEX) + testForR8(parameters.getBackend()) .addInnerClasses(InliningFromCurrentClassTest.class) .addKeepMainRule(TestClass.class) .enableInliningAnnotations() .enableMergeAnnotations() - .run(TestClass.class) + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), TestClass.class) .assertSuccessWithOutput(expectedOutput) .inspector(); @@ -52,18 +69,19 @@ ClassSubject classC = inspector.clazz(C.class); assertThat(classC, isPresent()); + MethodSubject testMethod = classB.uniqueMethodWithName("test"); + assertThat(testMethod, isPresent()); + MethodSubject inlineable1Method = classA.uniqueMethodWithName("inlineable1"); assertThat(inlineable1Method, not(isPresent())); MethodSubject inlineable2Method = classB.uniqueMethodWithName("inlineable2"); assertThat(inlineable2Method, not(isPresent())); - MethodSubject notInlineableMethod = classC.uniqueMethodWithName("notInlineable"); - assertThat(notInlineableMethod, isPresent()); - - MethodSubject testMethod = classB.uniqueMethodWithName("test"); - assertThat(testMethod, isPresent()); - assertThat(testMethod, invokesMethod(notInlineableMethod)); + MethodSubject inlineableWithInitClassMethod = + classC.uniqueMethodWithName("inlineableWithInitClass"); + assertThat(inlineableWithInitClassMethod, not(isPresent())); + assertThat(testMethod, accessesField(classC.uniqueFieldWithName("$r8$clinit"))); } static class TestClass { @@ -96,7 +114,7 @@ static void test() { A.inlineable1(); B.inlineable2(); - C.notInlineable(); + C.inlineableWithInitClass(); } static void inlineable2() { @@ -110,8 +128,8 @@ System.out.println("In C.<clinit>()"); } - static void notInlineable() { - System.out.println("In C.notInlineable()"); + static void inlineableWithInitClass() { + System.out.println("In C.inlineableWithInitClass()"); } } }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagationWithClassInitializationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagationWithClassInitializationTest.java index adc1f0b..50e52e2 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagationWithClassInitializationTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagationWithClassInitializationTest.java
@@ -66,7 +66,7 @@ assertThat(bClassSubject, isPresent()); MethodSubject methodSubject = bClassSubject.uniqueMethodWithName("method"); - assertThat(methodSubject, isPresent()); + assertThat(methodSubject, not(isPresent())); // TestClass.missingFieldValuePropagation() and TestClass.missingMethodValuePropagation() are // absent. @@ -85,11 +85,6 @@ .streamInstructions() .filter(InstructionSubject::isStaticGet) .anyMatch(x -> x.getField() == clinitFieldSubject.getField().field)); - assertTrue( - mainMethodSubject - .streamInstructions() - .filter(InstructionSubject::isInvokeStatic) - .anyMatch(x -> x.getMethod() == methodSubject.getMethod().method)); } static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithEscapingAliasTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithEscapingAliasTest.java new file mode 100644 index 0000000..2c61465 --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithEscapingAliasTest.java
@@ -0,0 +1,54 @@ +// 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 com.android.tools.r8.utils.StringUtils; +import com.google.common.collect.Sets; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class StringBuilderWithEscapingAliasTest extends TestBase { + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public StringBuilderWithEscapingAliasTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(TestClass.class) + .addKeepMainRule(TestClass.class) + .addOptionsModification( + options -> + options.itemFactory.libraryMethodsReturningReceiver = Sets.newIdentityHashSet()) + .setMinApi(parameters.getApiLevel()) + .compile() + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(StringUtils.times(StringUtils.lines("Hello world!"), 2)); + } + + static class TestClass { + + public static void main(String[] args) { + StringBuilder builder = new StringBuilder(); + StringBuilder alias = builder.append("Hello"); + builder.append(" world!"); + System.out.println(builder.toString()); + System.out.println(alias.toString()); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java index f9d9d21..f743bf2 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
@@ -85,12 +85,12 @@ ClassSubject mainClass = codeInspector.clazz(MAIN); MethodSubject mainMethod = mainClass.mainMethod(); assertThat(mainMethod, isPresent()); - int expectedCount = isR8 ? 3 : (isRelease ? 5 : 7); + int expectedCount = isR8 ? 4 : (isRelease ? 5 : 7); assertEquals(expectedCount, countCall(mainMethod, "String", "valueOf")); // Due to the different behavior regarding constant canonicalization. - expectedCount = isR8 ? (parameters.isCfRuntime() ? 2 : 1) : 1; + expectedCount = isR8 ? (parameters.isCfRuntime() ? 3 : 1) : 1; assertEquals(expectedCount, countConstNullNumber(mainMethod)); - expectedCount = isR8 ? (parameters.isCfRuntime() ? 2 : 1) : (isRelease ? 1 : 0); + expectedCount = isR8 ? 1 : (isRelease ? 1 : 0); assertEquals(expectedCount, countNullStringNumber(mainMethod)); MethodSubject hideNPE = mainClass.uniqueMethodWithName("hideNPE");
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java index 7b9380f..e86770c 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java
@@ -72,6 +72,25 @@ } @Test + public void smokeTest() throws Exception { + Path baseLibJar = baseLibJarMap.get(targetVersion); + Path extLibJar = extLibJarMap.get(targetVersion); + + Path output = + kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion) + .addClasspathFiles(baseLibJar, extLibJar) + .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/classpath_app", "main")) + .setOutputPath(temp.newFolder().toPath()) + .compile(); + + testForJvm() + .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), baseLibJar, extLibJar) + .addClasspath(output) + .run(parameters.getRuntime(), PKG + ".classpath_app.MainKt") + .assertSuccessWithOutput(EXPECTED); + } + + @Test public void testMetadataInClasspathType_merged() throws Exception { Path baseLibJar = baseLibJarMap.get(targetVersion); Path libJar =
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java index f77cdb9..7514ff5 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java
@@ -6,18 +6,19 @@ import static com.android.tools.r8.KotlinCompilerTool.KOTLINC; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed; -import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import com.android.tools.r8.TestParameters; +import com.android.tools.r8.ToolHelper; import com.android.tools.r8.ToolHelper.KotlinTargetVersion; -import com.android.tools.r8.ToolHelper.ProcessResult; import com.android.tools.r8.shaking.ProguardKeepAttributes; +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.FieldSubject; import com.android.tools.r8.utils.codeinspector.KmClassSubject; import com.android.tools.r8.utils.codeinspector.KmPropertySubject; import com.android.tools.r8.utils.codeinspector.MethodSubject; @@ -33,6 +34,9 @@ @RunWith(Parameterized.class) public class MetadataRewriteInCompanionTest extends KotlinMetadataTestBase { + private static final String EXPECTED = + StringUtils.lines( + "B.Companion::foo", "B.Companion::foo", "B.Companion::foo", "B.Companion::foo"); private final TestParameters parameters; @@ -63,6 +67,24 @@ } @Test + public void smokeTest() throws Exception { + Path libJar = companionLibJarMap.get(targetVersion); + + Path output = + kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion) + .addClasspathFiles(libJar) + .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/companion_app", "main")) + .setOutputPath(temp.newFolder().toPath()) + .compile(); + + testForJvm() + .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar) + .addClasspath(output) + .run(parameters.getRuntime(), PKG + ".companion_app.MainKt") + .assertSuccessWithOutput(EXPECTED); + } + + @Test public void testMetadataInCompanion_kept() throws Exception { Path libJar = testForR8(parameters.getBackend()) @@ -70,21 +92,27 @@ // Keep everything .addKeepRules("-keep class **.companion_lib.** { *; }") .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS) + // To keep @JvmField annotation + .addKeepAttributes(ProguardKeepAttributes.RUNTIME_INVISIBLE_ANNOTATIONS) + // To keep ...$Companion structure + .addKeepAttributes(ProguardKeepAttributes.INNER_CLASSES) + .addKeepAttributes(ProguardKeepAttributes.ENCLOSING_METHOD) .compile() .inspect(codeInspector -> inspect(codeInspector, true)) .writeToZip(); - ProcessResult kotlinTestCompileResult = + Path output = kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion) .addClasspathFiles(libJar) .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/companion_app", "main")) .setOutputPath(temp.newFolder().toPath()) - // TODO(b/70169921): update to just .compile() once fixed. - .compileRaw(); + .compile(); - // TODO(b/70169921): should be able to compile! - assertNotEquals(0, kotlinTestCompileResult.exitCode); - assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: elt2")); + testForJvm() + .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar) + .addClasspath(output) + .run(parameters.getRuntime(), PKG + ".companion_app.MainKt") + .assertSuccessWithOutput(EXPECTED); } @Test @@ -99,31 +127,39 @@ .addKeepRules("-keep class **.I { <methods>; }") // Keep getters for B$Companion.(eltN|foo) which will be referenced at the app. .addKeepRules("-keepclassmembers class **.B$* { *** get*(...); }") + // Keep the companion instance in the B class + .addKeepRules("-keepclassmembers class **.B { *** Companion; }") + // Keep the name of companion class + .addKeepRules("-keepnames class **.*$Companion") // No rule for Super, but will be kept and renamed. .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS) + // To keep @JvmField annotation + .addKeepAttributes(ProguardKeepAttributes.RUNTIME_INVISIBLE_ANNOTATIONS) + // To keep ...$Companion structure + .addKeepAttributes(ProguardKeepAttributes.INNER_CLASSES) + .addKeepAttributes(ProguardKeepAttributes.ENCLOSING_METHOD) .compile() .inspect(codeInspector -> inspect(codeInspector, false)) .writeToZip(); - ProcessResult kotlinTestCompileResult = + Path output = kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion) .addClasspathFiles(libJar) .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/companion_app", "main")) .setOutputPath(temp.newFolder().toPath()) - // TODO(b/70169921): update to just .compile() once fixed. - .compileRaw(); + .compile(); - // TODO(b/70169921): should be able to compile! - assertNotEquals(0, kotlinTestCompileResult.exitCode); - assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: elt1")); - assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: elt2")); - assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: foo")); + testForJvm() + .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar) + .addClasspath(output) + .run(parameters.getRuntime(), PKG + ".companion_app.MainKt") + .assertSuccessWithOutput(EXPECTED); } private void inspect(CodeInspector inspector, boolean keptAll) { final String superClassName = PKG + ".companion_lib.Super"; final String bClassName = PKG + ".companion_lib.B"; - final String companionClassName = PKG + ".companion_lib.B$Companion"; + final String companionClassName = bClassName + "$Companion"; ClassSubject sup = inspector.clazz(superClassName); if (keptAll) { @@ -147,6 +183,16 @@ supertype -> supertype.getFinalDescriptor().equals(sup.getFinalDescriptor()))); } + // The backing field for the property in the companion, with @JvmField + FieldSubject elt2 = impl.uniqueFieldWithName("elt2"); + assertThat(elt2, isPresent()); + assertThat(elt2, not(isRenamed())); + + FieldSubject companionObject = impl.uniqueFieldWithName("Companion"); + assertThat(companionObject, isPresent()); + assertThat(companionObject, not(isRenamed())); + assertEquals(companionObject.getFinalName(), kmClass.getCompanionObject()); + // Bridge for the property in the companion that needs a backing field. MethodSubject elt1Bridge = impl.uniqueMethodWithName("access$getElt1$cp"); if (keptAll) { @@ -155,6 +201,7 @@ } else { assertThat(elt1Bridge, isRenamed()); } + // With @JvmField, no bridge is added. MethodSubject elt2Bridge = impl.uniqueMethodWithName("access$getElt2$cp"); assertThat(elt2Bridge, not(isPresent())); @@ -164,23 +211,20 @@ assertThat(fooBridge, not(isPresent())); ClassSubject companion = inspector.clazz(companionClassName); - if (keptAll) { - assertThat(companion, isPresent()); - assertThat(companion, not(isRenamed())); - } else { - assertThat(companion, isRenamed()); - } + assertThat(companion, isPresent()); + assertThat(companion, not(isRenamed())); - // TODO(b/70169921): Assert impl's KmClass points to the correct companion object and class. + List<String> nestedClassDescriptors = kmClass.getNestedClassDescriptors(); + assertEquals(1, nestedClassDescriptors.size()); + assertEquals(companion.getFinalDescriptor(), nestedClassDescriptors.get(0)); kmClass = companion.getKmClass(); assertThat(kmClass, isPresent()); KmPropertySubject kmProperty = kmClass.kmPropertyWithUniqueName("elt1"); assertThat(kmProperty, isPresent()); - // TODO(b/70169921): property in companion with @JvmField is missing. kmProperty = kmClass.kmPropertyWithUniqueName("elt2"); - assertThat(kmProperty, not(isPresent())); + assertThat(kmProperty, isPresent()); kmProperty = kmClass.kmPropertyWithUniqueName("foo"); assertThat(kmProperty, isPresent());
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java index 85a1869..a9f1655 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
@@ -68,6 +68,24 @@ } @Test + public void smokeTest() throws Exception { + Path libJar = extLibJarMap.get(targetVersion); + + Path output = + kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion) + .addClasspathFiles(libJar) + .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/extension_function_app", "main")) + .setOutputPath(temp.newFolder().toPath()) + .compile(); + + testForJvm() + .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar) + .addClasspath(output) + .run(parameters.getRuntime(), PKG + ".extension_function_app.MainKt") + .assertSuccessWithOutput(EXPECTED); + } + + @Test public void testMetadataInExtensionFunction_merged() throws Exception { Path libJar = testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java index 06fcd3a..afd2ac6 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java
@@ -67,6 +67,24 @@ } @Test + public void smokeTest() throws Exception { + Path libJar = extLibJarMap.get(targetVersion); + + Path output = + kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion) + .addClasspathFiles(libJar) + .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/extension_property_app", "main")) + .setOutputPath(temp.newFolder().toPath()) + .compile(); + + testForJvm() + .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar) + .addClasspath(output) + .run(parameters.getRuntime(), PKG + ".extension_property_app.MainKt") + .assertSuccessWithOutput(EXPECTED); + } + + @Test public void testMetadataInExtensionProperty_merged() throws Exception { Path libJar = testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java index e72c314..fad1701 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
@@ -66,6 +66,24 @@ } @Test + public void smokeTest() throws Exception { + Path libJar = funLibJarMap.get(targetVersion); + + Path output = + kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion) + .addClasspathFiles(libJar) + .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/function_app", "main")) + .setOutputPath(temp.newFolder().toPath()) + .compile(); + + testForJvm() + .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar) + .addClasspath(output) + .run(parameters.getRuntime(), PKG + ".function_app.MainKt") + .assertSuccessWithOutput(EXPECTED); + } + + @Test public void testMetadataInFunction_merged() throws Exception { Path libJar = testForR8(parameters.getBackend()) @@ -170,7 +188,7 @@ KmClassSubject kmClass = sup.getKmClass(); assertThat(kmClass, isPresent()); - // TODO(b/70169921): would be better to look up function with the original name, "foo". + // TODO(b/151194869): would be better to look up function with the original name, "foo". KmFunctionSubject kmFunction = kmClass.kmFunctionWithUniqueName(foo.getFinalName()); assertThat(kmFunction, isPresent()); assertThat(kmFunction, not(isExtensionFunction()));
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithDefaultValueTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithDefaultValueTest.java index 0e41755..79ad6e5 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithDefaultValueTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithDefaultValueTest.java
@@ -69,6 +69,24 @@ } @Test + public void smokeTest() throws Exception { + Path libJar = defaultValueLibJarMap.get(targetVersion); + + Path output = + kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion) + .addClasspathFiles(libJar) + .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/default_value_app", "main")) + .setOutputPath(temp.newFolder().toPath()) + .compile(); + + testForJvm() + .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar) + .addClasspath(output) + .run(parameters.getRuntime(), PKG + ".default_value_app.MainKt") + .assertSuccessWithOutput(EXPECTED); + } + + @Test public void testMetadataInFunctionWithDefaultValue() throws Exception { Path libJar = testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java index 68714f6..fea7b3e 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java
@@ -69,6 +69,24 @@ } @Test + public void smokeTest() throws Exception { + Path libJar = varargLibJarMap.get(targetVersion); + + Path output = + kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion) + .addClasspathFiles(libJar) + .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/vararg_app", "main")) + .setOutputPath(temp.newFolder().toPath()) + .compile(); + + testForJvm() + .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar) + .addClasspath(output) + .run(parameters.getRuntime(), PKG + ".vararg_app.MainKt") + .assertSuccessWithOutput(EXPECTED); + } + + @Test public void testMetadataInFunctionWithVararg() throws Exception { Path libJar = testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java index 0bca5ee..71dd5df 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java
@@ -79,6 +79,15 @@ } @Test + public void smokeTest() throws Exception { + testForJvm() + .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), baseLibJarMap.get(targetVersion)) + .addClasspath(extLibJarMap.get(targetVersion), appJarMap.get(targetVersion)) + .run(parameters.getRuntime(), PKG + ".libtype_app.MainKt") + .assertSuccessWithOutput(EXPECTED); + } + + @Test public void testR8() throws Exception { String main = PKG + ".libtype_app.MainKt"; Path out =
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java index 5b9dfed..477c5e6 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java
@@ -16,10 +16,12 @@ import static org.junit.Assert.assertTrue; import com.android.tools.r8.TestParameters; +import com.android.tools.r8.ToolHelper; import com.android.tools.r8.ToolHelper.KotlinTargetVersion; import com.android.tools.r8.ToolHelper.ProcessResult; import com.android.tools.r8.shaking.ProguardKeepAttributes; import com.android.tools.r8.utils.DescriptorUtils; +import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.codeinspector.AnnotationSubject; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; @@ -39,6 +41,7 @@ @RunWith(Parameterized.class) public class MetadataRewriteInMultifileClassTest extends KotlinMetadataTestBase { + private static final String EXPECTED = StringUtils.lines(", 1, 2, 3"); private final TestParameters parameters; @@ -71,6 +74,24 @@ } @Test + public void smokeTest() throws Exception { + Path libJar = multifileLibJarMap.get(targetVersion); + + Path output = + kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion) + .addClasspathFiles(libJar) + .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/multifileclass_app", "main")) + .setOutputPath(temp.newFolder().toPath()) + .compile(); + + testForJvm() + .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar) + .addClasspath(output) + .run(parameters.getRuntime(), PKG + ".multifileclass_app.MainKt") + .assertSuccessWithOutput(EXPECTED); + } + + @Test public void testMetadataInMultifileClass_merged() throws Exception { Path libJar = testForR8(parameters.getBackend()) @@ -88,9 +109,9 @@ .addClasspathFiles(libJar) .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/multifileclass_app", "main")) .setOutputPath(temp.newFolder().toPath()) - // TODO(b/70169921): update to just .compile() once fixed. + // TODO(b/151193860): update to just .compile() once fixed. .compileRaw(); - // TODO(b/70169921): should be able to compile! + // TODO(b/151193860): should be able to compile! assertNotEquals(0, kotlinTestCompileResult.exitCode); assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: join")); } @@ -133,9 +154,9 @@ .addClasspathFiles(libJar) .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/multifileclass_app", "main")) .setOutputPath(temp.newFolder().toPath()) - // TODO(b/70169921): update to just .compile() once fixed. + // TODO(b/151193860): update to just .compile() once fixed. .compileRaw(); - // TODO(b/70169921): should be able to compile! + // TODO(b/151193860): should be able to compile! assertNotEquals(0, kotlinTestCompileResult.exitCode); assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: join")); } @@ -193,7 +214,7 @@ kmPackage.kmFunctionExtensionWithUniqueName("commaSeparatedJoinOfInt"); assertThat(kmFunction, isPresent()); assertThat(kmFunction, isExtensionFunction()); - // TODO(b/70169921): Inspect that parameter type has a correct type argument, Int. - // TODO(b/70169921): Inspect that the name in KmFunction is still 'join' so that apps can refer. + // TODO(b/151193860): Inspect parameter type has a correct type argument, Int. + // TODO(b/151193860): Inspect the name in KmFunction is still 'join' so that apps can refer. } }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java index 0e734ea..f4530b3 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java
@@ -63,6 +63,24 @@ } @Test + public void smokeTest() throws Exception { + Path libJar = nestedLibJarMap.get(targetVersion); + + Path output = + kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion) + .addClasspathFiles(libJar) + .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/nested_app", "main")) + .setOutputPath(temp.newFolder().toPath()) + .compile(); + + testForJvm() + .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar) + .addClasspath(output) + .run(parameters.getRuntime(), PKG + ".nested_app.MainKt") + .assertSuccessWithOutput(EXPECTED); + } + + @Test public void testMetadataInNestedClass() throws Exception { Path libJar = testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInParameterTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInParameterTypeTest.java index 05acc76..54cd15c 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInParameterTypeTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInParameterTypeTest.java
@@ -61,6 +61,24 @@ } @Test + public void smokeTest() throws Exception { + Path libJar = parameterTypeLibJarMap.get(targetVersion); + + Path output = + kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion) + .addClasspathFiles(libJar) + .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/parametertype_app", "main")) + .setOutputPath(temp.newFolder().toPath()) + .compile(); + + testForJvm() + .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar) + .addClasspath(output) + .run(parameters.getRuntime(), PKG + ".parametertype_app.MainKt") + .assertSuccessWithOutput(EXPECTED); + } + + @Test public void testMetadataInParameterType_renamed() throws Exception { Path libJar = testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java index 34abe12..e58725d 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
@@ -10,7 +10,6 @@ import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -18,6 +17,7 @@ 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.StringUtils; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.FieldSubject; @@ -35,6 +35,7 @@ @RunWith(Parameterized.class) public class MetadataRewriteInPropertyTest extends KotlinMetadataTestBase { + private static final String EXPECTED_GETTER = StringUtils.lines("true", "false", "Hey Jude"); private final TestParameters parameters; @@ -65,6 +66,25 @@ } @Test + public void smokeTest_getterApp() throws Exception { + Path libJar = propertyTypeLibJarMap.get(targetVersion); + + Path output = + kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion) + .addClasspathFiles(libJar) + .addSourceFiles( + getKotlinFileInTest(PKG_PREFIX + "/fragile_property_only_getter", "getter_user")) + .setOutputPath(temp.newFolder().toPath()) + .compile(); + + testForJvm() + .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar) + .addClasspath(output) + .run(parameters.getRuntime(), PKG + ".fragile_property_only_getter.Getter_userKt") + .assertSuccessWithOutput(EXPECTED_GETTER); + } + + @Test public void testMetadataInProperty_getterOnly() throws Exception { Path libJar = testForR8(parameters.getBackend()) @@ -89,7 +109,7 @@ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar) .addClasspath(output) .run(parameters.getRuntime(), PKG + ".fragile_property_only_getter.Getter_userKt") - .assertSuccessWithOutputLines("true", "false", "Hey Jude"); + .assertSuccessWithOutput(EXPECTED_GETTER); } private void inspectGetterOnly(CodeInspector inspector) { @@ -139,6 +159,25 @@ } @Test + public void smokeTest_setterApp() throws Exception { + Path libJar = propertyTypeLibJarMap.get(targetVersion); + + Path output = + kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion) + .addClasspathFiles(libJar) + .addSourceFiles( + getKotlinFileInTest(PKG_PREFIX + "/fragile_property_only_setter", "setter_user")) + .setOutputPath(temp.newFolder().toPath()) + .compile(); + + testForJvm() + .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar) + .addClasspath(output) + .run(parameters.getRuntime(), PKG + ".fragile_property_only_setter.Setter_userKt") + .assertSuccessWithOutputLines(); + } + + @Test public void testMetadataInProperty_setterOnly() throws Exception { Path libJar = testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTypeTest.java index 4fc0029..ceced0d 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTypeTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTypeTest.java
@@ -61,6 +61,24 @@ } @Test + public void smokeTest() throws Exception { + Path libJar = propertyTypeLibJarMap.get(targetVersion); + + Path output = + kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion) + .addClasspathFiles(libJar) + .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/propertytype_app", "main")) + .setOutputPath(temp.newFolder().toPath()) + .compile(); + + testForJvm() + .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar) + .addClasspath(output) + .run(parameters.getRuntime(), PKG + ".propertytype_app.MainKt") + .assertSuccessWithOutput(EXPECTED); + } + + @Test public void testMetadataInProperty_renamed() throws Exception { Path libJar = testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInReturnTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInReturnTypeTest.java index 21fcbb6..09d4a5a 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInReturnTypeTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInReturnTypeTest.java
@@ -61,6 +61,24 @@ } @Test + public void smokeTest() throws Exception { + Path libJar = returnTypeLibJarMap.get(targetVersion); + + Path output = + kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion) + .addClasspathFiles(libJar) + .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/returntype_app", "main")) + .setOutputPath(temp.newFolder().toPath()) + .compile(); + + testForJvm() + .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar) + .addClasspath(output) + .run(parameters.getRuntime(), PKG + ".returntype_app.MainKt") + .assertSuccessWithOutput(EXPECTED); + } + + @Test public void testMetadataInReturnType_renamed() throws Exception { Path libJar = testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java index 373dc40..b09ef7e 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
@@ -69,6 +69,24 @@ } @Test + public void smokeTest() throws Exception { + Path libJar = sealedLibJarMap.get(targetVersion); + + Path output = + kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion) + .addClasspathFiles(libJar) + .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/sealed_app", "valid")) + .setOutputPath(temp.newFolder().toPath()) + .compile(); + + testForJvm() + .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar) + .addClasspath(output) + .run(parameters.getRuntime(), PKG + ".sealed_app.ValidKt") + .assertSuccessWithOutput(EXPECTED); + } + + @Test public void testMetadataInSealedClass_valid() throws Exception { Path libJar = testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSuperTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSuperTypeTest.java index 9de95b4..3c02dac 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSuperTypeTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSuperTypeTest.java
@@ -63,6 +63,24 @@ } @Test + public void smokeTest() throws Exception { + Path libJar = superTypeLibJarMap.get(targetVersion); + + Path output = + kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion) + .addClasspathFiles(libJar) + .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/supertype_app", "main")) + .setOutputPath(temp.newFolder().toPath()) + .compile(); + + testForJvm() + .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar) + .addClasspath(output) + .run(parameters.getRuntime(), PKG + ".supertype_app.MainKt") + .assertSuccessWithOutput(EXPECTED); + } + + @Test public void testMetadataInSupertype_merged() throws Exception { Path libJar = testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java index 8b9f276..094362c 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
@@ -12,9 +12,11 @@ import static org.junit.Assert.assertNotEquals; import com.android.tools.r8.TestParameters; +import com.android.tools.r8.ToolHelper; import com.android.tools.r8.ToolHelper.KotlinTargetVersion; import com.android.tools.r8.ToolHelper.ProcessResult; import com.android.tools.r8.shaking.ProguardKeepAttributes; +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.KmPackageSubject; @@ -30,6 +32,9 @@ @RunWith(Parameterized.class) public class MetadataRewriteInTypeAliasTest extends KotlinMetadataTestBase { + private static final String EXPECTED = + StringUtils.lines("Impl::foo", "Program::foo", "true", "42"); + private final TestParameters parameters; @Parameterized.Parameters(name = "{0} target: {1}") @@ -59,6 +64,24 @@ } @Test + public void smokeTest() throws Exception { + Path libJar = typeAliasLibJarMap.get(targetVersion); + + Path output = + kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion) + .addClasspathFiles(libJar) + .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/typealias_app", "main")) + .setOutputPath(temp.newFolder().toPath()) + .compile(); + + testForJvm() + .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar) + .addClasspath(output) + .run(parameters.getRuntime(), PKG + ".typealias_app.MainKt") + .assertSuccessWithOutput(EXPECTED); + } + + @Test public void testMetadataInTypeAlias_renamed() throws Exception { Path libJar = testForR8(parameters.getBackend()) @@ -79,9 +102,9 @@ .addClasspathFiles(libJar) .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/typealias_app", "main")) .setOutputPath(temp.newFolder().toPath()) - // TODO(b/70169921): update to just .compile() once fixed. + // TODO(b/151194785): update to just .compile() once fixed. .compileRaw(); - // TODO(b/70169921): should be able to compile! + // TODO(b/151194785): should be able to compile! assertNotEquals(0, kotlinTestCompileResult.exitCode); assertThat( kotlinTestCompileResult.stderr, @@ -107,6 +130,6 @@ // API entry is kept, hence the presence of Metadata. KmPackageSubject kmPackage = libKt.getKmPackage(); assertThat(kmPackage, isPresent()); - // TODO(b/70169921): need further inspection: many kinds of type appearances in typealias. + // TODO(b/151194785): need further inspection: many kinds of type appearances in typealias. } }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java index 31cd07d..4d66342 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
@@ -52,7 +52,7 @@ .addKeepMainRule(mainClassName) .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS) .addKeepRules("-keep class kotlin.Metadata") - // TODO(b/70169921): if this option is settled down, this test is meaningless. + // TODO(b/151194540): if this option is settled down, this test is meaningless. .addOptionsModification(o -> o.enableKotlinMetadataRewritingForRenamedClasses = false) .allowDiagnosticWarningMessages() .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/companion_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_lib/lib.kt index e3da633..8e69219 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/companion_lib/lib.kt +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_lib/lib.kt
@@ -23,6 +23,6 @@ @JvmField val elt2: Super = B() val foo: String - get() = "B.Companion:foo" + get() = "B.Companion::foo" } }
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 03ab057..2b5270d 100644 --- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java +++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -47,6 +47,7 @@ import com.android.tools.r8.graph.DexTypeList; import com.android.tools.r8.graph.DirectMappedDexApplication; import com.android.tools.r8.graph.GraphLense; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.graph.MethodAccessFlags; import com.android.tools.r8.graph.ParameterAnnotationsList; import com.android.tools.r8.ir.code.CatchHandlers; @@ -866,6 +867,7 @@ options, null, GraphLense.getIdentityLense(), + InitClassLens.getDefault(), NamingLens.getIdentityLens(), null); ExecutorService executor = ThreadUtils.getExecutorService(options);
diff --git a/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java b/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java new file mode 100644 index 0000000..4de8e4b --- /dev/null +++ b/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java
@@ -0,0 +1,149 @@ +// 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.naming; + +import static com.android.tools.r8.utils.codeinspector.Matchers.containsLinePositions; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static junit.framework.TestCase.assertEquals; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.R8TestBuilder; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.naming.retrace.StackTrace; +import com.android.tools.r8.naming.testclasses.ClassToBeMinified; +import com.android.tools.r8.naming.testclasses.Main; +import com.android.tools.r8.references.MethodReference; +import com.android.tools.r8.references.Reference; +import com.android.tools.r8.shaking.ProguardKeepAttributes; +import com.android.tools.r8.utils.BooleanUtils; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.android.tools.r8.utils.codeinspector.Matchers.LinePosition; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.ExecutionException; +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 RenameSourceFileRetraceTest extends TestBase { + + private static final String FILENAME_RENAME = "FOO"; + private static final String FILENAME_MAIN = "Main.java"; + private static final String FILENAME_CLASS_TO_BE_MINIFIED = "ClassToBeMinified.java"; + + private final TestParameters parameters; + private final boolean isCompat; + private final boolean keepSourceFile; + + @Parameters(name = "{0}, is compat: {1}, keep source file attribute: {2}") + public static List<Object[]> data() { + return buildParameters( + getTestParameters().withAllRuntimesAndApiLevels().build(), + BooleanUtils.values(), + BooleanUtils.values()); + } + + public RenameSourceFileRetraceTest( + TestParameters parameters, Boolean isCompat, Boolean keepSourceFile) { + this.parameters = parameters; + this.isCompat = isCompat; + this.keepSourceFile = keepSourceFile; + } + + @Test + public void testR8() + throws ExecutionException, CompilationFailedException, IOException, NoSuchMethodException { + R8TestBuilder<? extends R8TestBuilder<?>> r8TestBuilder = + isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()); + if (keepSourceFile) { + r8TestBuilder.addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE); + } + String minifiedFileName = + (keepSourceFile && isCompat) ? FILENAME_CLASS_TO_BE_MINIFIED : getDefaultExpectedName(); + String mainFileName = keepSourceFile ? FILENAME_MAIN : getDefaultExpectedName(); + r8TestBuilder + .addProgramClasses(ClassToBeMinified.class, Main.class) + .addKeepAttributes(ProguardKeepAttributes.LINE_NUMBER_TABLE) + .addKeepMainRule(Main.class) + .setMinApi(parameters.getApiLevel()) + .enableInliningAnnotations() + .run(parameters.getRuntime(), Main.class) + .assertFailureWithErrorThatMatches(containsString("ClassToBeMinified.foo()")) + .inspectFailure(inspector -> inspectOutput(inspector, minifiedFileName, mainFileName)) + .inspectStackTrace(stackTrace -> inspectStackTrace(stackTrace, FILENAME_MAIN)); + } + + @Test + public void testRenameSourceFileR8() + throws ExecutionException, CompilationFailedException, IOException, NoSuchMethodException { + R8TestBuilder<? extends R8TestBuilder<?>> r8TestBuilder = + isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()); + if (keepSourceFile) { + r8TestBuilder.addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE); + } + String expectedName = getDefaultExpectedName(FILENAME_RENAME); + r8TestBuilder + .addProgramClasses(ClassToBeMinified.class, Main.class) + .addKeepAttributes(ProguardKeepAttributes.LINE_NUMBER_TABLE) + .addKeepRules("-renamesourcefileattribute " + FILENAME_RENAME) + .addKeepMainRule(Main.class) + .setMinApi(parameters.getApiLevel()) + .enableInliningAnnotations() + .run(parameters.getRuntime(), Main.class) + .assertFailureWithErrorThatMatches(containsString("ClassToBeMinified.foo()")) + .inspectFailure(inspector -> inspectOutput(inspector, expectedName, expectedName)) + .inspectStackTrace(stackTrace -> inspectStackTrace(stackTrace, expectedName)); + } + + private String getDefaultExpectedName() { + return getDefaultExpectedName(parameters.getBackend() == Backend.CF ? "SourceFile" : ""); + } + + private String getDefaultExpectedName(String name) { + if (!isCompat && !keepSourceFile) { + return null; + } else { + return name; + } + } + + private void inspectOutput( + CodeInspector inspector, String classToBeMinifiedFilename, String mainClassFilename) { + inspectSourceFileForClass(inspector, Main.class, mainClassFilename); + inspectSourceFileForClass(inspector, ClassToBeMinified.class, classToBeMinifiedFilename); + } + + private void inspectSourceFileForClass(CodeInspector inspector, Class<?> clazz, String expected) { + ClassSubject classToBeMinifiedSubject = inspector.clazz(clazz); + assertThat(classToBeMinifiedSubject, isPresent()); + DexClass dexClass = classToBeMinifiedSubject.getDexClass(); + String actualString = dexClass.sourceFile == null ? null : dexClass.sourceFile.toString(); + assertEquals(expected, actualString); + } + + private void inspectStackTrace(StackTrace stackTrace, String mainFileName) + throws NoSuchMethodException { + if (!keepSourceFile) { + return; + } + assertEquals(2, stackTrace.getStackTraceLines().size()); + MethodReference classToBeMinifiedFoo = + Reference.methodFromMethod(ClassToBeMinified.class.getDeclaredMethod("foo")); + MethodReference mainMain = + Reference.methodFromMethod(Main.class.getDeclaredMethod("main", String[].class)); + LinePosition expectedStack = + LinePosition.stack( + LinePosition.create(classToBeMinifiedFoo, 1, 13, FILENAME_CLASS_TO_BE_MINIFIED), + LinePosition.create(mainMain, 1, 10, mainFileName)); + assertThat(stackTrace, containsLinePositions(expectedStack)); + } +}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java index 9b2c7c4..a55e1d5 100644 --- a/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java +++ b/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
@@ -9,10 +9,12 @@ import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForFileNameAndLineNumber; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assume.assumeTrue; import com.android.tools.r8.CompilationMode; import com.android.tools.r8.TestParameters; import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine; +import com.android.tools.r8.utils.BooleanUtils; import com.google.common.collect.ImmutableList; import java.util.Collection; import org.junit.Test; @@ -23,14 +25,16 @@ @RunWith(Parameterized.class) public class DesugarLambdaRetraceTest extends RetraceTestBase { - @Parameters(name = "{0}, mode: {1}") + @Parameters(name = "{0}, mode: {1}, compat: {2}") public static Collection<Object[]> data() { return buildParameters( - getTestParameters().withAllRuntimesAndApiLevels().build(), CompilationMode.values()); + getTestParameters().withAllRuntimesAndApiLevels().build(), + CompilationMode.values(), + BooleanUtils.values()); } - public DesugarLambdaRetraceTest(TestParameters parameters, CompilationMode mode) { - super(parameters, mode); + public DesugarLambdaRetraceTest(TestParameters parameters, CompilationMode mode, boolean compat) { + super(parameters, mode, compat); } @Override @@ -122,12 +126,14 @@ @Test public void testLineNumberTableOnly() throws Exception { + assumeTrue(compat); runTest( ImmutableList.of("-keepattributes LineNumberTable"), this::checkIsSameExceptForFileName); } @Test public void testNoLineNumberTable() throws Exception { + assumeTrue(compat); runTest(ImmutableList.of(), this::checkIsSameExceptForFileNameAndLineNumber); } }
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsRetraceTest.java index 1427865..f049056 100644 --- a/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsRetraceTest.java +++ b/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsRetraceTest.java
@@ -10,6 +10,7 @@ import com.android.tools.r8.CompilationMode; import com.android.tools.r8.NeverInline; import com.android.tools.r8.TestParameters; +import com.android.tools.r8.utils.BooleanUtils; import com.google.common.collect.ImmutableList; import java.util.Collection; import org.junit.Test; @@ -20,14 +21,17 @@ @RunWith(Parameterized.class) public class DesugarStaticInterfaceMethodsRetraceTest extends RetraceTestBase { - @Parameters(name = "{0}, mode: {1}") + @Parameters(name = "{0}, mode: {1}, compat: {2}") public static Collection<Object[]> data() { return buildParameters( - getTestParameters().withAllRuntimesAndApiLevels().build(), CompilationMode.values()); + getTestParameters().withAllRuntimesAndApiLevels().build(), + CompilationMode.values(), + BooleanUtils.values()); } - public DesugarStaticInterfaceMethodsRetraceTest(TestParameters parameters, CompilationMode mode) { - super(parameters, mode); + public DesugarStaticInterfaceMethodsRetraceTest( + TestParameters parameters, CompilationMode mode, boolean compat) { + super(parameters, mode, compat); } @Override
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java index 4ffc7de..48fc320 100644 --- a/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java +++ b/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java
@@ -9,10 +9,12 @@ import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForFileNameAndLineNumber; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assume.assumeTrue; import com.android.tools.r8.CompilationMode; import com.android.tools.r8.ForceInline; import com.android.tools.r8.TestParameters; +import com.android.tools.r8.utils.BooleanUtils; import com.google.common.collect.ImmutableList; import java.util.Collection; import org.junit.Test; @@ -23,14 +25,16 @@ @RunWith(Parameterized.class) public class InliningRetraceTest extends RetraceTestBase { - @Parameters(name = "{0}, mode: {1}") + @Parameters(name = "{0}, mode: {1}, compat: {2}") public static Collection<Object[]> data() { return buildParameters( - getTestParameters().withAllRuntimesAndApiLevels().build(), CompilationMode.values()); + getTestParameters().withAllRuntimesAndApiLevels().build(), + CompilationMode.values(), + BooleanUtils.values()); } - public InliningRetraceTest(TestParameters parameters, CompilationMode mode) { - super(parameters, mode); + public InliningRetraceTest(TestParameters parameters, CompilationMode mode, boolean compat) { + super(parameters, mode, compat); } @Override @@ -54,6 +58,7 @@ @Test public void testLineNumberTableOnly() throws Exception { + assumeTrue(compat); runTest( ImmutableList.of("-keepattributes LineNumberTable"), (StackTrace actualStackTrace, StackTrace retracedStackTrace) -> { @@ -64,6 +69,7 @@ @Test public void testNoLineNumberTable() throws Exception { + assumeTrue(compat); runTest( ImmutableList.of(), (StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java index 3da28ae..adc5cb8 100644 --- a/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java +++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java
@@ -5,7 +5,7 @@ package com.android.tools.r8.naming.retrace; import com.android.tools.r8.CompilationMode; -import com.android.tools.r8.R8FullTestBuilder; +import com.android.tools.r8.R8TestBuilder; import com.android.tools.r8.R8TestRunResult; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; @@ -18,15 +18,17 @@ public abstract class RetraceTestBase extends TestBase { protected TestParameters parameters; protected CompilationMode mode; + protected boolean compat; - public RetraceTestBase(TestParameters parameters, CompilationMode mode) { + public RetraceTestBase(TestParameters parameters, CompilationMode mode, boolean compat) { this.parameters = parameters; this.mode = mode; + this.compat = compat; } public StackTrace expectedStackTrace; - public void configure(R8FullTestBuilder builder) {} + public void configure(R8TestBuilder builder) {} public Collection<Class<?>> getClasses() { return ImmutableList.of(getMainClass()); @@ -49,7 +51,7 @@ throws Exception { R8TestRunResult result = - testForR8(parameters.getBackend()) + (compat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend())) .setMode(mode) .enableProguardTestOptions() .addProgramClasses(getClasses())
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java index d4b758e..2d5e31b 100644 --- a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java +++ b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
@@ -205,6 +205,10 @@ return originalStderr; } + public List<StackTraceLine> getStackTraceLines() { + return stackTraceLines; + } + public static StackTrace extractFromArt(String stderr, DexVm vm) { List<StackTraceLine> stackTraceLines = new ArrayList<>(); List<String> stderrLines = StringUtils.splitLines(stderr);
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/VerticalClassMergingRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/VerticalClassMergingRetraceTest.java index 6cd268f..46da60b 100644 --- a/src/test/java/com/android/tools/r8/naming/retrace/VerticalClassMergingRetraceTest.java +++ b/src/test/java/com/android/tools/r8/naming/retrace/VerticalClassMergingRetraceTest.java
@@ -7,12 +7,14 @@ import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForFileNameAndLineNumber; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assume.assumeTrue; import com.android.tools.r8.CompilationMode; import com.android.tools.r8.NeverInline; -import com.android.tools.r8.R8FullTestBuilder; +import com.android.tools.r8.R8TestBuilder; import com.android.tools.r8.TestParameters; import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine; +import com.android.tools.r8.utils.BooleanUtils; import com.google.common.collect.ImmutableList; import java.util.Collection; import java.util.HashSet; @@ -26,18 +28,21 @@ public class VerticalClassMergingRetraceTest extends RetraceTestBase { private Set<StackTraceLine> haveSeenLines = new HashSet<>(); - @Parameters(name = "{0}, mode: {1}") + @Parameters(name = "{0}, mode: {1}, compat: {2}") public static Collection<Object[]> data() { return buildParameters( - getTestParameters().withAllRuntimesAndApiLevels().build(), CompilationMode.values()); + getTestParameters().withAllRuntimesAndApiLevels().build(), + CompilationMode.values(), + BooleanUtils.values()); } - public VerticalClassMergingRetraceTest(TestParameters parameters, CompilationMode mode) { - super(parameters, mode); + public VerticalClassMergingRetraceTest( + TestParameters parameters, CompilationMode mode, boolean compat) { + super(parameters, mode, compat); } @Override - public void configure(R8FullTestBuilder builder) { + public void configure(R8TestBuilder builder) { builder.enableInliningAnnotations(); } @@ -87,6 +92,7 @@ @Test public void testLineNumberTableOnly() throws Exception { + assumeTrue(compat); runTest( ImmutableList.of("-keepattributes LineNumberTable"), (StackTrace actualStackTrace, StackTrace retracedStackTrace) -> { @@ -125,6 +131,7 @@ // at com.android.tools.r8.naming.retraceproguard.ResourceWrapper.foo(ResourceWrapper.java:0) // at com.android.tools.r8.naming.retraceproguard.MainApp.main(MainApp.java:7) // since the synthetic bridge belongs to ResourceWrapper.foo. + assumeTrue(compat); haveSeenLines.clear(); runTest( ImmutableList.of(),
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarLambdaRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarLambdaRetraceTest.java index 42f2857..a527d93 100644 --- a/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarLambdaRetraceTest.java +++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarLambdaRetraceTest.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.CompilationMode; import com.android.tools.r8.ToolHelper; import com.android.tools.r8.naming.retraceproguard.StackTrace.StackTraceLine; +import com.android.tools.r8.utils.BooleanUtils; import com.google.common.collect.ImmutableList; import java.util.Collection; import org.junit.Ignore; @@ -23,13 +24,14 @@ @RunWith(Parameterized.class) public class DesugarLambdaRetraceTest extends RetraceTestBase { - @Parameters(name = "Backend: {0}, mode: {1}") + @Parameters(name = "Backend: {0}, mode: {1}, compat: {2}") public static Collection<Object[]> data() { - return buildParameters(ToolHelper.getBackends(), CompilationMode.values()); + return buildParameters( + ToolHelper.getBackends(), CompilationMode.values(), BooleanUtils.values()); } - public DesugarLambdaRetraceTest(Backend backend, CompilationMode mode) { - super(backend, mode); + public DesugarLambdaRetraceTest(Backend backend, CompilationMode mode, boolean compat) { + super(backend, mode, compat); } @Override
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarStaticInterfaceMethodsRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarStaticInterfaceMethodsRetraceTest.java index acd5baa..2034f5d 100644 --- a/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarStaticInterfaceMethodsRetraceTest.java +++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarStaticInterfaceMethodsRetraceTest.java
@@ -10,6 +10,7 @@ import com.android.tools.r8.CompilationMode; import com.android.tools.r8.NeverInline; import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.utils.BooleanUtils; import com.google.common.collect.ImmutableList; import java.util.Collection; import org.junit.Test; @@ -20,13 +21,15 @@ @RunWith(Parameterized.class) public class DesugarStaticInterfaceMethodsRetraceTest extends RetraceTestBase { - @Parameters(name = "Backend: {0}, mode: {1}") + @Parameters(name = "Backend: {0}, mode: {1}, compat: {2}") public static Collection<Object[]> data() { - return buildParameters(ToolHelper.getBackends(), CompilationMode.values()); + return buildParameters( + ToolHelper.getBackends(), CompilationMode.values(), BooleanUtils.values()); } - public DesugarStaticInterfaceMethodsRetraceTest(Backend backend, CompilationMode mode) { - super(backend, mode); + public DesugarStaticInterfaceMethodsRetraceTest( + Backend backend, CompilationMode mode, boolean compat) { + super(backend, mode, compat); } @Override
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/InliningRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/InliningRetraceTest.java index 4f84c8f..aafa229 100644 --- a/src/test/java/com/android/tools/r8/naming/retraceproguard/InliningRetraceTest.java +++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/InliningRetraceTest.java
@@ -8,11 +8,13 @@ import static com.android.tools.r8.naming.retraceproguard.StackTrace.isSameExceptForFileNameAndLineNumber; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assume.assumeTrue; import com.android.tools.r8.CompilationMode; import com.android.tools.r8.ForceInline; import com.android.tools.r8.ToolHelper; import com.android.tools.r8.ToolHelper.DexVm.Version; +import com.android.tools.r8.utils.BooleanUtils; import com.google.common.collect.ImmutableList; import java.util.Collection; import java.util.Collections; @@ -28,11 +30,12 @@ public static Collection<Object[]> data() { return ToolHelper.getDexVm().getVersion() == Version.V5_1_1 ? Collections.emptyList() - : buildParameters(ToolHelper.getBackends(), CompilationMode.values()); + : buildParameters( + ToolHelper.getBackends(), CompilationMode.values(), BooleanUtils.values()); } - public InliningRetraceTest(Backend backend, CompilationMode mode) { - super(backend, mode); + public InliningRetraceTest(Backend backend, CompilationMode mode, boolean value) { + super(backend, mode, value); } @Override @@ -57,6 +60,7 @@ @Test public void testLineNumberTableOnly() throws Exception { + assumeTrue(compat); runTest( ImmutableList.of("-keepattributes LineNumberTable"), (StackTrace actualStackTrace, StackTrace retracedStackTrace) -> { @@ -67,6 +71,7 @@ @Test public void testNoLineNumberTable() throws Exception { + assumeTrue(compat); runTest( ImmutableList.of(), (StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/RetraceTestBase.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/RetraceTestBase.java index 4a877e2..e94483f 100644 --- a/src/test/java/com/android/tools/r8/naming/retraceproguard/RetraceTestBase.java +++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/RetraceTestBase.java
@@ -5,7 +5,7 @@ package com.android.tools.r8.naming.retraceproguard; import com.android.tools.r8.CompilationMode; -import com.android.tools.r8.R8FullTestBuilder; +import com.android.tools.r8.R8TestBuilder; import com.android.tools.r8.R8TestRunResult; import com.android.tools.r8.TestBase; import com.google.common.collect.ImmutableList; @@ -17,15 +17,17 @@ public abstract class RetraceTestBase extends TestBase { protected Backend backend; protected CompilationMode mode; + protected boolean compat; - public RetraceTestBase(Backend backend, CompilationMode mode) { + public RetraceTestBase(Backend backend, CompilationMode mode, boolean compat) { this.backend = backend; this.mode = mode; + this.compat = compat; } public StackTrace expectedStackTrace; - public void configure(R8FullTestBuilder builder) {} + public void configure(R8TestBuilder builder) {} public Collection<Class<?>> getClasses() { return ImmutableList.of(getMainClass()); @@ -48,7 +50,7 @@ throws Exception { R8TestRunResult result = - testForR8(backend) + (compat ? testForR8Compat(backend) : testForR8(backend)) .setMode(mode) .enableProguardTestOptions() .addProgramClasses(getClasses())
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java index 42264a2..2874bf9 100644 --- a/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java +++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java
@@ -7,12 +7,14 @@ import static com.android.tools.r8.naming.retraceproguard.StackTrace.isSameExceptForFileNameAndLineNumber; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assume.assumeTrue; import com.android.tools.r8.CompilationMode; import com.android.tools.r8.NeverInline; -import com.android.tools.r8.R8FullTestBuilder; +import com.android.tools.r8.R8TestBuilder; import com.android.tools.r8.ToolHelper; import com.android.tools.r8.naming.retraceproguard.StackTrace.StackTraceLine; +import com.android.tools.r8.utils.BooleanUtils; import com.google.common.collect.ImmutableList; import java.util.Collection; import java.util.HashSet; @@ -26,17 +28,18 @@ public class VerticalClassMergingRetraceTest extends RetraceTestBase { private Set<StackTraceLine> haveSeenLines = new HashSet<>(); - @Parameters(name = "Backend: {0}, mode: {1}") + @Parameters(name = "Backend: {0}, mode: {1}, compat: {2}") public static Collection<Object[]> data() { - return buildParameters(ToolHelper.getBackends(), CompilationMode.values()); + return buildParameters( + ToolHelper.getBackends(), CompilationMode.values(), BooleanUtils.values()); } - public VerticalClassMergingRetraceTest(Backend backend, CompilationMode mode) { - super(backend, mode); + public VerticalClassMergingRetraceTest(Backend backend, CompilationMode mode, boolean compat) { + super(backend, mode, compat); } @Override - public void configure(R8FullTestBuilder builder) { + public void configure(R8TestBuilder builder) { builder.enableInliningAnnotations(); } @@ -83,6 +86,7 @@ @Test public void testLineNumberTableOnly() throws Exception { + assumeTrue(compat); runTest( ImmutableList.of("-keepattributes LineNumberTable"), (StackTrace actualStackTrace, StackTrace retracedStackTrace) -> { @@ -97,6 +101,7 @@ @Test public void testNoLineNumberTable() throws Exception { + assumeTrue(compat); haveSeenLines.clear(); runTest( ImmutableList.of(),
diff --git a/src/test/java/com/android/tools/r8/naming/testclasses/ClassToBeMinified.java b/src/test/java/com/android/tools/r8/naming/testclasses/ClassToBeMinified.java new file mode 100644 index 0000000..da6f9c0 --- /dev/null +++ b/src/test/java/com/android/tools/r8/naming/testclasses/ClassToBeMinified.java
@@ -0,0 +1,15 @@ +// 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.naming.testclasses; + +import com.android.tools.r8.NeverInline; + +public class ClassToBeMinified { + + @NeverInline + public static void foo() { + throw new RuntimeException("ClassToBeMinified.foo()"); + } +}
diff --git a/src/test/java/com/android/tools/r8/naming/testclasses/Main.java b/src/test/java/com/android/tools/r8/naming/testclasses/Main.java new file mode 100644 index 0000000..1e37d4c --- /dev/null +++ b/src/test/java/com/android/tools/r8/naming/testclasses/Main.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.naming.testclasses; + +public class Main { + + public static void main(String[] args) { + ClassToBeMinified.foo(); + } +}
diff --git a/src/test/java/com/android/tools/r8/resolution/singletarget/InstantiatedLowerBoundTest.java b/src/test/java/com/android/tools/r8/resolution/singletarget/InstantiatedLowerBoundTest.java new file mode 100644 index 0000000..deec1e3 --- /dev/null +++ b/src/test/java/com/android/tools/r8/resolution/singletarget/InstantiatedLowerBoundTest.java
@@ -0,0 +1,165 @@ +// 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.resolution.singletarget; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertNotNull; +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertNull; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.LookupResult; +import com.android.tools.r8.graph.ResolutionResult; +import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement; +import com.android.tools.r8.ir.analysis.type.Nullability; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.google.common.collect.Sets; +import java.util.ArrayList; +import java.util.Set; +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 InstantiatedLowerBoundTest extends TestBase { + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + public InstantiatedLowerBoundTest(TestParameters parameters) { + // Empty to satisfy construction of none-runtime. + } + + @Test + public void testSingleTargetLowerBoundInstantiated() throws Exception { + AppView<AppInfoWithLiveness> appView = + computeAppViewWithLiveness( + buildClasses(A.class, B.class, Main.class).build(), + factory -> new ArrayList<>(buildKeepRuleForClassAndMethods(Main.class, factory))); + AppInfoWithLiveness appInfo = appView.appInfo(); + DexType typeA = buildType(A.class, appInfo.dexItemFactory()); + DexType typeB = buildType(B.class, appInfo.dexItemFactory()); + DexType typeMain = buildType(Main.class, appInfo.dexItemFactory()); + DexMethod fooA = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory()); + ClassTypeLatticeElement latticeB = + ClassTypeLatticeElement.create(typeB, Nullability.definitelyNotNull(), appView); + DexEncodedMethod singleTarget = + appInfo.lookupSingleVirtualTarget(fooA, typeMain, false, t -> false, typeA, latticeB); + assertNotNull(singleTarget); + DexMethod fooB = buildNullaryVoidMethod(B.class, "foo", appInfo.dexItemFactory()); + assertEquals(fooB, singleTarget.method); + } + + @Test + public void testSingleTargetLowerBoundInMiddleInstantiated() throws Exception { + AppView<AppInfoWithLiveness> appView = + computeAppViewWithLiveness( + buildClasses(A.class, B.class, C.class, Main.class).build(), + factory -> new ArrayList<>(buildKeepRuleForClassAndMethods(Main.class, factory))); + AppInfoWithLiveness appInfo = appView.appInfo(); + DexType typeA = buildType(A.class, appInfo.dexItemFactory()); + DexType typeB = buildType(B.class, appInfo.dexItemFactory()); + DexType typeMain = buildType(Main.class, appInfo.dexItemFactory()); + DexMethod fooA = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory()); + ClassTypeLatticeElement latticeB = + ClassTypeLatticeElement.create(typeB, Nullability.definitelyNotNull(), appView); + DexEncodedMethod singleTarget = + appInfo.lookupSingleVirtualTarget(fooA, typeMain, false, t -> false, typeA, latticeB); + assertNotNull(singleTarget); + DexMethod fooB = buildNullaryVoidMethod(B.class, "foo", appInfo.dexItemFactory()); + assertEquals(fooB, singleTarget.method); + } + + @Test + public void testSingleTargetLowerAllInstantiated() throws Exception { + AppView<AppInfoWithLiveness> appView = + computeAppViewWithLiveness( + buildClasses(A.class, B.class, C.class, MainAllInstantiated.class).build(), + factory -> + new ArrayList<>( + buildKeepRuleForClassAndMethods(MainAllInstantiated.class, factory))); + AppInfoWithLiveness appInfo = appView.appInfo(); + DexType typeA = buildType(A.class, appInfo.dexItemFactory()); + DexType typeC = buildType(C.class, appInfo.dexItemFactory()); + DexType typeMain = buildType(MainAllInstantiated.class, appInfo.dexItemFactory()); + DexMethod fooA = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory()); + DexMethod fooB = buildNullaryVoidMethod(B.class, "foo", appInfo.dexItemFactory()); + DexMethod fooC = buildNullaryVoidMethod(C.class, "foo", appInfo.dexItemFactory()); + ResolutionResult resolution = appInfo.resolveMethod(typeA, fooA); + DexProgramClass context = appView.definitionForProgramType(typeMain); + DexProgramClass upperBound = appView.definitionForProgramType(typeA); + DexProgramClass lowerBound = appView.definitionForProgramType(typeC); + LookupResult lookupResult = + resolution.lookupVirtualDispatchTargets(context, appInfo, upperBound, lowerBound); + Set<DexMethod> expected = Sets.newIdentityHashSet(); + expected.add(fooA); + expected.add(fooB); + expected.add(fooC); + assertTrue(lookupResult.isLookupResultSuccess()); + Set<DexMethod> actual = Sets.newIdentityHashSet(); + lookupResult + .asLookupResultSuccess() + .forEach( + clazzAndMethod -> actual.add(clazzAndMethod.getMethod().method), + lambdaTarget -> { + assert false; + }); + assertEquals(expected, actual); + ClassTypeLatticeElement latticeC = + ClassTypeLatticeElement.create(typeC, Nullability.definitelyNotNull(), appView); + DexEncodedMethod singleTarget = + appInfo.lookupSingleVirtualTarget(fooA, typeMain, false, t -> false, typeA, latticeC); + assertNull(singleTarget); + } + + public static class A { + + public void foo() { + System.out.println("A.foo"); + } + } + + public static class B extends A { + + @Override + public void foo() { + System.out.println("B.foo"); + } + } + + public static class C extends B { + + @Override + public void foo() { + System.out.println("C.foo"); + } + } + + public static class Main { + + public static void main(String[] args) { + new B(); + } + } + + public static class MainAllInstantiated { + + public static void main(String[] args) { + new A(); + new B(); + new C(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/resolution/singletarget/SuccessAndInvalidLookupTest.java b/src/test/java/com/android/tools/r8/resolution/singletarget/SuccessAndInvalidLookupTest.java new file mode 100644 index 0000000..870c9cc --- /dev/null +++ b/src/test/java/com/android/tools/r8/resolution/singletarget/SuccessAndInvalidLookupTest.java
@@ -0,0 +1,95 @@ +// 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.resolution.singletarget; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertNotNull; +import static junit.framework.TestCase.assertNull; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import java.util.ArrayList; +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 SuccessAndInvalidLookupTest extends TestBase { + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + public SuccessAndInvalidLookupTest(TestParameters parameters) { + // Empty to satisfy construction of none-runtime. + } + + @Test + public void testSingleTargetWithInvalidInvokeInterfaceInvoke() throws Exception { + AppView<AppInfoWithLiveness> appView = + computeAppViewWithLiveness( + buildClasses(I.class, A.class, Main.class).build(), + factory -> new ArrayList<>(buildKeepRuleForClassAndMethods(Main.class, factory))); + AppInfoWithLiveness appInfo = appView.appInfo(); + DexType typeMain = buildType(Main.class, appInfo.dexItemFactory()); + DexType typeA = buildType(A.class, appInfo.dexItemFactory()); + DexMethod fooA = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory()); + DexEncodedMethod singleTarget = + appInfo.lookupSingleVirtualTarget(fooA, typeMain, false, t -> false, typeA, null); + assertNotNull(singleTarget); + assertEquals(fooA, singleTarget.method); + DexEncodedMethod invalidSingleTarget = + appInfo.lookupSingleVirtualTarget(fooA, typeMain, true, t -> false, typeA, null); + assertNull(invalidSingleTarget); + } + + @Test + public void testSingleTargetWithInvalidInvokeVirtualInvoke() throws Exception { + AppView<AppInfoWithLiveness> appView = + computeAppViewWithLiveness( + buildClasses(I.class, A.class, Main.class).build(), + factory -> new ArrayList<>(buildKeepRuleForClassAndMethods(Main.class, factory))); + AppInfoWithLiveness appInfo = appView.appInfo(); + DexType typeMain = buildType(Main.class, appInfo.dexItemFactory()); + DexType typeA = buildType(I.class, appInfo.dexItemFactory()); + DexMethod fooI = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory()); + DexMethod fooA = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory()); + DexEncodedMethod singleTarget = + appInfo.lookupSingleVirtualTarget(fooI, typeMain, true, t -> false, typeA, null); + assertNotNull(singleTarget); + assertEquals(fooA, singleTarget.method); + DexEncodedMethod invalidSingleTarget = + appInfo.lookupSingleVirtualTarget(fooI, typeMain, false, t -> false, typeA, null); + assertNull(invalidSingleTarget); + } + + public interface I { + + void foo(); + } + + public static class A implements I { + + @Override + public void foo() { + System.out.println("A.foo"); + } + } + + public static class Main { + + public static void main(String[] args) { + new A(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java b/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java index 809a521..3cabc0e 100644 --- a/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java +++ b/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.TestRuntime.CfVm; import com.android.tools.r8.naming.retrace.StackTrace; +import com.android.tools.r8.shaking.ProguardKeepAttributes; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; @@ -110,6 +111,7 @@ .addInnerClasses(InlineWithoutNullCheckTest.class) .addKeepMainRule(TestClassForInlineMethod.class) .enableInliningAnnotations() + .addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE) .setMinApi(parameters.getApiLevel()) .compile() .inspect(this::checkSomething) @@ -125,8 +127,6 @@ isSameExceptForFileNameAndLineNumber( createStackTraceBuilder() .addWithoutFileNameAndLineNumber( - Result.class, "methodWhichAccessInstanceMethod") - .addWithoutFileNameAndLineNumber( A.class, "inlineMethodWhichAccessInstanceMethod") .addWithoutFileNameAndLineNumber(TestClassForInlineMethod.class, "main") .build()))); @@ -138,6 +138,7 @@ .addInnerClasses(InlineWithoutNullCheckTest.class) .addKeepMainRule(TestClassForInlineField.class) .enableInliningAnnotations() + .addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE) .setMinApi(parameters.getApiLevel()) .compile() .inspect(this::checkSomething) @@ -153,8 +154,6 @@ isSameExceptForFileNameAndLineNumber( createStackTraceBuilder() .addWithoutFileNameAndLineNumber( - Result.class, "methodWhichAccessInstanceField") - .addWithoutFileNameAndLineNumber( A.class, "inlineMethodWhichAccessInstanceField") .addWithoutFileNameAndLineNumber(TestClassForInlineField.class, "main") .build()))); @@ -167,6 +166,7 @@ .addInnerClasses(InlineWithoutNullCheckTest.class) .addKeepMainRule(TestClassForInlineStaticField.class) .enableInliningAnnotations() + .addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE) .setMinApi(parameters.getApiLevel()) .compile() .inspect(this::checkSomething) @@ -183,8 +183,6 @@ isSameExceptForFileNameAndLineNumber( createStackTraceBuilder() .addWithoutFileNameAndLineNumber( - Result.class, "methodWhichAccessStaticField") - .addWithoutFileNameAndLineNumber( A.class, "inlineMethodWhichAccessStaticField") .addWithoutFileNameAndLineNumber( TestClassForInlineStaticField.class, "main") @@ -205,8 +203,8 @@ } private boolean canUseRequireNonNull() { - return parameters.isCfRuntime() - || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K); + return parameters.isDexRuntime() + && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K); } static class TestClassForInlineMethod {
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java index d70f22b..1fa811a 100644 --- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java +++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
@@ -6,7 +6,7 @@ import static com.android.tools.r8.Collectors.toSingle; import static com.android.tools.r8.KotlinCompilerTool.KOTLINC; import static com.android.tools.r8.ToolHelper.getFilesInTestFolderRelativeToClass; -import static com.android.tools.r8.utils.codeinspector.Matchers.containsInlinePosition; +import static com.android.tools.r8.utils.codeinspector.Matchers.containsLinePositions; import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineFrame; import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineStack; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; @@ -26,7 +26,7 @@ import com.android.tools.r8.naming.retrace.StackTrace; import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.InstructionSubject; -import com.android.tools.r8.utils.codeinspector.Matchers.InlinePosition; +import com.android.tools.r8.utils.codeinspector.Matchers.LinePosition; import com.android.tools.r8.utils.codeinspector.MethodSubject; import java.io.IOException; import java.nio.file.Path; @@ -40,6 +40,7 @@ @RunWith(Parameterized.class) public class KotlinInlineFunctionInSameFileRetraceTests extends TestBase { + private static final String FILENAME_INLINE = "InlineFunctionsInSameFile.kt"; private static final String MAIN = "retrace.InlineFunctionsInSameFileKt"; private final TestParameters parameters; @@ -97,16 +98,18 @@ .inspectStackTrace( (stackTrace, codeInspector) -> { MethodSubject mainSubject = codeInspector.clazz(MAIN).uniqueMethodWithName("main"); - InlinePosition inlineStack = - InlinePosition.stack( - InlinePosition.create( + LinePosition inlineStack = + LinePosition.stack( + LinePosition.create( kotlinInspector .clazz("retrace.InlineFunctionsInSameFileKt") .uniqueMethodWithName("foo") .asFoundMethodSubject(), 1, - 8), - InlinePosition.create(mainSubject.asFoundMethodSubject(), 1, 43)); + 8, + FILENAME_INLINE), + LinePosition.create( + mainSubject.asFoundMethodSubject(), 1, 43, FILENAME_INLINE)); checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack); }); } @@ -115,7 +118,7 @@ StackTrace stackTrace, CodeInspector codeInspector, MethodSubject mainSubject, - InlinePosition inlineStack) { + LinePosition inlineStack) { assertThat(mainSubject, isPresent()); RetraceMethodResult retraceResult = mainSubject @@ -125,6 +128,6 @@ .retraceLinePosition(codeInspector.retrace()); assertThat(retraceResult, isInlineFrame()); assertThat(retraceResult, isInlineStack(inlineStack)); - assertThat(stackTrace, containsInlinePosition(inlineStack)); + assertThat(stackTrace, containsLinePositions(inlineStack)); } }
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java index b4d302b..05b028a 100644 --- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java +++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
@@ -7,7 +7,7 @@ import static com.android.tools.r8.Collectors.toSingle; import static com.android.tools.r8.KotlinCompilerTool.KOTLINC; import static com.android.tools.r8.ToolHelper.getFilesInTestFolderRelativeToClass; -import static com.android.tools.r8.utils.codeinspector.Matchers.containsInlinePosition; +import static com.android.tools.r8.utils.codeinspector.Matchers.containsLinePositions; import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineFrame; import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineStack; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; @@ -28,7 +28,7 @@ import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.FoundMethodSubject; import com.android.tools.r8.utils.codeinspector.InstructionSubject; -import com.android.tools.r8.utils.codeinspector.Matchers.InlinePosition; +import com.android.tools.r8.utils.codeinspector.Matchers.LinePosition; import com.android.tools.r8.utils.codeinspector.MethodSubject; import java.io.IOException; import java.nio.file.Path; @@ -43,6 +43,9 @@ public class KotlinInlineFunctionRetraceTest extends TestBase { private final TestParameters parameters; + // TODO(b/151132660): Fix filename + private static final String FILENAME_INLINE_STATIC = "InlineFunctionKt.kt"; + private static final String FILENAME_INLINE_INSTANCE = "InlineFunction.kt"; @Parameters(name = "{0}") public static TestParametersCollection data() { @@ -93,6 +96,7 @@ public void testRetraceKotlinInlineStaticFunction() throws ExecutionException, CompilationFailedException, IOException { String main = "retrace.MainKt"; + String mainFileName = "Main.kt"; Path kotlinSources = compilationResults.apply(parameters.getRuntime()); CodeInspector kotlinInspector = new CodeInspector(kotlinSources); testForR8(parameters.getBackend()) @@ -109,10 +113,11 @@ .inspectStackTrace( (stackTrace, codeInspector) -> { MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main"); - InlinePosition inlineStack = - InlinePosition.stack( - InlinePosition.create(inlineExceptionStatic(kotlinInspector), 2, 8), - InlinePosition.create(mainSubject.asFoundMethodSubject(), 2, 15)); + LinePosition inlineStack = + LinePosition.stack( + LinePosition.create( + inlineExceptionStatic(kotlinInspector), 2, 8, FILENAME_INLINE_STATIC), + LinePosition.create(mainSubject.asFoundMethodSubject(), 2, 15, mainFileName)); checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack); }); } @@ -121,6 +126,7 @@ public void testRetraceKotlinInlineInstanceFunction() throws ExecutionException, CompilationFailedException, IOException { String main = "retrace.MainInstanceKt"; + String mainFileName = "MainInstance.kt"; Path kotlinSources = compilationResults.apply(parameters.getRuntime()); CodeInspector kotlinInspector = new CodeInspector(kotlinSources); testForR8(parameters.getBackend()) @@ -137,10 +143,14 @@ .inspectStackTrace( (stackTrace, codeInspector) -> { MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main"); - InlinePosition inlineStack = - InlinePosition.stack( - InlinePosition.create(inlineExceptionInstance(kotlinInspector), 2, 15), - InlinePosition.create(mainSubject.asFoundMethodSubject(), 2, 13)); + LinePosition inlineStack = + LinePosition.stack( + LinePosition.create( + inlineExceptionInstance(kotlinInspector), + 2, + 15, + FILENAME_INLINE_INSTANCE), + LinePosition.create(mainSubject.asFoundMethodSubject(), 2, 13, mainFileName)); checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack); }); } @@ -149,6 +159,7 @@ public void testRetraceKotlinNestedInlineFunction() throws ExecutionException, CompilationFailedException, IOException { String main = "retrace.MainNestedKt"; + String mainFileName = "MainNested.kt"; Path kotlinSources = compilationResults.apply(parameters.getRuntime()); CodeInspector kotlinInspector = new CodeInspector(kotlinSources); testForR8(parameters.getBackend()) @@ -165,12 +176,13 @@ .inspectStackTrace( (stackTrace, codeInspector) -> { MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main"); - InlinePosition inlineStack = - InlinePosition.stack( - InlinePosition.create(inlineExceptionStatic(kotlinInspector), 3, 8), + LinePosition inlineStack = + LinePosition.stack( + LinePosition.create( + inlineExceptionStatic(kotlinInspector), 3, 8, FILENAME_INLINE_STATIC), // TODO(b/146399675): There should be a nested frame on // retrace.NestedInlineFunctionKt.nestedInline(line 10). - InlinePosition.create(mainSubject.asFoundMethodSubject(), 3, 19)); + LinePosition.create(mainSubject.asFoundMethodSubject(), 3, 19, mainFileName)); checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack); }); } @@ -179,6 +191,7 @@ public void testRetraceKotlinNestedInlineFunctionOnFirstLine() throws ExecutionException, CompilationFailedException, IOException { String main = "retrace.MainNestedFirstLineKt"; + String mainFileName = "MainNestedFirstLine.kt"; Path kotlinSources = compilationResults.apply(parameters.getRuntime()); CodeInspector kotlinInspector = new CodeInspector(kotlinSources); testForR8(parameters.getBackend()) @@ -195,12 +208,13 @@ .inspectStackTrace( (stackTrace, codeInspector) -> { MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main"); - InlinePosition inlineStack = - InlinePosition.stack( - InlinePosition.create(inlineExceptionStatic(kotlinInspector), 2, 8), + LinePosition inlineStack = + LinePosition.stack( + LinePosition.create( + inlineExceptionStatic(kotlinInspector), 2, 8, FILENAME_INLINE_STATIC), // TODO(b/146399675): There should be a nested frame on // retrace.NestedInlineFunctionKt.nestedInlineOnFirstLine(line 15). - InlinePosition.create(mainSubject.asFoundMethodSubject(), 2, 20)); + LinePosition.create(mainSubject.asFoundMethodSubject(), 2, 20, mainFileName)); checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack); }); } @@ -209,7 +223,7 @@ StackTrace stackTrace, CodeInspector codeInspector, MethodSubject mainSubject, - InlinePosition inlineStack) { + LinePosition inlineStack) { assertThat(mainSubject, isPresent()); RetraceMethodResult retraceResult = mainSubject @@ -219,6 +233,6 @@ .retraceLinePosition(codeInspector.retrace()); assertThat(retraceResult, isInlineFrame()); assertThat(retraceResult, isInlineStack(inlineStack)); - assertThat(stackTrace, containsInlinePosition(inlineStack)); + assertThat(stackTrace, containsLinePositions(inlineStack)); } }
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java index 174dd5e..47dc5c3 100644 --- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java +++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -32,6 +32,7 @@ import com.android.tools.r8.retrace.stacktraces.RetraceAssertionErrorStackTrace; import com.android.tools.r8.retrace.stacktraces.StackTraceForTest; import com.android.tools.r8.retrace.stacktraces.SuppressedStackTrace; +import com.android.tools.r8.retrace.stacktraces.UnknownSourceStackTrace; import com.android.tools.r8.utils.BooleanUtils; import com.google.common.collect.ImmutableList; import java.util.Collection; @@ -172,6 +173,11 @@ runRetraceTest(new NamedModuleStackTrace()); } + @Test + public void testUnknownSourceStackTrace() { + runRetraceTest(new UnknownSourceStackTrace()); + } + private TestDiagnosticMessagesImpl runRetraceTest(StackTraceForTest stackTraceForTest) { TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl(); RetraceCommand retraceCommand =
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownSourceStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownSourceStackTrace.java new file mode 100644 index 0000000..4219979 --- /dev/null +++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownSourceStackTrace.java
@@ -0,0 +1,50 @@ +// 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.retrace.stacktraces; + +import com.android.tools.r8.utils.StringUtils; +import java.util.Arrays; +import java.util.List; + +public class UnknownSourceStackTrace implements StackTraceForTest { + + @Override + public List<String> obfuscatedStackTrace() { + return Arrays.asList( + "com.android.tools.r8.CompilationException: foo[parens](Source:3)", + " at a.a.a(Unknown Source)", + " at a.a.a(Unknown Source)", + " at com.android.tools.r8.R8.main(Unknown Source)", + "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)", + " at a.a.a(Unknown Source)", + " ... 42 more"); + } + + @Override + public List<String> retracedStackTrace() { + return Arrays.asList( + "com.android.tools.r8.CompilationException: foo[parens](Source:3)", + " at com.android.tools.r8.R8.bar(R8.java)", + " <OR> at com.android.tools.r8.R8.foo(R8.java)", + " at com.android.tools.r8.R8.bar(R8.java)", + " <OR> at com.android.tools.r8.R8.foo(R8.java)", + " at com.android.tools.r8.R8.main(R8.java)", + "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)", + " at com.android.tools.r8.R8.bar(R8.java)", + " <OR> at com.android.tools.r8.R8.foo(R8.java)", + " ... 42 more"); + } + + @Override + public String mapping() { + return StringUtils.lines( + "com.android.tools.r8.R8 -> a.a:", " void foo(int) -> a", " void bar(int, int) -> a"); + } + + @Override + public int expectedWarnings() { + return 0; + } +}
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 78a36f2..91f0c4d 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
@@ -49,6 +49,7 @@ private void configure(InternalOptions options) { options.enableEnumValueOptimization = enableOptimization; options.enableEnumSwitchMapRemoval = enableOptimization; + options.enableEnumUnboxing = false; } @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java b/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java index 397c054..ac18813 100644 --- a/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java +++ b/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java
@@ -252,7 +252,31 @@ " getstatic Empty/sField I", " return"); - ensureFieldExistsAndReadOnlyOnce(builder, main, mainMethod, empty, "sField"); + inspect( + builder, + inspector -> + ensureFieldExistsAndReadOnlyOnce( + inspector, main.name, mainMethod.name, empty, "sField", false), + inspector -> { + ClassSubject emptyClassSubject = inspector.clazz(empty.name); + assertThat(emptyClassSubject, isPresent()); + assertEquals(1, emptyClassSubject.allStaticFields().size()); + + FieldSubject clinitFieldSubject = emptyClassSubject.allStaticFields().get(0); + assertEquals("$r8$clinit", clinitFieldSubject.getOriginalName()); + + ClassSubject mainClassSubject = inspector.clazz(main.name); + assertThat(mainClassSubject, isPresent()); + assertThat(mainClassSubject.mainMethod(), isPresent()); + assertTrue( + mainClassSubject + .mainMethod() + .streamInstructions() + .filter(InstructionSubject::isStaticGet) + .anyMatch( + instruction -> + instruction.getField().equals(clinitFieldSubject.getField().field))); + }); } @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java index 0212a16..e23b80b 100644 --- a/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java +++ b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
@@ -14,6 +14,7 @@ import static org.junit.Assert.assertTrue; import com.android.tools.r8.DataEntryResource; +import com.android.tools.r8.NeverPropagateValue; import com.android.tools.r8.R8TestRunResult; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; @@ -48,7 +49,8 @@ @Parameters(name = "{1}, include WorldGreeter: {0}") public static List<Object[]> data() { - return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build()); + return buildParameters( + BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build()); } public ServiceLoaderTest(boolean includeWorldGreeter, TestParameters parameters) { @@ -89,7 +91,8 @@ options.enableInliningOfInvokesWithNullableReceivers = false; }) .enableGraphInspector() - .setMinApi(parameters.getRuntime()) + .enableMemberValuePropagationAnnotations() + .setMinApi(parameters.getApiLevel()) .run(parameters.getRuntime(), TestClass.class) .assertSuccessWithOutput(expectedOutput); @@ -226,6 +229,7 @@ public static class HelloGreeter implements Greeter { + @NeverPropagateValue @Override public String greeting() { return "Hello";
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/InliningClassVersionTest.java b/src/test/java/com/android/tools/r8/shaking/examples/InliningClassVersionTest.java index 8cb9074..e47222b 100644 --- a/src/test/java/com/android/tools/r8/shaking/examples/InliningClassVersionTest.java +++ b/src/test/java/com/android/tools/r8/shaking/examples/InliningClassVersionTest.java
@@ -9,18 +9,14 @@ import com.android.tools.r8.ArchiveClassFileProvider; import com.android.tools.r8.ByteDataView; import com.android.tools.r8.ClassFileConsumer; -import com.android.tools.r8.OutputMode; -import com.android.tools.r8.R8Command; +import com.android.tools.r8.NeverPropagateValue; import com.android.tools.r8.TestBase; import com.android.tools.r8.ToolHelper; import com.android.tools.r8.ToolHelper.ProcessResult; -import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.InternalOptions; import com.google.common.io.ByteStreams; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; import org.junit.Test; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; @@ -40,6 +36,8 @@ } private static class Inlinee { + + @NeverPropagateValue public static String foo() { return "Hello from Inlinee!"; } @@ -80,7 +78,13 @@ assertEquals(OLD_VERSION, getBaseClassVersion(inputJar)); ProcessResult runInput = run(inputJar); assertEquals(0, runInput.exitCode); - Path outputJar = runR8(inputJar); + Path outputJar = + testForR8(Backend.CF) + .addProgramFiles(inputJar) + .addKeepMainRule(Base.class) + .enableMemberValuePropagationAnnotations() + .compile() + .writeToZip(); ProcessResult runOutput = run(outputJar); assertEquals(runInput.toString(), runOutput.toString()); assertNotEquals( @@ -141,19 +145,4 @@ private ProcessResult run(Path jar) throws Exception { return ToolHelper.runJava(jar, Base.class.getName()); } - - private Path runR8(Path inputJar) throws Exception { - List<String> keepRule = - Collections.singletonList( - "-keep class " + Base.class.getName() + " { public static void main(...); }"); - Path outputJar = temp.getRoot().toPath().resolve("output.jar"); - ToolHelper.runR8( - R8Command.builder() - .addProgramFiles(inputJar) - .addLibraryFiles(ToolHelper.getJava8RuntimeJar()) - .addProguardConfiguration(keepRule, Origin.unknown()) - .setOutput(outputJar, OutputMode.ClassFile) - .build()); - return outputJar; - } }
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java index 3401322..5cdc80e 100644 --- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java +++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java
@@ -12,7 +12,6 @@ import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Iterator; import java.util.List; import org.junit.Assert; @@ -50,7 +49,7 @@ private static void shaking13EnsureFieldWritesCorrect(CodeInspector inspector) { ClassSubject mainClass = inspector.clazz("shaking13.Shaking"); - MethodSubject testMethod = mainClass.method("void", "fieldTest", Collections.emptyList()); + MethodSubject testMethod = mainClass.uniqueMethodWithName("fieldTest"); Assert.assertTrue(testMethod.isPresent()); Iterator<FieldAccessInstructionSubject> iterator = testMethod.iterateInstructions(InstructionSubject::isFieldAccess);
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java index 8ecb463..6aad725 100644 --- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java +++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -62,7 +62,6 @@ protected DexApplication buildApplication(AndroidApp input, InternalOptions options) { try { - options.itemFactory.resetSortedIndices(); return new ApplicationReader(input, options, Timing.empty()).read(); } catch (IOException | ExecutionException e) { throw new RuntimeException(e);
diff --git a/src/test/java/com/android/tools/r8/utils/Smali.java b/src/test/java/com/android/tools/r8/utils/Smali.java index 02aa682..cb9ace0 100644 --- a/src/test/java/com/android/tools/r8/utils/Smali.java +++ b/src/test/java/com/android/tools/r8/utils/Smali.java
@@ -10,6 +10,7 @@ import com.android.tools.r8.dex.ApplicationWriter; import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.GraphLense; +import com.android.tools.r8.graph.InitClassLens; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.origin.Origin; import com.google.common.collect.ImmutableList; @@ -117,6 +118,7 @@ options, null, GraphLense.getIdentityLense(), + InitClassLens.getDefault(), NamingLens.getIdentityLens(), null); writer.write(executor);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java index 7331a22..8786f30 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -40,6 +40,9 @@ public void forAllInstanceFields(Consumer<FoundFieldSubject> inspection) {} @Override + public void forAllStaticFields(Consumer<FoundFieldSubject> inspection) {} + + @Override public FieldSubject field(String type, String name) { return new AbsentFieldSubject(); }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java index c467da4..a009244 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
@@ -122,4 +122,9 @@ public List<ClassSubject> getSealedSubclasses() { return null; } + + @Override + public String getCompanionObject() { + return null; + } }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java index b693ad9..c8c6142 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -118,12 +118,20 @@ public abstract void forAllInstanceFields(Consumer<FoundFieldSubject> inspection); + public abstract void forAllStaticFields(Consumer<FoundFieldSubject> inspection); + public final List<FoundFieldSubject> allInstanceFields() { ImmutableList.Builder<FoundFieldSubject> builder = ImmutableList.builder(); forAllInstanceFields(builder::add); return builder.build(); } + public final List<FoundFieldSubject> allStaticFields() { + ImmutableList.Builder<FoundFieldSubject> builder = ImmutableList.builder(); + forAllStaticFields(builder::add); + return builder.build(); + } + public abstract FieldSubject field(String type, String name); public abstract FieldSubject uniqueFieldWithName(String name);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java index 5dacc5a..86a21c1 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
@@ -4,6 +4,7 @@ package com.android.tools.r8.utils.codeinspector; +import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexMethod; import java.util.function.Predicate; import org.hamcrest.Description; @@ -12,6 +13,35 @@ public class CodeMatchers { + public static Matcher<MethodSubject> accessesField(FieldSubject targetSubject) { + if (!targetSubject.isPresent()) { + throw new IllegalArgumentException(); + } + DexField target = targetSubject.getField().field; + return new TypeSafeMatcher<MethodSubject>() { + @Override + protected boolean matchesSafely(MethodSubject subject) { + if (!subject.isPresent()) { + return false; + } + if (!subject.getMethod().hasCode()) { + return false; + } + return subject.streamInstructions().anyMatch(isFieldAccessWithTarget(target)); + } + + @Override + public void describeTo(Description description) { + description.appendText("accesses field `" + target.toSourceString() + "`"); + } + + @Override + public void describeMismatchSafely(final MethodSubject subject, Description description) { + description.appendText("method did not"); + } + }; + } + public static Matcher<MethodSubject> invokesMethod(MethodSubject targetSubject) { if (!targetSubject.isPresent()) { throw new IllegalArgumentException(); @@ -44,4 +74,8 @@ public static Predicate<InstructionSubject> isInvokeWithTarget(DexMethod target) { return instruction -> instruction.isInvoke() && instruction.getMethod() == target; } + + public static Predicate<InstructionSubject> isFieldAccessWithTarget(DexField target) { + return instruction -> instruction.isFieldAccess() && instruction.getField() == target; + } }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java index dc65b71..fc5c38f 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -122,12 +122,8 @@ @Override public void forAllFields(Consumer<FoundFieldSubject> inspection) { - CodeInspector.forAll( - dexClass.staticFields(), - (dexField, clazz) -> new FoundFieldSubject(codeInspector, dexField, clazz), - this, - inspection); forAllInstanceFields(inspection); + forAllStaticFields(inspection); } @Override @@ -140,6 +136,15 @@ } @Override + public void forAllStaticFields(Consumer<FoundFieldSubject> inspection) { + CodeInspector.forAll( + dexClass.staticFields(), + (dexField, clazz) -> new FoundFieldSubject(codeInspector, dexField, clazz), + this, + inspection); + } + + @Override public FieldSubject field(String type, String name) { String obfuscatedType = codeInspector.getObfuscatedTypeName(type); MemberNaming fieldNaming = null;
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java index 7b57397..71b30cb 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
@@ -55,7 +55,7 @@ @Override public boolean isSynthetic() { - // TODO(b/70169921): This should return `true` conditionally if we start synthesizing @Metadata + // TODO(b/151194785): This should return `true` conditionally if we start synthesizing @Metadata // from scratch. return false; } @@ -110,4 +110,9 @@ .map(this::getClassSubjectFromDescriptor) .collect(Collectors.toList()); } + + @Override + public String getCompanionObject() { + return kmClass.getCompanionObject(); + } }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java index 5b3338b..626388f 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
@@ -80,7 +80,7 @@ } } - // TODO(b/70169921): Search both original and renamed names. + // TODO(b/151194869): Search both original and renamed names. default KmFunctionSubject kmFunctionOrExtensionWithUniqueName(String name, boolean isExtension) { KmFunction foundFunction = null; for (KmFunction kmFunction : getKmDeclarationContainer().getFunctions()) {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java index cdf57aa..f86f718 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java
@@ -29,13 +29,13 @@ @Override public boolean isRenamed() { - // TODO(b/70169921): need to know the corresponding DexEncodedMethod. + // TODO(b/151194869): need to know the corresponding DexEncodedMethod. return false; } @Override public boolean isSynthetic() { - // TODO(b/70169921): This should return `true` conditionally if we start synthesizing @Metadata + // TODO(b/151194785): This should return `true` conditionally if we start synthesizing @Metadata // from scratch. return false; }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPackageSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPackageSubject.java index b4e796c..d2c4fa6 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPackageSubject.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPackageSubject.java
@@ -46,7 +46,7 @@ @Override public boolean isSynthetic() { - // TODO(b/70169921): This should return `true` conditionally if we start synthesizing @Metadata + // TODO(b/151194785): This should return `true` conditionally if we start synthesizing @Metadata // from scratch. return false; }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java index 3cfe20d..d6bab2d 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java
@@ -31,14 +31,14 @@ @Override public boolean isRenamed() { - // TODO(b/70169921): How to determine it is renamed? + // TODO(b/151194869): How to determine it is renamed? // backing field renamed? If no backing field exists, then examine getter/setter? return false; } @Override public boolean isSynthetic() { - // TODO(b/70169921): This should return `true` conditionally if we start synthesizing @Metadata + // TODO(b/151194785): This should return `true` conditionally if we start synthesizing @Metadata // from scratch. return false; }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java index 7fb1f9f..3f5be34 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
@@ -22,4 +22,6 @@ public abstract List<String> getSealedSubclassDescriptors(); public abstract List<ClassSubject> getSealedSubclasses(); + + public abstract String getCompanionObject(); }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java index 5d7ec81..d671a7d 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
@@ -23,7 +23,7 @@ this.kmType = kmType; } - // TODO(b/145824437): This is a dup of DescriptorUtils#getDescriptorFromKmType + // TODO(b/151195430): This is a dup of DescriptorUtils#getDescriptorFromKmType static String getDescriptorFromKmType(KmType kmType) { if (kmType == null) { return null;
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java index 3fb684f..98f6b1e 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.retrace.RetraceMethodResult; import com.android.tools.r8.retrace.RetraceMethodResult.Element; import com.android.tools.r8.utils.Box; +import com.android.tools.r8.utils.Visibility; import com.google.common.collect.ImmutableList; import java.util.List; import java.util.stream.Collectors; @@ -21,33 +22,6 @@ public class Matchers { - private enum Visibility { - PUBLIC, - PROTECTED, - PRIVATE, - PACKAGE_PRIVATE; - - @Override - public String toString() { - switch (this) { - case PUBLIC: - return "public"; - - case PROTECTED: - return "protected"; - - case PRIVATE: - return "private"; - - case PACKAGE_PRIVATE: - return "package-private"; - - default: - throw new Unreachable("Unexpected visibility"); - } - } - } - private static String type(Subject subject) { String type = "<unknown subject type>"; if (subject instanceof ClassSubject) { @@ -428,16 +402,16 @@ }; } - public static Matcher<RetraceMethodResult> isInlineStack(InlinePosition startPosition) { + public static Matcher<RetraceMethodResult> isInlineStack(LinePosition startPosition) { return new TypeSafeMatcher<RetraceMethodResult>() { @Override protected boolean matchesSafely(RetraceMethodResult item) { - Box<InlinePosition> currentPosition = new Box<>(startPosition); + Box<LinePosition> currentPosition = new Box<>(startPosition); Box<Boolean> returnValue = new Box<>(); item.forEach( element -> { boolean sameMethod; - InlinePosition currentInline = currentPosition.get(); + LinePosition currentInline = currentPosition.get(); if (currentInline == null) { returnValue.set(false); return; @@ -492,70 +466,100 @@ }; } - public static Matcher<StackTrace> containsInlinePosition(InlinePosition inlinePosition) { + public static Matcher<StackTrace> containsLinePositions(LinePosition linePosition) { return new TypeSafeMatcher<StackTrace>() { @Override protected boolean matchesSafely(StackTrace item) { - return containsInlineStack(item, 0, inlinePosition); + return containsLinePosition(item, 0, linePosition); } @Override public void describeTo(Description description) { - description.appendText("cannot be found in stack trace"); + description.appendText(linePosition + " cannot be found in stack trace"); } - private boolean containsInlineStack( - StackTrace stackTrace, int index, InlinePosition currentPosition) { - if (currentPosition == null) { + private boolean containsLinePosition( + StackTrace stackTrace, int index, LinePosition linePosition) { + if (linePosition == null) { return true; } - if (index >= stackTrace.size()) { - return false; + Matcher<StackTraceLine> lineMatcher = Matchers.matchesLinePosition(linePosition); + for (int i = index; i < stackTrace.getStackTraceLines().size(); i++) { + StackTraceLine stackTraceLine = stackTrace.get(i); + if (lineMatcher.matches(stackTraceLine)) { + return containsLinePosition(stackTrace, index + 1, linePosition.caller); + } } - StackTraceLine stackTraceLine = stackTrace.get(index); - boolean resultHere = - stackTraceLine.className.equals(currentPosition.getClassName()) - && stackTraceLine.methodName.equals(currentPosition.getMethodName()) - && stackTraceLine.lineNumber == currentPosition.originalPosition; - if (resultHere && containsInlineStack(stackTrace, index + 1, currentPosition.caller)) { - return true; - } - // Maybe the inline position starts from the top on the next position. - return containsInlineStack(stackTrace, index + 1, inlinePosition); + return false; } }; } - public static class InlinePosition { + public static Matcher<StackTraceLine> matchesLinePosition(LinePosition linePosition) { + return new TypeSafeMatcher<StackTraceLine>() { + + @Override + protected boolean matchesSafely(StackTraceLine item) { + return containsLinePosition(item, linePosition); + } + + @Override + public void describeTo(Description description) { + description.appendText(linePosition + " cannot be found in stack trace"); + } + + private boolean containsLinePosition( + StackTraceLine stackTraceLine, LinePosition currentPosition) { + return stackTraceLine.className.equals(currentPosition.getClassName()) + && stackTraceLine.methodName.equals(currentPosition.getMethodName()) + && stackTraceLine.lineNumber == currentPosition.originalPosition + && stackTraceLine.fileName.equals(currentPosition.filename); + } + }; + } + + public static class LinePosition { private final MethodReference methodReference; private final int minifiedPosition; private final int originalPosition; + private final String filename; - private InlinePosition caller; + private LinePosition caller; - private InlinePosition( - MethodReference methodReference, int minifiedPosition, int originalPosition) { + private LinePosition( + MethodReference methodReference, + int minifiedPosition, + int originalPosition, + String filename) { this.methodReference = methodReference; this.minifiedPosition = minifiedPosition; this.originalPosition = originalPosition; + this.filename = filename; } - public static InlinePosition create( - MethodReference methodReference, int minifiedPosition, int originalPosition) { - return new InlinePosition(methodReference, minifiedPosition, originalPosition); + public static LinePosition create( + MethodReference methodReference, + int minifiedPosition, + int originalPosition, + String filename) { + return new LinePosition(methodReference, minifiedPosition, originalPosition, filename); } - public static InlinePosition create( - FoundMethodSubject methodSubject, int minifiedPosition, int originalPosition) { - return create(methodSubject.asMethodReference(), minifiedPosition, originalPosition); + public static LinePosition create( + FoundMethodSubject methodSubject, + int minifiedPosition, + int originalPosition, + String filename) { + return create( + methodSubject.asMethodReference(), minifiedPosition, originalPosition, filename); } - public static InlinePosition stack(InlinePosition... stack) { + public static LinePosition stack(LinePosition... stack) { setCaller(1, stack); return stack[0]; } - private static void setCaller(int index, InlinePosition... stack) { + private static void setCaller(int index, LinePosition... stack) { assert index > 0; if (index >= stack.length) { return; @@ -571,5 +575,10 @@ String getClassName() { return methodReference.getHolderClass().getTypeName(); } + + @Override + public String toString() { + return getClassName() + "." + getMethodName() + "(" + filename + ":" + originalPosition + ")"; + } } }
diff --git a/third_party/remapper.tar.gz.sha1 b/third_party/remapper.tar.gz.sha1 new file mode 100644 index 0000000..6600831 --- /dev/null +++ b/third_party/remapper.tar.gz.sha1
@@ -0,0 +1 @@ +da2ce26c22a1d787fe436f1ea03d4eee6306bb35 \ No newline at end of file
diff --git a/third_party/retrace_benchmark.tar.gz.sha1 b/third_party/retrace_benchmark.tar.gz.sha1 new file mode 100644 index 0000000..7396a0f --- /dev/null +++ b/third_party/retrace_benchmark.tar.gz.sha1
@@ -0,0 +1 @@ +d73cd63dea729786d634bf3503db1795e72a20ec \ No newline at end of file
diff --git a/tools/compare_apk_sizes.py b/tools/compare_apk_sizes.py index 76ef1fc..da505c7 100755 --- a/tools/compare_apk_sizes.py +++ b/tools/compare_apk_sizes.py
@@ -13,10 +13,10 @@ import sys import threading import time +import zipfile + import toolhelper import utils -import zipfile -import StringIO USAGE = """%prog [options] app1 app2 NOTE: This only makes sense if minification is disabled"""
diff --git a/tools/download_all_benchmark_dependencies.py b/tools/download_all_benchmark_dependencies.py index 1113370..95b92ed 100755 --- a/tools/download_all_benchmark_dependencies.py +++ b/tools/download_all_benchmark_dependencies.py
@@ -19,11 +19,15 @@ utils.DownloadFromX20( os.path.join( utils.THIRD_PARTY, 'benchmarks', 'android-sdk') + '.tar.gz.sha1') + utils.DownloadFromX20( + os.path.join(utils.THIRD_PARTY, 'remapper') + '.tar.gz.sha1') utils.DownloadFromGoogleCloudStorage(utils.SAMPLE_LIBRARIES_SHA_FILE) utils.DownloadFromGoogleCloudStorage(utils.OPENSOURCE_APPS_SHA_FILE) utils.DownloadFromGoogleCloudStorage(utils.ANDROID_SDK + '.tar.gz.sha1', bucket='r8-deps-internal', auth=True) + utils.DownloadFromGoogleCloudStorage( + os.path.join(utils.THIRD_PARTY, 'retrace_benchmark') + '.tar.gz.sha1') if __name__ == '__main__': sys.exit(Main())
diff --git a/tools/golem.py b/tools/golem.py index 466c3ab..586ac5e 100755 --- a/tools/golem.py +++ b/tools/golem.py
@@ -21,6 +21,8 @@ 'proguard', 'proguardsettings', 'r8', + 'remapper', + 'retrace_benchmarks', 'sample_libraries', 'youtube', ]
diff --git a/tools/retrace_benchmark.py b/tools/retrace_benchmark.py new file mode 100755 index 0000000..5e2bd3b --- /dev/null +++ b/tools/retrace_benchmark.py
@@ -0,0 +1,83 @@ +#!/usr/bin/env python +# 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. + +import argparse +import jdk +import golem +import os +import subprocess +import sys +import time +import toolhelper +import utils + +RETRACERS = ['r8', 'proguard', 'remapper'] + +def parse_arguments(argv): + parser = argparse.ArgumentParser( + description = 'Run r8 retrace bootstrap benchmarks.') + parser.add_argument('--golem', + help = 'Link in third party dependencies.', + default = False, + action = 'store_true') + parser.add_argument('--ignore-java-version', + help='Do not check java version', + default=False, + action='store_true') + parser.add_argument('--print-runtimeraw', + metavar='BENCHMARKNAME', + help='Print the line \'<BENCHMARKNAME>(RunTimeRaw):' + + ' <elapsed> ms\' at the end where <elapsed> is' + + ' the elapsed time in milliseconds.') + parser.add_argument('--retracer', + help='The retracer to use', + choices=RETRACERS, + required=True) + options = parser.parse_args(argv) + return options + + +def run_retrace(options, temp): + if options.retracer == 'r8': + retracer_args = [ + '-cp', utils.R8LIB_JAR, 'com.android.tools.r8.retrace.Retrace'] + elif options.retracer == 'proguard': + retracer_args = ['-jar', + os.path.join( + utils.THIRD_PARTY, + 'proguard', + 'proguard6.0.1', + 'lib', + 'retrace.jar')] + elif options.retracer == 'remapper': + retracer_args = ['-jar', + os.path.join( + utils.THIRD_PARTY, + 'remapper', + 'remapper_deploy.jar')] + else: + assert False, "Unexpected retracer " + options.retracer + retrace_args = [jdk.GetJavaExecutable()] + retracer_args + [ + os.path.join(utils.THIRD_PARTY, 'retrace_benchmark', 'r8lib.jar.map'), + os.path.join(utils.THIRD_PARTY, 'retrace_benchmark', 'stacktrace.txt')] + utils.PrintCmd(retrace_args) + t0 = time.time() + subprocess.check_call( + retrace_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + t1 = time.time() + if options.print_runtimeraw: + print('{}(RunTimeRaw): {} ms' + .format(options.print_runtimeraw, 1000.0 * (t1 - t0))) + + +if __name__ == '__main__': + options = parse_arguments(sys.argv[1:]) + if options.golem: + golem.link_third_party() + if not options.ignore_java_version: + utils.check_java_version() + with utils.TempDir() as temp: + run_retrace(options, temp) +
diff --git a/tools/run_on_app.py b/tools/run_on_app.py index 16db28d..07de70c 100755 --- a/tools/run_on_app.py +++ b/tools/run_on_app.py
@@ -167,6 +167,9 @@ help='Include timing', default=False, action='store_true') + result.add_option('--cpu-list', + help='Run under \'taskset\' with these CPUs. See ' + 'the \'taskset\' -c option for the format') return result.parse_args(argv) @@ -575,7 +578,9 @@ stdout=stdout, stderr=stderr, timeout=options.timeout, - quiet=quiet) + quiet=quiet, + cmd_prefix=[ + 'taskset', '-c', options.cpu_list] if options.cpu_list else []) if exit_code != 0: with open(stderr_path) as stderr: stderr_text = stderr.read()
diff --git a/tools/toolhelper.py b/tools/toolhelper.py index 2784ea5..cc1a9cb 100644 --- a/tools/toolhelper.py +++ b/tools/toolhelper.py
@@ -3,20 +3,24 @@ # BSD-style license that can be found in the LICENSE file. import glob -import gradle -import jdk import subprocess from threading import Timer + +import gradle +import jdk import utils + def run(tool, args, build=None, debug=True, profile=False, track_memory_file=None, extra_args=None, - stderr=None, stdout=None, return_stdout=False, timeout=0, quiet=False): + stderr=None, stdout=None, return_stdout=False, timeout=0, quiet=False, + cmd_prefix=[]): + cmd = [] + cmd.extend(cmd_prefix) if build is None: build, args = extract_build_from_args(args) if build: gradle.RunGradle(['r8lib' if tool.startswith('r8lib') else 'r8']) - cmd = [] if track_memory_file: cmd.extend(['tools/track_memory.sh', track_memory_file]) cmd.append(jdk.GetJavaExecutable())