Merge commit 'd94b8b458cf8f712c9f4598dfd6e174847fb4c91' into dev-release
diff --git a/infra/config/global/generated/cr-buildbucket.cfg b/infra/config/global/generated/cr-buildbucket.cfg index 661516d..5c3f1b9 100644 --- a/infra/config/global/generated/cr-buildbucket.cfg +++ b/infra/config/global/generated/cr-buildbucket.cfg
@@ -77,6 +77,42 @@ } } builders { + name: "cached" + swarming_host: "chrome-swarming.appspot.com" + swarming_tags: "vpython:native-python-wrapper" + dimensions: "cores:8" + dimensions: "cpu:x86-64" + dimensions: "os:Ubuntu-20.04" + dimensions: "pool:luci.r8.ci" + exe { + cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" + cipd_version: "refs/heads/master" + cmd: "luciexe" + } + properties: + '{' + ' "builder_group": "internal.client.r8",' + ' "recipe": "rex",' + ' "test_options": [' + ' "--runtimes=dex-default",' + ' "--command_cache_dir=/tmp/ccache",' + ' "--tool=r8",' + ' "--no_internal",' + ' "--one_line_per_test",' + ' "--archive_failures"' + ' ]' + '}' + priority: 25 + execution_timeout_secs: 21600 + expiration_secs: 126000 + build_numbers: YES + service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com" + experiments { + key: "luci.recipes.use_python3" + value: 100 + } + } + builders { name: "check" swarming_host: "chrome-swarming.appspot.com" swarming_tags: "vpython:native-python-wrapper"
diff --git a/infra/config/global/generated/luci-milo.cfg b/infra/config/global/generated/luci-milo.cfg index 8914541..b0f2546 100644 --- a/infra/config/global/generated/luci-milo.cfg +++ b/infra/config/global/generated/luci-milo.cfg
@@ -21,6 +21,11 @@ short_name: "check" } builders { + name: "buildbucket/luci.r8.ci/cached" + category: "R8" + short_name: "cached" + } + builders { name: "buildbucket/luci.r8.ci/linux-dex_default" category: "R8" short_name: "dex_default"
diff --git a/infra/config/global/generated/luci-notify.cfg b/infra/config/global/generated/luci-notify.cfg index 521dee4..048dc7a 100644 --- a/infra/config/global/generated/luci-notify.cfg +++ b/infra/config/global/generated/luci-notify.cfg
@@ -36,6 +36,18 @@ } builders { bucket: "ci" + name: "cached" + repository: "https://r8.googlesource.com/r8" + } +} +notifiers { + notifications { + on_failure: true + on_new_failure: true + notify_blamelist {} + } + builders { + bucket: "ci" name: "check" repository: "https://r8.googlesource.com/r8" }
diff --git a/infra/config/global/generated/luci-scheduler.cfg b/infra/config/global/generated/luci-scheduler.cfg index f990fdb..03691dd3 100644 --- a/infra/config/global/generated/luci-scheduler.cfg +++ b/infra/config/global/generated/luci-scheduler.cfg
@@ -35,6 +35,21 @@ } } job { + id: "cached" + realm: "ci" + acl_sets: "ci" + triggering_policy { + kind: GREEDY_BATCHING + max_concurrent_invocations: 1 + max_batch_size: 1 + } + buildbucket { + server: "cr-buildbucket.appspot.com" + bucket: "ci" + builder: "cached" + } +} +job { id: "check" realm: "ci" acl_sets: "ci" @@ -812,6 +827,7 @@ realm: "ci" acl_sets: "ci" triggers: "archive" + triggers: "cached" triggers: "check" triggers: "desugared_library-head" triggers: "desugared_library-jdk11_head"
diff --git a/infra/config/global/main.star b/infra/config/global/main.star index c2ce866..358def3 100755 --- a/infra/config/global/main.star +++ b/infra/config/global/main.star
@@ -301,6 +301,27 @@ expiration_timeout = time.hour * 35, ) +r8_builder( + "cached", + category = "R8", + dimensions = get_dimensions(), + triggering_policy = scheduler.policy( + kind = scheduler.GREEDY_BATCHING_KIND, + max_batch_size = 1, + max_concurrent_invocations = 1 + ), + priority = 25, + properties = { + "test_options" : ["--runtimes=dex-default", + "--command_cache_dir=/tmp/ccache"] + common_test_options, + "builder_group" : "internal.client.r8" + }, + execution_timeout = time.hour * 6, + expiration_timeout = time.hour * 35, +) + + + r8_tester_with_default("linux-dex_default", ["--runtimes=dex-default"]) r8_tester_with_default("linux-none", ["--runtimes=none"]) r8_tester_with_default("linux-jdk8", ["--runtimes=jdk8"])
diff --git a/src/main/java/com/android/tools/r8/AndroidResourceConsumer.java b/src/main/java/com/android/tools/r8/AndroidResourceConsumer.java new file mode 100644 index 0000000..634611f --- /dev/null +++ b/src/main/java/com/android/tools/r8/AndroidResourceConsumer.java
@@ -0,0 +1,12 @@ +// Copyright (c) 2023, 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; + +@Keep +public interface AndroidResourceConsumer { + + void accept(AndroidResourceOutput androidResource, DiagnosticsHandler diagnosticsHandler); + + default void finished(DiagnosticsHandler handler) {} +}
diff --git a/src/main/java/com/android/tools/r8/AndroidResourceInput.java b/src/main/java/com/android/tools/r8/AndroidResourceInput.java new file mode 100644 index 0000000..87ed346 --- /dev/null +++ b/src/main/java/com/android/tools/r8/AndroidResourceInput.java
@@ -0,0 +1,36 @@ +// Copyright (c) 2023, 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; + +import java.io.InputStream; + +/** + * Base interface for android resources (resource table, manifest, res folder files) + * + * <p>Android resources are provided to R8 to allow for combined code and resource shrinking. This + * allows us to reason about resources, and transitively other resources and code throughout the + * entire compilation pipeline. + */ +@Keep +public interface AndroidResourceInput extends Resource { + enum Kind { + // The AndroidManifest.xml file in proto format. + MANIFEST, + // The resource table, in proto format. + RESOURCE_TABLE, + // An xml file within the res folder, in proto format if not inside res/raw, otherwise + // in UTF-8 format. + XML_FILE, + // Any other binary file withing the res folder. + RES_FOLDER_FILE + } + + // The path, within the app, of the resource. + ResourcePath getPath(); + + Kind getKind(); + + InputStream getByteStream() throws ResourceException; +}
diff --git a/src/main/java/com/android/tools/r8/AndroidResourceOutput.java b/src/main/java/com/android/tools/r8/AndroidResourceOutput.java new file mode 100644 index 0000000..6b204b5 --- /dev/null +++ b/src/main/java/com/android/tools/r8/AndroidResourceOutput.java
@@ -0,0 +1,20 @@ +// Copyright (c) 2023, 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; + +/** + * Base interface for android resources (resource table, manifest, res folder files) + * + * <p>Android resources are provided to R8 to allow for combined code and resource shrinking. This + * allows us to reason about resources, and transitively other resources and code throughout the + * entire compilation pipeline. + */ +@Keep +public interface AndroidResourceOutput extends Resource { + // The path, within the app, of the resource. + ResourcePath getPath(); + + ByteDataView getByteDataView(); +}
diff --git a/src/main/java/com/android/tools/r8/AndroidResourceProvider.java b/src/main/java/com/android/tools/r8/AndroidResourceProvider.java new file mode 100644 index 0000000..cb44ff0 --- /dev/null +++ b/src/main/java/com/android/tools/r8/AndroidResourceProvider.java
@@ -0,0 +1,16 @@ +// Copyright (c) 2023, 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; + +import java.util.Collection; + +/** Android resource provider. */ +@Keep +public interface AndroidResourceProvider { + + // Provide all android resources + Collection<AndroidResourceInput> getAndroidResources() throws ResourceException; + + default void finished(DiagnosticsHandler handler) {} +}
diff --git a/src/main/java/com/android/tools/r8/ArchiveProtoAndroidResourceConsumer.java b/src/main/java/com/android/tools/r8/ArchiveProtoAndroidResourceConsumer.java new file mode 100644 index 0000000..c601e80 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ArchiveProtoAndroidResourceConsumer.java
@@ -0,0 +1,30 @@ +// Copyright (c) 2023, 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; + +import com.android.tools.r8.utils.ArchiveBuilder; +import com.android.tools.r8.utils.OutputBuilder; +import java.nio.file.Path; + +public class ArchiveProtoAndroidResourceConsumer implements AndroidResourceConsumer { + private final OutputBuilder outputBuilder; + + public ArchiveProtoAndroidResourceConsumer(Path outputPath) { + this.outputBuilder = new ArchiveBuilder(outputPath); + this.outputBuilder.open(); + } + + @Override + public void accept(AndroidResourceOutput androidResource, DiagnosticsHandler diagnosticsHandler) { + outputBuilder.addFile( + androidResource.getPath().location(), + androidResource.getByteDataView(), + diagnosticsHandler); + } + + @Override + public void finished(DiagnosticsHandler handler) { + outputBuilder.close(handler); + } +}
diff --git a/src/main/java/com/android/tools/r8/ArchiveProtoAndroidResourceProvider.java b/src/main/java/com/android/tools/r8/ArchiveProtoAndroidResourceProvider.java new file mode 100644 index 0000000..ecf9970 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ArchiveProtoAndroidResourceProvider.java
@@ -0,0 +1,123 @@ +// Copyright (c) 2023, 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; + +import com.android.tools.r8.AndroidResourceInput.Kind; +import com.android.tools.r8.errors.CompilationError; +import com.android.tools.r8.origin.ArchiveEntryOrigin; +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.utils.FileUtils; +import com.google.common.io.ByteStreams; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * Provides android resources from an archive. + * + * <p>The descriptor index is built eagerly upon creating the provider and subsequent requests for + * resources in the descriptor set will then force the read of zip entry contents. + */ +@Keep +public class ArchiveProtoAndroidResourceProvider implements AndroidResourceProvider { + private final Path archive; + private final Origin origin; + + private static final String MANIFEST_NAME = "AndroidManifest.xml"; + private static final String RESOURCE_TABLE = "resources.pb"; + private static final String RES_FOLDER = "res/"; + private static final String XML_SUFFIX = ".xml"; + + /** + * Creates an android resource provider from an archive. + * + * @param archive Zip archive to provide resources from. + * @param origin Origin of the archive. + */ + public ArchiveProtoAndroidResourceProvider(Path archive, Origin origin) { + this.archive = archive; + this.origin = origin; + } + + @Override + public Collection<AndroidResourceInput> getAndroidResources() throws ResourceException { + try (ZipFile zipFile = FileUtils.createZipFile(archive.toFile(), StandardCharsets.UTF_8)) { + List<AndroidResourceInput> resources = new ArrayList<>(); + final Enumeration<? extends ZipEntry> entries = zipFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + String name = entry.getName(); + ByteAndroidResourceInput resource = + new ByteAndroidResourceInput( + name, + getKindFromName(name), + ByteStreams.toByteArray(zipFile.getInputStream(entry)), + new ArchiveEntryOrigin(name, origin)); + resources.add(resource); + } + return resources; + } catch (IOException e) { + throw new ResourceException(origin, e); + } + } + + private Kind getKindFromName(String name) { + if (name.equals(MANIFEST_NAME)) { + return Kind.MANIFEST; + } + if (name.equals(RESOURCE_TABLE)) { + return Kind.RESOURCE_TABLE; + } + if (!name.startsWith(RES_FOLDER)) { + throw new CompilationError("Unexpected non resource entry " + name, origin); + } + if (name.endsWith(XML_SUFFIX)) { + return Kind.XML_FILE; + } + return Kind.RES_FOLDER_FILE; + } + + private static class ByteAndroidResourceInput implements AndroidResourceInput { + + private final String name; + private final Kind kind; + private final byte[] bytes; + private final Origin origin; + + public ByteAndroidResourceInput(String name, Kind kind, byte[] bytes, Origin origin) { + this.name = name; + this.kind = kind; + this.bytes = bytes; + this.origin = origin; + } + + @Override + public ResourcePath getPath() { + return () -> name; + } + + @Override + public Kind getKind() { + return kind; + } + + @Override + public InputStream getByteStream() throws ResourceException { + return new ByteArrayInputStream(bytes); + } + + @Override + public Origin getOrigin() { + return origin; + } + } +}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index 7bd8767..0ce7a4e 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java
@@ -62,16 +62,17 @@ import com.android.tools.r8.naming.ProguardMapMinifier; import com.android.tools.r8.naming.RecordRewritingNamingLens; import com.android.tools.r8.naming.signature.GenericSignatureRewriter; -import com.android.tools.r8.optimize.AccessModifier; import com.android.tools.r8.optimize.MemberRebindingAnalysis; import com.android.tools.r8.optimize.MemberRebindingIdentityLens; import com.android.tools.r8.optimize.MemberRebindingIdentityLensFactory; +import com.android.tools.r8.optimize.accessmodification.AccessModifier; import com.android.tools.r8.optimize.bridgehoisting.BridgeHoisting; import com.android.tools.r8.optimize.fields.FieldFinalizer; import com.android.tools.r8.optimize.interfaces.analysis.CfOpenClosedInterfacesAnalysis; import com.android.tools.r8.optimize.proto.ProtoNormalizer; import com.android.tools.r8.optimize.redundantbridgeremoval.RedundantBridgeRemover; import com.android.tools.r8.origin.CommandLineOrigin; +import com.android.tools.r8.origin.Origin; import com.android.tools.r8.profile.art.ArtProfileCompletenessChecker; import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions; import com.android.tools.r8.repackaging.Repackaging; @@ -102,6 +103,7 @@ import com.android.tools.r8.synthesis.SyntheticFinalization; import com.android.tools.r8.synthesis.SyntheticItems; import com.android.tools.r8.utils.AndroidApp; +import com.android.tools.r8.utils.ExceptionDiagnostic; import com.android.tools.r8.utils.ExceptionUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.SelfRetraceTest; @@ -448,14 +450,6 @@ // to clear the cache, so that we will recompute the type lattice elements. appView.dexItemFactory().clearTypeElementsCache(); - AccessModifier.run(appViewWithLiveness, executorService, timing); - if (appView.graphLens().isPublicizerLens()) { - // We can now remove redundant bridges. Note that we do not need to update the - // invoke-targets here, as the existing invokes will simply dispatch to the now - // visible super-method. MemberRebinding, if run, will then dispatch it correctly. - new RedundantBridgeRemover(appView.withLiveness()).run(null, executorService, timing); - } - // This pass attempts to reduce the number of nests and nest size to allow further passes, and // should therefore be run after the publicizer. new NestReducer(appViewWithLiveness).run(executorService, timing); @@ -463,6 +457,10 @@ new MemberRebindingAnalysis(appViewWithLiveness).run(executorService); appViewWithLiveness.appInfo().notifyMemberRebindingFinished(appViewWithLiveness); + assert ArtProfileCompletenessChecker.verify(appView); + + AccessModifier.run(appViewWithLiveness, executorService, timing); + boolean isKotlinLibraryCompilationWithInlinePassThrough = options.enableCfByteCodePassThrough && appView.hasCfByteCodePassThroughMethods(); @@ -808,6 +806,12 @@ new DesugaredLibraryKeepRuleGenerator(appView).runIfNecessary(timing); + if (options.androidResourceProvider != null && options.androidResourceConsumer != null) { + // Currently this is simply a pass through of all resources. + writeAndroidResources( + options.androidResourceProvider, options.androidResourceConsumer, appView.reporter()); + } + // Generate the resulting application resources. writeApplication(appView, inputApp, executorService); @@ -826,6 +830,44 @@ } } + private void writeAndroidResources( + AndroidResourceProvider androidResourceProvider, + AndroidResourceConsumer androidResourceConsumer, + DiagnosticsHandler diagnosticsHandler) { + try { + for (AndroidResourceInput androidResource : androidResourceProvider.getAndroidResources()) { + androidResourceConsumer.accept( + new AndroidResourceOutput() { + @Override + public ResourcePath getPath() { + return androidResource.getPath(); + } + + @Override + public ByteDataView getByteDataView() { + try { + return ByteDataView.of(ByteStreams.toByteArray(androidResource.getByteStream())); + } catch (IOException | ResourceException e) { + diagnosticsHandler.error(new ExceptionDiagnostic(e, androidResource.getOrigin())); + } + return null; + } + + @Override + public Origin getOrigin() { + return androidResource.getOrigin(); + } + }, + diagnosticsHandler); + } + } catch (ResourceException e) { + throw new RuntimeException("Cannot write android resources", e); + } finally { + androidResourceConsumer.finished(diagnosticsHandler); + androidResourceProvider.finished(diagnosticsHandler); + } + } + private static boolean allReferencesAssignedApiLevel( AppView<? extends AppInfoWithClassHierarchy> appView) { if (!appView.options().apiModelingOptions().isCheckAllApiReferencesAreSet()
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java index 7ec72c1..4e89913 100644 --- a/src/main/java/com/android/tools/r8/R8Command.java +++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -135,6 +135,8 @@ private boolean enableMissingLibraryApiModeling = false; private boolean enableExperimentalKeepAnnotations = false; private SemanticVersion fakeCompilerVersion = null; + private AndroidResourceProvider androidResourceProvider = null; + private AndroidResourceConsumer androidResourceConsumer = null; private final ProguardConfigurationParserOptions.Builder parserOptionsBuilder = ProguardConfigurationParserOptions.builder().readEnvironment(); @@ -506,6 +508,28 @@ return super.addStartupProfileProviders(startupProfileProviders); } + /** + * Exprimental API for supporting android resource shrinking. + * + * <p>Add an android resource provider, providing the resource table, manifest and res table + * entries. + */ + public Builder setAndroidResourceProvider(AndroidResourceProvider provider) { + this.androidResourceProvider = provider; + return this; + } + + /** + * Exprimental API for supporting android resource shrinking. + * + * <p>Add an android resource consumer, consuming the resource table, manifest and res table + * entries. + */ + public Builder setAndroidResourceConsumer(AndroidResourceConsumer consumer) { + this.androidResourceConsumer = consumer; + return this; + } + @Override void validate() { if (isPrintHelp()) { @@ -659,7 +683,9 @@ getArtProfilesForRewriting(), getStartupProfileProviders(), getClassConflictResolver(), - getCancelCompilationChecker()); + getCancelCompilationChecker(), + androidResourceProvider, + androidResourceConsumer); if (inputDependencyGraphConsumer != null) { inputDependencyGraphConsumer.finished(); @@ -847,6 +873,8 @@ private final FeatureSplitConfiguration featureSplitConfiguration; private final String synthesizedClassPrefix; private final boolean enableMissingLibraryApiModeling; + private final AndroidResourceProvider androidResourceProvider; + private final AndroidResourceConsumer androidResourceConsumer; /** Get a new {@link R8Command.Builder}. */ public static Builder builder() { @@ -941,7 +969,9 @@ List<ArtProfileForRewriting> artProfilesForRewriting, List<StartupProfileProvider> startupProfileProviders, ClassConflictResolver classConflictResolver, - CancelCompilationChecker cancelCompilationChecker) { + CancelCompilationChecker cancelCompilationChecker, + AndroidResourceProvider androidResourceProvider, + AndroidResourceConsumer androidResourceConsumer) { super( inputApp, mode, @@ -986,6 +1016,8 @@ this.featureSplitConfiguration = featureSplitConfiguration; this.synthesizedClassPrefix = synthesizedClassPrefix; this.enableMissingLibraryApiModeling = enableMissingLibraryApiModeling; + this.androidResourceProvider = androidResourceProvider; + this.androidResourceConsumer = androidResourceConsumer; } private R8Command(boolean printHelp, boolean printVersion) { @@ -1010,6 +1042,8 @@ featureSplitConfiguration = null; synthesizedClassPrefix = null; enableMissingLibraryApiModeling = false; + androidResourceProvider = null; + androidResourceConsumer = null; } public DexItemFactory getDexItemFactory() { @@ -1182,6 +1216,9 @@ internal.cancelCompilationChecker = getCancelCompilationChecker(); + internal.androidResourceProvider = androidResourceProvider; + internal.androidResourceConsumer = androidResourceConsumer; + if (!DETERMINISTIC_DEBUGGING) { assert internal.threadCount == ThreadUtils.NOT_SPECIFIED; internal.threadCount = getThreadCount();
diff --git a/src/main/java/com/android/tools/r8/ResourcePath.java b/src/main/java/com/android/tools/r8/ResourcePath.java new file mode 100644 index 0000000..eab4d7e --- /dev/null +++ b/src/main/java/com/android/tools/r8/ResourcePath.java
@@ -0,0 +1,11 @@ +// Copyright (c) 2023, 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; + +@Keep +public interface ResourcePath { + + // The location within the apk, bundle or resource directory, e.g., res/xml/foo.xml + String location(); +}
diff --git a/src/main/java/com/android/tools/r8/dex/DefaultMixedSectionLayoutStrategy.java b/src/main/java/com/android/tools/r8/dex/DefaultMixedSectionLayoutStrategy.java index dee0f55..d9f6def 100644 --- a/src/main/java/com/android/tools/r8/dex/DefaultMixedSectionLayoutStrategy.java +++ b/src/main/java/com/android/tools/r8/dex/DefaultMixedSectionLayoutStrategy.java
@@ -15,6 +15,7 @@ import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexTypeList; import com.android.tools.r8.graph.DexWritableCode; +import com.android.tools.r8.graph.DexWritableCode.DexWritableCacheKey; import com.android.tools.r8.graph.ParameterAnnotationsList; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.naming.ClassNameMapper; @@ -24,7 +25,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class DefaultMixedSectionLayoutStrategy extends MixedSectionLayoutStrategy { @@ -67,9 +70,46 @@ return getCodeLayoutForClasses(mixedSectionOffsets.getClassesWithData()); } + private static class DeduplicatedCodeCounts { + private Map<DexWritableCacheKey, Integer> counts; + private final AppView<?> appView; + + private DeduplicatedCodeCounts(AppView<?> appView) { + this.appView = appView; + } + + public void addCode(DexWritableCode dexWritableCode, ProgramMethod method) { + assert appView.options().canUseCanonicalizedCodeObjects(); + if (counts == null) { + counts = new HashMap<>(); + } + DexWritableCacheKey cacheKey = + dexWritableCode.getCacheLookupKey(method, appView.dexItemFactory()); + if (!counts.containsKey(cacheKey)) { + counts.put(cacheKey, 1); + } else { + counts.put(cacheKey, counts.get(cacheKey) + 1); + } + } + + public int getCount(ProgramMethod method) { + if (counts == null) { + assert !appView.options().canUseCanonicalizedCodeObjects() + || method.getDefinition().getDexWritableCodeOrNull() == null; + return 1; + } + DexWritableCode dexWritableCodeOrNull = method.getDefinition().getDexWritableCodeOrNull(); + DexWritableCacheKey cacheLookupKey = + dexWritableCodeOrNull.getCacheLookupKey(method, appView.dexItemFactory()); + assert counts.containsKey(cacheLookupKey); + return counts.get(cacheLookupKey); + } + } + final Collection<ProgramMethod> getCodeLayoutForClasses(Collection<DexProgramClass> classes) { - ProgramMethodMap<String> codeToSignatureMap = ProgramMethodMap.create(); + ProgramMethodMap<String> codeToDexSortingKeyMap = ProgramMethodMap.create(); List<ProgramMethod> codesSorted = new ArrayList<>(); + DeduplicatedCodeCounts codeCounts = new DeduplicatedCodeCounts(appView); for (DexProgramClass clazz : classes) { clazz.forEachProgramMethodMatching( DexEncodedMethod::hasCode, @@ -77,13 +117,23 @@ DexWritableCode code = method.getDefinition().getDexWritableCodeOrNull(); assert code != null || method.getDefinition().shouldNotHaveCode(); if (code != null) { + if (appView.options().canUseCanonicalizedCodeObjects()) { + codeCounts.addCode(code, method); + } codesSorted.add(method); - codeToSignatureMap.put( + codeToDexSortingKeyMap.put( method, getKeyForDexCodeSorting(method, appView.app().getProguardMap())); } }); } - codesSorted.sort(Comparator.comparing(codeToSignatureMap::get)); + Comparator<ProgramMethod> defaultCodeSorting = + Comparator.comparing(codeToDexSortingKeyMap::get); + if (appView.options().canUseCanonicalizedCodeObjects()) { + codesSorted.sort( + Comparator.comparingInt(codeCounts::getCount).thenComparing(defaultCodeSorting)); + } else { + codesSorted.sort(defaultCodeSorting); + } return codesSorted; }
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java index a76bdf5..3332852 100644 --- a/src/main/java/com/android/tools/r8/graph/Code.java +++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -15,6 +15,7 @@ import com.android.tools.r8.ir.code.Position; import com.android.tools.r8.ir.code.Position.PositionBuilder; import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions; +import com.android.tools.r8.lightir.LirCode; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.RetracerForCodePrinting; import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; @@ -148,6 +149,14 @@ public abstract int estimatedDexCodeSizeUpperBoundInBytes(); + public final boolean isLirCode() { + return asLirCode() != null; + } + + public LirCode<Integer> asLirCode() { + return null; + } + public CfCode asCfCode() { throw new Unreachable(getClass().getCanonicalName() + ".asCfCode()"); }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java index a45d542..67082ae 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -43,6 +43,7 @@ import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind; import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature; +import com.android.tools.r8.graph.lens.GraphLens; import com.android.tools.r8.graph.proto.ArgumentInfoCollection; import com.android.tools.r8.ir.code.NumericType; import com.android.tools.r8.ir.code.ValueType; @@ -1353,6 +1354,17 @@ return code == null ? null : code.asDexWritableCode(); } + public DexEncodedMethod rewrittenWithLens( + GraphLens lens, GraphLens appliedLens, DexDefinitionSupplier definitions) { + assert this != SENTINEL; + DexMethod newMethodReference = lens.getRenamedMethodSignature(getReference(), appliedLens); + DexClass newHolder = definitions.definitionFor(newMethodReference.getHolderType()); + assert newHolder != null; + DexEncodedMethod newMethod = newHolder.lookupMethod(newMethodReference); + assert newMethod != null; + return newMethod; + } + public static Builder syntheticBuilder() { return new Builder(true); }
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 41ca6f5..854ba5b 100644 --- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java +++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -2979,28 +2979,14 @@ return createProto(returnType, parameters.toArray(DexType.EMPTY_ARRAY)); } - public DexProto protoWithDifferentFirstParameter(DexProto proto, DexType firstParameter) { - DexType[] parameterTypes = proto.parameters.values.clone(); - parameterTypes[0] = firstParameter; - return createProto(proto.returnType, parameterTypes); - } - public DexProto prependHolderToProto(DexMethod method) { - return prependTypeToProto(method.holder, method.proto); + return method.getProto().prependParameter(method.getHolderType(), this); } public DexProto prependHolderToProtoIf(DexMethod method, boolean condition) { return condition ? prependHolderToProto(method) : method.getProto(); } - public DexProto prependTypeToProto(DexType extraFirstType, DexProto initialProto) { - DexType[] parameterTypes = new DexType[initialProto.parameters.size() + 1]; - parameterTypes[0] = extraFirstType; - System.arraycopy( - initialProto.parameters.values, 0, parameterTypes, 1, initialProto.parameters.size()); - return createProto(initialProto.returnType, parameterTypes); - } - public DexProto appendTypeToProto(DexProto initialProto, DexType extraLastType) { DexType[] parameterTypes = new DexType[initialProto.parameters.size() + 1]; System.arraycopy(
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 fe2f81d..1ad1e1b 100644 --- a/src/main/java/com/android/tools/r8/graph/DexMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -325,7 +325,7 @@ public DexMethod withExtraArgumentPrepended(DexType type, DexItemFactory dexItemFactory) { return dexItemFactory.createMethod( - holder, dexItemFactory.prependTypeToProto(type, proto), name); + holder, getProto().prependParameter(type, dexItemFactory), name); } public DexMethod withHolder(DexDefinition definition, DexItemFactory dexItemFactory) {
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 80ff41d..b85f0da 100644 --- a/src/main/java/com/android/tools/r8/graph/DexProto.java +++ b/src/main/java/com/android/tools/r8/graph/DexProto.java
@@ -91,6 +91,13 @@ return parameters.size(); } + public DexProto prependParameter(DexType parameter, DexItemFactory dexItemFactory) { + DexType[] parameterTypes = new DexType[getParameters().size() + 1]; + parameterTypes[0] = parameter; + System.arraycopy(getParameters().getBacking(), 0, parameterTypes, 1, getParameters().size()); + return dexItemFactory.createProto(getReturnType(), parameterTypes); + } + @Override public String toString() { return "Proto " + shorty + " " + returnType + " " + parameters;
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java index c1f869c..9987912 100644 --- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java +++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -201,4 +201,20 @@ } return appView.options().debug || getOrComputeReachabilitySensitive(appView); } + + public ProgramMethod rewrittenWithLens( + GraphLens lens, GraphLens appliedLens, DexDefinitionSupplier definitions) { + DexMethod newMethod = lens.getRenamedMethodSignature(getReference(), appliedLens); + if (newMethod == getReference() && !getDefinition().isObsolete()) { + assert verifyIsConsistentWithLookup(definitions); + return this; + } + return asProgramMethodOrNull(definitions.definitionFor(newMethod)); + } + + private boolean verifyIsConsistentWithLookup(DexDefinitionSupplier definitions) { + DexClassAndMethod lookupMethod = definitions.definitionFor(getReference()); + assert getDefinition() == lookupMethod.getDefinition(); + return true; + } }
diff --git a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescriptionMethodOptimizationInfoFixer.java b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescriptionMethodOptimizationInfoFixer.java index 6a72244..a897bd0 100644 --- a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescriptionMethodOptimizationInfoFixer.java +++ b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescriptionMethodOptimizationInfoFixer.java
@@ -149,12 +149,12 @@ } /** - * Function for rewriting the unused arguments on a piece of method optimization info after - * prototype changes were made. + * Function for rewriting a BitSet that stores a bit per argument on a piece of method + * optimization info after prototype changes were made. */ @Override - public BitSet fixupUnusedArguments(BitSet unusedArguments) { - return fixupArgumentInfo(unusedArguments); + public BitSet fixupArguments(BitSet arguments) { + return fixupArgumentInfo(arguments); } private BitSet fixupArgumentInfo(BitSet bitSet) {
diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java b/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java index ca19375..78c6259 100644 --- a/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java +++ b/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java
@@ -8,6 +8,7 @@ import com.android.tools.r8.graph.DexType; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.collections.BidirectionalManyToOneMap; +import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap; import com.android.tools.r8.utils.collections.EmptyBidirectionalOneToOneMap; import java.util.Collection; import java.util.Map; @@ -16,11 +17,11 @@ public class VerticallyMergedClasses implements MergedClasses { - private final BidirectionalManyToOneMap<DexType, DexType> mergedClasses; + private final BidirectionalManyToOneRepresentativeMap<DexType, DexType> mergedClasses; private final BidirectionalManyToOneMap<DexType, DexType> mergedInterfaces; public VerticallyMergedClasses( - BidirectionalManyToOneMap<DexType, DexType> mergedClasses, + BidirectionalManyToOneRepresentativeMap<DexType, DexType> mergedClasses, BidirectionalManyToOneMap<DexType, DexType> mergedInterfaces) { this.mergedClasses = mergedClasses; this.mergedInterfaces = mergedInterfaces; @@ -37,6 +38,10 @@ mergedClasses.forEachManyToOneMapping(consumer); } + public BidirectionalManyToOneRepresentativeMap<DexType, DexType> getBidirectionalMap() { + return mergedClasses; + } + public Map<DexType, DexType> getForwardMap() { return mergedClasses.getForwardMap(); }
diff --git a/src/main/java/com/android/tools/r8/graph/lens/AppliedGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/AppliedGraphLens.java index a736fe9..8154252 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/AppliedGraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/AppliedGraphLens.java
@@ -112,48 +112,38 @@ } @Override - public DexType getOriginalType(DexType type) { - return renamedTypeNames.getRepresentativeKeyOrDefault(type, type); - } - - @Override public Iterable<DexType> getOriginalTypes(DexType type) { Set<DexType> originalTypes = renamedTypeNames.getKeys(type); return originalTypes.isEmpty() ? ImmutableList.of(type) : originalTypes; } @Override - public DexField getOriginalFieldSignature(DexField field) { - return originalFieldSignatures.getOrDefault(field, field); - } - - @Override - public DexField getRenamedFieldSignature(DexField originalField, GraphLens codeLens) { - if (this == codeLens) { - return originalField; - } - return originalFieldSignatures.inverse().getOrDefault(originalField, originalField); - } - - @Override - public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) { - return this != applied - ? originalMethodSignatures.inverse().getOrDefault(originalMethod, originalMethod) - : originalMethod; - } - - @Override public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition( DexMethod method, GraphLens codeLens) { return GraphLens.getIdentityLens().lookupPrototypeChangesForMethodDefinition(method, codeLens); } @Override - public DexType internalDescribeLookupClassType(DexType previous) { + public DexType getPreviousClassType(DexType type) { + return renamedTypeNames.getRepresentativeKeyOrDefault(type, type); + } + + @Override + public DexType getNextClassType(DexType previous) { return renamedTypeNames.getOrDefault(previous, previous); } @Override + public DexField getPreviousFieldSignature(DexField field) { + return originalFieldSignatures.getOrDefault(field, field); + } + + @Override + public DexField getNextFieldSignature(DexField field) { + return originalFieldSignatures.inverse().getOrDefault(field, field); + } + + @Override public DexMethod getPreviousMethodSignature(DexMethod method) { if (extraOriginalMethodSignatures.containsKey(method)) { return extraOriginalMethodSignatures.get(method);
diff --git a/src/main/java/com/android/tools/r8/graph/lens/ClearCodeRewritingGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/ClearCodeRewritingGraphLens.java index 1635380..0b5348c 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/ClearCodeRewritingGraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/ClearCodeRewritingGraphLens.java
@@ -20,18 +20,6 @@ } @Override - public DexField getRenamedFieldSignature(DexField originalField, GraphLens codeLens) { - return this != codeLens ? getPrevious().getRenamedFieldSignature(originalField) : originalField; - } - - @Override - public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) { - return this != applied - ? getPrevious().getRenamedMethodSignature(originalMethod, applied) - : originalMethod; - } - - @Override public boolean isClearCodeRewritingLens() { return true; }
diff --git a/src/main/java/com/android/tools/r8/graph/lens/DefaultNonIdentityGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/DefaultNonIdentityGraphLens.java index bf57958..ffab2e0 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/DefaultNonIdentityGraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/DefaultNonIdentityGraphLens.java
@@ -32,13 +32,8 @@ // Class lookup APIs. @Override - protected DexType internalDescribeLookupClassType(DexType previous) { - return previous; - } - - @Override - public DexType getOriginalType(DexType type) { - return getPrevious().getOriginalType(type); + protected DexType getNextClassType(DexType type) { + return type; } @Override @@ -46,6 +41,11 @@ return getPrevious().getOriginalTypes(type); } + @Override + public DexType getPreviousClassType(DexType type) { + return type; + } + // Field lookup APIs. @Override @@ -54,16 +54,13 @@ } @Override - public DexField getOriginalFieldSignature(DexField field) { - return getPrevious().getOriginalFieldSignature(field); + public DexField getPreviousFieldSignature(DexField field) { + return field; } @Override - public DexField getRenamedFieldSignature(DexField originalField, GraphLens codeLens) { - if (this == codeLens) { - return originalField; - } - return getPrevious().getRenamedFieldSignature(originalField); + public DexField getNextFieldSignature(DexField field) { + return field; } // Method lookup APIs. @@ -84,14 +81,6 @@ return method; } - @Override - public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens codeLens) { - if (this == codeLens) { - return originalMethod; - } - return getNextMethodSignature(getPrevious().getRenamedMethodSignature(originalMethod)); - } - // Prototype lookup APIs. @Override
diff --git a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java index 5c884ce..7aff661 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
@@ -3,14 +3,12 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.graph.lens; -import static com.android.tools.r8.graph.DexClassAndMethod.asProgramMethodOrNull; import static com.android.tools.r8.utils.collections.ThrowingSet.isThrowingSet; +import static com.google.common.base.Predicates.alwaysFalse; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.DexCallSite; -import com.android.tools.r8.graph.DexClass; -import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexDefinitionSupplier; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; @@ -42,13 +40,16 @@ import com.google.common.collect.Sets; import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap; import it.unimi.dsi.fastutil.objects.Object2BooleanMap; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; +import java.util.Deque; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.BiFunction; +import java.util.function.Predicate; /** * A GraphLens implements a virtual view on top of the graph, used to delay global rewrites until @@ -70,21 +71,11 @@ public abstract static class Builder { - protected final MutableBidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap = - BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap(); protected final MutableBidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod> methodMap = BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap(); - protected final Map<DexType, DexType> typeMap = new IdentityHashMap<>(); protected Builder() {} - public void map(DexType from, DexType to) { - if (from == to) { - return; - } - typeMap.put(from, to); - } - public void move(DexMethod from, DexMethod to) { if (from == to) { return; @@ -92,13 +83,6 @@ methodMap.put(from, to); } - public void move(DexField from, DexField to) { - if (from == to) { - return; - } - fieldMap.put(from, to); - } - public abstract GraphLens build(AppView<?> appView); } @@ -112,41 +96,55 @@ return false; } - public abstract DexType getOriginalType(DexType type); + @Deprecated + public final DexType getOriginalType(DexType type) { + GraphLens appliedLens = getIdentityLens(); + return getOriginalType(type, appliedLens); + } + + public final DexType getOriginalType(DexType type, GraphLens appliedLens) { + return getOriginalReference(type, appliedLens, NonIdentityGraphLens::getPreviousClassType); + } public abstract Iterable<DexType> getOriginalTypes(DexType type); - public abstract DexField getOriginalFieldSignature(DexField field); - - public final DexMember<?, ?> getOriginalMemberSignature(DexMember<?, ?> member) { - return member.apply(this::getOriginalFieldSignature, this::getOriginalMethodSignature); + @Deprecated + public final DexField getOriginalFieldSignature(DexField field) { + GraphLens appliedLens = getIdentityLens(); + return getOriginalFieldSignature(field, appliedLens); } + public final DexField getOriginalFieldSignature(DexField field, GraphLens appliedLens) { + return getOriginalReference( + field, appliedLens, NonIdentityGraphLens::getPreviousFieldSignature); + } + + @Deprecated public final DexMethod getOriginalMethodSignature(DexMethod method) { - return getOriginalMethodSignature(method, null); + GraphLens appliedLens = getIdentityLens(); + return getOriginalMethodSignature(method, appliedLens); } - public final DexMethod getOriginalMethodSignature(DexMethod method, GraphLens atGraphLens) { - GraphLens current = this; - DexMethod original = method; - while (current.isNonIdentityLens() && current != atGraphLens) { - NonIdentityGraphLens nonIdentityLens = current.asNonIdentityLens(); - original = nonIdentityLens.getPreviousMethodSignature(original); - current = nonIdentityLens.getPrevious(); - } - assert atGraphLens == null ? current.isIdentityLens() : (current == atGraphLens); - return original; + public final DexMethod getOriginalMethodSignature(DexMethod method, GraphLens appliedLens) { + return getOriginalReference( + method, appliedLens, NonIdentityGraphLens::getPreviousMethodSignature); } public final DexMethod getOriginalMethodSignatureForMapping(DexMethod method) { + GraphLens appliedLens = getIdentityLens(); + return getOriginalReference( + method, appliedLens, NonIdentityGraphLens::getPreviousMethodSignatureForMapping); + } + + private <T extends DexReference> T getOriginalReference( + T reference, GraphLens appliedLens, BiFunction<NonIdentityGraphLens, T, T> previousFn) { GraphLens current = this; - DexMethod original = method; - while (current.isNonIdentityLens()) { + T original = reference; + while (current.isNonIdentityLens() && current != appliedLens) { NonIdentityGraphLens nonIdentityLens = current.asNonIdentityLens(); - original = nonIdentityLens.getPreviousMethodSignatureForMapping(original); + original = previousFn.apply(nonIdentityLens, original); current = nonIdentityLens.getPrevious(); } - assert current.isIdentityLens(); return original; } @@ -158,11 +156,16 @@ method -> getRenamedMethodSignature(method, codeLens)); } + @Deprecated public final DexField getRenamedFieldSignature(DexField originalField) { - return getRenamedFieldSignature(originalField, null); + GraphLens appliedLens = getIdentityLens(); + return getRenamedFieldSignature(originalField, appliedLens); } - public abstract DexField getRenamedFieldSignature(DexField originalField, GraphLens codeLens); + public final DexField getRenamedFieldSignature(DexField originalField, GraphLens appliedLens) { + return getRenamedReference( + originalField, appliedLens, NonIdentityGraphLens::getNextFieldSignature); + } public final DexMember<?, ?> getRenamedMemberSignature( DexMember<?, ?> originalMember, GraphLens codeLens) { @@ -171,50 +174,41 @@ : getRenamedMethodSignature(originalMember.asDexMethod(), codeLens); } + @Deprecated public final DexMethod getRenamedMethodSignature(DexMethod originalMethod) { - return getRenamedMethodSignature(originalMethod, null); + GraphLens appliedLens = getIdentityLens(); + return getRenamedMethodSignature(originalMethod, appliedLens); } - public abstract DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied); - - public DexEncodedMethod mapDexEncodedMethod( - DexEncodedMethod originalEncodedMethod, DexDefinitionSupplier definitions) { - return mapDexEncodedMethod(originalEncodedMethod, definitions, null); + public final DexMethod getRenamedMethodSignature(DexMethod method, GraphLens appliedLens) { + return getRenamedReference(method, appliedLens, NonIdentityGraphLens::getNextMethodSignature); } - public DexEncodedMethod mapDexEncodedMethod( - DexEncodedMethod originalEncodedMethod, - DexDefinitionSupplier definitions, - GraphLens applied) { - assert originalEncodedMethod != DexEncodedMethod.SENTINEL; - DexMethod newMethod = getRenamedMethodSignature(originalEncodedMethod.getReference(), applied); - // Note that: - // * Even if `newMethod` is the same as `originalEncodedMethod.method`, we still need to look it - // up, since `originalEncodedMethod` may be obsolete. - // * We can't directly use AppInfo#definitionFor(DexMethod) since definitions may not be - // updated either yet. - DexClass newHolder = definitions.definitionFor(newMethod.holder); - assert newHolder != null; - DexEncodedMethod newEncodedMethod = newHolder.lookupMethod(newMethod); - assert newEncodedMethod != null; - return newEncodedMethod; + private <T extends DexReference> T getRenamedReference( + T reference, GraphLens appliedLens, BiFunction<NonIdentityGraphLens, T, T> nextFn) { + return getRenamedReference(reference, appliedLens, nextFn, alwaysFalse()); } - public ProgramMethod mapProgramMethod( - ProgramMethod oldMethod, DexDefinitionSupplier definitions) { - DexMethod newMethod = getRenamedMethodSignature(oldMethod.getReference()); - if (newMethod == oldMethod.getReference() && !oldMethod.getDefinition().isObsolete()) { - assert verifyIsConsistentWithLookup(oldMethod, definitions); - return oldMethod; + private <T extends DexReference> T getRenamedReference( + T reference, + GraphLens appliedLens, + BiFunction<NonIdentityGraphLens, T, T> nextFn, + Predicate<T> stoppingCriterion) { + GraphLens current = this; + Deque<NonIdentityGraphLens> lenses = new ArrayDeque<>(); + while (current.isNonIdentityLens() && current != appliedLens) { + NonIdentityGraphLens nonIdentityLens = current.asNonIdentityLens(); + lenses.addLast(nonIdentityLens); + current = nonIdentityLens.getPrevious(); } - return asProgramMethodOrNull(definitions.definitionFor(newMethod)); - } - - private static boolean verifyIsConsistentWithLookup( - ProgramMethod method, DexDefinitionSupplier definitions) { - DexClassAndMethod lookupMethod = definitions.definitionFor(method.getReference()); - assert method.getDefinition() == lookupMethod.getDefinition(); - return true; + while (!lenses.isEmpty()) { + NonIdentityGraphLens lens = lenses.removeLast(); + reference = nextFn.apply(lens, reference); + if (stoppingCriterion.test(reference)) { + break; + } + } + return reference; } // Predicate indicating if a rewritten reference is a simple renaming, meaning the move from one @@ -248,17 +242,24 @@ public abstract String lookupPackageName(String pkg); - public DexType lookupClassType(DexType type) { - return lookupClassType(type, getIdentityLens()); + @Deprecated + public final DexType lookupClassType(DexType type) { + GraphLens appliedLens = getIdentityLens(); + return lookupClassType(type, appliedLens); } - public abstract DexType lookupClassType(DexType type, GraphLens applied); + public final DexType lookupClassType(DexType type, GraphLens appliedLens) { + return getRenamedReference( + type, appliedLens, NonIdentityGraphLens::getNextClassType, DexType::isPrimitiveType); + } + @Deprecated public DexType lookupType(DexType type) { - return lookupType(type, getIdentityLens()); + GraphLens appliedLens = getIdentityLens(); + return lookupType(type, appliedLens); } - public abstract DexType lookupType(DexType type, GraphLens applied); + public abstract DexType lookupType(DexType type, GraphLens appliedLens); public final MethodLookupResult lookupInvokeDirect(DexMethod method, ProgramMethod context) { return lookupMethod(method, context.getReference(), InvokeType.DIRECT);
diff --git a/src/main/java/com/android/tools/r8/graph/lens/IdentityGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/IdentityGraphLens.java index 02c6a27..e72f128 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/IdentityGraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/IdentityGraphLens.java
@@ -37,31 +37,11 @@ } @Override - public DexType getOriginalType(DexType type) { - return type; - } - - @Override public Iterable<DexType> getOriginalTypes(DexType type) { return IterableUtils.singleton(type); } @Override - public DexField getOriginalFieldSignature(DexField field) { - return field; - } - - @Override - public DexField getRenamedFieldSignature(DexField originalField, GraphLens codeLens) { - return originalField; - } - - @Override - public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) { - return originalMethod; - } - - @Override public String lookupPackageName(String pkg) { return pkg; } @@ -72,12 +52,6 @@ } @Override - public DexType lookupClassType(DexType type, GraphLens applied) { - assert type.isClassType(); - return type; - } - - @Override public MethodLookupResult lookupMethod( DexMethod method, DexMethod context, InvokeType type, GraphLens codeLens) { assert codeLens == null || codeLens.isIdentityLens();
diff --git a/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java index 694425b..93c76a0 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java
@@ -16,14 +16,13 @@ import com.android.tools.r8.utils.collections.BidirectionalManyToManyRepresentativeMap; import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap; import com.android.tools.r8.utils.collections.EmptyBidirectionalOneToOneMap; -import java.util.Collections; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; /** - * GraphLens implementation with a parent lens using a simple mapping for type, method and field - * mapping. + * GraphLens implementation with a parent lens where the mapping of types, methods and fields can be + * one-to-one, one-to-many, or many-to-one. * * <p>Subclasses can override the lookup methods. * @@ -37,11 +36,12 @@ new EmptyBidirectionalOneToOneMap<>(); protected static final EmptyBidirectionalOneToOneMap<DexMethod, DexMethod> EMPTY_METHOD_MAP = new EmptyBidirectionalOneToOneMap<>(); - protected static final Map<DexType, DexType> EMPTY_TYPE_MAP = Collections.emptyMap(); + protected static final EmptyBidirectionalOneToOneMap<DexType, DexType> EMPTY_TYPE_MAP = + new EmptyBidirectionalOneToOneMap<>(); protected final BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap; protected final Function<DexMethod, DexMethod> methodMap; - protected final Map<DexType, DexType> typeMap; + protected final BidirectionalManyToManyRepresentativeMap<DexType, DexType> typeMap; // Map that stores the new signature of methods that have been affected by class merging, unused // argument removal, repackaging, synthetic finalization, etc. This is needed to generate a @@ -81,7 +81,7 @@ AppView<?> appView, BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap, BidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod> methodMap, - Map<DexType, DexType> typeMap) { + BidirectionalManyToManyRepresentativeMap<DexType, DexType> typeMap) { this(appView, fieldMap, methodMap.getForwardMap(), typeMap, methodMap); } @@ -89,7 +89,7 @@ AppView<?> appView, BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap, Map<DexMethod, DexMethod> methodMap, - Map<DexType, DexType> typeMap, + BidirectionalManyToManyRepresentativeMap<DexType, DexType> typeMap, BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod> newMethodSignatures) { this(appView, fieldMap, methodMap::get, typeMap, newMethodSignatures); assert !typeMap.isEmpty() @@ -102,7 +102,7 @@ AppView<?> appView, BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap, Function<DexMethod, DexMethod> methodMap, - Map<DexType, DexType> typeMap, + BidirectionalManyToManyRepresentativeMap<DexType, DexType> typeMap, BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod> newMethodSignatures) { super(appView); this.fieldMap = fieldMap; @@ -111,17 +111,18 @@ this.newMethodSignatures = newMethodSignatures; } - protected DexType internalGetOriginalType(DexType previous) { - return previous; - } - - protected Iterable<DexType> internalGetOriginalTypes(DexType previous) { - return IterableUtils.singleton(internalGetOriginalType(previous)); + @Override + public DexType getPreviousClassType(DexType type) { + return typeMap.getRepresentativeKeyOrDefault(type, type); } @Override - public DexType getOriginalType(DexType type) { - return getPrevious().getOriginalType(internalGetOriginalType(type)); + protected DexType getNextClassType(DexType type) { + return typeMap.getRepresentativeValueOrDefault(type, type); + } + + protected Iterable<DexType> internalGetOriginalTypes(DexType type) { + return IterableUtils.singleton(getPreviousClassType(type)); } @Override @@ -130,35 +131,6 @@ } @Override - public DexField getOriginalFieldSignature(DexField field) { - DexField originalField = fieldMap.getRepresentativeKeyOrDefault(field, field); - return getPrevious().getOriginalFieldSignature(originalField); - } - - @Override - public DexField getRenamedFieldSignature(DexField originalField, GraphLens codeLens) { - if (this == codeLens) { - return originalField; - } - DexField renamedField = getPrevious().getRenamedFieldSignature(originalField, codeLens); - return internalGetNextFieldSignature(renamedField); - } - - @Override - public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) { - if (this == applied) { - return originalMethod; - } - DexMethod renamedMethod = getPrevious().getRenamedMethodSignature(originalMethod, applied); - return getNextMethodSignature(renamedMethod); - } - - @Override - protected DexType internalDescribeLookupClassType(DexType previous) { - return typeMap.getOrDefault(previous, previous); - } - - @Override protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) { if (previous.hasReboundReference()) { // Rewrite the rebound reference and then "fixup" the non-rebound reference. @@ -167,14 +139,12 @@ previous.getReference() == previous.getReboundReference() ? rewrittenReboundReference : rewrittenReboundReference.withHolder( - internalDescribeLookupClassType(previous.getReference().getHolderType()), - dexItemFactory()); + getNextClassType(previous.getReference().getHolderType()), dexItemFactory()); return FieldLookupResult.builder(this) .setReboundReference(rewrittenReboundReference) .setReference(rewrittenNonReboundReference) - .setReadCastType(previous.getRewrittenReadCastType(this::internalDescribeLookupClassType)) - .setWriteCastType( - previous.getRewrittenWriteCastType(this::internalDescribeLookupClassType)) + .setReadCastType(previous.getRewrittenReadCastType(this::getNextClassType)) + .setWriteCastType(previous.getRewrittenWriteCastType(this::getNextClassType)) .build(); } else { // TODO(b/168282032): We should always have the rebound reference, so this should become @@ -182,9 +152,8 @@ DexField rewrittenReference = previous.getRewrittenReference(fieldMap); return FieldLookupResult.builder(this) .setReference(rewrittenReference) - .setReadCastType(previous.getRewrittenReadCastType(this::internalDescribeLookupClassType)) - .setWriteCastType( - previous.getRewrittenWriteCastType(this::internalDescribeLookupClassType)) + .setReadCastType(previous.getRewrittenReadCastType(this::getNextClassType)) + .setWriteCastType(previous.getRewrittenWriteCastType(this::getNextClassType)) .build(); } } @@ -201,8 +170,7 @@ ? rewrittenReboundReference : // This assumes that the holder will always be moved in lock-step with the method! rewrittenReboundReference.withHolder( - internalDescribeLookupClassType(previous.getReference().getHolderType()), - dexItemFactory()); + getNextClassType(previous.getReference().getHolderType()), dexItemFactory()); return MethodLookupResult.builder(this) .setReference(rewrittenReference) .setReboundReference(rewrittenReboundReference) @@ -253,7 +221,13 @@ return prototypeChanges; } - protected DexField internalGetNextFieldSignature(DexField field) { + @Override + public DexField getPreviousFieldSignature(DexField field) { + return fieldMap.getRepresentativeKeyOrDefault(field, field); + } + + @Override + public DexField getNextFieldSignature(DexField field) { return fieldMap.getOrDefault(field, field); }
diff --git a/src/main/java/com/android/tools/r8/graph/lens/NonIdentityGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/NonIdentityGraphLens.java index ccbacc3..7680134 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/NonIdentityGraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/NonIdentityGraphLens.java
@@ -98,33 +98,22 @@ } @Override - public final DexType lookupType(DexType type, GraphLens applied) { - if (this == applied) { - return type; - } - if (type.isPrimitiveType() || type.isVoidType() || type.isNullValueType()) { - return type; + public final DexType lookupType(DexType type, GraphLens appliedLens) { + if (type.isClassType()) { + return lookupClassType(type, appliedLens); } if (type.isArrayType()) { DexType result = arrayTypeCache.get(type); if (result == null) { DexType baseType = type.toBaseType(dexItemFactory); - DexType newType = lookupType(baseType); + DexType newType = lookupType(baseType, appliedLens); result = baseType == newType ? type : type.replaceBaseType(newType, dexItemFactory); arrayTypeCache.put(type, result); } return result; } - return lookupClassType(type); - } - - @Override - public final DexType lookupClassType(DexType type, GraphLens applied) { - assert type.isClassType() : "Expected class type, but was `" + type.toSourceString() + "`"; - if (this == applied) { - return type; - } - return internalDescribeLookupClassType(getPrevious().lookupClassType(type)); + assert type.isNullValueType() || type.isPrimitiveType() || type.isVoidType(); + return type; } @Override @@ -169,10 +158,14 @@ protected abstract MethodLookupResult internalDescribeLookupMethod( MethodLookupResult previous, DexMethod context, GraphLens codeLens); - protected abstract DexType internalDescribeLookupClassType(DexType previous); + protected abstract DexType getNextClassType(DexType type); + + public abstract DexField getPreviousFieldSignature(DexField field); public abstract DexMethod getPreviousMethodSignature(DexMethod method); + public abstract DexType getPreviousClassType(DexType type); + /*** * The previous mapping for a method often coincides with the previous method signature, but it * may not, for example for bridges inserted in vertically merged classes where the original @@ -182,6 +175,8 @@ return getPreviousMethodSignature(method); } + public abstract DexField getNextFieldSignature(DexField field); + public abstract DexMethod getNextMethodSignature(DexMethod method); @Override
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java index cd2586e..b8f049d 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
@@ -42,7 +42,7 @@ BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap, Map<DexMethod, DexMethod> methodMap, BidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod> newMethodSignatures) { - super(appView, fieldMap, methodMap, mergedClasses.getForwardMap(), newMethodSignatures); + super(appView, fieldMap, methodMap, mergedClasses.getBidirectionalMap(), newMethodSignatures); this.methodExtraParameters = methodExtraParameters; this.mergedClasses = mergedClasses; } @@ -53,6 +53,11 @@ } @Override + public DexType getPreviousClassType(DexType type) { + return type; + } + + @Override public boolean isHorizontalClassMergerGraphLens() { return true; } @@ -94,7 +99,7 @@ lookup.getReference().getType() != previous.getReference().getType() ? lookupType(previous.getReference().getType()) : null) - .setWriteCastType(previous.getRewrittenWriteCastType(this::internalDescribeLookupClassType)) + .setWriteCastType(previous.getRewrittenWriteCastType(this::getNextClassType)) .build(); }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java index 9caf1a9..11313e4 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java
@@ -8,19 +8,20 @@ import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.classmerging.MergedClasses; import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.android.tools.r8.utils.collections.BidirectionalManyToOneHashMap; -import com.android.tools.r8.utils.collections.BidirectionalManyToOneMap; +import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap; +import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap; import com.android.tools.r8.utils.collections.EmptyBidirectionalOneToOneMap; -import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneMap; +import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneRepresentativeMap; import java.util.Map; import java.util.Set; import java.util.function.BiConsumer; public class HorizontallyMergedClasses implements MergedClasses { - private final BidirectionalManyToOneMap<DexType, DexType> mergedClasses; + private final BidirectionalManyToOneRepresentativeMap<DexType, DexType> mergedClasses; - public HorizontallyMergedClasses(BidirectionalManyToOneMap<DexType, DexType> mergedClasses) { + public HorizontallyMergedClasses( + BidirectionalManyToOneRepresentativeMap<DexType, DexType> mergedClasses) { this.mergedClasses = mergedClasses; } @@ -89,6 +90,10 @@ return this.hasBeenMergedIntoDifferentType(type) || isMergeTarget(type); } + BidirectionalManyToOneRepresentativeMap<DexType, DexType> getBidirectionalMap() { + return mergedClasses; + } + Map<DexType, DexType> getForwardMap() { return mergedClasses.getForwardMap(); } @@ -106,8 +111,8 @@ public static class Builder { - private final MutableBidirectionalManyToOneMap<DexType, DexType> mergedClasses = - BidirectionalManyToOneHashMap.newIdentityHashMap(); + private final MutableBidirectionalManyToOneRepresentativeMap<DexType, DexType> mergedClasses = + BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap(); void add(DexType source, DexType target) { assert !mergedClasses.containsKey(source);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java index 09e9911..b4d26cd 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
@@ -280,6 +280,9 @@ // Cleanup. code.removeBlocks(blocksToRemove); code.removeAllDeadAndTrivialPhis(); + code.removeRedundantBlocks(); + + assert code.isConsistentSSA(appView); return code; }
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 e6a6d31..ec226d9 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
@@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.ir.analysis.constant; +import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.ir.analysis.type.TypeAnalysis; import com.android.tools.r8.ir.code.BasicBlock; @@ -16,6 +17,8 @@ import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.code.StringSwitch; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.passes.CodeRewriterPass; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.google.common.collect.Sets; import java.util.ArrayList; import java.util.BitSet; @@ -31,9 +34,8 @@ * "Constant Propagation with Conditional Branches". * https://www.cs.utexas.edu/users/lin/cs380c/wegman.pdf */ -public class SparseConditionalConstantPropagation { +public class SparseConditionalConstantPropagation extends CodeRewriterPass<AppInfo> { - private final AppView<?> appView; private final IRCode code; private final Map<Value, LatticeElement> mapping = new HashMap<>(); // TODO(b/270398965): Replace LinkedList. @@ -43,19 +45,29 @@ @SuppressWarnings("JdkObsolete") private final Deque<BasicBlock> flowEdges = new LinkedList<>(); - private final int maxBlockNumber; private final BitSet[] executableFlowEdges; private final BitSet visitedBlocks; public SparseConditionalConstantPropagation(AppView<?> appView, IRCode code) { - this.appView = appView; + super(appView); this.code = code; - maxBlockNumber = code.getCurrentBlockNumber() + 1; + int maxBlockNumber = code.getCurrentBlockNumber() + 1; executableFlowEdges = new BitSet[maxBlockNumber]; visitedBlocks = new BitSet(maxBlockNumber); } - public void run() { + @Override + protected String getTimingId() { + return "SparseConditionalConstantPropagation"; + } + + @Override + protected boolean shouldRewriteCode(IRCode code) { + return true; + } + + @Override + protected CodeRewriterResult rewriteCode(IRCode code) { BasicBlock firstBlock = code.entryBlock(); visitInstructions(firstBlock); @@ -82,11 +94,12 @@ } } } - rewriteCode(); + rewriteConstants(); assert code.isConsistentSSA(appView); + return CodeRewriterResult.NONE; } - private void rewriteCode() { + private void rewriteConstants() { Set<Value> affectedValues = Sets.newIdentityHashSet(); List<BasicBlock> blockToAnalyze = new ArrayList<>(); mapping.entrySet().stream() @@ -258,7 +271,7 @@ private void setExecutableEdge(int from, int to) { BitSet previousExecutable = executableFlowEdges[to]; if (previousExecutable == null) { - previousExecutable = new BitSet(maxBlockNumber); + previousExecutable = new BitSet(executableFlowEdges.length); executableFlowEdges[to] = previousExecutable; } previousExecutable.set(from);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java index d871616..ca741c9 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
@@ -36,8 +36,14 @@ this.fieldBitAccessAnalysis = options.enableFieldBitAccessAnalysis ? new FieldBitAccessAnalysis() : null; this.fieldAssignmentTracker = new FieldAssignmentTracker(appView); - this.fieldReadForInvokeReceiverAnalysis = new FieldReadForInvokeReceiverAnalysis(appView); - this.fieldReadForWriteAnalysis = new FieldReadForWriteAnalysis(appView); + if (options.testing.canUseLir(appView)) { + // When using LIR the bytecode metadata is computed later during finalization via IR. + this.fieldReadForInvokeReceiverAnalysis = null; + this.fieldReadForWriteAnalysis = null; + } else { + this.fieldReadForInvokeReceiverAnalysis = new FieldReadForInvokeReceiverAnalysis(appView); + this.fieldReadForWriteAnalysis = new FieldReadForWriteAnalysis(appView); + } } @VisibleForTesting @@ -111,4 +117,32 @@ } } } + + public static BytecodeMetadataProvider computeBytecodeMetadata( + IRCode irCode, AppView<AppInfoWithLiveness> appView) { + // This rebuilding of metadata should only be used in the LIR pipeline where the info is + // discarded when translating from IR to LIR. + assert appView.options().testing.canUseLir(appView); + BytecodeMetadataProvider bytecodeMetadataProvider = BytecodeMetadataProvider.empty(); + if (irCode.metadata().mayHaveFieldInstruction()) { + BytecodeMetadataProvider.Builder builder = BytecodeMetadataProvider.builder(); + FieldReadForInvokeReceiverAnalysis fieldReadForInvokeReceiverAnalysis = + new FieldReadForInvokeReceiverAnalysis(appView); + FieldReadForWriteAnalysis fieldReadForWriteAnalysis = new FieldReadForWriteAnalysis(appView); + for (Instruction instruction : irCode.instructions()) { + if (instruction.isFieldInstruction()) { + FieldInstruction fieldInstruction = instruction.asFieldInstruction(); + ProgramField field = + appView.appInfo().resolveField(fieldInstruction.getField()).getProgramField(); + if (field != null) { + fieldReadForInvokeReceiverAnalysis.recordFieldAccess( + fieldInstruction, field, builder, irCode.context()); + fieldReadForWriteAnalysis.recordFieldAccess(fieldInstruction, field, builder); + } + } + } + bytecodeMetadataProvider = builder.build(); + } + return bytecodeMetadataProvider; + } }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForInvokeReceiverAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForInvokeReceiverAnalysis.java index 976020c6..6289b5d 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForInvokeReceiverAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForInvokeReceiverAnalysis.java
@@ -23,7 +23,7 @@ private final AppView<AppInfoWithLiveness> appView; - FieldReadForInvokeReceiverAnalysis(AppView<AppInfoWithLiveness> appView) { + public FieldReadForInvokeReceiverAnalysis(AppView<AppInfoWithLiveness> appView) { this.appView = appView; }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForWriteAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForWriteAnalysis.java index 6a5c420..2155676 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForWriteAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForWriteAnalysis.java
@@ -21,7 +21,7 @@ private final AppView<AppInfoWithLiveness> appView; - FieldReadForWriteAnalysis(AppView<AppInfoWithLiveness> appView) { + public FieldReadForWriteAnalysis(AppView<AppInfoWithLiveness> appView) { this.appView = appView; }
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 2a2813f..8941176 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
@@ -33,6 +33,7 @@ import com.android.tools.r8.ir.conversion.passes.CommonSubexpressionElimination; import com.android.tools.r8.ir.conversion.passes.DexConstantOptimizer; import com.android.tools.r8.ir.conversion.passes.KnownArrayLengthRewriter; +import com.android.tools.r8.ir.conversion.passes.MoveResultRewriter; import com.android.tools.r8.ir.conversion.passes.NaturalIntLoopRemover; import com.android.tools.r8.ir.conversion.passes.ParentConstructorHoistingCodeRewriter; import com.android.tools.r8.ir.conversion.passes.SplitBranch; @@ -182,7 +183,7 @@ this.classInitializerDefaultsOptimization = new ClassInitializerDefaultsOptimization(appView, this); this.stringOptimizer = new StringOptimizer(appView); - this.deadCodeRemover = new DeadCodeRemover(appView, codeRewriter); + this.deadCodeRemover = new DeadCodeRemover(appView); this.assertionsRewriter = new AssertionsRewriter(appView); this.idempotentFunctionCallCanonicalizer = new IdempotentFunctionCallCanonicalizer(appView); this.neverMerge = @@ -383,7 +384,7 @@ private void processAndFinalizeSimpleSynthesizedMethod(ProgramMethod method) { IRCode code = method.buildIR(appView); assert code != null; - codeRewriter.rewriteMoveResult(code); + new MoveResultRewriter(appView).run(code, Timing.empty()); removeDeadCodeAndFinalizeIR(code, OptimizationFeedbackIgnore.getInstance(), Timing.empty()); } @@ -615,12 +616,14 @@ assertionsRewriter.run(method, code, deadCodeRemover, timing); CheckNotNullConverter.runIfNecessary(appView, code); + previous = printMethod(code, "IR after disable assertions (SSA)", previous); if (serviceLoaderRewriter != null) { assert appView.appInfo().hasLiveness(); timing.begin("Rewrite service loaders"); serviceLoaderRewriter.rewrite(code, methodProcessor, methodProcessingContext); timing.end(); + previous = printMethod(code, "IR after service rewriting (SSA)", previous); } if (identifierNameStringMarker != null) { @@ -628,12 +631,14 @@ identifierNameStringMarker.decoupleIdentifierNameStringsInMethod(code); timing.end(); assert code.isConsistentSSA(appView); + previous = printMethod(code, "IR after identifier-name strings (SSA)", previous); } if (memberValuePropagation != null) { timing.begin("Propagate member values"); memberValuePropagation.run(code); timing.end(); + previous = printMethod(code, "IR after member-value propagation (SSA)", previous); } if (enumValueOptimizer != null) { @@ -641,16 +646,16 @@ timing.begin("Remove switch maps"); enumValueOptimizer.removeSwitchMaps(code); timing.end(); + previous = printMethod(code, "IR after enum-value optimization (SSA)", previous); } if (instanceInitializerOutliner != null) { instanceInitializerOutliner.rewriteInstanceInitializers( code, context, methodProcessor, methodProcessingContext); assert code.verifyTypes(appView); + previous = printMethod(code, "IR after instance initializer outlining (SSA)", previous); } - previous = printMethod(code, "IR after disable assertions (SSA)", previous); - // Update the IR code if collected call site optimization info has something useful. // While aggregation of parameter information at call sites would be more precise than static // types, those could be still less precise at one single call site, where specific arguments @@ -750,17 +755,11 @@ } commonSubexpressionElimination.run(code, timing); new ArrayConstructionSimplifier(appView).run(code, timing); - timing.begin("Rewrite move result"); - codeRewriter.rewriteMoveResult(code); - timing.end(); + new MoveResultRewriter(appView).run(code, timing); if (options.enableStringConcatenationOptimization && !isDebugMode) { - timing.begin("Rewrite string concat"); - StringBuilderAppendOptimizer.run(appView, code); - timing.end(); + new StringBuilderAppendOptimizer(appView).run(code, timing); } - timing.begin("Propagate sparse conditionals"); - new SparseConditionalConstantPropagation(appView, code).run(); - timing.end(); + new SparseConditionalConstantPropagation(appView, code).run(code, timing); timing.begin("Rewrite always throwing instructions"); new ThrowCatchOptimizer(appView).optimizeAlwaysThrowingInstructions(code); timing.end(); @@ -930,7 +929,7 @@ assert code.isConsistentSSA(appView); // TODO(b/214496607): Remove when dynamic types are safe w.r.t. interface assignment rules. - codeRewriter.rewriteMoveResult(code); + new MoveResultRewriter(appView).run(code, timing); } // Assert that we do not have unremoved non-sense code in the output, e.g., v <- non-null NULL. @@ -1049,6 +1048,7 @@ if (stringSwitchRemover != null) { stringSwitchRemover.run(code); } + code.removeRedundantBlocks(); deadCodeRemover.run(code, timing); finalizeIR( code, @@ -1066,7 +1066,11 @@ if (options.testing.roundtripThroughLir) { code = roundtripThroughLir(code, feedback, bytecodeMetadataProvider, timing); } - if (options.isGeneratingClassFiles()) { + if (options.testing.canUseLir(appView)) { + timing.begin("IR->LIR"); + finalizeToLir(code, feedback, bytecodeMetadataProvider, timing); + timing.end(); + } else if (options.isGeneratingClassFiles()) { timing.begin("IR->CF"); finalizeToCf(code, feedback, bytecodeMetadataProvider, timing); timing.end(); @@ -1106,7 +1110,7 @@ IRCode code, S strategy, String name, Timing timing) { timing.begin("IR->LIR (" + name + ")"); LirCode<EV> lirCode = - IR2LirConverter.translate(code, strategy.getEncodingStrategy(), appView.dexItemFactory()); + IR2LirConverter.translate(code, strategy.getEncodingStrategy(), appView.options()); timing.end(); // Check that printing does not fail. String lirString = lirCode.toString(); @@ -1114,11 +1118,26 @@ timing.begin("LIR->IR (" + name + ")"); IRCode irCode = Lir2IRConverter.translate( - code.context(), lirCode, strategy.getDecodingStrategy(lirCode), appView); + code.context(), lirCode, strategy.getDecodingStrategy(lirCode, null), appView); timing.end(); return irCode; } + private void finalizeToLir( + IRCode code, + OptimizationFeedback feedback, + BytecodeMetadataProvider bytecodeMetadataProvider, + Timing timing) { + assert deadCodeRemover.verifyNoDeadCode(code); + assert BytecodeMetadataProvider.empty() == bytecodeMetadataProvider; + LirCode<Integer> lirCode = + IR2LirConverter.translate( + code, LirStrategy.getDefaultStrategy().getEncodingStrategy(), appView.options()); + ProgramMethod method = code.context(); + method.setCode(lirCode, appView); + markProcessed(code, feedback); + } + private void finalizeToCf( IRCode code, OptimizationFeedback feedback, @@ -1256,4 +1275,32 @@ inliner.onMethodCodePruned(method); } } + + public void finalizeLirMethodToOutputFormat(ProgramMethod method) { + Code code = method.getDefinition().getCode(); + if (!(code instanceof LirCode)) { + return; + } + Timing onThreadTiming = Timing.empty(); + IRCode irCode = method.buildIR(appView); + BytecodeMetadataProvider bytecodeMetadataProvider = + FieldAccessAnalysis.computeBytecodeMetadata(irCode, appView.withLiveness()); + // During processing optimization info may cause previously live code to become dead. + // E.g., we may now have knowledge that an invoke does not have side effects. + // Thus, we re-run the dead-code remover now as it is assumed complete by CF/DEX finalization. + deadCodeRemover.run(irCode, onThreadTiming); + if (options.isGeneratingClassFiles()) { + method.setCode( + new IRToCfFinalizer(appView, deadCodeRemover) + .finalizeCode(irCode, bytecodeMetadataProvider, onThreadTiming), + appView); + } else { + assert options.isGeneratingDex(); + method.setCode( + new IRToDexFinalizer(appView, deadCodeRemover) + .finalizeCode(irCode, bytecodeMetadataProvider, onThreadTiming), + appView); + updateHighestSortingStrings(method.getDefinition()); + } + } }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java index dbd35f7..8c161b8 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java
@@ -38,6 +38,13 @@ } DexEncodedMethod method = code.method(); code.traceBlocks(); + workaroundBugs(code); + // Perform register allocation. + RegisterAllocator registerAllocator = performRegisterAllocation(code, method, timing); + return new DexBuilder(code, bytecodeMetadataProvider, registerAllocator, options).build(); + } + + private void workaroundBugs(IRCode code) { RuntimeWorkaroundCodeRewriter.workaroundNumberConversionRegisterAllocationBug(code, options); // Workaround massive dex2oat memory use for self-recursive methods. RuntimeWorkaroundCodeRewriter.workaroundDex2OatInliningIssue(appView, code); @@ -46,9 +53,7 @@ RuntimeWorkaroundCodeRewriter.workaroundDex2OatLinkedListBug(code, options); RuntimeWorkaroundCodeRewriter.workaroundForwardingInitializerBug(code, options); RuntimeWorkaroundCodeRewriter.workaroundExceptionTargetingLoopHeaderBug(code, options); - // Perform register allocation. - RegisterAllocator registerAllocator = performRegisterAllocation(code, method, timing); - return new DexBuilder(code, bytecodeMetadataProvider, registerAllocator, options).build(); + assert code.isConsistentSSA(appView); } private RegisterAllocator performRegisterAllocation(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java index c1a7b55..7327760 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -75,6 +75,9 @@ void classInitializerMayBePostponed(DexEncodedMethod method); + void setParametersWithBitwiseOperations( + ProgramMethod method, BitSet parametersWithBitwiseOperations); + void setUnusedArguments(ProgramMethod method, BitSet unusedArguments); // Unset methods.
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java index f8946d4..1187f29 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
@@ -16,6 +16,7 @@ import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed; import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.ThreadUtils; import com.android.tools.r8.utils.Timing; import com.android.tools.r8.utils.collections.ProgramMethodSet; import java.io.IOException; @@ -100,6 +101,7 @@ lastWaveDone(postMethodProcessorBuilder, executorService); eventConsumer.finished(appView); assert appView.graphLens() == graphLensForPrimaryOptimizationPass; + finalizeLirToOutputFormat(timing, executorService); timing.end(); } @@ -176,6 +178,7 @@ eventConsumer.finished(appView); assert appView.graphLens() == graphLensForSecondaryOptimizationPass; } + finalizeLirToOutputFormat(timing, executorService); timing.end(); } @@ -207,9 +210,28 @@ // Assure that no more optimization feedback left after post processing. assert feedback.noUpdatesLeft(); + finalizeLirToOutputFormat(timing, executorService); return builder.build(); } + private void finalizeLirToOutputFormat(Timing timing, ExecutorService executorService) + throws ExecutionException { + if (!options.testing.canUseLir(appView)) { + return; + } + String output = options.isGeneratingClassFiles() ? "CF" : "DEX"; + timing.begin("LIR->IR->" + output); + ThreadUtils.processItems( + appView.appInfo().classes(), + clazz -> clazz.forEachProgramMethod(this::finalizeLirMethodToOutputFormat), + executorService); + appView + .getSyntheticItems() + .getPendingSyntheticClasses() + .forEach(clazz -> clazz.forEachProgramMethod(this::finalizeLirMethodToOutputFormat)); + timing.end(); + } + private void clearDexMethodCompilationState() { appView.appInfo().classes().forEach(this::clearDexMethodCompilationState); }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java index 63e6b36..d6c709b 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java
@@ -20,6 +20,7 @@ import com.android.tools.r8.ir.code.NewArrayEmpty; import com.android.tools.r8.ir.code.NewArrayFilledData; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.InternalOptions.RewriteArrayOptions; import com.android.tools.r8.utils.SetUtils; @@ -92,12 +93,15 @@ } @Override - protected void rewriteCode(IRCode code) { + protected CodeRewriterResult rewriteCode(IRCode code) { WorkList<BasicBlock> worklist = WorkList.newIdentityWorkList(code.blocks); while (worklist.hasNext()) { BasicBlock block = worklist.next(); simplifyArrayConstructionBlock(block, worklist, code, appView.options()); } + // Do only when the rewriter pass has changed something. + code.removeRedundantBlocks(); + return CodeRewriterResult.NONE; } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java index 24fad2e..f684ff5 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java
@@ -27,6 +27,7 @@ import com.android.tools.r8.ir.code.Ushr; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.code.Xor; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.utils.WorkList; import com.google.common.collect.ImmutableMap; import java.util.Map; @@ -248,7 +249,7 @@ } @Override - public void rewriteCode(IRCode code) { + public CodeRewriterResult rewriteCode(IRCode code) { InstructionListIterator iterator = code.instructionListIterator(); while (iterator.hasNext()) { Instruction next = iterator.next(); @@ -268,6 +269,7 @@ code.removeAllDeadAndTrivialPhis(); code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); + return CodeRewriterResult.NONE; } private void successiveSimplification(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java index 94e30d3..aba955c 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java
@@ -11,6 +11,7 @@ import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.conversion.MethodProcessor; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.Timing; @@ -31,38 +32,43 @@ return (AppView<? extends T>) appView; } - public final void run( + public final CodeRewriterResult run( IRCode code, MethodProcessor methodProcessor, MethodProcessingContext methodProcessingContext, Timing timing) { - timing.time(getTimingId(), () -> run(code, methodProcessor, methodProcessingContext)); + return timing.time(getTimingId(), () -> run(code, methodProcessor, methodProcessingContext)); } - public final void run(IRCode code, Timing timing) { - timing.time(getTimingId(), () -> run(code, null, null)); + public final CodeRewriterResult run(IRCode code, Timing timing) { + return timing.time(getTimingId(), () -> run(code, null, null)); } - private void run( + private CodeRewriterResult run( IRCode code, MethodProcessor methodProcessor, MethodProcessingContext methodProcessingContext) { if (shouldRewriteCode(code)) { - rewriteCode(code, methodProcessor, methodProcessingContext); + return rewriteCode(code, methodProcessor, methodProcessingContext); } + return noChange(); + } + + protected CodeRewriterResult noChange() { + return CodeRewriterResult.NO_CHANGE; } protected abstract String getTimingId(); - protected void rewriteCode(IRCode code) { + protected CodeRewriterResult rewriteCode(IRCode code) { throw new Unreachable("Should Override or use overload"); } - protected void rewriteCode( + protected CodeRewriterResult rewriteCode( IRCode code, MethodProcessor methodProcessor, MethodProcessingContext methodProcessingContext) { - rewriteCode(code); + return rewriteCode(code); } protected abstract boolean shouldRewriteCode(IRCode code);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java index 5d88ded..7057abc 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java
@@ -15,6 +15,7 @@ import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.utils.InternalOptions; import com.google.common.base.Equivalence; import com.google.common.base.Equivalence.Wrapper; @@ -39,7 +40,7 @@ } @Override - protected void rewriteCode(IRCode code) { + protected CodeRewriterResult rewriteCode(IRCode code) { int noCandidate = code.reserveMarkingColor(); if (hasCSECandidate(code, noCandidate)) { final ListMultimap<Wrapper<Instruction>, Value> instructionToValue = @@ -79,6 +80,7 @@ code.returnMarkingColor(noCandidate); code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); + return CodeRewriterResult.NONE; } private static class CSEExpressionEquivalence extends Equivalence<Instruction> {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java index c6e07ed..bcfd88c 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java
@@ -31,6 +31,7 @@ import com.android.tools.r8.ir.code.Position; import com.android.tools.r8.ir.code.StaticGet; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.ir.optimize.ConstantCanonicalizer; import com.android.tools.r8.utils.LazyBox; import com.google.common.collect.Iterables; @@ -63,9 +64,10 @@ } @Override - protected void rewriteCode(IRCode code) { + protected CodeRewriterResult rewriteCode(IRCode code) { useDedicatedConstantForLitInstruction(code); shortenLiveRanges(code, constantCanonicalizer); + return CodeRewriterResult.NONE; } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/KnownArrayLengthRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/KnownArrayLengthRewriter.java index 9ae7aab..291f8b9 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/KnownArrayLengthRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/KnownArrayLengthRewriter.java
@@ -13,6 +13,7 @@ import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import java.util.Set; public class KnownArrayLengthRewriter extends CodeRewriterPass<AppInfo> { @@ -32,7 +33,7 @@ } @Override - protected void rewriteCode(IRCode code) { + protected CodeRewriterResult rewriteCode(IRCode code) { InstructionListIterator iterator = code.instructionListIterator(); while (iterator.hasNext()) { Instruction current = iterator.next(); @@ -78,5 +79,6 @@ } code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); + return CodeRewriterResult.NONE; } }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/MoveResultRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/MoveResultRewriter.java new file mode 100644 index 0000000..18c3757 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/MoveResultRewriter.java
@@ -0,0 +1,148 @@ +// Copyright (c) 2023, 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.conversion.passes; + +import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull; + +import com.android.tools.r8.graph.AppInfo; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClassAndMethod; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.ir.analysis.type.TypeAnalysis; +import com.android.tools.r8.ir.analysis.type.TypeElement; +import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.InstructionListIterator; +import com.android.tools.r8.ir.code.InvokeMethod; +import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; +import com.android.tools.r8.ir.optimize.AssumeRemover; +import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo; +import com.google.common.collect.Sets; +import java.util.Collections; +import java.util.ListIterator; +import java.util.Set; + +public class MoveResultRewriter extends CodeRewriterPass<AppInfo> { + + public MoveResultRewriter(AppView<?> appView) { + super(appView); + } + + @Override + protected String getTimingId() { + return "MoveResultRewriter"; + } + + @Override + protected boolean shouldRewriteCode(IRCode code) { + return options.isGeneratingDex() && code.metadata().mayHaveInvokeMethod(); + } + + // Replace result uses for methods where something is known about what is returned. + @Override + protected CodeRewriterResult rewriteCode(IRCode code) { + AssumeRemover assumeRemover = new AssumeRemover(appView, code); + boolean changed = false; + boolean mayHaveRemovedTrivialPhi = false; + Set<BasicBlock> blocksToBeRemoved = Sets.newIdentityHashSet(); + ListIterator<BasicBlock> blockIterator = code.listIterator(); + while (blockIterator.hasNext()) { + BasicBlock block = blockIterator.next(); + if (blocksToBeRemoved.contains(block)) { + continue; + } + + InstructionListIterator iterator = block.listIterator(code); + while (iterator.hasNext()) { + InvokeMethod invoke = iterator.next().asInvokeMethod(); + if (invoke == null || !invoke.hasOutValue() || invoke.outValue().hasLocalInfo()) { + continue; + } + + // Check if the invoked method is known to return one of its arguments. + DexClassAndMethod target = invoke.lookupSingleTarget(appView, code.context()); + if (target == null) { + continue; + } + + MethodOptimizationInfo optimizationInfo = target.getDefinition().getOptimizationInfo(); + if (!optimizationInfo.returnsArgument()) { + continue; + } + + int argumentIndex = optimizationInfo.getReturnedArgument(); + // Replace the out value of the invoke with the argument and ignore the out value. + if (argumentIndex < 0 || !checkArgumentType(invoke, argumentIndex)) { + continue; + } + + Value argument = invoke.arguments().get(argumentIndex); + Value outValue = invoke.outValue(); + assert outValue.verifyCompatible(argument.outType()); + + // Make sure that we are only narrowing information here. Note, in cases where we cannot + // find the definition of types, computing lessThanOrEqual will return false unless it is + // object. + if (!argument.getType().lessThanOrEqual(outValue.getType(), appView)) { + continue; + } + + Set<Value> affectedValues = + argument.getType().equals(outValue.getType()) + ? Collections.emptySet() + : outValue.affectedValues(); + + assumeRemover.markAssumeDynamicTypeUsersForRemoval(outValue); + mayHaveRemovedTrivialPhi |= outValue.numberOfPhiUsers() > 0; + outValue.replaceUsers(argument); + invoke.setOutValue(null); + changed = true; + + if (!affectedValues.isEmpty()) { + new TypeAnalysis(appView).narrowing(affectedValues); + } + } + } + assumeRemover.removeMarkedInstructions(blocksToBeRemoved).finish(); + Set<Value> affectedValues = Sets.newIdentityHashSet(); + if (!blocksToBeRemoved.isEmpty()) { + code.removeBlocks(blocksToBeRemoved); + code.removeAllDeadAndTrivialPhis(affectedValues); + assert code.getUnreachableBlocks().isEmpty(); + } else if (mayHaveRemovedTrivialPhi || assumeRemover.mayHaveIntroducedTrivialPhi()) { + code.removeAllDeadAndTrivialPhis(affectedValues); + } + if (!affectedValues.isEmpty()) { + new TypeAnalysis(appView).narrowing(affectedValues); + } + if (changed) { + code.removeRedundantBlocks(); + } + assert code.isConsistentSSA(appView); + return CodeRewriterResult.hasChanged(changed); + } + + private boolean checkArgumentType(InvokeMethod invoke, int argumentIndex) { + // TODO(sgjesse): Insert cast if required. + TypeElement returnType = + TypeElement.fromDexType(invoke.getInvokedMethod().proto.returnType, maybeNull(), appView); + TypeElement argumentType = + TypeElement.fromDexType(getArgumentType(invoke, argumentIndex), maybeNull(), appView); + return appView.enableWholeProgramOptimizations() + ? argumentType.lessThanOrEqual(returnType, appView) + : argumentType.equals(returnType); + } + + private DexType getArgumentType(InvokeMethod invoke, int argumentIndex) { + if (invoke.isInvokeStatic()) { + return invoke.getInvokedMethod().proto.parameters.values[argumentIndex]; + } + if (argumentIndex == 0) { + return invoke.getInvokedMethod().holder; + } + return invoke.getInvokedMethod().proto.parameters.values[argumentIndex - 1]; + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java index be7840f..2238b82 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java
@@ -15,6 +15,7 @@ import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.code.Sub; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.utils.WorkList; import com.google.common.collect.Sets; import java.util.Set; @@ -40,7 +41,7 @@ } @Override - protected void rewriteCode(IRCode code) { + protected CodeRewriterResult rewriteCode(IRCode code) { boolean loopRemoved = false; for (BasicBlock comparisonBlockCandidate : code.blocks) { if (isComparisonBlock(comparisonBlockCandidate)) { @@ -52,6 +53,7 @@ code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); } + return CodeRewriterResult.NONE; } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ParentConstructorHoistingCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ParentConstructorHoistingCodeRewriter.java index 4f96ce7..6a7c040 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ParentConstructorHoistingCodeRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ParentConstructorHoistingCodeRewriter.java
@@ -14,6 +14,7 @@ import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.InvokeDirect; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.shaking.KeepMethodInfo; import com.android.tools.r8.utils.CollectionUtils; import com.android.tools.r8.utils.IterableUtils; @@ -48,10 +49,11 @@ } @Override - protected void rewriteCode(IRCode code) { + protected CodeRewriterResult rewriteCode(IRCode code) { for (InvokeDirect invoke : getOrComputeSideEffectFreeConstructorCalls(code)) { hoistSideEffectFreeConstructorCall(code, invoke); } + return CodeRewriterResult.NONE; } private void hoistSideEffectFreeConstructorCall(IRCode code, InvokeDirect invoke) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java index fbb3afd..e4bac3e 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java
@@ -14,6 +14,7 @@ import com.android.tools.r8.ir.code.If; import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.utils.ListUtils; import com.android.tools.r8.utils.WorkList; import com.google.common.collect.Sets; @@ -43,24 +44,22 @@ } /** - * Simplify Boolean branches for example: <code> - * boolean b = i == j; if (b) { ... } else { ... } + * Simplify Boolean branches for example: <code> boolean b = i == j; if (b) { ... } else { ... } * </code> ends up first creating a branch for the boolean b, then a second branch on b. D8/R8 - * rewrites to: <code> - * if (i == j) { ... } else { ... } + * rewrites to: <code> if (i == j) { ... } else { ... } * </code> More complex control flow are also supported to some extent, including cases where the * input of the second branch comes from a set of dependent phis, and a subset of the inputs are * known boolean values. */ @Override - protected void rewriteCode(IRCode code) { + protected CodeRewriterResult rewriteCode(IRCode code) { List<BasicBlock> candidates = computeCandidates(code); if (candidates.isEmpty()) { - return; + return CodeRewriterResult.NONE; } Map<Goto, BasicBlock> newTargets = findGotosToRetarget(candidates); if (newTargets.isEmpty()) { - return; + return CodeRewriterResult.NONE; } retargetGotos(newTargets); Set<Value> affectedValues = Sets.newIdentityHashSet(); @@ -74,6 +73,7 @@ } code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); + return CodeRewriterResult.NONE; } private void retargetGotos(Map<Goto, BasicBlock> newTargets) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java index cbce46f..77104d5 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java
@@ -28,6 +28,7 @@ import com.android.tools.r8.ir.code.InvokeStatic; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.MethodProcessor; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.ir.optimize.CodeRewriter; import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations; import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations; @@ -54,7 +55,7 @@ } @Override - protected void rewriteCode( + protected CodeRewriterResult rewriteCode( IRCode code, MethodProcessor methodProcessor, MethodProcessingContext methodProcessingContext) { @@ -124,6 +125,7 @@ } code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); + return CodeRewriterResult.NONE; } enum RemoveCheckCastInstructionIfTrivialResult {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialGotosCollapser.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialGotosCollapser.java index 4f8ee2e..a08d82a 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialGotosCollapser.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialGotosCollapser.java
@@ -10,6 +10,7 @@ import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.If; import com.android.tools.r8.ir.code.Switch; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -33,7 +34,7 @@ } @Override - protected void rewriteCode(IRCode code) { + protected CodeRewriterResult rewriteCode(IRCode code) { assert code.isConsistentGraph(appView); List<BasicBlock> blocksToRemove = new ArrayList<>(); // Rewrite all non-fallthrough targets to the end of trivial goto chains and remove @@ -73,6 +74,7 @@ } assert removedTrivialGotos(code); assert code.isConsistentGraph(appView); + return CodeRewriterResult.NONE; } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/result/CodeRewriterResult.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/result/CodeRewriterResult.java new file mode 100644 index 0000000..68d5c0d --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/result/CodeRewriterResult.java
@@ -0,0 +1,40 @@ +// Copyright (c) 2023, 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.conversion.passes.result; + +import com.android.tools.r8.errors.Unreachable; + +public interface CodeRewriterResult { + + CodeRewriterResult NO_CHANGE = new DefaultCodeRewriterResult(false); + CodeRewriterResult HAS_CHANGED = new DefaultCodeRewriterResult(true); + CodeRewriterResult NONE = + new CodeRewriterResult() { + @Override + public boolean hasChanged() { + throw new Unreachable(); + } + }; + + static CodeRewriterResult hasChanged(boolean hasChanged) { + return hasChanged ? HAS_CHANGED : NO_CHANGE; + } + + class DefaultCodeRewriterResult implements CodeRewriterResult { + + private final boolean hasChanged; + + public DefaultCodeRewriterResult(boolean hasChanged) { + this.hasChanged = hasChanged; + } + + @Override + public boolean hasChanged() { + return hasChanged; + } + } + + boolean hasChanged(); +}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java index f715e77..bfcb585 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -1910,8 +1910,8 @@ } @Override - public DexProto getProto(DexItemFactory itemFactory) { - return itemFactory.prependTypeToProto(receiverType, super.getProto(itemFactory)); + public DexProto getProto(DexItemFactory factory) { + return method.getProto().prependParameter(receiverType, factory); } }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java index 9a75734..5460d4f 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
@@ -225,10 +225,7 @@ ProgramMethod context, MethodProcessingContext methodProcessingContext) { DexMethod method = invoke.getMethod(); - DexProto newProto = - invoke.isInvokeStatic() - ? method.proto - : factory.prependTypeToProto(method.getHolderType(), method.getProto()); + DexProto newProto = factory.prependHolderToProtoIf(method, !invoke.isInvokeStatic()); DexMethod returnConversion = computeReturnConversion( method, false, eventConsumer, context, methodProcessingContext::createUniqueContext);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java index 422dc72..7ac6713 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
@@ -17,7 +17,6 @@ import com.android.tools.r8.graph.Code; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedMethod; -import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexProto; @@ -429,11 +428,9 @@ public InterfaceProcessorNestedGraphLens( AppView<?> appView, - BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap, BidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod> methodMap, - Map<DexType, DexType> typeMap, BidirectionalOneToOneMap<DexMethod, DexMethod> extraNewMethodSignatures) { - super(appView, fieldMap, methodMap, typeMap); + super(appView, NestedGraphLens.EMPTY_FIELD_MAP, methodMap, NestedGraphLens.EMPTY_TYPE_MAP); this.extraNewMethodSignatures = extraNewMethodSignatures; } @@ -474,11 +471,10 @@ @Override public InterfaceProcessorNestedGraphLens build(AppView<?> appView) { - if (fieldMap.isEmpty() && methodMap.isEmpty() && extraNewMethodSignatures.isEmpty()) { + if (methodMap.isEmpty() && extraNewMethodSignatures.isEmpty()) { return null; } - return new InterfaceProcessorNestedGraphLens( - appView, fieldMap, methodMap, typeMap, extraNewMethodSignatures); + return new InterfaceProcessorNestedGraphLens(appView, methodMap, extraNewMethodSignatures); } } }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java index 1653ae2..6646ce0 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java
@@ -85,6 +85,7 @@ assert done; irConverter.removeDeadCodeAndFinalizeIR( irCode, OptimizationFeedbackIgnore.getInstance(), Timing.empty()); + irConverter.finalizeLirMethodToOutputFormat(programMethod); } public void rewriteRecordFieldArray(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java index e1f8d9b..e32a6f5 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
@@ -344,10 +344,11 @@ DexEncodedMethod method, IRCode code, DeadCodeRemover deadCodeRemover, Timing timing) { if (enabled) { timing.begin("Rewrite assertions"); - if (runInternal(method, code)) { + boolean needsDeadCodeRemoval = runInternal(method, code); + code.removeRedundantBlocks(); + if (needsDeadCodeRemoval) { deadCodeRemover.run(code, timing); } - code.removeRedundantBlocks(); assert code.isConsistentSSA(appView); timing.end(); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java index 8d2b4bb..9c94862 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
@@ -301,6 +301,9 @@ fieldsWithStaticValues.forEach(DexEncodedField::setStaticValue); } + if (!fieldsWithStaticValues.isEmpty()) { + code.removeRedundantBlocks(); + } return new ClassInitializerDefaultsResult(fieldsWithStaticValues); }
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 4984711..e696879 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
@@ -5,13 +5,11 @@ package com.android.tools.r8.ir.optimize; import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull; -import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DebugLocalInfo; import com.android.tools.r8.graph.DexClass; -import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; @@ -35,16 +33,13 @@ import com.android.tools.r8.ir.code.InstructionIterator; import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.InvokeInterface; -import com.android.tools.r8.ir.code.InvokeMethod; import com.android.tools.r8.ir.code.InvokeVirtual; import com.android.tools.r8.ir.code.Move; import com.android.tools.r8.ir.code.Position; import com.android.tools.r8.ir.code.Position.SyntheticPosition; import com.android.tools.r8.ir.code.StaticGet; import com.android.tools.r8.ir.code.Value; -import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo; import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator; -import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.LazyBox; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; @@ -59,7 +54,6 @@ import it.unimi.dsi.fastutil.longs.Long2ReferenceMap; import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.ListIterator; import java.util.Set; @@ -68,11 +62,9 @@ private final AppView<?> appView; private final DexItemFactory dexItemFactory; - private final InternalOptions options; public CodeRewriter(AppView<?> appView) { this.appView = appView; - this.options = appView.options(); this.dexItemFactory = appView.dexItemFactory(); } @@ -134,112 +126,6 @@ assert Streams.stream(code.instructions()).noneMatch(Instruction::isAssume); } - private boolean checkArgumentType(InvokeMethod invoke, int argumentIndex) { - // TODO(sgjesse): Insert cast if required. - TypeElement returnType = - TypeElement.fromDexType(invoke.getInvokedMethod().proto.returnType, maybeNull(), appView); - TypeElement argumentType = - TypeElement.fromDexType(getArgumentType(invoke, argumentIndex), maybeNull(), appView); - return appView.enableWholeProgramOptimizations() - ? argumentType.lessThanOrEqual(returnType, appView) - : argumentType.equals(returnType); - } - - private DexType getArgumentType(InvokeMethod invoke, int argumentIndex) { - if (invoke.isInvokeStatic()) { - return invoke.getInvokedMethod().proto.parameters.values[argumentIndex]; - } - if (argumentIndex == 0) { - return invoke.getInvokedMethod().holder; - } - return invoke.getInvokedMethod().proto.parameters.values[argumentIndex - 1]; - } - - // Replace result uses for methods where something is known about what is returned. - public boolean rewriteMoveResult(IRCode code) { - if (options.isGeneratingClassFiles() || !code.metadata().mayHaveInvokeMethod()) { - return false; - } - - AssumeRemover assumeRemover = new AssumeRemover(appView, code); - boolean changed = false; - boolean mayHaveRemovedTrivialPhi = false; - Set<BasicBlock> blocksToBeRemoved = Sets.newIdentityHashSet(); - ListIterator<BasicBlock> blockIterator = code.listIterator(); - while (blockIterator.hasNext()) { - BasicBlock block = blockIterator.next(); - if (blocksToBeRemoved.contains(block)) { - continue; - } - - InstructionListIterator iterator = block.listIterator(code); - while (iterator.hasNext()) { - InvokeMethod invoke = iterator.next().asInvokeMethod(); - if (invoke == null || !invoke.hasOutValue() || invoke.outValue().hasLocalInfo()) { - continue; - } - - // Check if the invoked method is known to return one of its arguments. - DexClassAndMethod target = invoke.lookupSingleTarget(appView, code.context()); - if (target == null) { - continue; - } - - MethodOptimizationInfo optimizationInfo = target.getDefinition().getOptimizationInfo(); - if (!optimizationInfo.returnsArgument()) { - continue; - } - - int argumentIndex = optimizationInfo.getReturnedArgument(); - // Replace the out value of the invoke with the argument and ignore the out value. - if (argumentIndex < 0 || !checkArgumentType(invoke, argumentIndex)) { - continue; - } - - Value argument = invoke.arguments().get(argumentIndex); - Value outValue = invoke.outValue(); - assert outValue.verifyCompatible(argument.outType()); - - // Make sure that we are only narrowing information here. Note, in cases where we cannot - // find the definition of types, computing lessThanOrEqual will return false unless it is - // object. - if (!argument.getType().lessThanOrEqual(outValue.getType(), appView)) { - continue; - } - - Set<Value> affectedValues = - argument.getType().equals(outValue.getType()) - ? Collections.emptySet() - : outValue.affectedValues(); - - assumeRemover.markAssumeDynamicTypeUsersForRemoval(outValue); - mayHaveRemovedTrivialPhi |= outValue.numberOfPhiUsers() > 0; - outValue.replaceUsers(argument); - invoke.setOutValue(null); - changed = true; - - if (!affectedValues.isEmpty()) { - new TypeAnalysis(appView).narrowing(affectedValues); - } - } - } - assumeRemover.removeMarkedInstructions(blocksToBeRemoved).finish(); - Set<Value> affectedValues = Sets.newIdentityHashSet(); - if (!blocksToBeRemoved.isEmpty()) { - code.removeBlocks(blocksToBeRemoved); - code.removeAllDeadAndTrivialPhis(affectedValues); - assert code.getUnreachableBlocks().isEmpty(); - } else if (mayHaveRemovedTrivialPhi || assumeRemover.mayHaveIntroducedTrivialPhi()) { - code.removeAllDeadAndTrivialPhis(affectedValues); - } - if (!affectedValues.isEmpty()) { - new TypeAnalysis(appView).narrowing(affectedValues); - } - code.removeRedundantBlocks(); - assert code.isConsistentSSA(appView); - return changed; - } - public static void removeOrReplaceByDebugLocalWrite( Instruction currentInstruction, InstructionListIterator it, Value inValue, Value outValue) { if (outValue.hasLocalInfo() && outValue.getLocalInfo() != inValue.getLocalInfo()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java index a207486..e9b180c 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -21,6 +21,7 @@ import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.code.ValueIsDeadAnalysis; import com.android.tools.r8.ir.conversion.passes.BranchSimplifier; +import com.android.tools.r8.ir.conversion.passes.MoveResultRewriter; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.Box; import com.android.tools.r8.utils.IterableUtils; @@ -35,21 +36,15 @@ public class DeadCodeRemover { private final AppView<?> appView; - private final CodeRewriter codeRewriter; - public DeadCodeRemover(AppView<?> appView, CodeRewriter codeRewriter) { + public DeadCodeRemover(AppView<?> appView) { this.appView = appView; - this.codeRewriter = codeRewriter; - } - - public CodeRewriter getCodeRewriter() { - return codeRewriter; } public void run(IRCode code, Timing timing) { timing.begin("Remove dead code"); - codeRewriter.rewriteMoveResult(code); + new MoveResultRewriter(appView).run(code, timing); BranchSimplifier branchSimplifier = new BranchSimplifier(appView); @@ -75,7 +70,7 @@ } public boolean verifyNoDeadCode(IRCode code) { - assert !codeRewriter.rewriteMoveResult(code); + assert !new MoveResultRewriter(appView).run(code, Timing.empty()).hasChanged(); assert !removeUnneededCatchHandlers(code); ValueIsDeadAnalysis valueIsDeadAnalysis = new ValueIsDeadAnalysis(appView, code); for (BasicBlock block : code.blocks) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java deleted file mode 100644 index bd0376b..0000000 --- a/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java +++ /dev/null
@@ -1,276 +0,0 @@ -// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. -package com.android.tools.r8.ir.optimize; - -import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexClass; -import com.android.tools.r8.graph.DexMember; -import com.android.tools.r8.graph.DexType; -import com.android.tools.r8.graph.SubtypingInfo; -import com.android.tools.r8.graph.TopDownClassHierarchyTraversal; -import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.android.tools.r8.utils.ThreadUtils; -import com.android.tools.r8.utils.Timing; -import com.android.tools.r8.utils.WorkList; -import com.google.common.base.Equivalence; -import com.google.common.base.Equivalence.Wrapper; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Deque; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; -import java.util.function.BiFunction; -import java.util.function.Predicate; - -// Per-class collection of member signatures. -public abstract class MemberPoolCollection<R extends DexMember<?, R>> { - - final Equivalence<R> equivalence; - final AppView<AppInfoWithLiveness> appView; - final SubtypingInfo subtypingInfo; - final Map<DexClass, MemberPool<R>> memberPools = new ConcurrentHashMap<>(); - - MemberPoolCollection( - AppView<AppInfoWithLiveness> appView, - Equivalence<R> equivalence, - SubtypingInfo subtypingInfo) { - this.appView = appView; - this.equivalence = equivalence; - this.subtypingInfo = subtypingInfo; - } - - public void buildAll(ExecutorService executorService, Timing timing) throws ExecutionException { - timing.begin("Building member pool collection"); - try { - List<Future<?>> futures = new ArrayList<>(); - - // Generate a future for each class that will build the member pool collection for the - // corresponding class. Note that, we visit the classes using a top-down class hierarchy - // traversal, since this ensures that we do not visit library classes that are not - // reachable from any program class. - TopDownClassHierarchyTraversal.forAllClasses(appView) - .visit(appView.appInfo().classes(), clazz -> submit(clazz, futures, executorService)); - ThreadUtils.awaitFutures(futures); - } finally { - timing.end(); - } - } - - public MemberPool<R> buildForHierarchy( - DexClass clazz, ExecutorService executorService, Timing timing) throws ExecutionException { - timing.begin("Building member pool collection"); - try { - List<Future<?>> futures = new ArrayList<>(); - submitAll( - getAllSuperTypesInclusive(clazz, memberPools::containsKey), futures, executorService); - submitAll(getAllSubTypesExclusive(clazz, memberPools::containsKey), futures, executorService); - ThreadUtils.awaitFutures(futures); - } finally { - timing.end(); - } - return get(clazz); - } - - public boolean hasPool(DexClass clazz) { - return memberPools.containsKey(clazz); - } - - public MemberPool<R> get(DexClass clazz) { - assert hasPool(clazz); - return memberPools.get(clazz); - } - - public boolean markIfNotSeen(DexClass clazz, R reference) { - MemberPool<R> memberPool = get(clazz); - Wrapper<R> key = equivalence.wrap(reference); - if (memberPool.hasSeen(key)) { - return true; - } - memberPool.seen(key); - return false; - } - - private void submitAll( - Iterable<? extends DexClass> classes, - List<Future<?>> futures, - ExecutorService executorService) { - for (DexClass clazz : classes) { - submit(clazz, futures, executorService); - } - } - - private void submit(DexClass clazz, List<Future<?>> futures, ExecutorService executorService) { - futures.add(executorService.submit(computeMemberPoolForClass(clazz))); - } - - abstract Runnable computeMemberPoolForClass(DexClass clazz); - - private Set<DexClass> getAllSuperTypesInclusive( - DexClass subject, Predicate<DexClass> stoppingCriterion) { - Set<DexClass> superTypes = new HashSet<>(); - Deque<DexClass> worklist = new ArrayDeque<>(); - worklist.add(subject); - while (!worklist.isEmpty()) { - DexClass clazz = worklist.pop(); - if (stoppingCriterion.test(clazz)) { - continue; - } - if (superTypes.add(clazz)) { - if (clazz.superType != null) { - addNonNull(worklist, appView.definitionFor(clazz.superType)); - } - for (DexType interfaceType : clazz.interfaces.values) { - addNonNull(worklist, appView.definitionFor(interfaceType)); - } - } - } - return superTypes; - } - - private Set<DexClass> getAllSubTypesExclusive( - DexClass subject, Predicate<DexClass> stoppingCriterion) { - Set<DexClass> subTypes = new HashSet<>(); - Deque<DexClass> worklist = new ArrayDeque<>(); - subtypingInfo.forAllImmediateExtendsSubtypes( - subject.type, type -> addNonNull(worklist, appView.definitionFor(type))); - subtypingInfo.forAllImmediateImplementsSubtypes( - subject.type, type -> addNonNull(worklist, appView.definitionFor(type))); - while (!worklist.isEmpty()) { - DexClass clazz = worklist.pop(); - if (stoppingCriterion.test(clazz)) { - continue; - } - if (subTypes.add(clazz)) { - subtypingInfo.forAllImmediateExtendsSubtypes( - clazz.type, type -> addNonNull(worklist, appView.definitionFor(type))); - subtypingInfo.forAllImmediateImplementsSubtypes( - clazz.type, type -> addNonNull(worklist, appView.definitionFor(type))); - } - } - return subTypes; - } - - public static class MemberPool<T> { - - private final DexClass clazz; - private final Equivalence<T> equivalence; - private MemberPool<T> superType; - private final Set<MemberPool<T>> interfaces = new HashSet<>(); - private final Set<MemberPool<T>> subTypes = new HashSet<>(); - private final Set<Wrapper<T>> memberPool = new HashSet<>(); - - MemberPool(Equivalence<T> equivalence, DexClass clazz) { - this.equivalence = equivalence; - this.clazz = clazz; - } - - synchronized void linkSupertype(MemberPool<T> superType) { - assert this.superType == null; - this.superType = superType; - } - - synchronized void linkSubtype(MemberPool<T> subType) { - boolean added = subTypes.add(subType); - assert added; - } - - synchronized void linkInterface(MemberPool<T> itf) { - boolean added = interfaces.add(itf); - assert added; - } - - public void seen(T member) { - seen(equivalence.wrap(member)); - } - - public synchronized void seen(Wrapper<T> member) { - boolean added = memberPool.add(member); - assert added; - } - - public boolean hasSeen(Wrapper<T> member) { - return fold(member, false, true, (t, ignored) -> true); - } - - public boolean hasSeenDirectly(Wrapper<T> member) { - return here(member, false, (t, ignored) -> true); - } - - public boolean hasSeenStrictlyAbove(Wrapper<T> member) { - return above(member, false, false, true, (t, ignored) -> true); - } - - public boolean hasSeenStrictlyBelow(Wrapper<T> member) { - return below(member, false, true, (t, ignored) -> true); - } - - private <S> S above( - Wrapper<T> member, - boolean inclusive, - S value, - S terminator, - BiFunction<DexClass, S, S> accumulator) { - WorkList<MemberPool<T>> workList = WorkList.newIdentityWorkList(this); - while (workList.hasNext()) { - MemberPool<T> next = workList.next(); - if (inclusive) { - value = next.here(member, value, accumulator); - if (value == terminator) { - return value; - } - } - inclusive = true; - if (next.superType != null) { - workList.addIfNotSeen(next.superType); - } - workList.addIfNotSeen(next.interfaces); - } - return value; - } - - private <S> S here(Wrapper<T> member, S value, BiFunction<DexClass, S, S> accumulator) { - if (memberPool.contains(member)) { - return accumulator.apply(clazz, value); - } - return value; - } - - public <S> S below( - Wrapper<T> member, S value, S terminator, BiFunction<DexClass, S, S> accumulator) { - WorkList<MemberPool<T>> workList = WorkList.newIdentityWorkList(this.subTypes); - while (workList.hasNext()) { - MemberPool<T> next = workList.next(); - value = next.here(member, value, accumulator); - if (value == terminator) { - return value; - } - workList.addIfNotSeen(next.interfaces); - workList.addIfNotSeen(next.subTypes); - } - return value; - } - - public <S> S fold( - Wrapper<T> member, S initialValue, S terminator, BiFunction<DexClass, S, S> accumulator) { - S value = above(member, true, initialValue, terminator, accumulator); - if (value == terminator) { - return value; - } - return below(member, initialValue, terminator, accumulator); - } - } - - private static <T> void addNonNull(Collection<T> collection, T item) { - if (item != null) { - collection.add(item); - } - } -}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java deleted file mode 100644 index 78eb343..0000000 --- a/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java +++ /dev/null
@@ -1,80 +0,0 @@ -// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -package com.android.tools.r8.ir.optimize; - -import com.android.tools.r8.graph.AppView; -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 com.android.tools.r8.graph.SubtypingInfo; -import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.android.tools.r8.utils.MethodSignatureEquivalence; -import com.google.common.base.Predicates; -import java.util.function.Predicate; - -// Per-class collection of method signatures. -// -// Example use cases: -// *) in publicizer, -// to determine if a private method does not collide with methods in that class hierarchy. -// *) in vertical class merger, -// before moving a default interface method to its subtype, check if it does not collide with one -// in the given class hierarchy. -public class MethodPoolCollection extends MemberPoolCollection<DexMethod> { - - private final Predicate<DexEncodedMethod> methodTester; - - public MethodPoolCollection(AppView<AppInfoWithLiveness> appView, SubtypingInfo subtypingInfo) { - this(appView, subtypingInfo, Predicates.alwaysTrue()); - } - - public MethodPoolCollection( - AppView<AppInfoWithLiveness> appView, - SubtypingInfo subtypingInfo, - Predicate<DexEncodedMethod> methodTester) { - super(appView, MethodSignatureEquivalence.get(), subtypingInfo); - this.methodTester = methodTester; - } - - public static boolean excludesPrivateInstanceMethod(DexEncodedMethod method) { - return !method.isPrivateMethod() || method.isStatic(); - } - - @Override - Runnable computeMemberPoolForClass(DexClass clazz) { - return () -> { - MemberPool<DexMethod> methodPool = - memberPools.computeIfAbsent(clazz, k -> new MemberPool<>(equivalence, k)); - clazz.forEachMethod( - encodedMethod -> { - if (methodTester.test(encodedMethod)) { - methodPool.seen(equivalence.wrap(encodedMethod.getReference())); - } - }); - if (clazz.superType != null) { - DexClass superClazz = appView.definitionFor(clazz.superType); - if (superClazz != null) { - MemberPool<DexMethod> superPool = - memberPools.computeIfAbsent( - superClazz, k -> new MemberPool<>(equivalence, superClazz)); - superPool.linkSubtype(methodPool); - methodPool.linkSupertype(superPool); - } - } - if (clazz.isInterface()) { - for (DexType subtype : subtypingInfo.allImmediateSubtypes(clazz.type)) { - DexClass subClazz = appView.definitionFor(subtype); - if (subClazz != null) { - MemberPool<DexMethod> childPool = - memberPools.computeIfAbsent(subClazz, k -> new MemberPool<>(equivalence, subClazz)); - methodPool.linkSubtype(childPool); - childPool.linkInterface(methodPool); - } - } - } - }; - } -}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java index 370a5d7..02e3b25 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java
@@ -66,6 +66,7 @@ code.blocks.add(rethrowBlock); // Add catch handler to the block containing the last recursive call. newBlock.appendCatchHandler(rethrowBlock, guard); + code.removeRedundantBlocks(); } } @@ -77,7 +78,7 @@ } private static void rewriteSwitchForMaxIntOnly(IRCode code, AppView<?> appView) { - boolean needToSplitCriticalEdges = false; + boolean hasChanged = false; BranchSimplifier branchSimplifier = new BranchSimplifier(appView); ListIterator<BasicBlock> blocksIterator = code.listIterator(); while (blocksIterator.hasNext()) { @@ -107,7 +108,7 @@ ImmutableList.of(newSwitchSequences), outliers); } - needToSplitCriticalEdges = true; + hasChanged = true; } } } @@ -116,8 +117,9 @@ // Rewriting of switches introduces new branching structure. It relies on critical edges // being split on the way in but does not maintain this property. We therefore split // critical edges at exit. - if (needToSplitCriticalEdges) { + if (hasChanged) { code.splitCriticalEdges(); + code.removeRedundantBlocks(); } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java index 12028fb..cf4a6fe 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
@@ -136,7 +136,7 @@ break; case RETURN: - // Wil not materialize after class inlining. + // Will not materialize after class inlining. if (appView.options().isGeneratingClassFiles()) { result++; } else {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java index 53f7f63..dd803f9 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
@@ -27,10 +27,13 @@ import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.BooleanUtils; +import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap; +import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap; import com.android.tools.r8.utils.collections.BidirectionalOneToManyRepresentativeHashMap; import com.android.tools.r8.utils.collections.BidirectionalOneToManyRepresentativeMap; import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap; import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap; +import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneRepresentativeMap; import com.android.tools.r8.utils.collections.MutableBidirectionalOneToManyRepresentativeMap; import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap; import com.google.common.collect.ImmutableMap; @@ -51,7 +54,7 @@ AppView<?> appView, BidirectionalOneToOneMap<DexField, DexField> fieldMap, BidirectionalOneToManyRepresentativeMap<DexMethod, DexMethod> renamedSignatures, - Map<DexType, DexType> typeMap, + BidirectionalManyToOneRepresentativeMap<DexType, DexType> typeMap, Map<DexMethod, DexMethod> methodMap, Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod, Set<DexMethod> dispatchMethods) { @@ -232,7 +235,8 @@ private final DexItemFactory dexItemFactory; private final AbstractValueFactory abstractValueFactory; - private final Map<DexType, DexType> typeMap = new IdentityHashMap<>(); + private final MutableBidirectionalManyToOneRepresentativeMap<DexType, DexType> typeMap = + BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap(); private final MutableBidirectionalOneToOneMap<DexField, DexField> newFieldSignatures = new BidirectionalOneToOneHashMap<>(); private final MutableBidirectionalOneToManyRepresentativeMap<DexMethod, DexMethod>
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java index c94b03e..46ed560 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
@@ -34,6 +34,7 @@ import com.android.tools.r8.ir.code.StaticGet; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.passes.CodeRewriterPass; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.ArrayUtils; @@ -60,8 +61,9 @@ } @Override - protected void rewriteCode(IRCode code) { + protected CodeRewriterResult rewriteCode(IRCode code) { rewriteConstantEnumMethodCalls(code); + return CodeRewriterResult.NONE; } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java index b1e1a69..f596d2a 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -148,6 +148,16 @@ } @Override + public boolean hasParametersWithBitwiseOperations() { + return false; + } + + @Override + public BitSet getParametersWithBitwiseOperations() { + return null; + } + + @Override public BitSet getUnusedArguments() { return null; }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java index 0d42b62..31775ae 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -80,6 +80,10 @@ public abstract SimpleInliningConstraint getSimpleInliningConstraint(); + public abstract boolean hasParametersWithBitwiseOperations(); + + public abstract BitSet getParametersWithBitwiseOperations(); + public final boolean hasUnusedArguments() { assert getUnusedArguments() == null || !getUnusedArguments().isEmpty(); return getUnusedArguments() != null;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java index ba4ba64..892f856 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
@@ -101,6 +101,7 @@ import com.android.tools.r8.kotlin.Kotlin; import com.android.tools.r8.kotlin.Kotlin.Intrinsics; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.Timing; import com.google.common.collect.Sets; @@ -156,6 +157,7 @@ computeReturnValueOnlyDependsOnArguments(feedback, definition, code, timing); BitSet nonNullParamOrThrow = computeNonNullParamOrThrow(feedback, definition, code, timing); computeNonNullParamOnNormalExits(feedback, code, nonNullParamOrThrow, timing); + computeParametersWithBitwiseOperations(method, code, feedback, timing); computeUnusedArguments(method, code, feedback, timing); } @@ -1173,6 +1175,36 @@ return true; } + private void computeParametersWithBitwiseOperations( + ProgramMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) { + timing.begin("Compute parameters with bitwise operations"); + computeParametersWithBitwiseOperations(method, code, feedback); + timing.end(); + } + + private void computeParametersWithBitwiseOperations( + ProgramMethod method, IRCode code, OptimizationFeedback feedback) { + BitSet parametersWithBitwiseOperations = new BitSet(method.getParameters().size()); + InstructionIterator instructionIterator = code.entryBlock().iterator(); + Argument argument = instructionIterator.next().asArgument(); + while (argument != null) { + if (hasBitwiseOperation(argument)) { + int parameterIndex = + argument.getIndex() - BooleanUtils.intValue(method.getDefinition().isInstance()); + parametersWithBitwiseOperations.set(parameterIndex); + } + argument = instructionIterator.next().asArgument(); + } + if (!parametersWithBitwiseOperations.isEmpty()) { + feedback.setParametersWithBitwiseOperations(method, parametersWithBitwiseOperations); + } + } + + private boolean hasBitwiseOperation(Argument argument) { + return argument.getOutType().isInt() + && argument.outValue().hasUserThatMatches(Instruction::isAnd); + } + private void computeUnusedArguments( ProgramMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) { timing.begin("Compute unused arguments"); @@ -1191,6 +1223,8 @@ } argument = instructionIterator.next().asArgument(); } - feedback.setUnusedArguments(method, unusedArguments); + if (!unusedArguments.isEmpty()) { + feedback.setUnusedArguments(method, unusedArguments); + } } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoFixer.java index 0044499..4279b3c 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoFixer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoFixer.java
@@ -43,5 +43,5 @@ SimpleInliningConstraint constraint, SimpleInliningConstraintFactory factory); - public abstract BitSet fixupUnusedArguments(BitSet unusedArguments); + public abstract BitSet fixupArguments(BitSet arguments); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java index 7dcb035..555bd6f 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
@@ -79,6 +79,7 @@ NeverSimpleInliningConstraint.getInstance(); private int maxRemovedAndroidLogLevel = MaximumRemovedAndroidLogLevelRule.NOT_SET; + private BitSet parametersWithBitwiseOperations = null; private BitSet unusedArguments = null; // To reduce the memory footprint of UpdatableMethodOptimizationInfo, all the boolean fields are @@ -159,6 +160,7 @@ .fixupNonNullParamOnNormalExits(fixer) .fixupNonNullParamOrThrow(fixer) .fixupReturnedArgumentIndex(fixer) + .fixupParametersWithBitwiseOperations(fixer) .fixupSimpleInliningConstraint(appView, fixer) .fixupUnusedArguments(fixer); } @@ -435,13 +437,41 @@ } @Override + public boolean hasParametersWithBitwiseOperations() { + return parametersWithBitwiseOperations != null; + } + + @Override + public BitSet getParametersWithBitwiseOperations() { + return parametersWithBitwiseOperations; + } + + public void setParametersWithBitwiseOperations(BitSet parametersWithBitwiseOperations) { + if (parametersWithBitwiseOperations != null && !parametersWithBitwiseOperations.isEmpty()) { + this.parametersWithBitwiseOperations = parametersWithBitwiseOperations; + } else { + this.parametersWithBitwiseOperations = null; + } + } + + public MutableMethodOptimizationInfo fixupParametersWithBitwiseOperations( + MethodOptimizationInfoFixer fixer) { + return fixupParametersWithBitwiseOperations(fixer.fixupArguments(unusedArguments)); + } + + public MutableMethodOptimizationInfo fixupParametersWithBitwiseOperations( + BitSet parametersWithBitwiseOperations) { + setParametersWithBitwiseOperations(parametersWithBitwiseOperations); + return this; + } + + @Override public BitSet getUnusedArguments() { return unusedArguments; } public MutableMethodOptimizationInfo fixupUnusedArguments(MethodOptimizationInfoFixer fixer) { - fixupUnusedArguments(fixer.fixupUnusedArguments(unusedArguments)); - return this; + return fixupUnusedArguments(fixer.fixupArguments(unusedArguments)); } public MutableMethodOptimizationInfo fixupUnusedArguments(BitSet unusedArguments) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java index 2148519..bbb1cec 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -277,6 +277,13 @@ } @Override + public void setParametersWithBitwiseOperations( + ProgramMethod method, BitSet parametersWithBitwiseOperations) { + getMethodOptimizationInfoForUpdating(method) + .setParametersWithBitwiseOperations(parametersWithBitwiseOperations); + } + + @Override public synchronized void setUnusedArguments(ProgramMethod method, BitSet unusedArguments) { getMethodOptimizationInfoForUpdating(method).setUnusedArguments(unusedArguments); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java index 3f46ce1..c3ff56a 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -126,6 +126,10 @@ public void classInitializerMayBePostponed(DexEncodedMethod method) {} @Override + public void setParametersWithBitwiseOperations( + ProgramMethod method, BitSet parametersWithBitwiseOperations) {} + + @Override public void setUnusedArguments(ProgramMethod method, BitSet unusedArguments) {} // Unset methods.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java index a0c9863..51c5bb6 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -213,6 +213,15 @@ } @Override + public void setParametersWithBitwiseOperations( + ProgramMethod method, BitSet parametersWithBitwiseOperations) { + method + .getDefinition() + .getMutableOptimizationInfo() + .setParametersWithBitwiseOperations(parametersWithBitwiseOperations); + } + + @Override public void setUnusedArguments(ProgramMethod method, BitSet unusedArguments) { method.getDefinition().getMutableOptimizationInfo().setUnusedArguments(unusedArguments); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java index 9cd00c7..1cedcc3 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java
@@ -62,6 +62,7 @@ import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions; import com.android.tools.r8.ir.conversion.MethodProcessorEventConsumer; import com.android.tools.r8.ir.conversion.SourceCode; +import com.android.tools.r8.ir.conversion.passes.MoveResultRewriter; import com.android.tools.r8.ir.optimize.CodeRewriter; import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; import com.android.tools.r8.ir.optimize.InliningConstraints; @@ -1368,7 +1369,7 @@ applyOutliningCandidate(code); converter.printMethod(code, "IR after outlining (SSA)", null); converter.memberValuePropagation.run(code); - converter.codeRewriter.rewriteMoveResult(code); + new MoveResultRewriter(appView).run(code, Timing.empty()); converter.removeDeadCodeAndFinalizeIR( code, OptimizationFeedbackIgnore.getInstance(), Timing.empty()); }, @@ -1399,7 +1400,7 @@ // optimizations needed for outlining: rewriteMoveResult() to remove out-values on // StringBuilder/StringBuffer method invocations, and removeDeadCode() to remove // unused out-values. - converter.codeRewriter.rewriteMoveResult(code); + new MoveResultRewriter(appView).run(code, Timing.empty()); converter.deadCodeRemover.run(code, Timing.empty()); CodeRewriter.removeAssumeInstructions(appView, code); consumer.accept(code);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java index 2798fb0..2ff8ec9 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
@@ -16,6 +16,7 @@ import static com.android.tools.r8.ir.optimize.string.StringBuilderNode.createToStringNode; import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument; +import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult.SuccessfulDataflowAnalysisResult; import com.android.tools.r8.ir.analysis.framework.intraprocedural.IntraProceduralDataflowAnalysisOptions; @@ -30,6 +31,8 @@ import com.android.tools.r8.ir.code.InvokeStatic; import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.passes.CodeRewriterPass; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.ir.optimize.string.StringBuilderNode.AppendNode; import com.android.tools.r8.ir.optimize.string.StringBuilderNode.ImplicitToStringNode; import com.android.tools.r8.ir.optimize.string.StringBuilderNode.InitNode; @@ -75,29 +78,33 @@ * * <p>Finally, based on all optimizations, the IR is updated to reflect the optimizations. */ -public class StringBuilderAppendOptimizer { +public class StringBuilderAppendOptimizer extends CodeRewriterPass<AppInfo> { - private final AppView<?> appView; private final StringBuilderOracle oracle; - private final IRCode code; - private static final int NUMBER_OF_MUNCHING_PASSES = 3; - private StringBuilderAppendOptimizer(AppView<?> appView, IRCode code) { - this.appView = appView; - this.code = code; + public StringBuilderAppendOptimizer(AppView<?> appView) { + super(appView); oracle = new DefaultStringBuilderOracle(appView.dexItemFactory()); } - public static void run(AppView<?> appView, IRCode code) { - new StringBuilderAppendOptimizer(appView, code).run(); + @Override + protected String getTimingId() { + return "StringBuilderAppendOptimizer"; } - private void run() { - Map<Value, StringBuilderNode> stringBuilderGraphs = computeStringBuilderGraphs(); - Map<Instruction, StringBuilderAction> actions = optimizeOnGraphs(stringBuilderGraphs); + @Override + protected boolean shouldRewriteCode(IRCode code) { + return code.metadata().mayHaveNewInstance() + || code.metadata().mayHaveInvokeMethodWithReceiver(); + } + + @Override + protected CodeRewriterResult rewriteCode(IRCode code) { + Map<Value, StringBuilderNode> stringBuilderGraphs = computeStringBuilderGraphs(code); + Map<Instruction, StringBuilderAction> actions = optimizeOnGraphs(code, stringBuilderGraphs); if (actions.isEmpty()) { - return; + return CodeRewriterResult.NO_CHANGE; } InstructionListIterator it = code.instructionListIterator(); while (it.hasNext()) { @@ -108,6 +115,7 @@ } } code.removeAllDeadAndTrivialPhis(); + return CodeRewriterResult.HAS_CHANGED; } private static class StringBuilderGraphState { @@ -137,7 +145,7 @@ * answer, for a given instruction, is a string builder value escaping and all escaped values * at the instruction. */ - private Map<Value, StringBuilderNode> computeStringBuilderGraphs() { + private Map<Value, StringBuilderNode> computeStringBuilderGraphs(IRCode code) { StringBuilderEscapeTransferFunction transferFunction = new StringBuilderEscapeTransferFunction(oracle); IntraproceduralDataflowAnalysis<StringBuilderEscapeState> analysis = @@ -516,7 +524,7 @@ * the munching and care about performance. */ private Map<Instruction, StringBuilderAction> optimizeOnGraphs( - Map<Value, StringBuilderNode> stringBuilderGraphs) { + IRCode code, Map<Value, StringBuilderNode> stringBuilderGraphs) { Map<Instruction, StringBuilderAction> actions = new IdentityHashMap<>(); // Build state to allow munching over the string builder graphs. Map<StringBuilderNode, NewInstanceNode> newInstances = new IdentityHashMap<>();
diff --git a/src/main/java/com/android/tools/r8/lightir/IR2LirConverter.java b/src/main/java/com/android/tools/r8/lightir/IR2LirConverter.java index ee53ceb..3bc2fe6 100644 --- a/src/main/java/com/android/tools/r8/lightir/IR2LirConverter.java +++ b/src/main/java/com/android/tools/r8/lightir/IR2LirConverter.java
@@ -3,7 +3,6 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.lightir; -import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.BasicBlockIterator; import com.android.tools.r8.ir.code.CatchHandlers; @@ -12,6 +11,7 @@ import com.android.tools.r8.ir.code.InstructionIterator; import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.ListUtils; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; @@ -24,24 +24,22 @@ public class IR2LirConverter<EV> { - private final DexItemFactory factory; private final IRCode irCode; private final LirEncodingStrategy<Value, EV> strategy; private final LirBuilder<Value, EV> builder; private IR2LirConverter( - DexItemFactory factory, IRCode irCode, LirEncodingStrategy<Value, EV> strategy) { - this.factory = factory; + InternalOptions options, IRCode irCode, LirEncodingStrategy<Value, EV> strategy) { this.irCode = irCode; this.strategy = strategy; this.builder = - new LirBuilder<>(irCode.context().getReference(), strategy, factory) + new LirBuilder<>(irCode.context().getReference(), strategy, options) .setMetadata(irCode.metadata()); } public static <EV> LirCode<EV> translate( - IRCode irCode, LirEncodingStrategy<Value, EV> strategy, DexItemFactory factory) { - return new IR2LirConverter<>(factory, irCode, strategy).internalTranslate(); + IRCode irCode, LirEncodingStrategy<Value, EV> strategy, InternalOptions options) { + return new IR2LirConverter<>(options, irCode, strategy).internalTranslate(); } private void recordBlock(BasicBlock block, int blockIndex) {
diff --git a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java index 8777089..4315a85 100644 --- a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java +++ b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
@@ -15,6 +15,11 @@ import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.graph.proto.ArgumentInfo; +import com.android.tools.r8.graph.proto.ArgumentInfoCollection; +import com.android.tools.r8.graph.proto.RemovedArgumentInfo; +import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription; +import com.android.tools.r8.graph.proto.RewrittenTypeInfo; import com.android.tools.r8.ir.analysis.type.Nullability; import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement; import com.android.tools.r8.ir.analysis.type.TypeElement; @@ -25,6 +30,7 @@ import com.android.tools.r8.ir.code.ArrayLength; import com.android.tools.r8.ir.code.ArrayPut; import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.CanonicalPositions; import com.android.tools.r8.ir.code.CatchHandlers; import com.android.tools.r8.ir.code.CheckCast; import com.android.tools.r8.ir.code.Cmp; @@ -116,7 +122,36 @@ LirCode<EV> lirCode, LirDecodingStrategy<Value, EV> strategy, AppView<?> appView) { - Parser<EV> parser = new Parser<>(lirCode, method.getReference(), appView, strategy); + return translate( + method, + lirCode, + strategy, + appView, + new NumberGenerator(), + null, + RewrittenPrototypeDescription.none(), + appView.graphLens().getOriginalMethodSignature(method.getReference())); + } + + public static <EV> IRCode translate( + ProgramMethod method, + LirCode<EV> lirCode, + LirDecodingStrategy<Value, EV> strategy, + AppView<?> appView, + NumberGenerator valueNumberGenerator, + Position callerPosition, + RewrittenPrototypeDescription protoChanges, + DexMethod originalMethod) { + Parser<EV> parser = + new Parser<>( + lirCode, + originalMethod, + method.getDefinition().isD8R8Synthesized(), + appView, + strategy, + valueNumberGenerator, + callerPosition, + protoChanges); parser.parseArguments(method); parser.ensureDebugInfo(); lirCode.forEach(view -> view.accept(parser)); @@ -134,8 +169,9 @@ private final AppView<?> appView; private final LirCode<EV> code; private final LirDecodingStrategy<Value, EV> strategy; - private final NumberGenerator valueNumberGenerator = new NumberGenerator(); + private final NumberGenerator valueNumberGenerator; private final NumberGenerator basicBlockNumberGenerator = new NumberGenerator(); + private final RewrittenPrototypeDescription protoChanges; private final Int2ReferenceMap<BasicBlock> blocks = new Int2ReferenceOpenHashMap<>(); @@ -145,18 +181,62 @@ private Position currentPosition; private PositionEntry nextPositionEntry = null; private int nextIndexInPositionsTable = 0; + private final PositionEntry[] positionTable; + + private final boolean buildForInlining; public Parser( LirCode<EV> code, DexMethod method, + boolean isD8R8Synthesized, AppView<?> appView, - LirDecodingStrategy<Value, EV> strategy) { + LirDecodingStrategy<Value, EV> strategy, + NumberGenerator valueNumberGenerator, + Position callerPosition, + RewrittenPrototypeDescription protoChanges) { super(code); this.appView = appView; this.code = code; this.strategy = strategy; - // Recreate the preamble position. This is active for arguments and code with no positions. - currentPosition = SyntheticPosition.builder().setLine(0).setMethod(method).build(); + this.valueNumberGenerator = valueNumberGenerator; + this.protoChanges = protoChanges; + assert protoChanges != null; + if (callerPosition == null) { + buildForInlining = false; + positionTable = code.getPositionTable(); + // Recreate the preamble position. This is active for arguments and code with no positions. + currentPosition = SyntheticPosition.builder().setLine(0).setMethod(method).build(); + } else { + buildForInlining = true; + PositionEntry[] inlineePositions = code.getPositionTable(); + Position inlineePreamble = null; + if (inlineePositions.length > 0 && inlineePositions[0].fromInstructionIndex == 0) { + inlineePreamble = inlineePositions[0].position; + } + CanonicalPositions canonicalPositions = + new CanonicalPositions( + callerPosition, + inlineePositions.length, + method, + isD8R8Synthesized, + inlineePreamble); + currentPosition = canonicalPositions.getPreamblePosition(); + positionTable = new PositionEntry[inlineePositions.length]; + for (int i = 0; i < inlineePositions.length; i++) { + PositionEntry inlineeEntry = inlineePositions[i]; + Position inlineePosition = inlineeEntry.position; + positionTable[i] = + new PositionEntry( + inlineeEntry.fromInstructionIndex, + canonicalPositions.getCanonical( + inlineePosition + .builderWithCopy() + .setCallerPosition( + canonicalPositions.canonicalizeCallerPosition( + inlineePosition.getCallerPosition())) + .build())); + } + } } @Override @@ -197,23 +277,47 @@ private void advanceNextPositionEntry() { nextPositionEntry = - nextIndexInPositionsTable < code.getPositionTable().length - ? code.getPositionTable()[nextIndexInPositionsTable++] + nextIndexInPositionsTable < positionTable.length + ? positionTable[nextIndexInPositionsTable++] : null; } public void parseArguments(ProgramMethod method) { + ArgumentInfoCollection argumentsInfo = protoChanges.getArgumentInfoCollection(); currentBlock = getBasicBlock(ENTRY_BLOCK_INDEX); boolean hasReceiverArgument = !method.getDefinition().isStatic(); - assert code.getArgumentCount() - == method.getParameters().size() + (hasReceiverArgument ? 1 : 0); + + int index = 0; if (hasReceiverArgument) { + assert argumentsInfo.getNewArgumentIndex(0) == 0; addThisArgument(method.getHolderType()); + index++; } - int index = hasReceiverArgument ? 1 : 0; - for (DexType parameter : method.getParameters()) { - addArgument(parameter, index++); + + int originalNumberOfArguments = + method.getParameters().size() + + argumentsInfo.numberOfRemovedArguments() + + method.getDefinition().getFirstNonReceiverArgumentIndex() + - protoChanges.numberOfExtraParameters(); + + int numberOfRemovedArguments = 0; + while (index < originalNumberOfArguments) { + ArgumentInfo argumentInfo = argumentsInfo.getArgumentInfo(index); + if (argumentInfo.isRemovedArgumentInfo()) { + RemovedArgumentInfo removedArgumentInfo = argumentInfo.asRemovedArgumentInfo(); + addArgument(removedArgumentInfo.getType(), index++); + numberOfRemovedArguments++; + } else if (argumentInfo.isRewrittenTypeInfo()) { + RewrittenTypeInfo rewrittenTypeInfo = argumentInfo.asRewrittenTypeInfo(); + int newArgumentIndex = argumentsInfo.getNewArgumentIndex(index, numberOfRemovedArguments); + assert method.getArgumentType(newArgumentIndex) == rewrittenTypeInfo.getNewType(); + addArgument(rewrittenTypeInfo.getOldType(), index++); + } else { + int newArgumentIndex = argumentsInfo.getNewArgumentIndex(index, numberOfRemovedArguments); + addArgument(method.getArgumentType(newArgumentIndex), index++); + } } + // Set up position state after adding arguments. advanceNextPositionEntry(); } @@ -252,8 +356,14 @@ } } } - for (int i = 0; i < peekNextInstructionIndex(); ++i) { - valueNumberGenerator.next(); + if (!buildForInlining) { + // The decoding strategy will increment this on demand when built for inlining. + // Not incrementing for normal building results in nice order of instruction index and + // value number. + int lastValueIndex = getCurrentValueIndex(); + for (int i = 0; i < lastValueIndex; ++i) { + valueNumberGenerator.next(); + } } return new IRCode( appView.options(), @@ -262,7 +372,7 @@ blockList, valueNumberGenerator, basicBlockNumberGenerator, - code.getMetadata(), + code.getMetadataForIR(), method.getOrigin(), new MutableMethodConversionOptions(appView.options())); } @@ -365,7 +475,7 @@ index, typeElement, code::getDebugLocalInfo); Argument argument = new Argument(dest, index, type.isBooleanType()); assert currentBlock != null; - assert currentPosition.isSyntheticPosition(); + assert currentPosition.isSyntheticPosition() || buildForInlining; argument.setPosition(currentPosition); currentBlock.getInstructions().add(argument); argument.setBlock(currentBlock); @@ -512,7 +622,7 @@ public void onConstClass(DexType type, boolean ignoreCompatRules) { Value dest = getOutValueForNextInstruction( - type.toTypeElement(appView, Nullability.definitelyNotNull())); + TypeElement.classClassType(appView, Nullability.definitelyNotNull())); addInstruction(new ConstClass(dest, type, ignoreCompatRules)); } @@ -727,8 +837,12 @@ @Override public void onReturn(EV value) { - addInstruction(new Return(getValue(value))); - closeCurrentBlock(); + if (protoChanges.hasBeenChangedToReturnVoid()) { + onReturnVoid(); + } else { + addInstruction(new Return(getValue(value))); + closeCurrentBlock(); + } } @Override
diff --git a/src/main/java/com/android/tools/r8/lightir/LirBuilder.java b/src/main/java/com/android/tools/r8/lightir/LirBuilder.java index f6ca030..8c8e4c5 100644 --- a/src/main/java/com/android/tools/r8/lightir/LirBuilder.java +++ b/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
@@ -37,6 +37,7 @@ import com.android.tools.r8.lightir.LirCode.PositionEntry; import com.android.tools.r8.lightir.LirCode.TryCatchTable; import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo; +import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.ListUtils; import com.google.common.collect.ImmutableList; import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; @@ -60,6 +61,7 @@ private static final long DOUBLE_0 = Double.doubleToRawLongBits(0); private static final long DOUBLE_1 = Double.doubleToRawLongBits(1); + private final boolean useDexEstimationStrategy; private final DexItemFactory factory; private final ByteArrayWriter byteWriter = new ByteArrayWriter(); private final LirWriter writer = new LirWriter(byteWriter); @@ -140,8 +142,10 @@ } } - public LirBuilder(DexMethod method, LirEncodingStrategy<V, EV> strategy, DexItemFactory factory) { - this.factory = factory; + public LirBuilder( + DexMethod method, LirEncodingStrategy<V, EV> strategy, InternalOptions options) { + useDexEstimationStrategy = options.isGeneratingDex(); + factory = options.dexItemFactory(); constants = new Reference2IntOpenHashMap<>(); positionTable = new ArrayList<>(); this.strategy = strategy; @@ -174,7 +178,9 @@ public LirBuilder<V, EV> setCurrentPosition(Position position) { assert position != null; - currentPosition = position; + if (!position.isNone()) { + currentPosition = position; + } return this; } @@ -721,7 +727,8 @@ instructionCount, tryCatchTable, debugTable, - strategy.getStrategyInfo()); + strategy.getStrategyInfo(), + useDexEstimationStrategy); } private int getCmpOpcode(NumericType type, Cmp.Bias bias) {
diff --git a/src/main/java/com/android/tools/r8/lightir/LirCode.java b/src/main/java/com/android/tools/r8/lightir/LirCode.java index f1c04ec..75a1aa3 100644 --- a/src/main/java/com/android/tools/r8/lightir/LirCode.java +++ b/src/main/java/com/android/tools/r8/lightir/LirCode.java
@@ -3,20 +3,43 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.lightir; +import com.android.tools.r8.dex.code.CfOrDexInstruction; +import com.android.tools.r8.errors.Unimplemented; +import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.ArgumentUse; +import com.android.tools.r8.graph.ClasspathMethod; +import com.android.tools.r8.graph.Code; import com.android.tools.r8.graph.DebugLocalInfo; +import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexItem; 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.graph.ProgramMethod; +import com.android.tools.r8.graph.UseRegistry; +import com.android.tools.r8.graph.bytecodemetadata.BytecodeInstructionMetadata; +import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadata; +import com.android.tools.r8.graph.lens.GraphLens; +import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription; +import com.android.tools.r8.ir.analysis.type.TypeAnalysis; import com.android.tools.r8.ir.code.CatchHandlers; +import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.IRMetadata; +import com.android.tools.r8.ir.code.NumberGenerator; import com.android.tools.r8.ir.code.Position; +import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions; +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.RetracerForCodePrinting; import com.google.common.collect.ImmutableMap; import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; import java.util.Map; import java.util.function.BiConsumer; +import java.util.function.Consumer; -public class LirCode<EV> implements Iterable<LirInstructionView> { +public class LirCode<EV> extends Code implements Iterable<LirInstructionView> { public static class PositionEntry { final int fromInstructionIndex; @@ -72,7 +95,9 @@ private final LirStrategyInfo<EV> strategyInfo; - private final IRMetadata metadata; + private final boolean useDexEstimationStrategy; + + private final IRMetadata irMetadata; /** Constant pool of items. */ private final DexItem[] constants; @@ -95,13 +120,13 @@ private final DebugLocalInfoTable<EV> debugLocalInfoTable; public static <V, EV> LirBuilder<V, EV> builder( - DexMethod method, LirEncodingStrategy<V, EV> strategy, DexItemFactory factory) { - return new LirBuilder<>(method, strategy, factory); + DexMethod method, LirEncodingStrategy<V, EV> strategy, InternalOptions options) { + return new LirBuilder<>(method, strategy, options); } /** Should be constructed using {@link LirBuilder}. */ LirCode( - IRMetadata metadata, + IRMetadata irMetadata, DexItem[] constants, PositionEntry[] positions, int argumentCount, @@ -109,8 +134,9 @@ int instructionCount, TryCatchTable tryCatchTable, DebugLocalInfoTable<EV> debugLocalInfoTable, - LirStrategyInfo<EV> strategyInfo) { - this.metadata = metadata; + LirStrategyInfo<EV> strategyInfo, + boolean useDexEstimationStrategy) { + this.irMetadata = irMetadata; this.constants = constants; this.positionTable = positions; this.argumentCount = argumentCount; @@ -119,6 +145,24 @@ this.tryCatchTable = tryCatchTable; this.debugLocalInfoTable = debugLocalInfoTable; this.strategyInfo = strategyInfo; + this.useDexEstimationStrategy = useDexEstimationStrategy; + } + + @SuppressWarnings("unchecked") + @Override + public LirCode<Integer> asLirCode() { + // TODO(b/225838009): Unchecked cast will be removed once the encoding strategy is definitive. + return (LirCode<Integer>) this; + } + + @Override + protected int computeHashCode() { + throw new Unreachable("LIR code should not be subject to hashing."); + } + + @Override + protected boolean computeEquals(Object other) { + throw new Unreachable("LIR code should not be subject to equality checks."); } public EV decodeValueIndex(int encodedValueIndex, int currentValueIndex) { @@ -143,8 +187,8 @@ return instructionCount; } - public IRMetadata getMetadata() { - return metadata; + public IRMetadata getMetadataForIR() { + return irMetadata; } public DexItem getConstantItem(int index) { @@ -172,12 +216,184 @@ } @Override + public BytecodeMetadata<? extends CfOrDexInstruction> getMetadata() { + // Bytecode metadata is recomputed when finalizing via IR. + throw new Unreachable(); + } + + @Override + public BytecodeInstructionMetadata getMetadata(CfOrDexInstruction instruction) { + // Bytecode metadata is recomputed when finalizing via IR. + throw new Unreachable(); + } + + @Override public LirIterator iterator() { return new LirIterator(new ByteArrayIterator(instructions)); } @Override + public IRCode buildIR( + ProgramMethod method, + AppView<?> appView, + Origin origin, + MutableMethodConversionOptions conversionOptions) { + LirCode<Integer> typedLir = asLirCode(); + return Lir2IRConverter.translate( + method, + typedLir, + LirStrategy.getDefaultStrategy().getDecodingStrategy(typedLir, null), + appView); + } + + @Override + public IRCode buildInliningIR( + ProgramMethod context, + ProgramMethod method, + AppView<?> appView, + GraphLens codeLens, + NumberGenerator valueNumberGenerator, + Position callerPosition, + Origin origin, + RewrittenPrototypeDescription protoChanges) { + assert valueNumberGenerator != null; + assert callerPosition != null; + assert protoChanges != null; + LirCode<Integer> typedLir = asLirCode(); + IRCode irCode = + Lir2IRConverter.translate( + method, + typedLir, + LirStrategy.getDefaultStrategy().getDecodingStrategy(typedLir, valueNumberGenerator), + appView, + valueNumberGenerator, + callerPosition, + protoChanges, + appView.graphLens().getOriginalMethodSignature(method.getReference())); + // TODO(b/225838009): Should we keep track of which code objects need to be narrowed? + // In particular, the encoding of phis does not maintain interfaces. + new TypeAnalysis(appView).narrowing(irCode); + return irCode; + } + + @Override + public void registerCodeReferences(ProgramMethod method, UseRegistry registry) { + assert registry.getTraversalContinuation().shouldContinue(); + LirUseRegistryCallback<EV> registryCallbacks = new LirUseRegistryCallback<>(this, registry); + for (LirInstructionView view : this) { + registryCallbacks.onInstructionView(view); + if (registry.getTraversalContinuation().shouldBreak()) { + return; + } + } + if (tryCatchTable != null) { + for (CatchHandlers<Integer> handler : tryCatchTable.tryCatchHandlers.values()) { + for (DexType guard : handler.getGuards()) { + registry.registerExceptionGuard(guard); + if (registry.getTraversalContinuation().shouldBreak()) { + return; + } + } + } + } + } + + @Override + public void registerCodeReferencesForDesugaring(ClasspathMethod method, UseRegistry registry) { + throw new Unimplemented(); + } + + @Override + public Int2ReferenceMap<DebugLocalInfo> collectParameterInfo( + DexEncodedMethod encodedMethod, AppView<?> appView) { + throw new Unimplemented(); + } + + @Override + public void registerArgumentReferences(DexEncodedMethod method, ArgumentUse registry) { + throw new Unimplemented(); + } + + @Override public String toString() { return new LirPrinter<>(this).prettyPrint(); } + + @Override + public String toString(DexEncodedMethod method, RetracerForCodePrinting retracer) { + // TODO(b/225838009): Add retracing to printer. + return toString(); + } + + @Override + public int estimatedDexCodeSizeUpperBoundInBytes() { + throw new Unimplemented(); + } + + @Override + public int estimatedSizeForInlining() { + if (useDexEstimationStrategy) { + LirSizeEstimation<EV> estimation = new LirSizeEstimation<>(this); + for (LirInstructionView view : this) { + estimation.onInstructionView(view); + } + return estimation.getSizeEstimate(); + } else { + // TODO(b/225838009): Currently the size estimation for CF has size one for each instruction + // (even switches!) and ignores stack instructions, thus loads to arguments are not included. + // The result is a much smaller estimate than for DEX. Once LIR is in place we should use the + // same estimate for both. + return instructionCount; + } + } + + @Override + public boolean estimatedSizeForInliningAtMost(int threshold) { + if (useDexEstimationStrategy) { + LirSizeEstimation<EV> estimation = new LirSizeEstimation<>(this); + for (LirInstructionView view : this) { + estimation.onInstructionView(view); + if (estimation.getSizeEstimate() > threshold) { + return false; + } + } + return true; + } else { + return estimatedSizeForInlining() <= threshold; + } + } + + @Override + public Code getCodeAsInlining(DexMethod caller, DexEncodedMethod callee, DexItemFactory factory) { + throw new Unimplemented(); + } + + @Override + public boolean isEmptyVoidMethod() { + for (LirInstructionView view : this) { + int opcode = view.getOpcode(); + if (opcode != LirOpcodes.RETURN && opcode != LirOpcodes.DEBUGPOS) { + return false; + } + } + return true; + } + + @Override + public boolean hasMonitorInstructions() { + for (LirInstructionView view : this) { + int opcode = view.getOpcode(); + if (opcode == LirOpcodes.MONITORENTER || opcode == LirOpcodes.MONITOREXIT) { + return true; + } + } + return false; + } + + @Override + public void forEachPosition(Consumer<Position> positionConsumer) { + for (PositionEntry entry : positionTable) { + positionConsumer.accept(entry.position); + } + } }
diff --git a/src/main/java/com/android/tools/r8/lightir/LirDecodingStrategy.java b/src/main/java/com/android/tools/r8/lightir/LirDecodingStrategy.java index 0b0a51d..f0eaeef 100644 --- a/src/main/java/com/android/tools/r8/lightir/LirDecodingStrategy.java +++ b/src/main/java/com/android/tools/r8/lightir/LirDecodingStrategy.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.graph.DebugLocalInfo; import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.NumberGenerator; import com.android.tools.r8.ir.code.Phi; import java.util.function.Function; import java.util.function.IntFunction; @@ -13,6 +14,16 @@ /** Abstraction for how to decode SSA values (and basic blocks) when reading LIR. */ public abstract class LirDecodingStrategy<V, EV> { + private final NumberGenerator valueNumberGenerator; + + public LirDecodingStrategy(NumberGenerator valueNumberGenerator) { + this.valueNumberGenerator = valueNumberGenerator; + } + + public final int getValueNumber(int encodedValueIndex) { + return valueNumberGenerator == null ? encodedValueIndex : valueNumberGenerator.next(); + } + public abstract V getValue(EV encodedValue, LirStrategyInfo<EV> strategyInfo); public abstract V getValueDefinitionForInstructionIndex(
diff --git a/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java b/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java index 6803d0b..fc1ccc6 100644 --- a/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java +++ b/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java
@@ -151,7 +151,7 @@ // int JSR = 168; // int RET = 169; int TABLESWITCH = 170; - int LOOKUPSWITCH = 171; + // int LOOKUPSWITCH = 171; // int IRETURN = 172; // int LRETURN = 173; // int FRETURN = 174; @@ -447,8 +447,7 @@ // case RET: return "RET"; case TABLESWITCH: return "TABLESWITCH"; - case LOOKUPSWITCH: - return "LOOKUPSWITCH"; + // case LOOKUPSWITCH: case ARETURN: return "ARETURN"; case RETURN:
diff --git a/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java b/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java index 444df4e..d979ada 100644 --- a/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java +++ b/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
@@ -448,13 +448,21 @@ onFieldInstruction(field); } - public abstract void onInstanceGet(DexField field, EV object); + public void onInstanceGet(DexField field, EV object) { + onFieldInstruction(field); + } - public abstract void onInstancePut(DexField field, EV object, EV value); + public void onInstancePut(DexField field, EV object, EV value) { + onFieldInstruction(field); + } - public abstract void onNewArrayEmpty(DexType type, EV size); + public void onNewArrayEmpty(DexType type, EV size) { + onInstruction(); + } - public abstract void onThrow(EV exception); + public void onThrow(EV exception) { + onInstruction(); + } public void onReturnVoid() { onInstruction(); @@ -493,9 +501,17 @@ onInstruction(); } - public abstract void onMonitorEnter(EV value); + public void onMonitorInstruction(EV value) { + onInstruction(); + } - public abstract void onMonitorExit(EV value); + public void onMonitorEnter(EV value) { + onMonitorInstruction(value); + } + + public void onMonitorExit(EV value) { + onMonitorInstruction(value); + } public void onNewUnboxedEnumInstance(DexType type, int ordinal) { onInstruction();
diff --git a/src/main/java/com/android/tools/r8/lightir/LirPrinter.java b/src/main/java/com/android/tools/r8/lightir/LirPrinter.java index 96d9cef..cfe1a08 100644 --- a/src/main/java/com/android/tools/r8/lightir/LirPrinter.java +++ b/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.graph.DexReference; import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler; import com.android.tools.r8.ir.code.IfType; import com.android.tools.r8.ir.code.MemberType; import com.android.tools.r8.ir.code.NumericType; @@ -61,7 +62,7 @@ } private String fmtValueIndex(EV valueIndex) { - return valueIndex.toString(); + return "v" + valueIndex.toString(); } private String fmtInsnIndex(int instructionIndex) { @@ -86,6 +87,22 @@ advanceToNextValueIndex(); } code.forEach(this::onInstructionView); + if (code.getTryCatchTable() != null) { + builder.append("try-catch-handlers:\n"); + code.getTryCatchTable() + .tryCatchHandlers + .forEach( + (index, handlers) -> { + builder.append(index).append(":\n"); + for (CatchHandler<Integer> handler : handlers) { + builder + .append(handler.getGuard()) + .append(" -> ") + .append(handler.getTarget()) + .append('\n'); + } + }); + } return builder.toString(); }
diff --git a/src/main/java/com/android/tools/r8/lightir/LirSizeEstimation.java b/src/main/java/com/android/tools/r8/lightir/LirSizeEstimation.java new file mode 100644 index 0000000..eaa93d0 --- /dev/null +++ b/src/main/java/com/android/tools/r8/lightir/LirSizeEstimation.java
@@ -0,0 +1,339 @@ +// Copyright (c) 2023, 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.lightir; + +import com.android.tools.r8.dex.code.DexAget; +import com.android.tools.r8.dex.code.DexAput; +import com.android.tools.r8.dex.code.DexArrayLength; +import com.android.tools.r8.dex.code.DexBase1Format; +import com.android.tools.r8.dex.code.DexBase2Format; +import com.android.tools.r8.dex.code.DexBase3Format; +import com.android.tools.r8.dex.code.DexCheckCast; +import com.android.tools.r8.dex.code.DexConst16; +import com.android.tools.r8.dex.code.DexConst4; +import com.android.tools.r8.dex.code.DexConstClass; +import com.android.tools.r8.dex.code.DexConstString; +import com.android.tools.r8.dex.code.DexConstWide16; +import com.android.tools.r8.dex.code.DexFillArrayData; +import com.android.tools.r8.dex.code.DexFillArrayDataPayload; +import com.android.tools.r8.dex.code.DexFilledNewArray; +import com.android.tools.r8.dex.code.DexGoto; +import com.android.tools.r8.dex.code.DexInstanceOf; +import com.android.tools.r8.dex.code.DexInvokeCustom; +import com.android.tools.r8.dex.code.DexMonitorEnter; +import com.android.tools.r8.dex.code.DexMonitorExit; +import com.android.tools.r8.dex.code.DexMove; +import com.android.tools.r8.dex.code.DexMoveException; +import com.android.tools.r8.dex.code.DexNewArray; +import com.android.tools.r8.dex.code.DexNewInstance; +import com.android.tools.r8.dex.code.DexNotInt; +import com.android.tools.r8.dex.code.DexNotLong; +import com.android.tools.r8.dex.code.DexPackedSwitch; +import com.android.tools.r8.dex.code.DexPackedSwitchPayload; +import com.android.tools.r8.dex.code.DexSget; +import com.android.tools.r8.dex.code.DexThrow; +import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.lightir.LirBuilder.IntSwitchPayload; + +public class LirSizeEstimation<EV> extends LirParsedInstructionCallback<EV> implements LirOpcodes { + + private int sizeEstimate = 0; + + LirSizeEstimation(LirCode<EV> code) { + super(code); + } + + @Override + public int getCurrentValueIndex() { + // We don't use value information. + return 0; + } + + public int getSizeEstimate() { + return sizeEstimate; + } + + /** + * Most size information can be found just using opcode. + * + * <p>We overwrite the base view callback and only in the few payload instruction cases do we make + * use of the parsed-instruction callbacks. + */ + @Override + public void onInstructionView(LirInstructionView view) { + sizeEstimate += instructionSize(view.getOpcode(), view); + } + + @Override + public void onIntSwitch(EV unusedValue, IntSwitchPayload payload) { + sizeEstimate += + DexPackedSwitch.SIZE + + DexPackedSwitchPayload.SIZE + + (2 * payload.keys.length) + + (2 * payload.targets.length); + } + + @Override + public void onNewArrayFilledData(int elementWidth, long size, short[] data, EV unusedSrc) { + sizeEstimate += DexFillArrayData.SIZE + DexFillArrayDataPayload.SIZE + 4 + data.length; + } + + private int instructionSize(int opcode, LirInstructionView view) { + switch (opcode) { + case TABLESWITCH: + case NEWARRAYFILLEDDATA: + // The payload instructions use the "parsed callback" to compute the payloads. + super.onInstructionView(view); + // The full size is added by the callbacks so return zero here. + return 0; + + case ACONST_NULL: + case ICONST_M1: + case ICONST_0: + case ICONST_1: + case ICONST_2: + case ICONST_3: + case ICONST_4: + case ICONST_5: + return DexConst4.SIZE; + + case LCONST_0: + case LCONST_1: + case FCONST_0: + case FCONST_1: + case FCONST_2: + case DCONST_0: + case DCONST_1: + return DexConstWide16.SIZE; + + case LDC: + // Most of the const loads are the same size (2). + return DexConstString.SIZE; + + case IALOAD: + case LALOAD: + case FALOAD: + case DALOAD: + case AALOAD: + case BALOAD: + case CALOAD: + case SALOAD: + // The loads are all size 2. + return DexAget.SIZE; + + case IASTORE: + case LASTORE: + case FASTORE: + case DASTORE: + case AASTORE: + case BASTORE: + case CASTORE: + case SASTORE: + // The loads are all size 2. + return DexAput.SIZE; + + case IADD: + case LADD: + case FADD: + case DADD: + case ISUB: + case LSUB: + case FSUB: + case DSUB: + case IMUL: + case LMUL: + case FMUL: + case DMUL: + case IDIV: + case LDIV: + case FDIV: + case DDIV: + case IREM: + case LREM: + case FREM: + case DREM: + // The binary ops are all size 2. + return DexBase2Format.SIZE; + + case INEG: + case LNEG: + case FNEG: + case DNEG: + // The negs are all size 1. + return DexBase1Format.SIZE; + + case ISHL: + case LSHL: + case ISHR: + case LSHR: + case IUSHR: + case LUSHR: + case IAND: + case LAND: + case IOR: + case LOR: + case IXOR: + case LXOR: + // The binary ops are all size 2. + return DexBase2Format.SIZE; + + case I2L: + case I2F: + case I2D: + case L2I: + case L2F: + case L2D: + case F2I: + case F2L: + case F2D: + case D2I: + case D2L: + case D2F: + case I2B: + case I2C: + case I2S: + // Number conversions are all size 1. + return DexBase1Format.SIZE; + + case LCMP: + case FCMPL: + case FCMPG: + case DCMPL: + case DCMPG: + return DexBase2Format.SIZE; + + case IFEQ: + case IFNE: + case IFLT: + case IFGE: + case IFGT: + case IFLE: + case IF_ICMPEQ: + case IF_ICMPNE: + case IF_ICMPLT: + case IF_ICMPGE: + case IF_ICMPGT: + case IF_ICMPLE: + case IF_ACMPEQ: + case IF_ACMPNE: + return DexBase2Format.SIZE; + + case GOTO: + return DexGoto.SIZE; + + case ARETURN: + case RETURN: + return DexBase1Format.SIZE; + + case GETSTATIC: + case PUTSTATIC: + case GETFIELD: + case PUTFIELD: + return DexBase2Format.SIZE; + + case INVOKEVIRTUAL: + case INVOKESPECIAL: + case INVOKESTATIC: + case INVOKEINTERFACE: + return DexBase3Format.SIZE; + + case INVOKEDYNAMIC: + return DexInvokeCustom.SIZE; + + case NEW: + return DexNewInstance.SIZE; + case NEWARRAY: + return DexNewArray.SIZE; + case ARRAYLENGTH: + return DexArrayLength.SIZE; + case ATHROW: + return DexThrow.SIZE; + case CHECKCAST: + return DexCheckCast.SIZE; + case INSTANCEOF: + return DexInstanceOf.SIZE; + case MONITORENTER: + return DexMonitorEnter.SIZE; + case MONITOREXIT: + return DexMonitorExit.SIZE; + case MULTIANEWARRAY: + return DexFilledNewArray.SIZE; + + case IFNULL: + case IFNONNULL: + return DexBase1Format.SIZE; + + // Non-CF instructions. + case ICONST: + case LCONST: + case FCONST: + case DCONST: + return DexConst16.SIZE; + + case INVOKESTATIC_ITF: + case INVOKEDIRECT: + case INVOKEDIRECT_ITF: + case INVOKESUPER: + case INVOKESUPER_ITF: + return DexBase3Format.SIZE; + + case DEBUGPOS: + // Often debug positions will be associated with instructions so assume size 0. + return 0; + + case PHI: + // Assume a move per phi. + return DexMove.SIZE; + + case FALLTHROUGH: + // Hopefully fallthrough points will not materialize as instructions. + return 0; + + case MOVEEXCEPTION: + return DexMoveException.SIZE; + + case DEBUGLOCALWRITE: + return DexMove.SIZE; + + case INVOKENEWARRAY: + return DexFilledNewArray.SIZE; + + case ITEMBASEDCONSTSTRING: + return DexConstString.SIZE; + + case NEWUNBOXEDENUMINSTANCE: + return DexConst16.SIZE; + + case INOT: + return DexNotInt.SIZE; + case LNOT: + return DexNotLong.SIZE; + + case DEBUGLOCALREAD: + // These reads do not materialize after register allocation. + return 0; + + case INITCLASS: + return DexSget.SIZE; + + case INVOKEPOLYMORPHIC: + return DexBase3Format.SIZE; + + case RECORDFIELDVALUES: + // Rewritten to new-array in DEX. + return DexNewArray.SIZE; + + case CHECKCAST_SAFE: + case CHECKCAST_IGNORE_COMPAT: + return DexCheckCast.SIZE; + + case CONSTCLASS_IGNORE_COMPAT: + return DexConstClass.SIZE; + + default: + throw new Unreachable("Unexpected LIR opcode: " + opcode); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/lightir/LirStrategy.java b/src/main/java/com/android/tools/r8/lightir/LirStrategy.java index 108586f..07ac579 100644 --- a/src/main/java/com/android/tools/r8/lightir/LirStrategy.java +++ b/src/main/java/com/android/tools/r8/lightir/LirStrategy.java
@@ -8,6 +8,7 @@ import com.android.tools.r8.graph.DebugLocalInfo; import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.NumberGenerator; import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.code.Phi.RegisterReadType; import com.android.tools.r8.ir.code.Value; @@ -29,9 +30,15 @@ * the possible encoding for phi and non-phi values. */ public abstract class LirStrategy<V, EV> { + + public static LirStrategy<Value, Integer> getDefaultStrategy() { + return new PhiInInstructionsStrategy(); + } + public abstract LirEncodingStrategy<V, EV> getEncodingStrategy(); - public abstract LirDecodingStrategy<V, EV> getDecodingStrategy(LirCode<EV> code); + public abstract LirDecodingStrategy<V, EV> getDecodingStrategy( + LirCode<EV> code, NumberGenerator valueNumberGenerator); /** * Encoding of a value with a phi-bit. @@ -139,8 +146,9 @@ } @Override - public LirDecodingStrategy<Value, PhiOrValue> getDecodingStrategy(LirCode<PhiOrValue> code) { - return new DecodingStrategy(code); + public LirDecodingStrategy<Value, PhiOrValue> getDecodingStrategy( + LirCode<PhiOrValue> code, NumberGenerator valueNumberGenerator) { + return new DecodingStrategy(code, valueNumberGenerator); } private static class StrategyInfo extends LirStrategyInfo<PhiOrValue> { @@ -234,7 +242,8 @@ private final Value[] values; private final int firstPhiValueIndex; - DecodingStrategy(LirCode<PhiOrValue> code) { + DecodingStrategy(LirCode<PhiOrValue> code, NumberGenerator valueNumberGenerator) { + super(valueNumberGenerator); values = new Value[code.getArgumentCount() + code.getInstructionCount()]; int phiValueIndex = -1; for (LirInstructionView view : code) { @@ -270,7 +279,7 @@ int index = decode(encodedValue, strategyInfo); Value value = values[index]; if (value == null) { - value = new Value(index, TypeElement.getBottom(), null); + value = new Value(getValueNumber(index), TypeElement.getBottom(), null); values[index] = value; } return value; @@ -284,7 +293,7 @@ DebugLocalInfo localInfo = getLocalInfo.apply(encodedValue); Value value = values[index]; if (value == null) { - value = new Value(index, type, localInfo); + value = new Value(getValueNumber(index), type, localInfo); values[index] = value; } else { value.setType(type); @@ -306,7 +315,8 @@ PhiOrValue encodedValue = getEncodedPhiForAbsoluteValueIndex(valueIndex, strategyInfo); BasicBlock block = getBlock.apply(encodedValue.getBlockIndex()); DebugLocalInfo localInfo = getLocalInfo.apply(encodedValue); - Phi phi = new Phi(valueIndex, block, type, localInfo, RegisterReadType.NORMAL); + Phi phi = + new Phi(getValueNumber(valueIndex), block, type, localInfo, RegisterReadType.NORMAL); Value value = values[valueIndex]; if (value != null) { // A fake ssa value has already been created, replace the users by the actual phi.
diff --git a/src/main/java/com/android/tools/r8/lightir/LirUseRegistryCallback.java b/src/main/java/com/android/tools/r8/lightir/LirUseRegistryCallback.java new file mode 100644 index 0000000..67f7a4e --- /dev/null +++ b/src/main/java/com/android/tools/r8/lightir/LirUseRegistryCallback.java
@@ -0,0 +1,153 @@ +// Copyright (c) 2023, 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.lightir; + +import com.android.tools.r8.graph.DexCallSite; +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexMethodHandle; +import com.android.tools.r8.graph.DexProto; +import com.android.tools.r8.graph.DexReference; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.UseRegistry; +import com.android.tools.r8.graph.UseRegistry.MethodHandleUse; +import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo; +import java.util.List; + +public class LirUseRegistryCallback<EV> extends LirParsedInstructionCallback<EV> { + + private final UseRegistry registry; + + public LirUseRegistryCallback(LirCode<EV> code, UseRegistry registry) { + super(code); + this.registry = registry; + } + + @Override + public int getCurrentValueIndex() { + // The registry of instructions does not require knowledge of value indexes. + return 0; + } + + @Override + public void onCheckCast(DexType type, EV value, boolean ignoreCompatRules) { + registry.registerCheckCast(type, ignoreCompatRules); + } + + @Override + public void onSafeCheckCast(DexType type, EV value) { + registry.registerSafeCheckCast(type); + } + + @SuppressWarnings("unchecked") + @Override + public void onConstClass(DexType type, boolean ignoreCompatRules) { + registry.registerConstClass(type, null, ignoreCompatRules); + } + + @Override + public void onConstMethodHandle(DexMethodHandle methodHandle) { + registry.registerMethodHandle(methodHandle, MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY); + } + + @Override + public void onConstMethodType(DexProto methodType) { + registry.registerProto(methodType); + } + + @Override + public void onDexItemBasedConstString( + DexReference item, NameComputationInfo<?> nameComputationInfo) { + if (nameComputationInfo.needsToRegisterReference()) { + assert item.isDexType(); + registry.registerTypeReference(item.asDexType()); + } + } + + @Override + public void onInitClass(DexType clazz) { + registry.registerInitClass(clazz); + } + + @Override + public void onInstanceGet(DexField field, EV object) { + registry.registerInstanceFieldRead(field); + } + + @Override + public void onInstancePut(DexField field, EV object, EV value) { + registry.registerInstanceFieldWrite(field); + } + + @Override + public void onStaticGet(DexField field) { + registry.registerStaticFieldRead(field); + } + + @Override + public void onStaticPut(DexField field, EV value) { + registry.registerStaticFieldWrite(field); + } + + @Override + public void onInstanceOf(DexType type, EV value) { + registry.registerInstanceOf(type); + } + + @Override + public void onInvokeCustom(DexCallSite callSite, List<EV> arguments) { + registry.registerCallSite(callSite); + } + + @Override + public void onInvokeDirect(DexMethod method, List<EV> arguments, boolean isInterface) { + registry.registerInvokeDirect(method); + } + + @Override + public void onInvokeInterface(DexMethod method, List<EV> arguments) { + registry.registerInvokeInterface(method); + } + + @Override + public void onInvokeStatic(DexMethod method, List<EV> arguments, boolean isInterface) { + registry.registerInvokeStatic(method); + } + + @Override + public void onInvokeSuper(DexMethod method, List<EV> arguments, boolean isInterface) { + registry.registerInvokeSuper(method); + } + + @Override + public void onInvokeVirtual(DexMethod method, List<EV> arguments) { + registry.registerInvokeVirtual(method); + } + + @Override + public void onInvokeMultiNewArray(DexType type, List<EV> arguments) { + registry.registerTypeReference(type); + } + + @Override + public void onInvokeNewArray(DexType type, List<EV> arguments) { + registry.registerTypeReference(type); + } + + @Override + public void onNewArrayEmpty(DexType type, EV size) { + registry.registerTypeReference(type); + } + + @Override + public void onNewInstance(DexType clazz) { + registry.registerNewInstance(clazz); + } + + @Override + public void onNewUnboxedEnumInstance(DexType type, int ordinal) { + registry.registerNewUnboxedEnumInstance(type); + } +}
diff --git a/src/main/java/com/android/tools/r8/lightir/PhiInInstructionsStrategy.java b/src/main/java/com/android/tools/r8/lightir/PhiInInstructionsStrategy.java index 0428126..6e9366f 100644 --- a/src/main/java/com/android/tools/r8/lightir/PhiInInstructionsStrategy.java +++ b/src/main/java/com/android/tools/r8/lightir/PhiInInstructionsStrategy.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.graph.DebugLocalInfo; import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.NumberGenerator; import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.code.Phi.RegisterReadType; import com.android.tools.r8.ir.code.Value; @@ -23,8 +24,9 @@ } @Override - public LirDecodingStrategy<Value, Integer> getDecodingStrategy(LirCode<Integer> code) { - return new DecodingStrategy(code); + public LirDecodingStrategy<Value, Integer> getDecodingStrategy( + LirCode<Integer> code, NumberGenerator valueNumberGenerator) { + return new DecodingStrategy(code, valueNumberGenerator); } private static class EncodingStrategy extends LirEncodingStrategy<Value, Integer> { @@ -94,7 +96,8 @@ private final Value[] values; - DecodingStrategy(LirCode<Integer> code) { + DecodingStrategy(LirCode<Integer> code, NumberGenerator valueNumberGenerator) { + super(valueNumberGenerator); values = new Value[code.getArgumentCount() + code.getInstructionCount()]; } @@ -103,7 +106,7 @@ int index = encodedValue; Value value = values[index]; if (value == null) { - value = new Value(index, TypeElement.getBottom(), null); + value = new Value(getValueNumber(index), TypeElement.getBottom(), null); values[index] = value; } return value; @@ -115,7 +118,7 @@ DebugLocalInfo localInfo = getLocalInfo.apply(index); Value value = values[index]; if (value == null) { - value = new Value(index, type, localInfo); + value = new Value(getValueNumber(index), type, localInfo); values[index] = value; } else { value.setType(type); @@ -138,7 +141,8 @@ LirStrategyInfo<Integer> strategyInfo) { BasicBlock block = getBlock.apply(valueIndex); DebugLocalInfo localInfo = getLocalInfo.apply(valueIndex); - Phi phi = new Phi(valueIndex, block, type, localInfo, RegisterReadType.NORMAL); + Phi phi = + new Phi(getValueNumber(valueIndex), block, type, localInfo, RegisterReadType.NORMAL); Value value = values[valueIndex]; if (value != null) { // A fake ssa value has already been created, replace the users by the actual phi.
diff --git a/src/main/java/com/android/tools/r8/optimize/AccessModifier.java b/src/main/java/com/android/tools/r8/optimize/AccessModifier.java deleted file mode 100644 index dea6346..0000000 --- a/src/main/java/com/android/tools/r8/optimize/AccessModifier.java +++ /dev/null
@@ -1,215 +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.optimize; - -import static com.android.tools.r8.dex.Constants.ACC_PRIVATE; -import static com.android.tools.r8.dex.Constants.ACC_PROTECTED; -import static com.android.tools.r8.dex.Constants.ACC_PUBLIC; -import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; - -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.FieldAccessFlags; -import com.android.tools.r8.graph.InnerClassAttribute; -import com.android.tools.r8.graph.MethodAccessFlags; -import com.android.tools.r8.graph.ProgramDefinition; -import com.android.tools.r8.graph.ProgramField; -import com.android.tools.r8.graph.ProgramMethod; -import com.android.tools.r8.graph.SubtypingInfo; -import com.android.tools.r8.ir.optimize.MemberPoolCollection.MemberPool; -import com.android.tools.r8.ir.optimize.MethodPoolCollection; -import com.android.tools.r8.optimize.PublicizerLens.PublicizedLensBuilder; -import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.android.tools.r8.utils.MethodSignatureEquivalence; -import com.android.tools.r8.utils.OptionalBool; -import com.android.tools.r8.utils.Timing; -import com.google.common.base.Equivalence.Wrapper; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; - -public final class AccessModifier { - - private final AppView<AppInfoWithLiveness> appView; - private final SubtypingInfo subtypingInfo; - private final MethodPoolCollection methodPoolCollection; - - private final PublicizedLensBuilder lensBuilder = PublicizerLens.createBuilder(); - - private AccessModifier(AppView<AppInfoWithLiveness> appView) { - this.appView = appView; - this.subtypingInfo = appView.appInfo().computeSubtypingInfo(); - this.methodPoolCollection = - // We will add private instance methods when we promote them. - new MethodPoolCollection( - appView, subtypingInfo, MethodPoolCollection::excludesPrivateInstanceMethod); - } - - /** - * Marks all package private and protected methods and fields as public. Makes all private static - * methods public. Makes private instance methods public final instance methods, if possible. - * - * <p>This will destructively update the DexApplication passed in as argument. - */ - public static void run( - AppView<AppInfoWithLiveness> appView, ExecutorService executorService, Timing timing) - throws ExecutionException { - if (appView.options().getProguardConfiguration().isAccessModificationAllowed()) { - timing.begin("Access modification"); - new AccessModifier(appView).internalRun(executorService, timing); - timing.end(); - } - } - - private void internalRun(ExecutorService executorService, Timing timing) - throws ExecutionException { - // Phase 1: Collect methods to check if private instance methods don't have conflicts. - methodPoolCollection.buildAll(executorService, timing); - - // Phase 2: Visit classes and promote class/member to public if possible. - timing.begin("Phase 2: promoteToPublic"); - appView.appInfo().forEachReachableInterface(clazz -> processType(clazz.getType())); - processType(appView.dexItemFactory().objectType); - timing.end(); - - PublicizerLens publicizerLens = lensBuilder.build(appView); - if (publicizerLens != null) { - appView.setGraphLens(publicizerLens); - } - - appView.notifyOptimizationFinishedForTesting(); - } - - private void doPublicize(ProgramDefinition definition) { - definition.getAccessFlags().promoteToPublic(); - } - - private void processType(DexType type) { - DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type)); - if (clazz != null) { - processClass(clazz); - } - subtypingInfo.forAllImmediateExtendsSubtypes(type, this::processType); - } - - private void processClass(DexProgramClass clazz) { - if (appView.appInfo().isAccessModificationAllowed(clazz)) { - doPublicize(clazz); - } - - // Publicize fields. - clazz.forEachProgramField(this::processField); - - // Publicize methods. - Set<DexEncodedMethod> privateInstanceMethods = new LinkedHashSet<>(); - clazz.forEachProgramMethod( - method -> { - if (publicizeMethod(method)) { - privateInstanceMethods.add(method.getDefinition()); - } - }); - if (!privateInstanceMethods.isEmpty()) { - clazz.virtualizeMethods(privateInstanceMethods); - } - - // Publicize inner class attribute. - InnerClassAttribute attr = clazz.getInnerClassAttributeForThisClass(); - if (attr != null) { - int accessFlags = ((attr.getAccess() | ACC_PUBLIC) & ~ACC_PRIVATE) & ~ACC_PROTECTED; - clazz.replaceInnerClassAttributeForThisClass( - new InnerClassAttribute( - accessFlags, attr.getInner(), attr.getOuter(), attr.getInnerName())); - } - } - - private void processField(ProgramField field) { - if (appView.appInfo().isAccessModificationAllowed(field)) { - publicizeField(field); - } - } - - private void publicizeField(ProgramField field) { - FieldAccessFlags flags = field.getAccessFlags(); - if (!flags.isPublic()) { - flags.promoteToPublic(); - } - } - - private boolean publicizeMethod(ProgramMethod method) { - MethodAccessFlags accessFlags = method.getAccessFlags(); - if (accessFlags.isPublic()) { - return false; - } - // If this method is mentioned in keep rules, do not transform (rule applications changed). - DexEncodedMethod definition = method.getDefinition(); - if (!appView.appInfo().isAccessModificationAllowed(method)) { - // TODO(b/131130038): Also do not publicize package-private and protected methods that are - // kept. - if (definition.isPrivate()) { - return false; - } - } - - if (method.getDefinition().isInstanceInitializer() || accessFlags.isProtected()) { - doPublicize(method); - return false; - } - - if (accessFlags.isPackagePrivate()) { - // If we publicize a package private method we have to ensure there is no overrides of it. We - // could potentially publicize a method if it only has package-private overrides. - // TODO(b/182136236): See if we can break the hierarchy for clusters. - MemberPool<DexMethod> memberPool = methodPoolCollection.get(method.getHolder()); - Wrapper<DexMethod> methodKey = MethodSignatureEquivalence.get().wrap(method.getReference()); - if (memberPool.below( - methodKey, - false, - true, - (clazz, ignored) -> - !method.getContextType().getPackageName().equals(clazz.getType().getPackageName()))) { - return false; - } - doPublicize(method); - return false; - } - - assert accessFlags.isPrivate(); - - if (accessFlags.isStatic()) { - // For private static methods we can just relax the access to public, since - // even though JLS prevents from declaring static method in derived class if - // an instance method with same signature exists in superclass, JVM actually - // does not take into account access of the static methods. - doPublicize(method); - return false; - } - - // We can't publicize private instance methods in interfaces or methods that are copied from - // interfaces to lambda-desugared classes because this will be added as a new default method. - // TODO(b/111118390): It might be possible to transform it into static methods, though. - if (method.getHolder().isInterface() || accessFlags.isSynthetic()) { - return false; - } - - boolean wasSeen = methodPoolCollection.markIfNotSeen(method.getHolder(), method.getReference()); - if (wasSeen) { - // We can't do anything further because even renaming is not allowed due to the keep rule. - if (!appView.appInfo().isMinificationAllowed(method)) { - return false; - } - // TODO(b/111118390): Renaming will enable more private instance methods to be publicized. - return false; - } - lensBuilder.add(method.getReference()); - accessFlags.promoteToFinal(); - doPublicize(method); - // The method just became public and is therefore not a library override. - definition.setLibraryMethodOverride(OptionalBool.FALSE); - return true; - } -}
diff --git a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifier.java b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifier.java new file mode 100644 index 0000000..ea25b09 --- /dev/null +++ b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifier.java
@@ -0,0 +1,314 @@ +// Copyright (c) 2023, 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.optimize.accessmodification; + +import static com.android.tools.r8.dex.Constants.ACC_PRIVATE; +import static com.android.tools.r8.dex.Constants.ACC_PROTECTED; +import static com.android.tools.r8.dex.Constants.ACC_PUBLIC; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexMethodSignature; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.FieldAccessFlags; +import com.android.tools.r8.graph.FieldAccessInfo; +import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo; +import com.android.tools.r8.graph.InnerClassAttribute; +import com.android.tools.r8.graph.MethodAccessFlags; +import com.android.tools.r8.graph.ProgramDefinition; +import com.android.tools.r8.graph.ProgramField; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.optimize.accessmodification.AccessModifierTraversal.BottomUpTraversalState; +import com.android.tools.r8.optimize.argumentpropagation.utils.ProgramClassesBidirectedGraph; +import com.android.tools.r8.optimize.utils.ConcurrentNonProgramMethodsCollection; +import com.android.tools.r8.optimize.utils.NonProgramMethodsCollection; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.shaking.KeepMethodInfo; +import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.ListUtils; +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.BiMap; +import com.google.common.collect.HashBiMap; +import java.util.Comparator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; + +public class AccessModifier { + + private final AppView<AppInfoWithLiveness> appView; + private final ImmediateProgramSubtypingInfo immediateSubtypingInfo; + private final AccessModifierLens.Builder lensBuilder = AccessModifierLens.builder(); + private final NonProgramMethodsCollection nonProgramMethodsCollection; + private final InternalOptions options; + + private AccessModifier(AppView<AppInfoWithLiveness> appView) { + this.appView = appView; + this.immediateSubtypingInfo = + ImmediateProgramSubtypingInfo.createWithDeterministicOrder(appView); + this.nonProgramMethodsCollection = + ConcurrentNonProgramMethodsCollection.createVirtualMethodsCollection(appView); + this.options = appView.options(); + } + + public static void run( + AppView<AppInfoWithLiveness> appView, ExecutorService executorService, Timing timing) + throws ExecutionException { + timing.begin("Access modification"); + if (appView.options().getAccessModifierOptions().isAccessModificationEnabled()) { + new AccessModifier(appView) + .processStronglyConnectedComponents(executorService) + .installLens(executorService, timing); + } + timing.end(); + } + + private AccessModifier processStronglyConnectedComponents(ExecutorService executorService) + throws ExecutionException { + // Compute the connected program classes and process the components in parallel. + List<Set<DexProgramClass>> stronglyConnectedComponents = + new ProgramClassesBidirectedGraph(appView, immediateSubtypingInfo) + .computeStronglyConnectedComponents(); + ThreadUtils.processItems( + stronglyConnectedComponents, this::processStronglyConnectedComponent, executorService); + return this; + } + + private void processStronglyConnectedComponent(Set<DexProgramClass> stronglyConnectedComponent) { + // Perform a top-down traversal over the class hierarchy. + new AccessModifierTraversal( + appView, + immediateSubtypingInfo, + this, + AccessModifierNamingState.createInitialNamingState( + appView, stronglyConnectedComponent, nonProgramMethodsCollection)) + .run(ListUtils.sort(stronglyConnectedComponent, Comparator.comparing(DexClass::getType))); + } + + private void installLens(ExecutorService executorService, Timing timing) + throws ExecutionException { + if (!lensBuilder.isEmpty()) { + appView.rewriteWithLens(lensBuilder.build(appView), executorService, timing); + } + } + + // Publicizing of classes and members. + + void processClass( + DexProgramClass clazz, + AccessModifierNamingState namingState, + BottomUpTraversalState traversalState) { + publicizeClass(clazz); + publicizeFields(clazz); + publicizeMethods(clazz, namingState, traversalState); + // TODO(b/278736230): Also finalize classes and methods here. + finalizeFields(clazz); + } + + private void publicizeClass(DexProgramClass clazz) { + if (isAccessModificationAllowed(clazz) && !clazz.getAccessFlags().isPublic()) { + clazz.getAccessFlags().promoteToPublic(); + } + + // Update inner class attribute. + // TODO(b/285494837): Carry-over from the legacy access modifier. We should never publicize + // items unconditionally, but account for keep info. + InnerClassAttribute attr = clazz.getInnerClassAttributeForThisClass(); + if (attr != null) { + int accessFlags = ((attr.getAccess() | ACC_PUBLIC) & ~ACC_PRIVATE) & ~ACC_PROTECTED; + clazz.replaceInnerClassAttributeForThisClass( + new InnerClassAttribute( + accessFlags, attr.getInner(), attr.getOuter(), attr.getInnerName())); + } + } + + private void publicizeFields(DexProgramClass clazz) { + clazz.forEachProgramField(this::publicizeField); + } + + private void publicizeField(ProgramField field) { + if (isAccessModificationAllowed(field) && !field.getAccessFlags().isPublic()) { + field.getAccessFlags().promoteToPublic(); + } + } + + private void publicizeMethods( + DexProgramClass clazz, + AccessModifierNamingState namingState, + BottomUpTraversalState traversalState) { + // Create a local naming state to keep track of the methods present on the current class. + // Start by reserving the pinned method signatures on the current class. + BiMap<DexMethod, DexMethod> localNamingState = HashBiMap.create(); + clazz.forEachProgramMethod( + method -> { + if (!method.getDefinition().isInitializer() && !isRenamingAllowed(method)) { + localNamingState.put(method.getReference(), method.getReference()); + } + }); + clazz + .getMethodCollection() + .<ProgramMethod>replaceClassAndMethods( + method -> publicizeMethod(method, localNamingState, namingState, traversalState)); + } + + private DexEncodedMethod publicizeMethod( + ProgramMethod method, + BiMap<DexMethod, DexMethod> localNamingState, + AccessModifierNamingState namingState, + BottomUpTraversalState traversalState) { + MethodAccessFlags accessFlags = method.getAccessFlags(); + if (accessFlags.isPublic() || !isAccessModificationAllowed(method)) { + return commitMethod(method, localNamingState, namingState); + } + + if (method.getDefinition().isInstanceInitializer() + || (accessFlags.isPackagePrivate() + && !traversalState.hasIllegalOverrideOfPackagePrivateMethod(method)) + || accessFlags.isProtected()) { + method.getAccessFlags().promoteToPublic(); + return commitMethod(method, localNamingState, namingState); + } + + if (accessFlags.isPrivate()) { + if (isRenamingAllowed(method)) { + method.getAccessFlags().promoteToPublic(); + return commitMethod(method, localNamingState, namingState); + } + assert localNamingState.containsKey(method.getReference()); + assert localNamingState.get(method.getReference()) == method.getReference(); + if (namingState.isFree(method.getMethodSignature())) { + method.getAccessFlags().promoteToPublic(); + namingState.addBlockedMethodSignature(method.getMethodSignature()); + } + return commitMethod(method, method.getReference()); + } + + // TODO(b/279126633): Add support for publicizing package-private methods by renaming. + assert accessFlags.isPackagePrivate(); + assert traversalState.hasIllegalOverrideOfPackagePrivateMethod(method); + return commitMethod(method, localNamingState, namingState); + } + + private DexMethod getAndReserveNewMethodReference( + ProgramMethod method, + BiMap<DexMethod, DexMethod> localNamingState, + AccessModifierNamingState namingState) { + if (method.getDefinition().isInitializer()) { + return method.getReference(); + } + if (!isRenamingAllowed(method)) { + assert localNamingState.containsKey(method.getReference()); + assert localNamingState.get(method.getReference()) == method.getReference(); + assert method.getAccessFlags().isPrivate() + || method + .getMethodSignature() + .equals(namingState.getReservedSignature(method.getMethodSignature())); + return method.getReference(); + } + DexItemFactory dexItemFactory = appView.dexItemFactory(); + if (method.getAccessFlags().isPrivate()) { + // Find a fresh method name and reserve it for the current class. + DexMethod newMethodReference = + dexItemFactory.createFreshMethodNameWithoutHolder( + method.getName().toString(), + method.getProto(), + method.getHolderType(), + candidate -> + !localNamingState.containsValue(candidate) + && namingState.isFree(candidate.getSignature())); + localNamingState.put(method.getReference(), newMethodReference); + return newMethodReference; + } + // Check if a mapping already exists for this method signature. + if (!method.getAccessFlags().isPromotedFromPrivateToPublic()) { + DexMethodSignature reservedSignature = + namingState.getReservedSignature(method.getMethodSignature()); + if (reservedSignature != null) { + return reservedSignature.withHolder(method, appView.dexItemFactory()); + } + } + // Find a fresh method name and block/reserve it globally. + DexMethod newMethodReference = + dexItemFactory.createFreshMethodNameWithoutHolder( + method.getName().toString(), + method.getProto(), + method.getHolderType(), + candidate -> + !localNamingState.containsValue(candidate) + && namingState.isFree(candidate.getSignature())); + if (method.getAccessFlags().belongsToVirtualPool()) { + if (method.getAccessFlags().isPromotedFromPrivateToPublic()) { + namingState.addBlockedMethodSignature(newMethodReference.getSignature()); + } else { + namingState.addRenaming(method.getMethodSignature(), newMethodReference.getSignature()); + } + } + return newMethodReference; + } + + private boolean isAccessModificationAllowed(ProgramDefinition definition) { + // TODO(b/278687711): Also check that the definition does not have any illegal accesses to it. + return appView.getKeepInfo(definition).isAccessModificationAllowed(options); + } + + private boolean isRenamingAllowed(ProgramMethod method) { + KeepMethodInfo keepInfo = appView.getKeepInfo(method); + return keepInfo.isOptimizationAllowed(options) && keepInfo.isShrinkingAllowed(options); + } + + private DexEncodedMethod commitMethod( + ProgramMethod method, + BiMap<DexMethod, DexMethod> localNamingState, + AccessModifierNamingState namingState) { + return commitMethod( + method, getAndReserveNewMethodReference(method, localNamingState, namingState)); + } + + private DexEncodedMethod commitMethod(ProgramMethod method, DexMethod newMethodReference) { + DexProgramClass holder = method.getHolder(); + if (newMethodReference != method.getReference()) { + lensBuilder.recordMove(method.getReference(), newMethodReference); + method = + new ProgramMethod( + holder, method.getDefinition().toTypeSubstitutedMethod(newMethodReference)); + } + if (method.getAccessFlags().isPromotedFromPrivateToPublic() + && method.getAccessFlags().belongsToVirtualPool()) { + lensBuilder.addPublicizedPrivateVirtualMethod(method.getHolder(), newMethodReference); + method.getDefinition().setLibraryMethodOverride(OptionalBool.FALSE); + } + return method.getDefinition(); + } + + // Finalization of classes and members. + + private void finalizeFields(DexProgramClass clazz) { + clazz.forEachProgramField(this::finalizeField); + } + + private void finalizeField(ProgramField field) { + FieldAccessFlags flags = field.getAccessFlags(); + FieldAccessInfo accessInfo = + appView.appInfo().getFieldAccessInfoCollection().get(field.getReference()); + if (!appView.getKeepInfo(field).isPinned(options) + && !accessInfo.hasReflectiveWrite() + && !accessInfo.isWrittenFromMethodHandle() + && accessInfo.isWrittenOnlyInMethodSatisfying( + method -> + method.getDefinition().isInitializer() + && method.getAccessFlags().isStatic() == flags.isStatic() + && method.getHolder() == field.getHolder()) + && !flags.isFinal() + && !flags.isVolatile()) { + flags.promoteToFinal(); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierLens.java b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierLens.java new file mode 100644 index 0000000..76da486 --- /dev/null +++ b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierLens.java
@@ -0,0 +1,117 @@ +// Copyright (c) 2023, 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.optimize.accessmodification; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.lens.DefaultNonIdentityGraphLens; +import com.android.tools.r8.graph.lens.GraphLens; +import com.android.tools.r8.graph.lens.MethodLookupResult; +import com.android.tools.r8.ir.code.InvokeType; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap; +import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap; +import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap; +import com.google.common.collect.Sets; +import java.util.Set; + +public class AccessModifierLens extends DefaultNonIdentityGraphLens { + + private final BidirectionalOneToOneMap<DexMethod, DexMethod> methodMap; + + // Private interface methods that have been publicized. Invokes targeting these methods must be + // rewritten from invoke-direct to invoke-interface. + private final Set<DexMethod> publicizedPrivateInterfaceMethods; + + // Private class methods that have been publicized. Invokes targeting these methods must be + // rewritten from invoke-direct to invoke-virtual. + private final Set<DexMethod> publicizedPrivateVirtualMethods; + + AccessModifierLens( + AppView<AppInfoWithLiveness> appView, + BidirectionalOneToOneMap<DexMethod, DexMethod> methodMap, + Set<DexMethod> publicizedPrivateInterfaceMethods, + Set<DexMethod> publicizedPrivateVirtualMethods) { + super(appView); + this.methodMap = methodMap; + this.publicizedPrivateInterfaceMethods = publicizedPrivateInterfaceMethods; + this.publicizedPrivateVirtualMethods = publicizedPrivateVirtualMethods; + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public DexMethod getNextMethodSignature(DexMethod method) { + return methodMap.getOrDefault(method, method); + } + + @Override + public DexMethod getPreviousMethodSignature(DexMethod method) { + return methodMap.getRepresentativeKeyOrDefault(method, method); + } + + @Override + public MethodLookupResult internalDescribeLookupMethod( + MethodLookupResult previous, DexMethod context, GraphLens codeLens) { + assert !previous.hasReboundReference(); + DexMethod newMethod = getNextMethodSignature(previous.getReference()); + InvokeType newInvokeType = previous.getType(); + if (previous.getType() == InvokeType.DIRECT) { + if (publicizedPrivateInterfaceMethods.contains(newMethod)) { + newInvokeType = InvokeType.INTERFACE; + } else if (publicizedPrivateVirtualMethods.contains(newMethod)) { + newInvokeType = InvokeType.VIRTUAL; + } + } + if (newInvokeType != previous.getType() || newMethod != previous.getReference()) { + return MethodLookupResult.builder(this) + .setReference(newMethod) + .setPrototypeChanges(previous.getPrototypeChanges()) + .setType(newInvokeType) + .build(); + } + return previous; + } + + public static class Builder { + + private final MutableBidirectionalOneToOneMap<DexMethod, DexMethod> methodMap = + new BidirectionalOneToOneHashMap<>(); + private final Set<DexMethod> publicizedPrivateInterfaceMethods = Sets.newConcurrentHashSet(); + private final Set<DexMethod> publicizedPrivateVirtualMethods = Sets.newConcurrentHashSet(); + + public Builder addPublicizedPrivateVirtualMethod(DexProgramClass holder, DexMethod method) { + if (holder.isInterface()) { + publicizedPrivateInterfaceMethods.add(method); + } else { + publicizedPrivateVirtualMethods.add(method); + } + return this; + } + + public Builder recordMove(DexMethod from, DexMethod to) { + assert from != to; + synchronized (methodMap) { + methodMap.put(from, to); + } + return this; + } + + public boolean isEmpty() { + return methodMap.isEmpty() + && publicizedPrivateInterfaceMethods.isEmpty() + && publicizedPrivateVirtualMethods.isEmpty(); + } + + public AccessModifierLens build(AppView<AppInfoWithLiveness> appView) { + assert !isEmpty(); + return new AccessModifierLens( + appView, methodMap, publicizedPrivateInterfaceMethods, publicizedPrivateVirtualMethods); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierNamingState.java b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierNamingState.java new file mode 100644 index 0000000..cae79be --- /dev/null +++ b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierNamingState.java
@@ -0,0 +1,86 @@ +// Copyright (c) 2023, 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.optimize.accessmodification; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.ClasspathOrLibraryClass; +import com.android.tools.r8.graph.DexMethodSignature; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.optimize.utils.NonProgramMethodsCollection; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.shaking.KeepMethodInfo; +import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.collections.DexMethodSignatureBiMap; +import com.android.tools.r8.utils.collections.DexMethodSignatureSet; +import com.google.common.collect.Sets; +import java.util.Set; + +public class AccessModifierNamingState { + + // The set of private method signatures that have been publicized. These method signatures are + // "blocked" to ensure that virtual methods with the same method signature are given a different + // name. + private final DexMethodSignatureSet blockedMethodSignatures = DexMethodSignatureSet.create(); + + // Records which method signatures in the component have been mapped to. This uses a bidirectional + // map to allow efficiently finding a fresh method signature in the component. + private final DexMethodSignatureBiMap<DexMethodSignature> reservedMethodSignatures; + + private AccessModifierNamingState( + DexMethodSignatureBiMap<DexMethodSignature> reservedMethodSignatures) { + this.reservedMethodSignatures = reservedMethodSignatures; + } + + static AccessModifierNamingState createInitialNamingState( + AppView<AppInfoWithLiveness> appView, + Set<DexProgramClass> stronglyConnectedComponent, + NonProgramMethodsCollection nonProgramMethodsCollection) { + DexMethodSignatureBiMap<DexMethodSignature> reservedSignatures = + new DexMethodSignatureBiMap<>(); + Set<ClasspathOrLibraryClass> seenNonProgramClasses = Sets.newIdentityHashSet(); + for (DexProgramClass clazz : stronglyConnectedComponent) { + // Reserve the signatures that are pinned in this class. + clazz.forEachProgramMethodMatching( + method -> !method.isInstanceInitializer() && !method.getAccessFlags().isPrivate(), + method -> { + KeepMethodInfo keepInfo = appView.getKeepInfo(method); + InternalOptions options = appView.options(); + if (!keepInfo.isOptimizationAllowed(options) || !keepInfo.isShrinkingAllowed(options)) { + DexMethodSignature methodSignature = method.getMethodSignature(); + reservedSignatures.put(methodSignature, methodSignature); + } + }); + // Reserve the signatures in the library. + clazz.forEachImmediateSuperClassMatching( + appView, + (supertype, superclass) -> + superclass != null + && !superclass.isProgramClass() + && seenNonProgramClasses.add(superclass.asClasspathOrLibraryClass()), + (supertype, superclass) -> + reservedSignatures.putAllToIdentity( + nonProgramMethodsCollection.getOrComputeNonProgramMethods( + superclass.asClasspathOrLibraryClass()))); + } + return new AccessModifierNamingState(reservedSignatures); + } + + void addBlockedMethodSignature(DexMethodSignature signature) { + blockedMethodSignatures.add(signature); + } + + void addRenaming(DexMethodSignature signature, DexMethodSignature newSignature) { + reservedMethodSignatures.put(signature, newSignature); + } + + DexMethodSignature getReservedSignature(DexMethodSignature signature) { + return reservedMethodSignatures.get(signature); + } + + boolean isFree(DexMethodSignature signature) { + return !blockedMethodSignatures.contains(signature) + && !reservedMethodSignatures.containsValue(signature); + } +}
diff --git a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierOptions.java b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierOptions.java new file mode 100644 index 0000000..a9d69d6 --- /dev/null +++ b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierOptions.java
@@ -0,0 +1,21 @@ +// Copyright (c) 2023, 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.optimize.accessmodification; + +import com.android.tools.r8.utils.InternalOptions; + +public class AccessModifierOptions { + + private InternalOptions options; + + public AccessModifierOptions(InternalOptions options) { + this.options = options; + } + + public boolean isAccessModificationEnabled() { + return options.hasProguardConfiguration() + && options.getProguardConfiguration().isAccessModificationAllowed(); + } +}
diff --git a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierTraversal.java b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierTraversal.java new file mode 100644 index 0000000..1fef67f --- /dev/null +++ b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierTraversal.java
@@ -0,0 +1,177 @@ +// Copyright (c) 2023, 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.optimize.accessmodification; + +import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; +import static com.android.tools.r8.utils.MapUtils.ignoreKey; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.optimize.argumentpropagation.utils.DepthFirstTopDownClassHierarchyTraversal; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.MapUtils; +import com.android.tools.r8.utils.collections.DexMethodSignatureMap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; +import java.util.Collections; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Set; + +class AccessModifierTraversal extends DepthFirstTopDownClassHierarchyTraversal { + + private final AccessModifier accessModifier; + private final AccessModifierNamingState namingState; + + private final Map<DexType, TraversalState> states = new IdentityHashMap<>(); + + AccessModifierTraversal( + AppView<AppInfoWithLiveness> appView, + ImmediateProgramSubtypingInfo immediateSubtypingInfo, + AccessModifier accessModifier, + AccessModifierNamingState namingState) { + super(appView, immediateSubtypingInfo); + this.accessModifier = accessModifier; + this.namingState = namingState; + } + + /** Predicate that specifies which program classes the depth-first traversal should start from. */ + @Override + public boolean isRoot(DexProgramClass clazz) { + return Iterables.all( + clazz.allImmediateSupertypes(), + supertype -> asProgramClassOrNull(appView.definitionFor(supertype)) == null); + } + + /** Called when {@param clazz} is visited for the first time during the downwards traversal. */ + @Override + public void visit(DexProgramClass clazz) { + // TODO(b/279126633): Store a top down traversal state for the current class, which contains the + // protected and public method signatures when traversing downwards to enable publicizing of + // package private methods with illegal overrides. + states.put(clazz.getType(), TopDownTraversalState.empty()); + } + + /** Called during backtracking when all subclasses of {@param clazz} have been processed. */ + @Override + public void prune(DexProgramClass clazz) { + // Remove the traversal state since all subclasses have now been processed. + states.remove(clazz.getType()); + + // Remove and join the bottom up traversal states of the subclasses. + BottomUpTraversalState state = new BottomUpTraversalState(); + forEachSubClass( + clazz, + subclass -> { + BottomUpTraversalState subState = + MapUtils.removeOrDefault(states, subclass.getType(), BottomUpTraversalState.empty()) + .asBottomUpTraversalState(); + state.add(subState); + }); + + // Apply access modification to the class and its members. + accessModifier.processClass(clazz, namingState, state); + + // Add the methods of the current class. + clazz.forEachProgramVirtualMethod(state::addMethod); + + // Store the bottom up traversal state for the current class. + if (state.isEmpty()) { + states.remove(clazz.getType()); + } else { + states.put(clazz.getType(), state); + } + } + + abstract static class TraversalState { + + BottomUpTraversalState asBottomUpTraversalState() { + return null; + } + + TopDownTraversalState asTopDownTraversalState() { + return null; + } + } + + // TODO(b/279126633): Collect the protected and public method signatures when traversing downwards + // to enable publicizing of package private methods with illegal overrides. + static class TopDownTraversalState extends TraversalState { + + private static final TopDownTraversalState EMPTY = new TopDownTraversalState(); + + static TopDownTraversalState empty() { + return EMPTY; + } + + @Override + TopDownTraversalState asTopDownTraversalState() { + return this; + } + + boolean isEmpty() { + return true; + } + } + + static class BottomUpTraversalState extends TraversalState { + + private static final BottomUpTraversalState EMPTY = + new BottomUpTraversalState(DexMethodSignatureMap.empty()); + + // The set of non-private virtual methods below the current class. + DexMethodSignatureMap<Set<String>> nonPrivateVirtualMethods; + + BottomUpTraversalState() { + this(DexMethodSignatureMap.create()); + } + + BottomUpTraversalState(DexMethodSignatureMap<Set<String>> packagePrivateMethods) { + this.nonPrivateVirtualMethods = packagePrivateMethods; + } + + static BottomUpTraversalState empty() { + return EMPTY; + } + + @Override + BottomUpTraversalState asBottomUpTraversalState() { + return this; + } + + void add(BottomUpTraversalState backtrackingState) { + backtrackingState.nonPrivateVirtualMethods.forEach( + (methodSignature, packageDescriptors) -> + this.nonPrivateVirtualMethods + .computeIfAbsent(methodSignature, ignoreKey(HashSet::new)) + .addAll(packageDescriptors)); + } + + void addMethod(ProgramMethod method) { + assert method.getDefinition().belongsToVirtualPool(); + nonPrivateVirtualMethods + .computeIfAbsent(method.getMethodSignature(), ignoreKey(Sets::newIdentityHashSet)) + .add(method.getHolderType().getPackageDescriptor()); + } + + boolean hasIllegalOverrideOfPackagePrivateMethod(ProgramMethod method) { + assert method.getAccessFlags().isPackagePrivate(); + String methodPackageDescriptor = method.getHolderType().getPackageDescriptor(); + return Iterables.any( + nonPrivateVirtualMethods.getOrDefault( + method.getMethodSignature(), Collections.emptySet()), + methodOverridePackageDescriptor -> + !methodOverridePackageDescriptor.equals(methodPackageDescriptor)); + } + + boolean isEmpty() { + return nonPrivateVirtualMethods.isEmpty(); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java index 4441690..553962d 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java
@@ -160,7 +160,7 @@ @Override public DexField fixupFieldReference(DexField field) { - return graphLens.internalGetNextFieldSignature(field); + return graphLens.getNextFieldSignature(field); } @Override
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java index 42213aa..af130e8 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java
@@ -86,21 +86,6 @@ } @Override - public DexMethod getPreviousMethodSignature(DexMethod method) { - return super.getPreviousMethodSignature(method); - } - - @Override - public DexField internalGetNextFieldSignature(DexField field) { - return super.internalGetNextFieldSignature(field); - } - - @Override - public DexMethod getNextMethodSignature(DexMethod method) { - return super.getNextMethodSignature(method); - } - - @Override protected InvokeType mapInvocationType( DexMethod newMethod, DexMethod originalMethod, InvokeType type) { return hasPrototypeChanges(newMethod)
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java index acdb13b..aa0bde9 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
@@ -230,7 +230,7 @@ ProgramField resolvedField = resolutionResult.getSingleProgramField(); DexField rewrittenFieldReference = - graphLens.internalGetNextFieldSignature(resolvedField.getReference()); + graphLens.getNextFieldSignature(resolvedField.getReference()); if (rewrittenFieldReference != resolvedField.getReference()) { markAffected(); }
diff --git a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingLens.java b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingLens.java index 69f9248..694096a 100644 --- a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingLens.java +++ b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingLens.java
@@ -7,7 +7,6 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.lens.DefaultNonIdentityGraphLens; -import com.android.tools.r8.graph.lens.GraphLens; import com.android.tools.r8.utils.collections.BidirectionalManyToOneMap; import java.util.Set; @@ -24,12 +23,6 @@ } @Override - public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) { - DexMethod renamedMethod = getPrevious().getRenamedMethodSignature(originalMethod, applied); - return getNextMethodSignature(renamedMethod); - } - - @Override public DexMethod getPreviousMethodSignature(DexMethod method) { Set<DexMethod> bridges = bridgeToHoistedBridgeMap.getKeys(method); return bridges.isEmpty() ? method : bridges.iterator().next();
diff --git a/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalLens.java b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalLens.java index 3307010..9de23a8 100644 --- a/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalLens.java +++ b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalLens.java
@@ -33,13 +33,8 @@ // Methods. @Override - public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) { - if (this == applied) { - return originalMethod; - } - DexMethod previousMethodSignature = - getPrevious().getRenamedMethodSignature(originalMethod, applied); - return methodMap.getOrDefault(previousMethodSignature, previousMethodSignature); + public DexMethod getNextMethodSignature(DexMethod method) { + return methodMap.getOrDefault(method, method); } @Override
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java index 7a462ed..7abe044 100644 --- a/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java +++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java
@@ -16,20 +16,18 @@ import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap; import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap; import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap; -import com.google.common.collect.BiMap; -import com.google.common.collect.HashBiMap; import java.util.Map; public class RepackagingLens extends NestedGraphLens { - private final BiMap<DexType, DexType> newTypes; + private final BidirectionalOneToOneMap<DexType, DexType> newTypes; private final Map<String, String> packageRenamings; private RepackagingLens( AppView<AppInfoWithLiveness> appView, BidirectionalOneToOneMap<DexField, DexField> newFieldSignatures, BidirectionalOneToOneMap<DexMethod, DexMethod> newMethodSignatures, - BiMap<DexType, DexType> newTypes, + BidirectionalOneToOneMap<DexType, DexType> newTypes, Map<String, String> packageRenamings) { super(appView, newFieldSignatures, newMethodSignatures, newTypes); this.newTypes = newTypes; @@ -42,12 +40,6 @@ } @Override - public DexType getOriginalType(DexType type) { - DexType previous = newTypes.inverse().getOrDefault(type, type); - return getPrevious().getOriginalType(previous); - } - - @Override public <T extends DexReference> boolean isSimpleRenaming(T from, T to) { if (from == to) { assert false : "The from and to references should not be equal"; @@ -87,7 +79,8 @@ public static class Builder { - protected final BiMap<DexType, DexType> newTypes = HashBiMap.create(); + protected final MutableBidirectionalOneToOneMap<DexType, DexType> newTypes = + new BidirectionalOneToOneHashMap<>(); protected final MutableBidirectionalOneToOneMap<DexField, DexField> newFieldSignatures = new BidirectionalOneToOneHashMap<>(); protected final MutableBidirectionalOneToOneMap<DexMethod, DexMethod> newMethodSignatures =
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingRewriter.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingRewriter.java index 79165d1..48312bd 100644 --- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingRewriter.java +++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingRewriter.java
@@ -47,7 +47,7 @@ EnqueuerDeferredTracingRewriter(AppView<? extends AppInfoWithClassHierarchy> appView) { this.appView = appView; this.codeRewriter = new CodeRewriter(appView); - this.deadCodeRemover = new DeadCodeRemover(appView, codeRewriter); + this.deadCodeRemover = new DeadCodeRemover(appView); } public CodeRewriter getCodeRewriter() {
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 4c152b6..a7106fc 100644 --- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java +++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -30,6 +30,7 @@ import com.android.tools.r8.graph.DexEncodedMember; 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.DexMember; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProgramClass; @@ -68,8 +69,6 @@ import com.android.tools.r8.ir.code.InvokeType; import com.android.tools.r8.ir.code.Position.SyntheticPosition; import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; -import com.android.tools.r8.ir.optimize.MemberPoolCollection.MemberPool; -import com.android.tools.r8.ir.optimize.MethodPoolCollection; import com.android.tools.r8.ir.optimize.info.OptimizationFeedback; import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple; import com.android.tools.r8.ir.synthetic.AbstractSynthesizedCode; @@ -84,7 +83,9 @@ import com.android.tools.r8.utils.Timing; import com.android.tools.r8.utils.TraversalContinuation; import com.android.tools.r8.utils.collections.BidirectionalManyToOneHashMap; +import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap; import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneMap; +import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneRepresentativeMap; import com.google.common.base.Equivalence; import com.google.common.base.Equivalence.Wrapper; import com.google.common.collect.Iterables; @@ -157,7 +158,6 @@ private final InternalOptions options; private final SubtypingInfo subtypingInfo; private final ExecutorService executorService; - private final MethodPoolCollection methodPoolCollection; private final Timing timing; private Collection<DexMethod> invokes; private final AndroidApiLevelCompute apiLevelCompute; @@ -168,8 +168,8 @@ private final Set<DexProgramClass> mergeCandidates = new LinkedHashSet<>(); // Map from source class to target class. - private final MutableBidirectionalManyToOneMap<DexType, DexType> mergedClasses = - BidirectionalManyToOneHashMap.newIdentityHashMap(); + private final MutableBidirectionalManyToOneRepresentativeMap<DexType, DexType> mergedClasses = + BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap(); private final MutableBidirectionalManyToOneMap<DexType, DexType> mergedInterfaces = BidirectionalManyToOneHashMap.newIdentityHashMap(); @@ -197,7 +197,6 @@ this.mainDexInfo = appInfo.getMainDexInfo(); this.subtypingInfo = appInfo.computeSubtypingInfo(); this.executorService = executorService; - this.methodPoolCollection = new MethodPoolCollection(appView, subtypingInfo); this.lensBuilder = new VerticalClassMergerGraphLens.Builder(appView.dexItemFactory()); this.apiLevelCompute = appView.apiLevelCompute(); this.timing = timing; @@ -998,21 +997,17 @@ // due to the way invoke-super works on default interface methods. In order to be able // to hit this method directly after the merge, we need to make it public, and find a // method name that does not collide with one in the hierarchy of this class. - MemberPool<DexMethod> methodPoolForTarget = - methodPoolCollection.buildForHierarchy(target, executorService, timing); - resultingMethod = - renameMethod( - virtualMethod, - method -> - availableMethodSignatures.test(method) - && !methodPoolForTarget.hasSeen( - MethodSignatureEquivalence.get().wrap(method)), - Rename.ALWAYS, - appView.dexItemFactory().prependHolderToProto(virtualMethod.getReference())); + DexItemFactory dexItemFactory = appView.dexItemFactory(); + String resultingMethodBaseName = + virtualMethod.getName().toString() + '$' + source.getTypeName().replace('.', '$'); + DexMethod resultingMethodReference = + dexItemFactory.createMethod( + target.getType(), + virtualMethod.getProto().prependParameter(source.getType(), dexItemFactory), + dexItemFactory.createGloballyFreshMemberString(resultingMethodBaseName)); + assert availableMethodSignatures.test(resultingMethodReference); + resultingMethod = virtualMethod.toTypeSubstitutedMethod(resultingMethodReference); makeStatic(resultingMethod); - - // Update method pool collection now that we are adding a new public method. - methodPoolForTarget.seen(resultingMethod.getReference()); } else { // This virtual method could be called directly from a sub class via an invoke-super in- // struction. Therefore, we translate this virtual method into an instance method with a @@ -1891,36 +1886,31 @@ } @Override - public DexType getOriginalType(DexType type) { - throw new Unreachable(); - } - - @Override public Iterable<DexType> getOriginalTypes(DexType type) { throw new Unreachable(); } @Override - public DexField getOriginalFieldSignature(DexField field) { + public DexType getPreviousClassType(DexType type) { throw new Unreachable(); } @Override - public DexField getRenamedFieldSignature(DexField originalField, GraphLens codeLens) { + public final DexType getNextClassType(DexType type) { + return type == source ? target.type : mergedClasses.getOrDefault(type, type); + } + + @Override + public DexField getPreviousFieldSignature(DexField field) { throw new Unreachable(); } @Override - public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) { + public DexField getNextFieldSignature(DexField field) { throw new Unreachable(); } @Override - public final DexType internalDescribeLookupClassType(DexType previous) { - return previous == source ? target.type : mergedClasses.getOrDefault(previous, previous); - } - - @Override public DexMethod getPreviousMethodSignature(DexMethod method) { throw new Unreachable(); }
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java index 3aeaad0..0f89af1 100644 --- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java +++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
@@ -83,7 +83,7 @@ BidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod> newMethodSignatures, Map<DexMethod, DexMethod> originalMethodSignaturesForBridges, Map<DexMethod, RewrittenPrototypeDescription> prototypeChanges) { - super(appView, fieldMap, methodMap, mergedClasses.getForwardMap(), newMethodSignatures); + super(appView, fieldMap, methodMap, mergedClasses.getBidirectionalMap(), newMethodSignatures); this.appView = appView; this.mergedClasses = mergedClasses; this.contextualVirtualToDirectMethodMaps = contextualVirtualToDirectMethodMaps; @@ -98,6 +98,11 @@ } @Override + public DexType getPreviousClassType(DexType type) { + return type; + } + + @Override protected Iterable<DexType> internalGetOriginalTypes(DexType previous) { Collection<DexType> originalTypes = mergedClasses.getSourcesFor(previous); Iterable<DexType> currentType = IterableUtils.singleton(previous);
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java index 9ed366e..462895f 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java +++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -90,11 +90,16 @@ AppView<?> appView, BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap, BidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod> methodMap, - Map<DexType, DexType> typeMap) { + BidirectionalManyToOneRepresentativeMap<DexType, DexType> typeMap) { super(appView, fieldMap, methodMap, typeMap); } @Override + public DexType getPreviousClassType(DexType type) { + return type; + } + + @Override public boolean isSyntheticFinalizationGraphLens() { return true; } @@ -111,7 +116,8 @@ BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap(); private final MutableBidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod> methodMap = BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap(); - private final Map<DexType, DexType> typeMap = new IdentityHashMap<>(); + private final MutableBidirectionalManyToOneRepresentativeMap<DexType, DexType> typeMap = + BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap(); boolean isEmpty() { if (typeMap.isEmpty()) {
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 538b9ee..09f5a8c 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -6,6 +6,8 @@ import static com.android.tools.r8.utils.AndroidApiLevel.B; import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyForDevelopmentOrDefault; +import com.android.tools.r8.AndroidResourceConsumer; +import com.android.tools.r8.AndroidResourceProvider; import com.android.tools.r8.CancelCompilationChecker; import com.android.tools.r8.ClassFileConsumer; import com.android.tools.r8.CompilationMode; @@ -74,6 +76,7 @@ import com.android.tools.r8.naming.ClassNameMapper; import com.android.tools.r8.naming.MapConsumer; import com.android.tools.r8.naming.MapVersion; +import com.android.tools.r8.optimize.accessmodification.AccessModifierOptions; import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorEventConsumer; import com.android.tools.r8.optimize.redundantbridgeremoval.RedundantBridgeRemovalOptions; import com.android.tools.r8.origin.Origin; @@ -172,6 +175,8 @@ // The state can only ever transition from false to true. private final AtomicBoolean cancelled = new AtomicBoolean(false); public CancelCompilationChecker cancelCompilationChecker = null; + public AndroidResourceProvider androidResourceProvider = null; + public AndroidResourceConsumer androidResourceConsumer = null; public boolean checkIfCancelled() { if (cancelCompilationChecker == null) { @@ -843,8 +848,7 @@ @Override public boolean isAccessModificationEnabled() { - return getProguardConfiguration() != null - && getProguardConfiguration().isAccessModificationAllowed(); + return accessModifierOptions.isAccessModificationEnabled(); } @Override @@ -879,6 +883,7 @@ public boolean debug = false; + private final AccessModifierOptions accessModifierOptions = new AccessModifierOptions(this); private final RewriteArrayOptions rewriteArrayOptions = new RewriteArrayOptions(); private final CallSiteOptimizationOptions callSiteOptimizationOptions = new CallSiteOptimizationOptions(); @@ -956,6 +961,10 @@ return desugarSpecificOptions; } + public AccessModifierOptions getAccessModifierOptions() { + return accessModifierOptions; + } + public CfCodeAnalysisOptions getCfCodeAnalysisOptions() { return cfCodeAnalysisOptions; } @@ -2072,10 +2081,24 @@ public static class TestingOptions { + public boolean roundtripThroughLir = false; + private boolean useLir = false; + + public void enableLir() { + useLir = true; + } + + public void disableLir() { + useLir = false; + } + + public boolean canUseLir(AppView<?> appView) { + return useLir && appView.enableWholeProgramOptimizations(); + } + // If false, use the desugared library implementation when desugared library is enabled. public boolean alwaysBackportListSetMapMethods = true; public boolean neverReuseCfLocalRegisters = false; - public boolean roundtripThroughLir = false; public boolean checkReceiverAlwaysNullInCallSiteOptimization = true; public boolean forceInlineAPIConversions = false; public boolean ignoreValueNumbering = false;
diff --git a/src/main/java/com/android/tools/r8/utils/LensUtils.java b/src/main/java/com/android/tools/r8/utils/LensUtils.java index 4097730..b0eae54 100644 --- a/src/main/java/com/android/tools/r8/utils/LensUtils.java +++ b/src/main/java/com/android/tools/r8/utils/LensUtils.java
@@ -99,9 +99,10 @@ public static Set<DexEncodedMethod> rewrittenWithRenamedSignature( Set<DexEncodedMethod> methods, DexDefinitionSupplier definitions, GraphLens lens) { + GraphLens appliedLens = GraphLens.getIdentityLens(); Set<DexEncodedMethod> result = Sets.newIdentityHashSet(); for (DexEncodedMethod method : methods) { - result.add(lens.mapDexEncodedMethod(method, definitions)); + result.add(method.rewrittenWithLens(lens, appliedLens, definitions)); } return result; }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneHashMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneHashMap.java index 2961111..aca734e 100644 --- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneHashMap.java +++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneHashMap.java
@@ -26,6 +26,10 @@ this.backing = backing; } + public BidirectionalOneToOneHashMap(Map<K, V> backing) { + this(HashBiMap.create(backing)); + } + @Override public void clear() { backing.clear();
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java index 0ac1e7c..a53de2f 100644 --- a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java +++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java
@@ -87,10 +87,11 @@ } public ProgramMethodSet rewrittenWithLens(DexDefinitionSupplier definitions, GraphLens lens) { + GraphLens appliedLens = GraphLens.getIdentityLens(); List<ProgramMethod> elementsToRemove = null; ProgramMethodSet rewrittenMethods = null; for (ProgramMethod method : this) { - ProgramMethod rewrittenMethod = lens.mapProgramMethod(method, definitions); + ProgramMethod rewrittenMethod = method.rewrittenWithLens(lens, appliedLens, definitions); if (rewrittenMethod == null) { assert lens.isEnumUnboxerLens(); // If everything has been unchanged up until now, then record that we should remove this
diff --git a/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java b/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java index aa24dde..79f582b 100644 --- a/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java +++ b/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java
@@ -58,8 +58,12 @@ @Override public SortedProgramMethodSet rewrittenWithLens( DexDefinitionSupplier definitions, GraphLens lens) { + GraphLens appliedLens = GraphLens.getIdentityLens(); return create( - consumer -> forEach(method -> consumer.accept(lens.mapProgramMethod(method, definitions)))); + consumer -> + forEach( + method -> + consumer.accept(method.rewrittenWithLens(lens, appliedLens, definitions)))); } @Override
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java index 22f879d..fa1d579 100644 --- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java +++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -132,10 +132,11 @@ .run(); test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring") + .withOptionConsumer(o -> o.testing.enableLir()) .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K)) .withBuilderTransformation( b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown())) - .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaring")) + .withDexCheck(inspector -> checkLambdaCount(inspector, 1, "lambdadesugaring")) .run(); } @@ -172,9 +173,10 @@ test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring") .withMinApiLevel(AndroidApiLevel.N) + .withOptionConsumer(opts -> opts.testing.enableLir()) .withBuilderTransformation( b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown())) - .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaring")) + .withDexCheck(inspector -> checkLambdaCount(inspector, 1, "lambdadesugaring")) .run(); }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java index 2d41bdb..069acbf 100644 --- a/src/test/java/com/android/tools/r8/ToolHelper.java +++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -593,7 +593,9 @@ } public void cacheResult(ProcessResult result) { - if (useCache()) { + // Only cache succeding runs, otherwise a flaky or killed art run can + // put invalid entries into the cache. + if (useCache() && result.exitCode == 0) { assert artResultCacheLookupKey != null; CommandResultCache.getInstance().putResult(result, artResultCacheLookupKey); } @@ -827,19 +829,19 @@ .put(DexVm.ART_6_0_1_HOST, "bin/art") .build(); - private static final List<String> DALVIK_BOOT_LIBS = - ImmutableList.of( - "core-libart-hostdex.jar", - "core-hostdex.jar", - "apache-xml-hostdex.jar"); + private static final List<String> DALVIK_4_0_BOOT_LIBS = + ImmutableList.of("core-hostdex.jar", "apache-xml-hostdex.jar"); - private static final List<String> ART_BOOT_LIBS = - ImmutableList.of( - "core-libart-hostdex.jar", - "core-oj-hostdex.jar", - "apache-xml-hostdex.jar"); + private static final List<String> DALVIK_4_4_BOOT_LIBS = + ImmutableList.of("core-libart-hostdex.jar", "core-hostdex.jar", "apache-xml-hostdex.jar"); - private static final List<String> NEWER_ART_BOOT_LIBS = + private static final List<String> ART_5_TO_6_BOOT_LIBS = + ImmutableList.of("core-libart-hostdex.jar"); + + private static final List<String> ART_7_TO_10_BOOT_LIBS = + ImmutableList.of("core-libart-hostdex.jar", "core-oj-hostdex.jar", "apache-xml-hostdex.jar"); + + private static final List<String> ART_12_PLUS_BOOT_LIBS = ImmutableList.of( "core-libart-hostdex.jar", "core-oj-hostdex.jar", @@ -851,18 +853,18 @@ static { ImmutableMap.Builder<DexVm, List<String>> builder = ImmutableMap.builder(); builder - .put(DexVm.ART_DEFAULT, ART_BOOT_LIBS) - .put(DexVm.ART_14_0_0_HOST, NEWER_ART_BOOT_LIBS) - .put(DexVm.ART_13_0_0_HOST, NEWER_ART_BOOT_LIBS) - .put(DexVm.ART_12_0_0_HOST, NEWER_ART_BOOT_LIBS) - .put(DexVm.ART_10_0_0_HOST, ART_BOOT_LIBS) - .put(DexVm.ART_9_0_0_HOST, ART_BOOT_LIBS) - .put(DexVm.ART_8_1_0_HOST, ART_BOOT_LIBS) - .put(DexVm.ART_7_0_0_HOST, ART_BOOT_LIBS) - .put(DexVm.ART_6_0_1_HOST, ART_BOOT_LIBS) - .put(DexVm.ART_5_1_1_HOST, ART_BOOT_LIBS) - .put(DexVm.ART_4_4_4_HOST, DALVIK_BOOT_LIBS) - .put(DexVm.ART_4_0_4_HOST, DALVIK_BOOT_LIBS); + .put(DexVm.ART_DEFAULT, ART_7_TO_10_BOOT_LIBS) + .put(DexVm.ART_14_0_0_HOST, ART_12_PLUS_BOOT_LIBS) + .put(DexVm.ART_13_0_0_HOST, ART_12_PLUS_BOOT_LIBS) + .put(DexVm.ART_12_0_0_HOST, ART_12_PLUS_BOOT_LIBS) + .put(DexVm.ART_10_0_0_HOST, ART_7_TO_10_BOOT_LIBS) + .put(DexVm.ART_9_0_0_HOST, ART_7_TO_10_BOOT_LIBS) + .put(DexVm.ART_8_1_0_HOST, ART_7_TO_10_BOOT_LIBS) + .put(DexVm.ART_7_0_0_HOST, ART_7_TO_10_BOOT_LIBS) + .put(DexVm.ART_6_0_1_HOST, ART_5_TO_6_BOOT_LIBS) + .put(DexVm.ART_5_1_1_HOST, ART_5_TO_6_BOOT_LIBS) + .put(DexVm.ART_4_4_4_HOST, DALVIK_4_4_BOOT_LIBS) + .put(DexVm.ART_4_0_4_HOST, DALVIK_4_0_BOOT_LIBS); BOOT_LIBS = builder.build(); } @@ -886,7 +888,6 @@ PRODUCT = builder.build(); } - private static Path getDexVmPath(DexVm vm) { DexVm.Version version = vm.getVersion(); Path base = Paths.get(TOOLS_DIR, "linux");
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/EffectiveFinalFieldMarkedFinalTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/EffectiveFinalFieldMarkedFinalTest.java index e08fd3e..d491189 100644 --- a/src/test/java/com/android/tools/r8/accessrelaxation/EffectiveFinalFieldMarkedFinalTest.java +++ b/src/test/java/com/android/tools/r8/accessrelaxation/EffectiveFinalFieldMarkedFinalTest.java
@@ -52,12 +52,7 @@ assertThat(mainClassSubject, isPresent()); assertThat( mainClassSubject.uniqueFieldWithOriginalName("instanceField"), - allOf( - isPresent(), - onlyIf( - allowAccessModification - && !parameters.canInitNewInstanceUsingSuperclassConstructor(), - isFinal()))); + allOf(isPresent(), onlyIf(allowAccessModification, isFinal()))); assertThat( mainClassSubject.uniqueFieldWithOriginalName("staticField"), allOf(isPresent(), onlyIf(allowAccessModification, isFinal())));
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/InnerClassAttributePublicizerTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/InnerClassAttributePublicizerTest.java index 41c73cf..8c3c7c3 100644 --- a/src/test/java/com/android/tools/r8/accessrelaxation/InnerClassAttributePublicizerTest.java +++ b/src/test/java/com/android/tools/r8/accessrelaxation/InnerClassAttributePublicizerTest.java
@@ -5,6 +5,7 @@ package com.android.tools.r8.accessrelaxation; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -52,6 +53,7 @@ private void inspect(CodeInspector inspector) { ClassSubject classSubject = inspector.clazz(Outer.Inner.class); assertThat(classSubject, isPresent()); + assertThat(classSubject, isPublic()); InnerClassAttribute innerClassAttribute = classSubject.getDexProgramClass().getInnerClassAttributeForThisClass();
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/InterfaceMethodAndSiblingConsistentRenamingPublicizerTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/InterfaceMethodAndSiblingConsistentRenamingPublicizerTest.java new file mode 100644 index 0000000..0cf8434 --- /dev/null +++ b/src/test/java/com/android/tools/r8/accessrelaxation/InterfaceMethodAndSiblingConsistentRenamingPublicizerTest.java
@@ -0,0 +1,129 @@ +// Copyright (c) 2023, 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.accessrelaxation; + +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.assertNotEquals; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class InterfaceMethodAndSiblingConsistentRenamingPublicizerTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() + .enableNoHorizontalClassMergingAnnotations() + .setMinApi(parameters) + .compile() + .inspect( + inspector -> { + // Check that B.foo(), C.foo() and I.foo() are all present in the output. + ClassSubject bClassSubject = inspector.clazz(B.class); + assertThat(bClassSubject, isPresent()); + + MethodSubject bMethodSubject = bClassSubject.uniqueMethodWithOriginalName("foo"); + assertThat(bMethodSubject, isPresent()); + + ClassSubject cClassSubject = inspector.clazz(C.class); + assertThat(cClassSubject, isPresent()); + + MethodSubject cMethodSubject = cClassSubject.uniqueMethodWithOriginalName("foo"); + assertThat(cMethodSubject, isPresent()); + + ClassSubject iClassSubject = inspector.clazz(I.class); + assertThat(iClassSubject, isPresent()); + + MethodSubject iMethodSubject = iClassSubject.uniqueMethodWithOriginalName("foo"); + assertThat(iMethodSubject, isPresent()); + + // Check that B.foo() is renamed to one name, and C.foo() and I.foo() is renamed to + // another name. The reason for B.foo() being given another name is that we use a + // single naming state for the classes, which means that we must give B.foo() another + // name to ensure we do not introduce new method overriding relationships from + // publicizing. + assertNotEquals(bMethodSubject.getFinalName(), cMethodSubject.getFinalName()); + assertEquals(cMethodSubject.getFinalName(), iMethodSubject.getFinalName()); + }) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("B", "C", "C"); + } + + static class Main { + + public static void main(String[] args) { + new B().foo(); + new C().foo(); + I i = System.currentTimeMillis() > 0 ? new D() : new E(); + i.foo(); + } + } + + interface I { + + void foo(); + } + + static class A {} + + @NeverClassInline + @NoHorizontalClassMerging + static class B extends A { + + @NeverInline + private void foo() { + System.out.println("B"); + } + } + + @NeverClassInline + @NoHorizontalClassMerging + static class C extends A { + + // Implements I.foo(). + @NeverInline + public void foo() { + System.out.println("C"); + } + } + + @NoHorizontalClassMerging + static class D extends C implements I {} + + @NoHorizontalClassMerging + static class E extends C implements I { + + @Override + public void foo() { + System.out.println("E"); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java index 5c85d69..87a0a49 100644 --- a/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java +++ b/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java
@@ -10,12 +10,12 @@ import com.android.tools.r8.NeverInline; import com.android.tools.r8.NeverPropagateValue; import com.android.tools.r8.NoVerticalClassMerging; -import com.android.tools.r8.R8TestCompileResult; +import com.android.tools.r8.ProguardVersion; import com.android.tools.r8.TestParameters; +import com.android.tools.r8.ToolHelper.DexVm.Version; import com.android.tools.r8.naming.MemberNaming.MethodSignature; 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.StringUtils; import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.google.common.collect.ImmutableList; @@ -25,12 +25,12 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; -import java.nio.file.Path; +import java.security.NoSuchAlgorithmException; import java.util.List; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; @NoVerticalClassMerging class MySerializable implements Serializable { @@ -99,9 +99,8 @@ ); private final boolean accessModification; - private Path configuration; - @Parameterized.Parameters(name = "{0}, access-modification: {1}") + @Parameters(name = "{0}, access-modification: {1}") public static List<Object[]> data() { return buildParameters( getTestParameters() @@ -116,25 +115,17 @@ this.accessModification = accessModification; } - @Before - public void setUpConfiguration() throws Exception { - configuration = temp.newFile("pg.conf").toPath().toAbsolutePath(); - FileUtils.writeTextFile(configuration, StringUtils.lines( - keepMainProguardConfiguration(MAIN), - accessModification ? "-allowaccessmodification" : "" - )); - } - @Test public void testProguard_withKeepRules() throws Exception { assumeTrue(parameters.isCfRuntime()); - testForProguard() + testForProguard(ProguardVersion.getLatest()) .addProgramClasses(CLASSES) - .addKeepRuleFiles(configuration) + .addKeepMainRule(MAIN) .addKeepRules(KEEPMEMBER_RULES) .addInliningAnnotations() .addMemberValuePropagationAnnotations() .addNoVerticalClassMergingAnnotations() + .allowAccessModification(accessModification) .compile() .run(parameters.getRuntime(), MAIN) .assertSuccessWithOutput(EXPECTED_OUTPUT) @@ -143,34 +134,42 @@ @Test public void testR8_withKeepRules() throws Exception { - R8TestCompileResult result = - testForR8(parameters.getBackend()) - .addProgramClasses(CLASSES) - .addMemberValuePropagationAnnotations() - .addNoVerticalClassMergingAnnotations() - .enableInliningAnnotations() - .addKeepRuleFiles(configuration) - .addKeepRules(KEEPMEMBER_RULES) - .setMinApi(parameters) - .compile() - .inspect(this::inspect); - // TODO(b/117302947): Need to update ART binary. - if (parameters.isCfRuntime()) { - result - .run(parameters.getRuntime(), MAIN) - .assertSuccessWithOutput(EXPECTED_OUTPUT); - } + testForR8(parameters.getBackend()) + .addProgramClasses(CLASSES) + .addMemberValuePropagationAnnotations() + .addNoVerticalClassMergingAnnotations() + .enableInliningAnnotations() + .addKeepMainRule(MAIN) + .addKeepRules(KEEPMEMBER_RULES) + .allowAccessModification(accessModification) + .setMinApi(parameters) + .compile() + .inspect(this::inspect) + .run(parameters.getRuntime(), MAIN) + .applyIf( + parameters.isCfRuntime() + || parameters.getDexRuntimeVersion().isEqualToOneOf(Version.V5_1_1, Version.V8_1_0) + || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V10_0_0), + runResult -> runResult.assertSuccessWithOutput(EXPECTED_OUTPUT), + parameters.isDexRuntimeVersion(Version.DEFAULT) + || parameters.isDexRuntimeVersion(Version.V6_0_1) + || parameters.isDexRuntimeVersion(Version.V7_0_0) + || parameters.isDexRuntimeVersion(Version.V9_0_0), + runResult -> runResult.assertFailureWithErrorThatThrows(UnsatisfiedLinkError.class), + runResult -> + runResult.assertFailureWithErrorThatThrows(NoSuchAlgorithmException.class)); } @Test public void testProguard_withoutKeepRules() throws Exception { assumeTrue(parameters.isCfRuntime()); - testForProguard() + testForProguard(ProguardVersion.getLatest()) .addProgramClasses(CLASSES) .addInliningAnnotations() .addMemberValuePropagationAnnotations() .addNoVerticalClassMergingAnnotations() - .addKeepRuleFiles(configuration) + .addKeepMainRule(MAIN) + .allowAccessModification(accessModification) .compile() .run(parameters.getRuntime(), MAIN) .assertFailureWithErrorThatMatches(containsString("Could not deserialize")); @@ -178,21 +177,30 @@ @Test public void testR8_withoutKeepRules() throws Exception { - R8TestCompileResult result = - testForR8(parameters.getBackend()) - .addProgramClasses(CLASSES) - .addNoVerticalClassMergingAnnotations() - .enableInliningAnnotations() - .enableMemberValuePropagationAnnotations() - .addKeepRuleFiles(configuration) - .setMinApi(parameters) - .compile(); - // TODO(b/117302947): Need to update ART binary. - if (parameters.isCfRuntime()) { - result - .run(parameters.getRuntime(), MAIN) - .assertFailureWithErrorThatMatches(containsString("Could not deserialize")); - } + testForR8(parameters.getBackend()) + .addProgramClasses(CLASSES) + .addNoVerticalClassMergingAnnotations() + .enableInliningAnnotations() + .enableMemberValuePropagationAnnotations() + .addKeepMainRule(MAIN) + .allowAccessModification(accessModification) + .setMinApi(parameters) + .compile() + .run(parameters.getRuntime(), MAIN) + .applyIf( + parameters.isCfRuntime() + || parameters.getDexRuntimeVersion().isEqualToOneOf(Version.V5_1_1, Version.V8_1_0) + || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V10_0_0), + runResult -> + runResult.assertFailureWithErrorThatMatches( + containsString("Could not deserialize")), + parameters.isDexRuntimeVersion(Version.DEFAULT) + || parameters.isDexRuntimeVersion(Version.V6_0_1) + || parameters.isDexRuntimeVersion(Version.V7_0_0) + || parameters.isDexRuntimeVersion(Version.V9_0_0), + runResult -> runResult.assertFailureWithErrorThatThrows(UnsatisfiedLinkError.class), + runResult -> + runResult.assertFailureWithErrorThatThrows(NoSuchAlgorithmException.class)); } private void inspect(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java index 1c6d936..d41508e 100644 --- a/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java +++ b/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
@@ -47,35 +47,17 @@ } @Test - public void testStaticMethodRelaxation() throws Exception { - String expectedOutput = - StringUtils.lines( - "A::baz()", - "A::bar()", - "A::bar(int)", - "A::blah(int)", - "B::blah(int)", - "BB::blah(int)", - "A::foo()A::baz()A::bar()A::bar(int)", - "B::bar() >> java.lang.IllegalAccessError", - "java.lang.IllegalAccessError", - "A::foo()A::baz()A::bar()A::bar(int)", - "B::blah(int)", - "A::foo()A::baz()A::bar()A::bar(int)", - "B::bar() >> java.lang.IllegalAccessError", - "C::bar(int)java.lang.IllegalAccessErrorB::bar() >> " - + "java.lang.IllegalAccessErrorB::bar() >> java.lang.IllegalAccessError", - "B::foo()A::foo()A::baz()A::bar()A::bar(int)", - "C::blah(int)"); - Class<?> mainClass = C.class; - if (parameters.isCfRuntime()) { - // Only run JVM reference on CF runtimes. - testForJvm(parameters) - .addTestClasspath() - .run(parameters.getRuntime(), mainClass) - .assertSuccessWithOutput(expectedOutput); - } + public void testStaticMethodRelaxationJvm() throws Exception { + parameters.assumeJvmTestParameters(); + testForJvm(parameters) + .addTestClasspath() + .run(parameters.getRuntime(), C.class) + .assertSuccessWithOutput(getExpectedOutputForStaticMethodRelaxationTest()); + } + @Test + public void testStaticMethodRelaxation() throws Exception { + Class<?> mainClass = C.class; R8TestRunResult result = testForR8(parameters.getBackend()) .addProgramFiles(ToolHelper.getClassFilesForTestPackage(mainClass.getPackage())) @@ -105,9 +87,8 @@ "}") .allowAccessModification() .setMinApi(parameters) - .run(parameters.getRuntime(), mainClass); - - assertEquals(expectedOutput, result.getStdOut()); + .run(parameters.getRuntime(), mainClass) + .assertSuccessWithOutput(getExpectedOutputForStaticMethodRelaxationTest()); CodeInspector inspector = result.inspector(); @@ -121,13 +102,43 @@ MethodSignature blahMethodSignatureAfterArgumentRemoval = new MethodSignature( - "blah", + enableUnusedArgumentRemoval ? "blah$1" : "blah", STRING, enableUnusedArgumentRemoval ? ImmutableList.of() : ImmutableList.of("int")); assertPublic(inspector, A.class, blahMethodSignatureAfterArgumentRemoval); assertPublic(inspector, BB.class, blahMethodSignatureAfterArgumentRemoval); } + private static String getExpectedOutputForStaticMethodRelaxationTest() { + return StringUtils.lines( + "A::baz()", + "A::bar()", + "A::bar(int)", + "A::blah(int)", + "B::blah(int)", + "BB::blah(int)", + "A::foo()A::baz()A::bar()A::bar(int)", + "B::bar() >> java.lang.IllegalAccessError", + "java.lang.IllegalAccessError", + "A::foo()A::baz()A::bar()A::bar(int)", + "B::blah(int)", + "A::foo()A::baz()A::bar()A::bar(int)", + "B::bar() >> java.lang.IllegalAccessError", + "C::bar(int)java.lang.IllegalAccessErrorB::bar() >> " + + "java.lang.IllegalAccessErrorB::bar() >> java.lang.IllegalAccessError", + "B::foo()A::foo()A::baz()A::bar()A::bar(int)", + "C::blah(int)"); + } + + @Test + public void testInstanceMethodRelaxationJvm() throws Exception { + parameters.assumeJvmTestParameters(); + testForJvm(parameters) + .addTestClasspath() + .run(parameters.getRuntime(), TestMain.class) + .assertSuccessWithOutput(getExpectedOutputForInstanceMethodRelaxationTest()); + } + @Test public void testInstanceMethodRelaxationWithVerticalClassMerging() throws Exception { testInstanceMethodRelaxation(true); @@ -139,29 +150,7 @@ } private void testInstanceMethodRelaxation(boolean enableVerticalClassMerging) throws Exception { - String expectedOutput = - StringUtils.lines( - "Base::foo()", - "Base::foo1()", - "Base::foo2()", - "Base::foo3()", - "Sub1::foo1()", - "Itf1::foo1(0) >> Sub1::foo1()", - "Sub1::bar1(0)", - "Sub1::foo3()", - "Sub2::foo2()", - "Itf2::foo2(0) >> Sub2::foo2()", - "Sub2::bar2(0)", - "Sub2::foo3()"); Class<?> mainClass = TestMain.class; - if (parameters.isCfRuntime()) { - // Only run JVM reference on CF runtimes. - testForJvm(parameters) - .addTestClasspath() - .run(parameters.getRuntime(), mainClass) - .assertSuccessWithOutput(expectedOutput); - } - R8TestRunResult result = testForR8(parameters.getBackend()) .addProgramFiles(ToolHelper.getClassFilesForTestPackage(mainClass.getPackage())) @@ -188,7 +177,7 @@ .run(parameters.getRuntime(), mainClass); assertEquals( - expectedOutput, + getExpectedOutputForInstanceMethodRelaxationTest(), result .getStdOut() .replace("java.lang.IncompatibleClassChangeError", "java.lang.IllegalAccessError")); @@ -199,10 +188,10 @@ CodeInspector codeInspector = result.inspector(); assertPublic(codeInspector, Base.class, new MethodSignature("foo", STRING, ImmutableList.of())); - // Base#foo?() can't be publicized due to Itf<1>#foo<1>(). - assertNotPublic( + // Base#foo?() is publicized by renaming due to Itf<1>#foo<1>(). + assertPublic( codeInspector, Base.class, new MethodSignature("foo1", STRING, ImmutableList.of())); - assertNotPublic( + assertPublic( codeInspector, Base.class, new MethodSignature("foo2", STRING, ImmutableList.of())); if (!enableVerticalClassMerging) { @@ -217,4 +206,20 @@ codeInspector, Sub2.class, new MethodSignature("bar2", STRING, ImmutableList.of("int"))); } } + + private static String getExpectedOutputForInstanceMethodRelaxationTest() { + return StringUtils.lines( + "Base::foo()", + "Base::foo1()", + "Base::foo2()", + "Base::foo3()", + "Sub1::foo1()", + "Itf1::foo1(0) >> Sub1::foo1()", + "Sub1::bar1(0)", + "Sub1::foo3()", + "Sub2::foo2()", + "Itf2::foo2(0) >> Sub2::foo2()", + "Sub2::bar2(0)", + "Sub2::foo3()"); + } }
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/PrivateOverrideOfPublicMethodTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/PrivateOverrideOfPublicMethodTest.java new file mode 100644 index 0000000..82ceae1 --- /dev/null +++ b/src/test/java/com/android/tools/r8/accessrelaxation/PrivateOverrideOfPublicMethodTest.java
@@ -0,0 +1,125 @@ +// Copyright (c) 2023, 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.accessrelaxation; + +import static com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate.onName; + +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoVerticalClassMerging; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper.DexVm.Version; +import com.android.tools.r8.utils.StringUtils; +import java.io.IOException; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class PrivateOverrideOfPublicMethodTest extends TestBase { + + private static final String EXPECTED_OUTPUT = StringUtils.lines("A", "B", "IAE", "A", "B", "A"); + private static final String EXPECTED_OUTPUT_5_TO_6 = + StringUtils.lines("A", "A", "A", "A", "A", "A"); + private static final String EXPECTED_OUTPUT_7 = StringUtils.lines("A", "B", "A", "A", "B", "A"); + private static final String EXPECTED_OUTPUT_R8 = StringUtils.lines("A", "B", "B", "A", "B", "A"); + + private static byte[] programClassFileData; + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @BeforeClass + public static void setup() throws IOException { + programClassFileData = + transformer(B.class) + .renameMethod(onName("bar"), "foo") + .transformMethodInsnInMethod( + "<init>", + (opcode, owner, name, descriptor, isInterface, continuation) -> + continuation.visitMethodInsn( + opcode, owner, name.equals("bar") ? "foo" : name, descriptor, isInterface)) + .transform(); + } + + @Test + public void testRuntime() throws Exception { + testForRuntime(parameters) + .addProgramClasses(Main.class, A.class) + .addProgramClassFileData(programClassFileData) + .run(parameters.getRuntime(), Main.class) + .applyIf( + parameters.isDexRuntimeVersionNewerThanOrEqual(Version.V5_1_1) + && parameters.isDexRuntimeVersionOlderThanOrEqual(Version.V6_0_1), + runResult -> runResult.assertSuccessWithOutput(EXPECTED_OUTPUT_5_TO_6), + parameters.isDexRuntimeVersion(Version.V7_0_0), + runResult -> runResult.assertSuccessWithOutput(EXPECTED_OUTPUT_7), + runResult -> runResult.assertSuccessWithOutput(EXPECTED_OUTPUT)); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(Main.class, A.class) + .addProgramClassFileData(programClassFileData) + .addKeepMainRule(Main.class) + .allowAccessModification() + .enableInliningAnnotations() + .enableNoVerticalClassMergingAnnotations() + .setMinApi(parameters) + .run(parameters.getRuntime(), Main.class) + // TODO(b/278687711): Access modifier should preserve IllegalAccessErrors. + .assertSuccessWithOutput(EXPECTED_OUTPUT_R8); + } + + static class Main { + + public static void main(String[] args) { + new A().foo(); + try { + new B().foo(); + } catch (IllegalAccessError e) { + System.out.println("IAE"); + } + A a = System.currentTimeMillis() > 0 ? new A() : new B(); + a.foo(); + A b = System.currentTimeMillis() > 0 ? new B() : new A(); + b.foo(); + } + } + + @NoVerticalClassMerging + static class A { + + @NeverInline + public void foo() { + System.out.println("A"); + } + } + + static class B extends A { + + B() { + if (System.currentTimeMillis() > 0) { + bar(); + } + } + + // Renamed to foo. + @NeverInline + private void bar() { + System.out.println("B"); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/PrivateShadowOfPrivateMethodTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/PrivateShadowOfPrivateMethodTest.java new file mode 100644 index 0000000..ce6a9d0 --- /dev/null +++ b/src/test/java/com/android/tools/r8/accessrelaxation/PrivateShadowOfPrivateMethodTest.java
@@ -0,0 +1,103 @@ +// Copyright (c) 2023, 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.accessrelaxation; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertNotEquals; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoVerticalClassMerging; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class PrivateShadowOfPrivateMethodTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .allowAccessModification() + .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() + .enableNoVerticalClassMergingAnnotations() + .setMinApi(parameters) + .compile() + .inspect( + inspector -> { + // Check that A.foo() is publicized. + ClassSubject aClassSubject = inspector.clazz(A.class); + assertThat(aClassSubject, isPresent()); + + MethodSubject aMethodSubject = aClassSubject.uniqueMethodWithOriginalName("foo"); + assertThat(aMethodSubject, isPresent()); + assertThat(aMethodSubject, isPublic()); + + // Check that B.foo is publicized. + ClassSubject bClassSubject = inspector.clazz(B.class); + assertThat(bClassSubject, isPresent()); + + MethodSubject bMethodSubject = bClassSubject.uniqueMethodWithOriginalName("foo"); + assertThat(bMethodSubject, isPresent()); + assertThat(bMethodSubject, isPublic()); + + // Verify that the two methods are given different names. + assertNotEquals(aMethodSubject.getFinalName(), bMethodSubject.getFinalName()); + }) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("A", "B", "A", "A"); + } + + static class Main { + + public static void main(String[] args) { + new A().foo(); + new B().foo(); + A a = System.currentTimeMillis() > 0 ? new A() : new B(); + a.foo(); + A b = System.currentTimeMillis() > 0 ? new B() : new A(); + b.foo(); + } + } + + @NeverClassInline + @NoVerticalClassMerging + static class A { + + @NeverInline + private void foo() { + System.out.println("A"); + } + } + + @NeverClassInline + static class B extends A { + + @NeverInline + private void foo() { + System.out.println("B"); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/PublicOverrideOfPrivateMethodTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/PublicOverrideOfPrivateMethodTest.java new file mode 100644 index 0000000..09d3456 --- /dev/null +++ b/src/test/java/com/android/tools/r8/accessrelaxation/PublicOverrideOfPrivateMethodTest.java
@@ -0,0 +1,91 @@ +// Copyright (c) 2023, 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.accessrelaxation; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoVerticalClassMerging; +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 org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class PublicOverrideOfPrivateMethodTest extends TestBase { + + private static final String EXPECTED_OUTPUT = StringUtils.lines("A", "B", "B", "A", "B", "A"); + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void testRuntime() throws Exception { + testForRuntime(parameters) + .addInnerClasses(getClass()) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .allowAccessModification() + .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() + .enableNoVerticalClassMergingAnnotations() + .setMinApi(parameters) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + + static class Main { + + public static void main(String[] args) { + new A().foo(); + new B().foo(); + A a = System.currentTimeMillis() > 0 ? new A() : new B(); + a.foo(); + A b = System.currentTimeMillis() > 0 ? new B() : new A(); + b.foo(); + } + } + + @NeverClassInline + @NoVerticalClassMerging + static class A { + + @NeverInline + private void foo() { + System.out.println("A"); + } + } + + @NeverClassInline + static class B extends A { + + B() { + if (System.currentTimeMillis() > 0) { + foo(); + } + } + + @NeverInline + public void foo() { + System.out.println("B"); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java index 0c25396..dcc51cd 100644 --- a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java +++ b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java
@@ -3,9 +3,11 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.accessrelaxation.privateinstance; +import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; import com.android.tools.r8.NeverPropagateValue; +@NeverClassInline public class Base { @NeverPropagateValue
diff --git a/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java b/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java new file mode 100644 index 0000000..b58587b --- /dev/null +++ b/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
@@ -0,0 +1,70 @@ +// Copyright (c) 2023, 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.androidresources; + +import java.nio.charset.StandardCharsets; + +public class AndroidResourceTestingUtils { + + // Taken from default empty android studio activity template + public static String TEST_MANIFEST = + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + + " xmlns:tools=\"http://schemas.android.com/tools\">\n" + + "\n" + + " <application\n" + + " android:allowBackup=\"true\"\n" + + " android:dataExtractionRules=\"@xml/data_extraction_rules\"\n" + + " android:fullBackupContent=\"@xml/backup_rules\"\n" + + " android:icon=\"@mipmap/ic_launcher\"\n" + + " android:label=\"@string/app_name\"\n" + + " android:roundIcon=\"@mipmap/ic_launcher_round\"\n" + + " android:supportsRtl=\"true\"\n" + + " android:theme=\"@style/Theme.MyApplication\"\n" + + " tools:targetApi=\"31\">\n" + + " <activity\n" + + " android:name=\".MainActivity\"\n" + + " android:exported=\"true\"\n" + + " android:label=\"@string/app_name\"\n" + + " android:theme=\"@style/Theme.MyApplication.NoActionBar\">\n" + + " <intent-filter>\n" + + " <action android:name=\"android.intent.action.MAIN\" />\n" + + "\n" + + " <category android:name=\"android.intent.category.LAUNCHER\" />\n" + + " </intent-filter>\n" + + "\n" + + " <meta-data\n" + + " android:name=\"android.app.lib_name\"\n" + + " android:value=\"\" />\n" + + " </activity>\n" + + " </application>\n" + + "\n" + + "</manifest>"; + + // TODO(287399385): Add testing utils for generating/consuming resource tables. + public static byte[] TEST_RESOURCE_TABLE = "RESOURCE_TABLE".getBytes(StandardCharsets.UTF_8); + + // The below byte arrays are lifted from the resource shrinkers DummyContent + + // A 1x1 pixel PNG of type BufferedImage.TYPE_BYTE_GRAY + public static final byte[] TINY_PNG = + new byte[] { + (byte) -119, (byte) 80, (byte) 78, (byte) 71, (byte) 13, (byte) 10, + (byte) 26, (byte) 10, (byte) 0, (byte) 0, (byte) 0, (byte) 13, + (byte) 73, (byte) 72, (byte) 68, (byte) 82, (byte) 0, (byte) 0, + (byte) 0, (byte) 1, (byte) 0, (byte) 0, (byte) 0, (byte) 1, + (byte) 8, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 58, + (byte) 126, (byte) -101, (byte) 85, (byte) 0, (byte) 0, (byte) 0, + (byte) 10, (byte) 73, (byte) 68, (byte) 65, (byte) 84, (byte) 120, + (byte) -38, (byte) 99, (byte) 96, (byte) 0, (byte) 0, (byte) 0, + (byte) 2, (byte) 0, (byte) 1, (byte) -27, (byte) 39, (byte) -34, + (byte) -4, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 73, + (byte) 69, (byte) 78, (byte) 68, (byte) -82, (byte) 66, (byte) 96, + (byte) -126 + }; + + // The XML document <x/> as a proto packed with AAPT2 + public static final byte[] TINY_PROTO_XML = + new byte[] {0xa, 0x3, 0x1a, 0x1, 0x78, 0x1a, 0x2, 0x8, 0x1}; +}
diff --git a/src/test/java/com/android/tools/r8/androidresources/AndroidResourcesPassthroughTest.java b/src/test/java/com/android/tools/r8/androidresources/AndroidResourcesPassthroughTest.java new file mode 100644 index 0000000..f0bb82a --- /dev/null +++ b/src/test/java/com/android/tools/r8/androidresources/AndroidResourcesPassthroughTest.java
@@ -0,0 +1,84 @@ +// Copyright (c) 2023, 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.androidresources; + +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.ArchiveProtoAndroidResourceConsumer; +import com.android.tools.r8.ArchiveProtoAndroidResourceProvider; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.origin.PathOrigin; +import com.android.tools.r8.utils.ZipUtils; +import com.android.tools.r8.utils.ZipUtils.ZipBuilder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.Arrays; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class AndroidResourcesPassthroughTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection parameters() { + return getTestParameters().withDexRuntimes().withAllApiLevels().build(); + } + + @Test + public void testR8() throws Exception { + String manifestPath = "AndroidManifest.xml"; + String resourcePath = "resources.pb"; + String pngPath = "res/drawable/foo.png"; + String xmlPath = "res/xml/bar.xml"; + Path resources = + ZipBuilder.builder(temp.newFile("resources.zip").toPath()) + .addText(manifestPath, AndroidResourceTestingUtils.TEST_MANIFEST) + .addBytes(resourcePath, AndroidResourceTestingUtils.TEST_RESOURCE_TABLE) + .addBytes(pngPath, AndroidResourceTestingUtils.TINY_PNG) + .addBytes(xmlPath, AndroidResourceTestingUtils.TINY_PROTO_XML) + .build(); + Path output = temp.newFile("resources_out.zip").toPath(); + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .setMinApi(parameters) + .addOptionsModification( + o -> { + o.androidResourceProvider = + new ArchiveProtoAndroidResourceProvider(resources, new PathOrigin(resources)); + o.androidResourceConsumer = new ArchiveProtoAndroidResourceConsumer(output); + }) + .addKeepMainRule(FooBar.class) + .run(parameters.getRuntime(), FooBar.class) + .assertSuccessWithOutputLines("Hello World"); + assertTrue( + Arrays.equals( + ZipUtils.readSingleEntry(output, manifestPath), + AndroidResourceTestingUtils.TEST_MANIFEST.getBytes(StandardCharsets.UTF_8))); + assertTrue( + Arrays.equals( + ZipUtils.readSingleEntry(output, resourcePath), + AndroidResourceTestingUtils.TEST_RESOURCE_TABLE)); + assertTrue( + Arrays.equals( + ZipUtils.readSingleEntry(output, pngPath), AndroidResourceTestingUtils.TINY_PNG)); + assertTrue( + Arrays.equals( + ZipUtils.readSingleEntry(output, xmlPath), AndroidResourceTestingUtils.TINY_PROTO_XML)); + } + + public static class FooBar { + + public static void main(String[] args) { + System.out.println("Hello World"); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/dagger/DaggerBasicNotSingletonUsingBindsTest.java b/src/test/java/com/android/tools/r8/dagger/DaggerBasicNotSingletonUsingBindsTest.java index f0bc1e5..173efa1 100644 --- a/src/test/java/com/android/tools/r8/dagger/DaggerBasicNotSingletonUsingBindsTest.java +++ b/src/test/java/com/android/tools/r8/dagger/DaggerBasicNotSingletonUsingBindsTest.java
@@ -68,7 +68,7 @@ } private void inspect(CodeInspector inspector) { - assertEquals(parameters.isCfRuntime() ? 1 : 2, inspector.allClasses().size()); + assertEquals(1, inspector.allClasses().size()); } @Test @@ -77,6 +77,7 @@ .addProgramFiles(getProgramFiles(target)) .setMinApi(parameters) .addKeepMainRule(MAIN_CLASS) + .addOptionsModification(o -> o.testing.enableLir()) .run(parameters.getRuntime(), MAIN_CLASS) .inspect(this::inspect) .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
diff --git a/src/test/java/com/android/tools/r8/dex/DexCodeDeduppingTest.java b/src/test/java/com/android/tools/r8/dex/DexCodeDeduppingTest.java index 4b0b57a..d5b8f32 100644 --- a/src/test/java/com/android/tools/r8/dex/DexCodeDeduppingTest.java +++ b/src/test/java/com/android/tools/r8/dex/DexCodeDeduppingTest.java
@@ -4,6 +4,7 @@ package com.android.tools.r8.dex; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import com.android.tools.r8.CompilationFailedException; import com.android.tools.r8.D8TestCompileResult; @@ -15,10 +16,14 @@ import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.startup.utils.MixedSectionLayoutInspector; import com.android.tools.r8.utils.AndroidApiLevel; import com.google.common.collect.ImmutableList; import java.io.IOException; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map; import org.junit.Test; @@ -31,6 +36,12 @@ private final TestParameters parameters; private static final List<String> EXPECTED = ImmutableList.of("foo", "bar", "foo", "bar"); + private static final int ONE_CLASS_COUNT = 4; + private static final int ONE_CLASS_DEDUPLICATED_COUNT = 3; + + private static final int TWO_CLASS_COUNT = 6; + private static final int TWO_CLASS_DEDUPLICATED_COUNT = 3; + @Parameters(name = "{0}") public static TestParametersCollection data() { return getTestParameters().withDexRuntimes().withAllApiLevels().build(); @@ -97,6 +108,14 @@ testForR8(parameters.getBackend()) .addProgramClasses(Foo.class, Bar.class) .setMinApi(parameters) + .addOptionsModification( + options -> + options + .getTestingOptions() + .setMixedSectionLayoutStrategyInspector( + codeLayoutInspector( + parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.S), + true))) .addKeepAllClassesRule() .compile(); compile.run(parameters.getRuntime(), Foo.class).assertSuccessWithOutputLines(EXPECTED); @@ -110,6 +129,14 @@ .addProgramClasses(Foo.class, Bar.class) .addKeepAttributeLineNumberTable() .setMinApi(parameters) + .addOptionsModification( + options -> + options + .getTestingOptions() + .setMixedSectionLayoutStrategyInspector( + codeLayoutInspector( + parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.S), + true))) .addKeepAllClassesRule() .compile(); compile.run(parameters.getRuntime(), Foo.class).assertSuccessWithOutputLines(EXPECTED); @@ -122,6 +149,14 @@ testForD8(parameters.getBackend()) .addProgramClasses(Foo.class, Bar.class) .setMinApi(parameters) + .addOptionsModification( + options -> + options + .getTestingOptions() + .setMixedSectionLayoutStrategyInspector( + codeLayoutInspector( + parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.S), + true))) .release() .internalEnableMappingOutput() .compile(); @@ -135,6 +170,11 @@ testForD8(parameters.getBackend()) .addProgramClasses(Foo.class, Bar.class) .setMinApi(parameters) + .addOptionsModification( + options -> + options + .getTestingOptions() + .setMixedSectionLayoutStrategyInspector(codeLayoutInspector(false, true))) .release() .compile(); compile.run(parameters.getRuntime(), Foo.class).assertSuccessWithOutputLines(EXPECTED); @@ -142,12 +182,46 @@ assertSizes(compile.writeToZip(), 6, 6); } + private MixedSectionLayoutInspector codeLayoutInspector( + boolean assumeDeduplicated, boolean twoClasses) { + return new MixedSectionLayoutInspector() { + @Override + public void inspectCodeLayout(int virtualFile, Collection<ProgramMethod> layout) { + // We have as many methods, the ones sharing code are just last. + assertTrue(layout.size() == (twoClasses ? TWO_CLASS_COUNT : ONE_CLASS_COUNT)); + + ArrayList<ProgramMethod> programMethods = new ArrayList<>(layout); + if (twoClasses) { + if (assumeDeduplicated) { + // The two init methods are shared. + // The two foo methods AND the bar method are shared, so they should be last. + // We sort by count, then fully qualified name. + assertTrue(programMethods.get(0).toString().contains("Foo.main")); + assertTrue(programMethods.get(1).toString().contains("Bar.<init>")); + assertTrue(programMethods.get(2).toString().contains("Foo.<init>")); + assertTrue(programMethods.get(3).toString().contains("Bar.foo")); + assertTrue(programMethods.get(4).toString().contains("Foo.bar")); + assertTrue(programMethods.get(5).toString().contains("Foo.foo")); + } else { + // We sort by name. + assertTrue(programMethods.get(0).toString().contains("Bar.<init>")); + assertTrue(programMethods.get(1).toString().contains("Bar.foo")); + assertTrue(programMethods.get(2).toString().contains("Foo.<init>")); + assertTrue(programMethods.get(3).toString().contains("Foo.bar")); + assertTrue(programMethods.get(4).toString().contains("Foo.foo")); + assertTrue(programMethods.get(5).toString().contains("Foo.main")); + } + } + } + }; + } + private void assertFooSizes(Path output) throws Exception { - assertSizes(output, 3, 4); + assertSizes(output, ONE_CLASS_DEDUPLICATED_COUNT, ONE_CLASS_COUNT); } private void assertFooAndBarSizes(Path output) throws Exception { - assertSizes(output, 3, 6); + assertSizes(output, TWO_CLASS_DEDUPLICATED_COUNT, TWO_CLASS_COUNT); } private void assertSizes(Path output, int deduppedSize, int originalSize)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java index 84f5003..ec1f304 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java
@@ -172,14 +172,14 @@ assertNotNull(argument); Value argumentValue = argument.outValue(); - BasicBlock block1 = irCode.blocks.get(1); + BasicBlock block1 = irCode.blocks.stream().filter(b -> b.getNumber() == 1).findFirst().get(); assertTrue(block1.exit().isIf()); - BasicBlock block3 = irCode.blocks.get(3); + BasicBlock block3 = irCode.blocks.stream().filter(b -> b.getNumber() == 3).findFirst().get(); assertTrue(block1.getSuccessors().contains(block3)); assertTrue(block3.exit().isGoto()); - BasicBlock block4 = irCode.blocks.get(4); + BasicBlock block4 = irCode.blocks.stream().filter(b -> b.getNumber() == 4).findFirst().get(); assertSame(block3.getUniqueNormalSuccessor(), block4); Phi firstPhi =
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java index 5ece012..483ed33 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -12,7 +12,6 @@ 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 com.android.tools.r8.SingleTestRunResult; import com.android.tools.r8.TestParameters; @@ -42,7 +41,6 @@ import com.android.tools.r8.utils.codeinspector.FoundClassSubject; import com.google.common.collect.Sets; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.stream.Collectors; import org.junit.Assume; @@ -312,6 +310,8 @@ .allowAccessModification() .enableInliningAnnotations() .addDontObfuscate() + // Using LIR changes the inlining heuristics so enable it consistently. + .addOptionsModification(o -> o.testing.enableLir()) .setMinApi(parameters) .run(parameters.getRuntime(), main) .assertSuccessWithOutput(javaOutput); @@ -324,16 +324,14 @@ .filter(FoundClassSubject::isSynthesizedJavaLambdaClass) .map(FoundClassSubject::getFinalName) .collect(Collectors.toList()); + assertEquals(Collections.emptyList(), synthesizedJavaLambdaClasses); - // TODO(b/120814598): Should only be "java.lang.StringBuilder". assertEquals( - new HashSet<>(synthesizedJavaLambdaClasses), + Collections.singleton("java.lang.StringBuilder"), collectTypes(clazz.uniqueMethodWithOriginalName("testStatelessLambda"))); - assertTrue( - inspector.allClasses().stream().anyMatch(ClassSubject::isSynthesizedJavaLambdaClass)); assertEquals( - Sets.newHashSet("java.lang.StringBuilder"), + Collections.singleton("java.lang.StringBuilder"), collectTypes(clazz.uniqueMethodWithOriginalName("testStatefulLambda"))); }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/constantpropagation/KotlinDefaultArgumentsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/constantpropagation/KotlinDefaultArgumentsTest.java new file mode 100644 index 0000000..9c951c1 --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/optimize/constantpropagation/KotlinDefaultArgumentsTest.java
@@ -0,0 +1,177 @@ +// Copyright (c) 2023, 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.constantpropagation; + +import static com.android.tools.r8.ir.optimize.constantpropagation.KotlinDefaultArgumentsTest.Greeter.getHello; +import static com.android.tools.r8.ir.optimize.constantpropagation.KotlinDefaultArgumentsTest.Greeter.getWorld; + +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; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class KotlinDefaultArgumentsTest extends TestBase { + + enum Effect { + MATERIALIZED, + NONE, + REMOVED + } + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void testFirstBranchRemoved() throws Exception { + test(MainRemovedNone.class, Effect.REMOVED, Effect.NONE); + } + + @Test + public void testBothBranchesRemoved() throws Exception { + test(MainRemovedRemoved.class, Effect.REMOVED, Effect.REMOVED); + } + + @Test + public void testFirstBranchRemovedAndSecondBranchMaterialized() throws Exception { + test(MainRemovedMaterialized.class, Effect.REMOVED, Effect.MATERIALIZED); + } + + @Test + public void testFirstBranchMaterialized() throws Exception { + test(MainMaterializedNone.class, Effect.MATERIALIZED, Effect.NONE); + } + + @Test + public void testFirstBranchMaterializedAndSecondBranchRemoved() throws Exception { + test(MainMaterializedRemoved.class, Effect.MATERIALIZED, Effect.REMOVED); + } + + @Test + public void testBothBranchesMaterialized() throws Exception { + test(MainMaterializedMaterialized.class, Effect.MATERIALIZED, Effect.MATERIALIZED); + } + + private void test(Class<?> mainClass, Effect firstBranchEffect, Effect secondBranchEffect) + throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(mainClass, Greeter.class) + .addKeepMainRule(mainClass) + .enableInliningAnnotations() + .setMinApi(parameters) + .compile() + .inspect( + inspector -> { + // TODO(b/196017578): If the branch effect is NONE, then check that the IF instruction + // AND its body are retained. + // TODO(b/196017578): If the branch effect is REMOVED, then check that the IF + // instruction AND its body is removed. + // TODO(b/196017578): If the branch effect is MATERIALIZED, then check that the IF + // instruction is removed but NOT its body. + // TODO(b/196017578): If the branch effect is MATERIALIZED, then check that the unused + // parameter is removed. + }) + .run(parameters.getRuntime(), mainClass) + .assertSuccessWithOutputLines("Hello, world!"); + } + + // Test where first parameter X is always given at the call site. + // The first branch in greet() should be removed. + static class MainRemovedNone { + + public static void main(String[] args) { + Greeter.greet(getHello(), null, 2); + if (System.currentTimeMillis() < 0) { + Greeter.greet(getHello(), getWorld(), 0); + } + } + } + + // Test where both parameters X and Y are always given at the call site. + // Both branches in greet() should be removed. + static class MainRemovedRemoved { + + public static void main(String[] args) { + Greeter.greet(getHello(), getWorld(), 0); + } + } + + // Test where the first parameter X is always given and the second parameter Y is never given at + // the call site. + // The first branch in greet() should be removed and the second branch in greet() should be + // "materialized". + static class MainRemovedMaterialized { + + public static void main(String[] args) { + Greeter.greet(getHello(), null, 2); + } + } + + // Test where the first parameter X is never given at the call site. + // The first branch in greet() should be materialized. + static class MainMaterializedNone { + + public static void main(String[] args) { + Greeter.greet(null, null, 3); + if (System.currentTimeMillis() < 0) { + Greeter.greet(null, getWorld(), 1); + } + } + } + + // Test where the first parameter X is never given and the second parameter Y is always given at + // the call site. + // The first branch in greet() should be materialized and the second branch should be removed. + static class MainMaterializedRemoved { + + public static void main(String[] args) { + Greeter.greet(null, getWorld(), 1); + } + } + + // Test where none of the parameters X and Y are given at the call site. + // Both branches in greet() should be materialized. + static class MainMaterializedMaterialized { + + public static void main(String[] args) { + Greeter.greet(null, null, 3); + } + } + + static class Greeter { + + @NeverInline + static void greet(String x, String y, int defaults) { + if ((defaults & 1) != 0) { + x = "Hello"; + } + if ((defaults & 2) != 0) { + y = ", world!"; + } + System.out.print(x); + System.out.println(y); + } + + @NeverInline + static String getHello() { + return System.currentTimeMillis() > 0 ? "Hello" : ""; + } + + @NeverInline + static String getWorld() { + return System.currentTimeMillis() > 0 ? ", world!" : ""; + } + } +}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldReadForWriteTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldReadForWriteTest.java index 8f8541c..37e6c57 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldReadForWriteTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldReadForWriteTest.java
@@ -39,7 +39,7 @@ HorizontallyMergedClassesInspector::assertNoClassesMerged) .enableNoHorizontalClassMergingAnnotations() .setMinApi(parameters) - .addOptionsModification(o -> o.testing.roundtripThroughLir = true) + .addOptionsModification(o -> o.testing.enableLir()) .run(parameters.getRuntime(), Main.class) .assertSuccessWithOutputLines("42") .inspect(inspector -> assertThat(inspector.clazz(anim.class), isAbsent()));
diff --git a/src/test/java/com/android/tools/r8/lightir/LirBasicCallbackTest.java b/src/test/java/com/android/tools/r8/lightir/LirBasicCallbackTest.java index abd980d..909c083 100644 --- a/src/test/java/com/android/tools/r8/lightir/LirBasicCallbackTest.java +++ b/src/test/java/com/android/tools/r8/lightir/LirBasicCallbackTest.java
@@ -12,12 +12,12 @@ import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.errors.Unreachable; -import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.IRMetadata; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.references.Reference; +import com.android.tools.r8.utils.InternalOptions; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -74,10 +74,13 @@ @Test public void test() { - DexItemFactory factory = new DexItemFactory(); - DexMethod method = factory.createMethod(Reference.methodFromDescriptor("LFoo;", "bar", "()V")); + InternalOptions options = new InternalOptions(); + DexMethod method = + options + .dexItemFactory() + .createMethod(Reference.methodFromDescriptor("LFoo;", "bar", "()V")); LirCode<?> code = - LirCode.builder(method, new ThrowingStrategy(), factory) + LirCode.builder(method, new ThrowingStrategy(), options) .setMetadata(IRMetadata.unknown()) .addConstNull() .addConstInt(42)
diff --git a/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java index ffdda98..ab72433 100644 --- a/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java +++ b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
@@ -4,11 +4,11 @@ package com.android.tools.r8.naming.b72391662; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPackagePrivate; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import com.android.tools.r8.NeverPropagateValue; import com.android.tools.r8.ToolHelper; @@ -144,20 +144,16 @@ // Test the totally unused method. MethodSubject staticMethod = testClass.uniqueMethodWithOriginalName("unused"); assertThat(staticMethod, isPresent()); + assertThat(staticMethod, isPackagePrivate()); assertEquals(minify, staticMethod.isRenamed()); - if (shrinker.isR8()) { - assertEquals(allowAccessModification, staticMethod.getMethod().accessFlags.isPublic()); - } else { - assertFalse(staticMethod.getMethod().accessFlags.isPublic()); - } // Test an indirectly referred method. staticMethod = testClass.uniqueMethodWithOriginalName("staticMethod"); assertThat(staticMethod, isPresent()); assertEquals(minify, staticMethod.isRenamed()); - boolean publicizeCondition = shrinker.isR8() ? allowAccessModification - : minify && repackagePrefix != null && allowAccessModification; - assertEquals(publicizeCondition, staticMethod.getMethod().accessFlags.isPublic()); + boolean publicizeCondition = + shrinker.isProguard() && minify && repackagePrefix != null && allowAccessModification; + assertEquals(publicizeCondition, staticMethod.getMethod().isPublic()); } @Test @@ -195,20 +191,16 @@ // Test the totally unused method. MethodSubject staticMethod = testClass.uniqueMethodWithOriginalName("unused"); assertThat(staticMethod, isPresent()); + assertThat(staticMethod, isPackagePrivate()); assertEquals(minify, staticMethod.isRenamed()); - if (shrinker.isR8()) { - assertEquals(allowAccessModification, staticMethod.getMethod().accessFlags.isPublic()); - } else { - assertFalse(staticMethod.getMethod().accessFlags.isPublic()); - } // Test an indirectly referred method. staticMethod = testClass.uniqueMethodWithOriginalName("staticMethod"); assertThat(staticMethod, isPresent()); assertEquals(minify, staticMethod.isRenamed()); - boolean publicizeCondition = shrinker.isR8() ? allowAccessModification - : minify && repackagePrefix != null && allowAccessModification; - assertEquals(publicizeCondition, staticMethod.getMethod().accessFlags.isPublic()); + boolean publicizeCondition = + shrinker.isProguard() && minify && repackagePrefix != null && allowAccessModification; + assertEquals(publicizeCondition, staticMethod.getMethod().isPublic()); } @Test
diff --git a/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java b/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java index c4fa84b..a39c770 100644 --- a/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java +++ b/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java
@@ -4,7 +4,6 @@ package com.android.tools.r8.regress; import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent; -import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static org.hamcrest.MatcherAssert.assertThat; import com.android.tools.r8.TestBase; @@ -51,6 +50,7 @@ .addKeepMainRule(TestClass.class) .addInnerClasses(Regress160394262Test.class) .setMinApi(parameters) + .addOptionsModification(o -> o.testing.enableLir()) .run(parameters.getRuntime(), TestClass.class) .assertSuccessWithOutput(EXPECTED) .inspect(this::checkJoinerIsClassInlined); @@ -58,14 +58,7 @@ private void checkJoinerIsClassInlined(CodeInspector inspector) { assertThat(inspector.clazz(Joiner.class.getTypeName() + "$1"), isAbsent()); - // TODO(b/160640028): Joiner should be class inlined. - // When line info tables are kept we appear to successfully inline Joiner. Reason unknown. - if (parameters.isCfRuntime() - || parameters.getApiLevel().isLessThan(apiLevelWithPcAsLineNumberSupport())) { - assertThat(inspector.clazz(Joiner.class), isPresent()); - } else { - assertThat(inspector.clazz(Joiner.class), isAbsent()); - } + assertThat(inspector.clazz(Joiner.class), isAbsent()); } static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java index 9aec797..3f426a5 100644 --- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java +++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
@@ -3,10 +3,10 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.shaking.ifrule; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPackagePrivate; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeFalse; @@ -176,7 +176,7 @@ assertThat(methodSubject, not(isPresent())); methodSubject = classSubject.uniqueMethodWithOriginalName("nonPublicMethod"); assertThat(methodSubject, isPresent()); - assertEquals(shrinker.isR8(), methodSubject.getMethod().accessFlags.isPublic()); + assertThat(methodSubject, isPackagePrivate()); }); } @@ -255,7 +255,7 @@ assertThat(methodSubject, not(isPresent())); methodSubject = classSubject.uniqueMethodWithOriginalName("nonPublicMethod"); assertThat(methodSubject, isPresent()); - assertEquals(shrinker.isR8(), methodSubject.getMethod().accessFlags.isPublic()); + assertThat(methodSubject, isPackagePrivate()); }); } }