Merge commit 'ecd7ced0dd6cdd020f111a7fac9c41e25866b96f' into dev-release
diff --git a/.gitignore b/.gitignore index 26634e8..8534e4c 100644 --- a/.gitignore +++ b/.gitignore
@@ -80,6 +80,8 @@ third_party/gradle/gradle third_party/gradle/gradle.tar.gz third_party/internal/* +third_party/internal-apps/youtube_15_33 +third_party/internal-apps/youtube_15_33.tar.gz third_party/iosched_2019 third_party/iosched_2019.tar.gz third_party/jacoco/0.8.2/*
diff --git a/infra/config/global/cr-buildbucket.cfg b/infra/config/global/cr-buildbucket.cfg index 5960a22..a4e4210 100644 --- a/infra/config/global/cr-buildbucket.cfg +++ b/infra/config/global/cr-buildbucket.cfg
@@ -145,7 +145,7 @@ mixins: "normal" priority: 26 recipe { - properties_j: "test_options:[\"--desugared-library\", \"HEAD\"]" + properties_j: "test_options:[\"--no_internal\", \"--desugared-library\", \"HEAD\"]" } } builders {
diff --git a/infra/config/global/luci-milo.cfg b/infra/config/global/luci-milo.cfg index 2cf08a6..0e7614f 100644 --- a/infra/config/global/luci-milo.cfg +++ b/infra/config/global/luci-milo.cfg
@@ -175,6 +175,14 @@ category: "win release" short_name: "win" } + +} +consoles { + id: "kotlin" + name: "Kotlin" + repo_url: "https://github.com/google/kotlin" + refs: "regexp:refs/heads/.*" + manifest_name: "REVISION" builders { name: "buildbucket/luci.r8.ci/kotlin-builder" category: "kotlin"
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java index 998b838..cdbcdd8 100644 --- a/src/main/java/com/android/tools/r8/D8.java +++ b/src/main/java/com/android/tools/r8/D8.java
@@ -29,6 +29,7 @@ import com.android.tools.r8.kotlin.KotlinMetadataRewriter; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.naming.PrefixRewritingNamingLens; +import com.android.tools.r8.naming.RecordRewritingNamingLens; import com.android.tools.r8.naming.signature.GenericSignatureRewriter; import com.android.tools.r8.origin.CommandLineOrigin; import com.android.tools.r8.origin.Origin; @@ -184,14 +185,6 @@ AppView<AppInfo> appView = readApp(inputApp, options, executor, timing); SyntheticItems.collectSyntheticInputs(appView); - if (!options.mainDexKeepRules.isEmpty()) { - MainDexInfo mainDexInfo = - new GenerateMainDexList(options) - .traceMainDex( - executor, appView.appInfo().app(), appView.appInfo().getMainDexInfo()); - appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(mainDexInfo)); - } - final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null; if (AssertionsRewriter.isEnabled(options)) { @@ -267,26 +260,22 @@ Marker.checkCompatibleDesugaredLibrary(markers, options.reporter); InspectorImpl.runInspections(options.outputInspections, appView.appInfo().classes()); + NamingLens namingLens = NamingLens.getIdentityLens(); + if (appView.rewritePrefix.isRewriting()) { + namingLens = PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView, namingLens); + } + if (appView.options().shouldDesugarRecords()) { + namingLens = RecordRewritingNamingLens.createRecordRewritingNamingLens(appView, namingLens); + } if (options.isGeneratingClassFiles()) { // TODO(b/158159959): Move this out so it is shared for both CF and DEX pipelines. SyntheticFinalization.finalize(appView); - new CfApplicationWriter( - appView, - marker, - GraphLens.getIdentityLens(), - appView.rewritePrefix.isRewriting() - ? PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView) - : NamingLens.getIdentityLens(), - null) + new CfApplicationWriter(appView, marker, GraphLens.getIdentityLens(), namingLens, null) .write(options.getClassFileConsumer()); } else { - NamingLens namingLens; if (!hasDexResources || !hasClassResources || !appView.rewritePrefix.isRewriting()) { // All inputs are either dex or cf, or there is nothing to rewrite. - namingLens = - hasDexResources - ? NamingLens.getIdentityLens() - : PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView); + namingLens = hasDexResources ? NamingLens.getIdentityLens() : namingLens; new GenericSignatureRewriter(appView, namingLens) .run(appView.appInfo().classes(), executor); new KotlinMetadataRewriter(appView, namingLens).runForD8(executor); @@ -298,7 +287,13 @@ timing.begin("Rewrite non-dex inputs"); DexApplication app = rewriteNonDexInputs( - appView, inputApp, options, executor, timing, appView.appInfo().app()); + appView, + inputApp, + options, + executor, + timing, + appView.appInfo().app(), + namingLens); timing.end(); appView.setAppInfo( new AppInfo( @@ -307,6 +302,16 @@ namingLens = NamingLens.getIdentityLens(); } + // Since tracing is not lens aware, this needs to be done prior to synthetic finalization + // which will construct a graph lens. + if (!options.mainDexKeepRules.isEmpty()) { + MainDexInfo mainDexInfo = + new GenerateMainDexList(options) + .traceMainDex( + executor, appView.appInfo().app(), appView.appInfo().getMainDexInfo()); + appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(mainDexInfo)); + } + // TODO(b/158159959): Move this out so it is shared for both CF and DEX pipelines. SyntheticFinalization.finalize(appView); @@ -337,7 +342,8 @@ InternalOptions options, ExecutorService executor, Timing timing, - DexApplication app) + DexApplication app, + NamingLens desugaringLens) throws IOException, ExecutionException { // TODO(b/154575955): Remove the naming lens in D8. appView @@ -363,17 +369,15 @@ appView.appInfo().getSyntheticItems().commit(cfApp), appView.appInfo().getMainDexInfo())); ConvertedCfFiles convertedCfFiles = new ConvertedCfFiles(); - NamingLens prefixRewritingNamingLens = - PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView); - new GenericSignatureRewriter(appView, prefixRewritingNamingLens) + new GenericSignatureRewriter(appView, desugaringLens) .run(appView.appInfo().classes(), executor); - new KotlinMetadataRewriter(appView, prefixRewritingNamingLens).runForD8(executor); + new KotlinMetadataRewriter(appView, desugaringLens).runForD8(executor); new ApplicationWriter( appView, null, GraphLens.getIdentityLens(), InitClassLens.getDefault(), - prefixRewritingNamingLens, + desugaringLens, null, convertedCfFiles) .write(executor);
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java index c5eac58..05c2e8e 100644 --- a/src/main/java/com/android/tools/r8/D8Command.java +++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -515,6 +515,7 @@ return builder .setIntermediate(intermediate) .setDesugaredLibraryConfiguration(libraryConfiguration) + .setMainDexKeepRules(mainDexKeepRules) .build(); } }
diff --git a/src/main/java/com/android/tools/r8/DumpOptions.java b/src/main/java/com/android/tools/r8/DumpOptions.java index cd2ac79..3feeee3 100644 --- a/src/main/java/com/android/tools/r8/DumpOptions.java +++ b/src/main/java/com/android/tools/r8/DumpOptions.java
@@ -8,8 +8,10 @@ import com.android.tools.r8.features.FeatureSplitConfiguration; import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration; import com.android.tools.r8.shaking.ProguardConfiguration; +import com.android.tools.r8.shaking.ProguardConfigurationRule; import com.android.tools.r8.utils.InternalOptions.DesugarState; import com.android.tools.r8.utils.ThreadUtils; +import java.util.List; import java.util.Optional; @SuppressWarnings("OptionalUsedAsFieldOrParameterType") @@ -49,6 +51,7 @@ private final DesugaredLibraryConfiguration desugaredLibraryConfiguration; private final FeatureSplitConfiguration featureSplitConfiguration; private final ProguardConfiguration proguardConfiguration; + private final List<ProguardConfigurationRule> mainDexKeepRules; // Reporting only. private final boolean dumpInputToFile; @@ -68,6 +71,7 @@ Optional<Boolean> forceProguardCompatibility, FeatureSplitConfiguration featureSplitConfiguration, ProguardConfiguration proguardConfiguration, + List<ProguardConfigurationRule> mainDexKeepRules, boolean dumpInputToFile) { this.tool = tool; this.compilationMode = compilationMode; @@ -83,6 +87,7 @@ this.forceProguardCompatibility = forceProguardCompatibility; this.featureSplitConfiguration = featureSplitConfiguration; this.proguardConfiguration = proguardConfiguration; + this.mainDexKeepRules = mainDexKeepRules; this.dumpInputToFile = dumpInputToFile; } @@ -137,6 +142,14 @@ return proguardConfiguration == null ? null : proguardConfiguration.getParsedConfiguration(); } + public boolean hasMainDexKeepRules() { + return mainDexKeepRules != null; + } + + public List<ProguardConfigurationRule> getMainDexKeepRules() { + return mainDexKeepRules; + } + public boolean dumpInputToFile() { return dumpInputToFile; } @@ -161,6 +174,7 @@ private DesugaredLibraryConfiguration desugaredLibraryConfiguration; private FeatureSplitConfiguration featureSplitConfiguration; private ProguardConfiguration proguardConfiguration; + private List<ProguardConfigurationRule> mainDexKeepRules; // Reporting only. private boolean dumpInputToFile; @@ -241,6 +255,11 @@ return this; } + public Builder setMainDexKeepRules(List<ProguardConfigurationRule> mainDexKeepRules) { + this.mainDexKeepRules = mainDexKeepRules; + return this; + } + public DumpOptions build() { return new DumpOptions( tool, @@ -257,6 +276,7 @@ forceProguardCompatibility, featureSplitConfiguration, proguardConfiguration, + mainDexKeepRules, dumpInputToFile); } }
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java index 07783b3..2a794ab 100644 --- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java +++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -5,6 +5,7 @@ import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException; +import com.android.tools.r8.StringConsumer.ForwardingConsumer; import com.android.tools.r8.dex.ApplicationReader; import com.android.tools.r8.experimental.graphinfo.GraphConsumer; import com.android.tools.r8.graph.AppInfoWithClassHierarchy; @@ -21,14 +22,13 @@ import com.android.tools.r8.shaking.RootSetUtils.MainDexRootSet; import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer; import com.android.tools.r8.utils.AndroidApp; -import com.android.tools.r8.utils.Box; import com.android.tools.r8.utils.ExceptionUtils; import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.SortingStringConsumer; import com.android.tools.r8.utils.ThreadUtils; import com.android.tools.r8.utils.Timing; import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -42,21 +42,13 @@ this.options = options; } - private List<String> run(AndroidApp app, ExecutorService executor) + private void run(AndroidApp app, ExecutorService executor, SortingStringConsumer consumer) throws IOException { try { - // TODO(b/178231294): Clean up this such that we do not both return the result and call the - // consumer. DexApplication application = new ApplicationReader(app, options, timing).read(executor); - List<String> result = new ArrayList<>(); traceMainDex(executor, application, MainDexInfo.none()) - .forEach(type -> result.add(type.toBinaryName() + ".class")); - Collections.sort(result); - if (options.mainDexListConsumer != null) { - options.mainDexListConsumer.accept(String.join("\n", result), options.reporter); - options.mainDexListConsumer.finished(options.reporter); - } - return result; + .forEach(type -> consumer.accept(type.toBinaryName() + ".class", options.reporter)); + consumer.finished(options.reporter); } catch (ExecutionException e) { throw unwrapExecutionException(e); } @@ -137,11 +129,11 @@ /** * Main API entry for computing the main-dex list. * - * The main-dex list is represented as a list of strings, each string specifies one class to + * <p>The main-dex list is represented as a list of strings, each string specifies one class to * keep in the primary dex file (<code>classes.dex</code>). * - * A class is specified using the following format: "com/example/MyClass.class". That is - * "/" as separator between package components, and a trailing ".class". + * <p>A class is specified using the following format: "com/example/MyClass.class". That is "/" as + * separator between package components, and a trailing ".class". * * @param command main dex-list generator command. * @param executor executor service from which to get threads for multi-threaded processing. @@ -151,17 +143,28 @@ throws CompilationFailedException { AndroidApp app = command.getInputApp(); InternalOptions options = command.getInternalOptions(); - Box<List<String>> result = new Box<>(); + List<String> result = new ArrayList<>(); ExceptionUtils.withMainDexListHandler( command.getReporter(), () -> { try { - result.set(new GenerateMainDexList(options).run(app, executor)); + new GenerateMainDexList(options) + .run( + app, + executor, + new SortingStringConsumer( + new ForwardingConsumer(options.mainDexListConsumer) { + @Override + public void accept(String string, DiagnosticsHandler handler) { + result.add(string); + super.accept(string, handler); + } + })); } finally { executor.shutdown(); } }); - return result.get(); + return result; } public static void main(String[] args) throws CompilationFailedException {
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java index b08a738..85bc305 100644 --- a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java +++ b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
@@ -14,6 +14,7 @@ import com.android.tools.r8.shaking.ProguardConfigurationSourceStrings; import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.JoiningStringConsumer; import com.android.tools.r8.utils.Reporter; import com.android.tools.r8.utils.StringDiagnostic; import com.google.common.collect.ImmutableList; @@ -115,7 +116,7 @@ factory, getAppBuilder().build(), mainDexKeepRules, - mainDexListConsumer, + new JoiningStringConsumer(mainDexListConsumer, "\n"), mainDexKeptGraphConsumer, getReporter()); }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index 9158d3b..b77c8ed 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java
@@ -495,16 +495,13 @@ && options.isShrinking()) { timing.begin("HorizontalClassMerger"); HorizontalClassMerger merger = new HorizontalClassMerger(appViewWithLiveness); - DirectMappedDexApplication.Builder appBuilder = - appView.appInfo().app().asDirect().builder(); HorizontalClassMergerResult horizontalClassMergerResult = - merger.run(appBuilder, runtimeTypeCheckInfo); + merger.run(runtimeTypeCheckInfo); if (horizontalClassMergerResult != null) { // Must rewrite AppInfoWithLiveness before pruning the merged classes, to ensure that // allocations sites, fields accesses, etc. are correctly transferred to the target // classes. - appView.rewriteWithLensAndApplication( - horizontalClassMergerResult.getGraphLens(), appBuilder.build()); + appView.rewriteWithLens(horizontalClassMergerResult.getGraphLens()); horizontalClassMergerResult .getFieldAccessInfoCollectionModifier() .modify(appViewWithLiveness); @@ -513,7 +510,7 @@ PrunedItems.builder() .setPrunedApp(appView.appInfo().app()) .addRemovedClasses(appView.horizontallyMergedClasses().getSources()) - .addNoLongerSyntheticItems(appView.horizontallyMergedClasses().getTargets()) + .addNoLongerSyntheticItems(appView.horizontallyMergedClasses().getSources()) .build()); } timing.end();
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java index 8d07b18..9663d3f 100644 --- a/src/main/java/com/android/tools/r8/R8Command.java +++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -1002,6 +1002,7 @@ .setForceProguardCompatibility(forceProguardCompatibility) .setFeatureSplitConfiguration(featureSplitConfiguration) .setProguardConfiguration(proguardConfiguration) + .setMainDexKeepRules(mainDexKeepRules) .setDesugaredLibraryConfiguration(libraryConfiguration) .build(); }
diff --git a/src/main/java/com/android/tools/r8/ResourceShrinker.java b/src/main/java/com/android/tools/r8/ResourceShrinker.java index b01f9a3..77abde6 100644 --- a/src/main/java/com/android/tools/r8/ResourceShrinker.java +++ b/src/main/java/com/android/tools/r8/ResourceShrinker.java
@@ -240,7 +240,7 @@ Stream<DexAnnotation> classAnnotations = classDef.annotations().stream(); Stream<DexAnnotation> fieldAnnotations = Streams.stream(classDef.fields()) - .filter(DexEncodedField::hasAnnotation) + .filter(DexEncodedField::hasAnnotations) .flatMap(f -> f.annotations().stream()); Stream<DexAnnotation> methodAnnotations = Streams.stream(classDef.methods())
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java index 09f14ce..9862f27 100644 --- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java +++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -59,6 +59,7 @@ import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexMethodHandle; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.DexValue; import com.android.tools.r8.ir.code.If; import com.android.tools.r8.ir.code.MemberType; import com.android.tools.r8.ir.code.Monitor; @@ -396,10 +397,14 @@ builder.append(callSite.methodName); builder.append(callSite.methodProto.toDescriptorString()); if (callSite.bootstrapArgs.size() > 1) { - DexMethodHandle handle = callSite.bootstrapArgs.get(1).asDexValueMethodHandle().getValue(); - builder.append(", handle:"); - builder.append(handle.toSourceString()); - builder.append(", itf: ").append(handle.isInterface); + DexValue.DexValueMethodHandle dexValueMethodHandle = + callSite.bootstrapArgs.get(1).asDexValueMethodHandle(); + if (dexValueMethodHandle != null) { + DexMethodHandle handle = dexValueMethodHandle.getValue(); + builder.append(", handle:"); + builder.append(handle.toSourceString()); + builder.append(", itf: ").append(handle.isInterface); + } } builder.append(", bsm:"); appendMethod(bootstrapMethod.asMethod());
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java index 22421de..4fa3241 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
@@ -82,6 +82,6 @@ InitClassLens initClassLens) { // ..., arrayref → // ..., length - frameBuilder.popAndDiscard(factory.objectArrayType).push(factory.intType); + frameBuilder.popAndDiscardInitialized(factory.objectArrayType).push(factory.intType); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java index c82e53e..4f851a0 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
@@ -128,7 +128,7 @@ InitClassLens initClassLens) { // ..., arrayref, index → // ..., value - frameBuilder.popAndDiscard(factory.objectArrayType, factory.intType); + frameBuilder.popAndDiscardInitialized(factory.objectArrayType, factory.intType); frameBuilder.push(FrameType.fromMemberType(type, factory)); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java index 5d64188..cc0cd63 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
@@ -120,6 +120,6 @@ // ... frameBuilder .popAndDiscard(FrameType.fromMemberType(type, factory)) - .popAndDiscard(factory.objectArrayType, factory.intType); + .popAndDiscardInitialized(factory.objectArrayType, factory.intType); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java index 721f3f7..1e25ca7 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
@@ -26,7 +26,7 @@ import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; -public class CfCheckCast extends CfInstruction { +public class CfCheckCast extends CfInstruction implements CfTypeInstruction { private final DexType type; @@ -34,11 +34,27 @@ this.type = type; } + @Override + public CfTypeInstruction asTypeInstruction() { + return this; + } + + @Override + public boolean isTypeInstruction() { + return true; + } + + @Override public DexType getType() { return type; } @Override + public CfInstruction withType(DexType newType) { + return new CfCheckCast(newType); + } + + @Override public int getCompareToId() { return Opcodes.CHECKCAST; } @@ -102,6 +118,6 @@ InitClassLens initClassLens) { // ..., objectref → // ..., objectref - frameBuilder.popAndDiscard(factory.objectType).push(type); + frameBuilder.popAndDiscardInitialized(factory.objectType).push(type); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java index 863dce0..a6a0933 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
@@ -26,7 +26,7 @@ import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; -public class CfConstClass extends CfInstruction { +public class CfConstClass extends CfInstruction implements CfTypeInstruction { private final DexType type; @@ -45,11 +45,27 @@ return type.acceptCompareTo(((CfConstClass) other).type, visitor); } + @Override + public CfTypeInstruction asTypeInstruction() { + return this; + } + + @Override + public boolean isTypeInstruction() { + return true; + } + + @Override public DexType getType() { return type; } @Override + public CfInstruction withType(DexType newType) { + return new CfConstClass(newType); + } + + @Override public void write( AppView<?> appView, ProgramMethod context,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java index be6176b..2e8eedf 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
@@ -3,6 +3,8 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.cf.code; +import static com.android.tools.r8.utils.BiPredicateUtils.or; + import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; @@ -189,7 +191,7 @@ case Opcodes.GETFIELD: // ..., objectref → // ..., value - frameBuilder.popAndDiscard(field.holder).push(field.type); + frameBuilder.popAndDiscardInitialized(field.holder).push(field.type); return; case Opcodes.GETSTATIC: // ..., → @@ -199,12 +201,18 @@ case Opcodes.PUTFIELD: // ..., objectref, value → // ..., - frameBuilder.popAndDiscard(field.holder, field.type); + frameBuilder + .popAndDiscardInitialized(field.type) + .pop( + field.holder, + or( + frameBuilder::isUninitializedThisAndTarget, + frameBuilder::isAssignableAndInitialized)); return; case Opcodes.PUTSTATIC: // ..., value → // ... - frameBuilder.pop(field.type); + frameBuilder.popAndDiscardInitialized(field.type); return; default: throw new Unreachable("Unexpected opcode " + opcode);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java index 713d764..98bc101 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -371,6 +371,14 @@ return stack.size(); } + public int computeStackSize() { + int size = 0; + for (FrameType frameType : stack) { + size += frameType.isWide() ? 2 : 1; + } + return size; + } + private Object[] computeStackTypes(int stackCount, GraphLens graphLens, NamingLens namingLens) { assert stackCount == stack.size(); if (stackCount == 0) { @@ -452,7 +460,7 @@ DexType returnType, DexItemFactory factory, InitClassLens initClassLens) { - frameBuilder.verifyFrameAndSet(this); + frameBuilder.checkFrameAndSet(this); } public CfFrame markInstantiated(FrameType uninitializedType, DexType initType) {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java index 7ce2440..9272226 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
@@ -4,6 +4,8 @@ package com.android.tools.r8.cf.code; +import static com.android.tools.r8.utils.BiPredicateUtils.or; + import com.android.tools.r8.cf.code.CfFrame.FrameType; import com.android.tools.r8.graph.CfCodeStackMapValidatingException; import com.android.tools.r8.graph.DexItemFactory; @@ -39,6 +41,7 @@ private final DexItemFactory factory; private final List<CfTryCatch> tryCatchRanges; private final GraphLens graphLens; + private final int maxStackHeight; private final Deque<CfTryCatch> currentCatchRanges = new ArrayDeque<>(); private final Set<CfLabel> tryCatchRangeLabels; @@ -49,13 +52,15 @@ List<CfTryCatch> tryCatchRanges, BiPredicate<DexType, DexType> isJavaAssignable, DexItemFactory factory, - GraphLens graphLens) { + GraphLens graphLens, + int maxStackHeight) { this.context = context; this.stateMap = stateMap; this.tryCatchRanges = tryCatchRanges; this.isJavaAssignable = isJavaAssignable; this.factory = factory; this.graphLens = graphLens; + this.maxStackHeight = maxStackHeight; throwStack = ImmutableDeque.of(FrameType.initialized(factory.throwableType)); // Compute all labels that marks a start or end to catch ranges. tryCatchRangeLabels = Sets.newIdentityHashSet(); @@ -70,50 +75,60 @@ } public FrameType readLocal(int index, DexType expectedType) { - verifyFrameIsSet(); + checkFrameIsSet(); FrameType frameType = currentFrame.getLocals().get(index); if (frameType == null) { throw CfCodeStackMapValidatingException.error("No local at index " + index); } - verifyIsAssignable(frameType, expectedType); + checkIsAssignable( + frameType, + expectedType, + or( + this::isUninitializedThisAndTarget, + this::isUninitializedNewAndTarget, + this::isAssignableAndInitialized)); return frameType; } public void storeLocal(int index, FrameType frameType) { - verifyFrameIsSet(); + checkFrameIsSet(); currentFrame.getLocals().put(index, frameType); } public FrameType pop() { - verifyFrameIsSet(); + checkFrameIsSet(); if (currentFrame.getStack().isEmpty()) { throw CfCodeStackMapValidatingException.error("Cannot pop() from an empty stack"); } return currentFrame.getStack().removeLast(); } - public FrameType pop(DexType expectedType) { + public FrameType popInitialized(DexType expectedType) { + return pop(expectedType, this::isAssignableAndInitialized); + } + + public FrameType pop(DexType expectedType, BiPredicate<FrameType, DexType> isAssignable) { FrameType frameType = pop(); - verifyIsAssignable(frameType, expectedType); + checkIsAssignable(frameType, expectedType, isAssignable); return frameType; } - public CfFrameVerificationHelper popAndDiscard(DexType... expectedTypes) { - verifyFrameIsSet(); + public CfFrameVerificationHelper popAndDiscardInitialized(DexType... expectedTypes) { + checkFrameIsSet(); for (int i = expectedTypes.length - 1; i >= 0; i--) { - pop(expectedTypes[i]); + popInitialized(expectedTypes[i]); } return this; } public FrameType pop(FrameType expectedType) { FrameType frameType = pop(); - verifyIsAssignable(frameType, expectedType); + checkIsAssignable(frameType, expectedType); return frameType; } public CfFrameVerificationHelper popAndDiscard(FrameType... expectedTypes) { - verifyFrameIsSet(); + checkFrameIsSet(); for (int i = expectedTypes.length - 1; i >= 0; i--) { pop(expectedTypes[i]); } @@ -121,18 +136,30 @@ } public void popAndInitialize(DexType context, DexType methodHolder) { - verifyFrameIsSet(); - FrameType objectRef = pop(factory.objectType); + checkFrameIsSet(); + FrameType objectRef = + pop( + factory.objectType, + or(this::isUninitializedThisAndTarget, this::isUninitializedNewAndTarget)); CfFrame newFrame = currentFrame.markInstantiated( objectRef, objectRef.isUninitializedNew() ? methodHolder : context); setNoFrame(); - verifyFrameAndSet(newFrame); + checkFrameAndSet(newFrame); } public CfFrameVerificationHelper push(FrameType type) { - verifyFrameIsSet(); + checkFrameIsSet(); currentFrame.getStack().addLast(type); + if (currentFrame.computeStackSize() > maxStackHeight) { + throw CfCodeStackMapValidatingException.error( + "The max stack height of " + + maxStackHeight + + " is violated when pushing type " + + type + + " to existing stack of size " + + currentFrame.getStack().size()); + } return this; } @@ -153,7 +180,7 @@ if (destinationFrame == null) { throw CfCodeStackMapValidatingException.error("No frame for target catch range target"); } - verifyStackIsAssignable( + checkStackIsAssignable( destinationFrame.getStack(), throwStack, factory, isJavaAssignable); } } @@ -162,15 +189,15 @@ return this; } - private void verifyFrameIsSet() { + private void checkFrameIsSet() { if (currentFrame == NO_FRAME) { throw CfCodeStackMapValidatingException.error("Unexpected state change"); } } - public void verifyFrameAndSet(CfFrame newFrame) { + public void checkFrameAndSet(CfFrame newFrame) { if (currentFrame != NO_FRAME) { - verifyFrame(newFrame); + checkFrame(newFrame); } setFrame(newFrame); } @@ -182,7 +209,7 @@ new Int2ReferenceAVLTreeMap<>(frame.getLocals()), new ArrayDeque<>(frame.getStack())); } - public void verifyExceptionEdges() { + public void checkExceptionEdges() { for (CfTryCatch currentCatchRange : currentCatchRanges) { for (CfLabel target : currentCatchRange.targets) { CfFrame destinationFrame = stateMap.get(target); @@ -192,7 +219,7 @@ // We have to check all current handler targets have assignable locals and a 1-element // stack assignable to throwable. It is not required that the the thrown error is // handled. - verifyLocalsIsAssignable( + checkLocalsIsAssignable( currentFrame.getLocals(), destinationFrame.getLocals(), factory, isJavaAssignable); } } @@ -202,19 +229,19 @@ return currentFrame; } - public void verifyTarget(CfLabel label) { - verifyFrame(stateMap.get(label)); + public void checkTarget(CfLabel label) { + checkFrame(stateMap.get(label)); } - public void verifyFrame(CfFrame destinationFrame) { + public void checkFrame(CfFrame destinationFrame) { if (destinationFrame == null) { throw CfCodeStackMapValidatingException.error("No destination frame"); } - verifyFrame(destinationFrame.getLocals(), destinationFrame.getStack()); + checkFrame(destinationFrame.getLocals(), destinationFrame.getStack()); } - public void verifyFrame(Int2ReferenceSortedMap<FrameType> locals, Deque<FrameType> stack) { - verifyIsAssignable( + public void checkFrame(Int2ReferenceSortedMap<FrameType> locals, Deque<FrameType> stack) { + checkIsAssignable( currentFrame.getLocals(), currentFrame.getStack(), locals, @@ -227,30 +254,37 @@ currentFrame = NO_FRAME; } - public void clearStack() { - verifyFrameIsSet(); - currentFrame.getStack().clear(); + public boolean isUninitializedThisAndTarget(FrameType source, DexType target) { + if (!source.isUninitializedThis()) { + return false; + } + return target == factory.objectType || graphLens.lookupClassType(target) == context; } - public void verifyIsAssignable(FrameType source, DexType target) { + public boolean isUninitializedNewAndTarget(FrameType source, DexType target) { + if (!source.isUninitializedNew()) { + return false; + } + return target == factory.objectType || graphLens.lookupClassType(target) == context; + } + + public boolean isAssignableAndInitialized(FrameType source, DexType target) { if (!source.isInitialized()) { - DexType rewrittenTarget = graphLens.lookupClassType(target); - if (source.isUninitializedThis() && rewrittenTarget == context) { - return; - } - if (rewrittenTarget == factory.objectType) { - return; - } - throw CfCodeStackMapValidatingException.error( - "The expected type " + source + " is not assignable to " + target.toSourceString()); + return false; } - if (!isJavaAssignable.test(source.getInitializedType(), target)) { - throw CfCodeStackMapValidatingException.error( - "The expected type " + source + " is not assignable to " + target.toSourceString()); - } + return isJavaAssignable.test(source.getInitializedType(), target); } - public void verifyIsAssignable(FrameType source, FrameType target) { + public void checkIsAssignable( + FrameType source, DexType target, BiPredicate<FrameType, DexType> predicate) { + if (predicate.test(source, target)) { + return; + } + throw CfCodeStackMapValidatingException.error( + "The expected type " + source + " is not assignable to " + target.toSourceString()); + } + + public void checkIsAssignable(FrameType source, FrameType target) { if (!canBeAssigned(source, target, factory, isJavaAssignable)) { throw CfCodeStackMapValidatingException.error( "The expected type " + source + " is not assignable to " + target); @@ -258,18 +292,18 @@ } // Based on https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1.4. - public static void verifyIsAssignable( + public static void checkIsAssignable( Int2ReferenceSortedMap<FrameType> sourceLocals, Deque<FrameType> sourceStack, Int2ReferenceSortedMap<FrameType> destLocals, Deque<FrameType> destStack, DexItemFactory factory, BiPredicate<DexType, DexType> isJavaAssignable) { - verifyLocalsIsAssignable(sourceLocals, destLocals, factory, isJavaAssignable); - verifyStackIsAssignable(sourceStack, destStack, factory, isJavaAssignable); + checkLocalsIsAssignable(sourceLocals, destLocals, factory, isJavaAssignable); + checkStackIsAssignable(sourceStack, destStack, factory, isJavaAssignable); } - private static void verifyLocalsIsAssignable( + private static void checkLocalsIsAssignable( Int2ReferenceSortedMap<FrameType> sourceLocals, Int2ReferenceSortedMap<FrameType> destLocals, DexItemFactory factory, @@ -305,7 +339,7 @@ } } - private static void verifyStackIsAssignable( + private static void checkStackIsAssignable( Deque<FrameType> sourceStack, Deque<FrameType> destStack, DexItemFactory factory,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java index 2f4a549..b3043c9 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
@@ -97,7 +97,7 @@ DexType returnType, DexItemFactory factory, InitClassLens initClassLens) { - frameBuilder.verifyTarget(target); + frameBuilder.checkTarget(target); frameBuilder.setNoFrame(); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIf.java b/src/main/java/com/android/tools/r8/cf/code/CfIf.java index b331673..7653d51 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfIf.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
@@ -135,8 +135,8 @@ InitClassLens initClassLens) { // ..., value → // ... - frameBuilder.pop( + frameBuilder.popAndDiscardInitialized( type.isObject() ? factory.objectType : type.toPrimitiveType().toDexType(factory)); - frameBuilder.verifyTarget(target); + frameBuilder.checkTarget(target); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java index 45b426b..3e5c38a 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
@@ -138,7 +138,7 @@ // ... DexType type = this.type.isObject() ? factory.objectType : this.type.toPrimitiveType().toDexType(factory); - frameBuilder.popAndDiscard(type, type); - frameBuilder.verifyTarget(target); + frameBuilder.popAndDiscardInitialized(type, type); + frameBuilder.checkTarget(target); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java index 815500d..0b8240a 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
@@ -55,6 +55,11 @@ } @Override + public boolean isInitClass() { + return true; + } + + @Override public void write( AppView<?> appView, ProgramMethod context,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java index c18eb57..b4d6d43 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
@@ -25,7 +25,7 @@ import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; -public class CfInstanceOf extends CfInstruction { +public class CfInstanceOf extends CfInstruction implements CfTypeInstruction { private final DexType type; @@ -33,11 +33,27 @@ this.type = type; } + @Override + public CfTypeInstruction asTypeInstruction() { + return this; + } + + @Override + public boolean isTypeInstruction() { + return true; + } + + @Override public DexType getType() { return type; } @Override + public CfInstruction withType(DexType newType) { + return new CfInstanceOf(newType); + } + + @Override public int getCompareToId() { return Opcodes.INSTANCEOF; } @@ -110,6 +126,6 @@ InitClassLens initClassLens) { // ..., objectref → // ..., result - frameBuilder.popAndDiscard(factory.objectType).push(factory.intType); + frameBuilder.popAndDiscardInitialized(factory.objectType).push(factory.intType); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java index c69ba09..1009080 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -221,6 +221,18 @@ return false; } + public CfTypeInstruction asTypeInstruction() { + return null; + } + + public boolean isTypeInstruction() { + return false; + } + + public boolean isInitClass() { + return false; + } + public CfDexItemBasedConstString asDexItemBasedConstString() { return null; }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java index db81a5f..87cae25 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -354,11 +354,11 @@ // OR, for static method calls: // ..., [arg1, [arg2 ...]] → // ... - frameBuilder.popAndDiscard(this.method.proto.parameters.values); + frameBuilder.popAndDiscardInitialized(this.method.proto.parameters.values); if (opcode == Opcodes.INVOKESPECIAL && method.isInstanceInitializer(factory)) { frameBuilder.popAndInitialize(context, method.holder); } else if (opcode != Opcodes.INVOKESTATIC) { - frameBuilder.pop(method.holder); + frameBuilder.popInitialized(method.holder); } if (this.method.proto.returnType != factory.voidType) { frameBuilder.push(this.method.proto.returnType);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java index 88ffb82..3a98a8e 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -167,7 +167,7 @@ InitClassLens initClassLens) { // ..., [arg1, [arg2 ...]] → // ... - frameBuilder.popAndDiscard(callSite.methodProto.parameters.values); + frameBuilder.popAndDiscardInitialized(callSite.methodProto.parameters.values); if (callSite.methodProto.returnType != factory.voidType) { frameBuilder.push(callSite.methodProto.returnType); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java index 9180511..682c857 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
@@ -27,7 +27,7 @@ import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; -public class CfMultiANewArray extends CfInstruction { +public class CfMultiANewArray extends CfInstruction implements CfTypeInstruction { private final DexType type; private final int dimensions; @@ -41,10 +41,26 @@ this.dimensions = dimensions; } + @Override + public CfTypeInstruction asTypeInstruction() { + return this; + } + + @Override + public boolean isTypeInstruction() { + return true; + } + + @Override public DexType getType() { return type; } + @Override + public CfInstruction withType(DexType newType) { + return new CfMultiANewArray(newType, dimensions); + } + public int getDimensions() { return dimensions; } @@ -114,7 +130,7 @@ // ..., count1, [count2, ...] → // ..., arrayref for (int i = 0; i < dimensions; i++) { - frameBuilder.pop(factory.intType); + frameBuilder.popInitialized(factory.intType); } frameBuilder.push(type); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNew.java b/src/main/java/com/android/tools/r8/cf/code/CfNew.java index 7140404..53fc40c 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfNew.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
@@ -26,7 +26,7 @@ import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; -public class CfNew extends CfInstruction { +public class CfNew extends CfInstruction implements CfTypeInstruction { private final DexType type; @@ -34,11 +34,27 @@ this.type = type; } + @Override + public CfTypeInstruction asTypeInstruction() { + return this; + } + + @Override + public boolean isTypeInstruction() { + return true; + } + + @Override public DexType getType() { return type; } @Override + public CfInstruction withType(DexType newType) { + return new CfNew(newType); + } + + @Override public int getCompareToId() { return Opcodes.NEW; }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java index bd36756..ad50c17 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
@@ -28,7 +28,7 @@ import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; -public class CfNewArray extends CfInstruction { +public class CfNewArray extends CfInstruction implements CfTypeInstruction { private final DexType type; @@ -37,11 +37,27 @@ this.type = type; } + @Override + public CfTypeInstruction asTypeInstruction() { + return this; + } + + @Override + public boolean isTypeInstruction() { + return true; + } + + @Override public DexType getType() { return type; } @Override + public CfInstruction withType(DexType newType) { + return new CfNewArray(newType); + } + + @Override public int getCompareToId() { return type.isPrimitiveArrayType() ? Opcodes.NEWARRAY : Opcodes.ANEWARRAY; } @@ -150,6 +166,6 @@ // ..., count → // ..., arrayref assert type.isArrayType(); - frameBuilder.popAndDiscard(factory.intType).push(type); + frameBuilder.popAndDiscardInitialized(factory.intType).push(type); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java index 51d1283..2b4eed5 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
@@ -113,7 +113,7 @@ DexItemFactory factory, InitClassLens initClassLens) { assert returnType != null; - frameBuilder.popAndDiscard(returnType); + frameBuilder.popAndDiscardInitialized(returnType); frameBuilder.setNoFrame(); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java index bfdbe0a..4fd9829 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
@@ -368,7 +368,7 @@ // ... final FrameType pop = frameBuilder.pop(); if (!pop.isWide()) { - frameBuilder.verifyIsAssignable(pop, FrameType.oneWord()); + frameBuilder.checkIsAssignable(pop, FrameType.oneWord()); frameBuilder.pop(FrameType.oneWord()); } return; @@ -399,7 +399,7 @@ FrameType value1 = frameBuilder.pop(FrameType.oneWord()); FrameType value2 = frameBuilder.pop(); if (!value2.isWide()) { - frameBuilder.verifyIsAssignable(value2, FrameType.oneWord()); + frameBuilder.checkIsAssignable(value2, FrameType.oneWord()); FrameType value3 = frameBuilder.pop(FrameType.oneWord()); frameBuilder.push(value1).push(value3); } else { @@ -417,7 +417,7 @@ // ..., value, value FrameType value1 = frameBuilder.pop(); if (!value1.isWide()) { - frameBuilder.verifyIsAssignable(value1, FrameType.oneWord()); + frameBuilder.checkIsAssignable(value1, FrameType.oneWord()); FrameType value2 = frameBuilder.pop(FrameType.oneWord()); frameBuilder.push(value2).push(value1).push(value2); } else { @@ -436,7 +436,7 @@ FrameType value1 = frameBuilder.pop(); FrameType value2 = frameBuilder.pop(FrameType.oneWord()); if (!value1.isWide()) { - frameBuilder.verifyIsAssignable(value1, FrameType.oneWord()); + frameBuilder.checkIsAssignable(value1, FrameType.oneWord()); FrameType value3 = frameBuilder.pop(FrameType.oneWord()); frameBuilder.push(value2).push(value1).push(value3); } else { @@ -466,9 +466,9 @@ FrameType value3 = frameBuilder.pop(); if (!value3.isWide()) { // (1) - frameBuilder.verifyIsAssignable(value1, FrameType.oneWord()); - frameBuilder.verifyIsAssignable(value2, FrameType.oneWord()); - frameBuilder.verifyIsAssignable(value3, FrameType.oneWord()); + frameBuilder.checkIsAssignable(value1, FrameType.oneWord()); + frameBuilder.checkIsAssignable(value2, FrameType.oneWord()); + frameBuilder.checkIsAssignable(value3, FrameType.oneWord()); FrameType value4 = frameBuilder.pop(FrameType.oneWord()); frameBuilder .push(value2) @@ -479,13 +479,13 @@ .push(value1); } else { // (3) - frameBuilder.verifyIsAssignable(value1, FrameType.oneWord()); - frameBuilder.verifyIsAssignable(value2, FrameType.oneWord()); + frameBuilder.checkIsAssignable(value1, FrameType.oneWord()); + frameBuilder.checkIsAssignable(value2, FrameType.oneWord()); frameBuilder.push(value2).push(value1).push(value3).push(value2).push(value1); } } else if (!value2.isWide()) { // (2) - frameBuilder.verifyIsAssignable(value2, FrameType.oneWord()); + frameBuilder.checkIsAssignable(value2, FrameType.oneWord()); FrameType value3 = frameBuilder.pop(FrameType.oneWord()); frameBuilder.push(value1).push(value3).push(value2).push(value1); } else {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStore.java b/src/main/java/com/android/tools/r8/cf/code/CfStore.java index d74eb39..b87c87c 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfStore.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
@@ -3,6 +3,8 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.cf.code; +import static com.android.tools.r8.utils.BiPredicateUtils.or; + import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.cf.code.CfFrame.FrameType; import com.android.tools.r8.errors.Unreachable; @@ -129,24 +131,34 @@ FrameType pop = frameBuilder.pop(); switch (type) { case OBJECT: - frameBuilder.verifyIsAssignable(pop, factory.objectType); + frameBuilder.checkIsAssignable( + pop, + factory.objectType, + or( + frameBuilder::isUninitializedThisAndTarget, + frameBuilder::isUninitializedNewAndTarget, + frameBuilder::isAssignableAndInitialized)); frameBuilder.storeLocal(var, pop); return; case INT: - frameBuilder.verifyIsAssignable(pop, factory.intType); + frameBuilder.checkIsAssignable( + pop, factory.intType, frameBuilder::isAssignableAndInitialized); frameBuilder.storeLocal(var, FrameType.initialized(factory.intType)); return; case FLOAT: - frameBuilder.verifyIsAssignable(pop, factory.floatType); + frameBuilder.checkIsAssignable( + pop, factory.floatType, frameBuilder::isAssignableAndInitialized); frameBuilder.storeLocal(var, FrameType.initialized(factory.floatType)); return; case LONG: - frameBuilder.verifyIsAssignable(pop, factory.longType); + frameBuilder.checkIsAssignable( + pop, factory.longType, frameBuilder::isAssignableAndInitialized); frameBuilder.storeLocal(var, FrameType.initialized(factory.longType)); frameBuilder.storeLocal(var + 1, FrameType.initialized(factory.longType)); return; case DOUBLE: - frameBuilder.verifyIsAssignable(pop, factory.doubleType); + frameBuilder.checkIsAssignable( + pop, factory.doubleType, frameBuilder::isAssignableAndInitialized); frameBuilder.storeLocal(var, FrameType.initialized(factory.doubleType)); frameBuilder.storeLocal(var + 1, FrameType.initialized(factory.doubleType)); return;
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java index f07011a..b84fc14 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
@@ -149,10 +149,10 @@ InitClassLens initClassLens) { // ..., index/key → // ... - frameBuilder.pop(factory.intType); - frameBuilder.verifyTarget(defaultTarget); + frameBuilder.popInitialized(factory.intType); + frameBuilder.checkTarget(defaultTarget); for (CfLabel target : targets) { - frameBuilder.verifyTarget(target); + frameBuilder.checkTarget(target); } frameBuilder.setNoFrame(); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java index aad20a8..90d25c5 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
@@ -95,7 +95,7 @@ InitClassLens initClassLens) { // ..., objectref → // objectref - frameBuilder.pop(factory.throwableType); + frameBuilder.popInitialized(factory.throwableType); // The exception edges are verified in CfCode since this is a throwing instruction. frameBuilder.setNoFrame(); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfTypeInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfTypeInstruction.java new file mode 100644 index 0000000..1762c7a --- /dev/null +++ b/src/main/java/com/android/tools/r8/cf/code/CfTypeInstruction.java
@@ -0,0 +1,14 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.cf.code; + +import com.android.tools.r8.graph.DexType; + +public interface CfTypeInstruction { + + DexType getType(); + + CfInstruction withType(DexType newType); +}
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java index da3b04c..fa7e21d 100644 --- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java +++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -643,7 +643,10 @@ // for all code objects and write the processed results into that map. Map<DexEncodedMethod, DexCode> codeMapping = new IdentityHashMap<>(); for (DexProgramClass clazz : classes) { - boolean isSharedSynthetic = clazz.getSynthesizedFrom().size() > 1; + // TODO(b/181636450): Reconsider the code mapping setup now that synthetics are never + // duplicated in outputs. + boolean isSharedSynthetic = + appView.getSyntheticItems().getSynthesizingContexts(clazz.getType()).size() > 1; clazz.forEachMethod( method -> { DexCode code = @@ -672,7 +675,7 @@ provider, objectMapping, codeMapping, - appView.appInfo().app(), + appView.appInfo(), options, namingLens, desugaredLibraryCodeToKeep);
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java index bbe4a90..adfab44 100644 --- a/src/main/java/com/android/tools/r8/dex/FileWriter.java +++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.errors.InvokeCustomDiagnostic; import com.android.tools.r8.errors.PrivateInterfaceMethodDiagnostic; import com.android.tools.r8.errors.StaticInterfaceMethodDiagnostic; +import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.DexAnnotation; import com.android.tools.r8.graph.DexAnnotationDirectory; import com.android.tools.r8.graph.DexAnnotationElement; @@ -99,6 +100,7 @@ private final ObjectToOffsetMapping mapping; private final MethodToCodeObjectMapping codeMapping; + private final AppInfo appInfo; private final DexApplication application; private final InternalOptions options; private final GraphLens graphLens; @@ -112,13 +114,14 @@ ByteBufferProvider provider, ObjectToOffsetMapping mapping, MethodToCodeObjectMapping codeMapping, - DexApplication application, + AppInfo appInfo, InternalOptions options, NamingLens namingLens, CodeToKeep desugaredLibraryCodeToKeep) { this.mapping = mapping; this.codeMapping = codeMapping; - this.application = application; + this.appInfo = appInfo; + this.application = appInfo.app(); this.options = options; this.graphLens = mapping.getGraphLens(); this.namingLens = namingLens; @@ -680,7 +683,8 @@ dest.putUleb128(clazz.getMethodCollection().numberOfVirtualMethods()); writeEncodedFields(clazz.staticFields()); writeEncodedFields(clazz.instanceFields()); - boolean isSharedSynthetic = clazz.getSynthesizedFrom().size() > 1; + boolean isSharedSynthetic = + appInfo.getSyntheticItems().getSynthesizingContexts(clazz.getType()).size() > 1; writeEncodedMethods(clazz.directMethods(), isSharedSynthetic); writeEncodedMethods(clazz.virtualMethods(), isSharedSynthetic); }
diff --git a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java index 84459b4..29987e7 100644 --- a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java +++ b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
@@ -304,7 +304,8 @@ if (getClassNameSeparator() != '/') { javaPackage = javaPackage.replace(getClassNameSeparator(), '/'); } - String minifiedJavaPackage = namingLens.lookupPackageName(javaPackage); + String packageName = appView.graphLens().lookupPackageName(javaPackage); + String minifiedJavaPackage = namingLens.lookupPackageName(packageName); if (!javaPackage.equals(minifiedJavaPackage)) { outputRangeFromInput(outputFrom, from); outputJavaType(
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java index 135fcb4..e44973a 100644 --- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java +++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -36,7 +36,6 @@ import com.android.tools.r8.utils.SetUtils; import com.android.tools.r8.utils.Timing; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; import com.google.common.collect.Sets; import java.io.IOException; @@ -330,7 +329,11 @@ Collection<DexProgramClass> synthetics = new ArrayList<>(); // Assign dedicated virtual files for all program classes. for (DexProgramClass clazz : appView.appInfo().classes()) { - if (!combineSyntheticClassesWithPrimaryClass || clazz.getSynthesizedFrom().isEmpty()) { + Collection<DexType> contexts = + appView.getSyntheticItems().getSynthesizingContexts(clazz.getType()); + // TODO(b/181636450): Simplify this making use of the assumption that synthetics are never + // duplicated. + if (!combineSyntheticClassesWithPrimaryClass || contexts.isEmpty()) { VirtualFile file = new VirtualFile( virtualFiles.size(), @@ -350,7 +353,9 @@ } } for (DexProgramClass synthetic : synthetics) { - for (DexProgramClass inputType : synthetic.getSynthesizedFrom()) { + for (DexType context : + appView.getSyntheticItems().getSynthesizingContexts(synthetic.getType())) { + DexProgramClass inputType = appView.definitionForProgramType(context); VirtualFile file = files.get(inputType); file.addClass(synthetic); file.commitTransaction(); @@ -471,23 +476,6 @@ classes.removeAll(featureClasses); } } - List<DexProgramClass> toRemove = new ArrayList<>(); - for (DexProgramClass dexProgramClass : classes) { - if (appView.getSyntheticItems().isLegacySyntheticClass(dexProgramClass)) { - Collection<DexProgramClass> synthesizedFrom = dexProgramClass.getSynthesizedFrom(); - if (!synthesizedFrom.isEmpty()) { - DexProgramClass from = Iterables.getFirst(synthesizedFrom, null); - FeatureSplit featureSplit = - classToFeatureSplitMap.getFeatureSplit(from, appView.getSyntheticItems()); - if (!featureSplit.isBase()) { - Set<DexProgramClass> dexProgramClasses = featureSplitClasses.get(featureSplit); - dexProgramClasses.add(dexProgramClass); - toRemove.add(dexProgramClass); - } - } - } - } - classes.removeAll(toRemove); return featureSplitClasses; }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java index f2064b3..5d62aae 100644 --- a/src/main/java/com/android/tools/r8/graph/AppInfo.java +++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.synthesis.SyntheticItems; import com.android.tools.r8.utils.BooleanBox; import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.IterableUtils; import java.util.List; import java.util.function.Consumer; @@ -120,20 +121,22 @@ return syntheticItems; } - public void addSynthesizedClass(DexProgramClass clazz, boolean addToMainDex) { + public void addSynthesizedClassForLibraryDesugaring(DexProgramClass clazz) { assert checkIfObsolete(); - syntheticItems.addLegacySyntheticClass(clazz); - if (addToMainDex) { - mainDexInfo.addSyntheticClass(clazz); - } + assert options().desugaredLibraryConfiguration != null; + syntheticItems.addLegacySyntheticClassForLibraryDesugaring(clazz); } public void addSynthesizedClass(DexProgramClass clazz, ProgramDefinition context) { assert checkIfObsolete(); - syntheticItems.addLegacySyntheticClass(clazz); - if (context != null) { - mainDexInfo.addLegacySyntheticClass(clazz, context); - } + assert context != null; + syntheticItems.addLegacySyntheticClass(clazz, context); + } + + public void addSynthesizedClass(DexProgramClass clazz, Iterable<DexProgramClass> contexts) { + assert checkIfObsolete(); + assert !IterableUtils.isEmpty(contexts); + contexts.forEach(context -> addSynthesizedClass(clazz, context)); } public List<DexProgramClass> classes() { @@ -168,7 +171,7 @@ } DexClass definition = definitionFor(type); if (definition != null && !definition.isLibraryClass() && !dependent.isLibraryClass()) { - InterfaceMethodRewriter.reportDependencyEdge(dependent, definition, options()); + InterfaceMethodRewriter.reportDependencyEdge(dependent, definition, this); } return definition; }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java index cd90e16..c8d9834 100644 --- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java +++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -100,14 +100,6 @@ commit, getClassToFeatureSplitMap(), getMainDexInfo(), getMissingClasses()); } - public final AppInfoWithClassHierarchy rebuildWithClassHierarchy(MissingClasses missingClasses) { - return new AppInfoWithClassHierarchy( - getSyntheticItems().commit(app()), - getClassToFeatureSplitMap(), - getMainDexInfo(), - missingClasses); - } - public AppInfoWithClassHierarchy rebuildWithClassHierarchy( Function<DexApplication, DexApplication> fn) { assert checkIfObsolete(); @@ -123,7 +115,10 @@ assert getClass() == AppInfoWithClassHierarchy.class; assert checkIfObsolete(); return new AppInfoWithClassHierarchy( - getSyntheticItems().commit(app()), classToFeatureSplitMap, mainDexInfo, missingClasses); + getSyntheticItems().commit(app()), + getClassToFeatureSplitMap(), + mainDexInfo, + getMissingClasses()); } @Override
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java index ed43ca9..71b938f 100644 --- a/src/main/java/com/android/tools/r8/graph/CfCode.java +++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -67,8 +67,17 @@ public enum StackMapStatus { NOT_VERIFIED, - INVALID_OR_NOT_PRESENT, - VALID + NOT_PRESENT, + INVALID, + VALID; + + public boolean isValid() { + return this == VALID || this == NOT_PRESENT; + } + + public boolean isInvalidOrNotPresent() { + return this == INVALID || this == NOT_PRESENT; + } } public static class LocalVariableInfo { @@ -305,7 +314,8 @@ LensCodeRewriterUtils rewriter, MethodVisitor visitor) { GraphLens graphLens = appView.graphLens(); - assert verifyFrames(method.getDefinition(), appView, null, false); + assert verifyFrames(method.getDefinition(), appView, null, false).isValid() + : "Could not validate stack map frames"; DexItemFactory dexItemFactory = appView.dexItemFactory(); InitClassLens initClassLens = appView.initClassLens(); InternalOptions options = appView.options(); @@ -432,7 +442,8 @@ AppView<?> appView, Origin origin, boolean shouldApplyCodeRewritings) { - if (!verifyFrames(method, appView, origin, shouldApplyCodeRewritings)) { + stackMapStatus = verifyFrames(method, appView, origin, shouldApplyCodeRewritings); + if (!stackMapStatus.isValid()) { ArrayList<CfInstruction> copy = new ArrayList<>(instructions); copy.removeIf(CfInstruction::isFrame); setInstructions(copy); @@ -705,16 +716,14 @@ thisLocalInfo.index, debugLocalInfo, thisLocalInfo.start, thisLocalInfo.end)); } - public boolean verifyFrames( + public StackMapStatus verifyFrames( DexEncodedMethod method, AppView<?> appView, Origin origin, boolean applyProtoTypeChanges) { if (!appView.options().canUseInputStackMaps() || appView.options().testing.disableStackMapVerification) { - stackMapStatus = StackMapStatus.INVALID_OR_NOT_PRESENT; - return true; + return StackMapStatus.NOT_PRESENT; } if (method.hasClassFileVersion() && method.getClassFileVersion().isLessThan(CfVersion.V1_7)) { - stackMapStatus = StackMapStatus.INVALID_OR_NOT_PRESENT; - return true; + return StackMapStatus.NOT_PRESENT; } if (!method.isInstanceInitializer() && appView @@ -722,8 +731,7 @@ .getOriginalMethodSignature(method.method) .isInstanceInitializer(appView.dexItemFactory())) { // We cannot verify instance initializers if they are moved. - stackMapStatus = StackMapStatus.INVALID_OR_NOT_PRESENT; - return true; + return StackMapStatus.NOT_PRESENT; } // Build a map from labels to frames. Map<CfLabel, CfFrame> stateMap = new IdentityHashMap<>(); @@ -790,12 +798,13 @@ tryCatchRanges, isAssignablePredicate(appView), appView.dexItemFactory(), - appView.graphLens()); + appView.graphLens(), + maxStack); if (stateMap.containsKey(null)) { assert !shouldComputeInitialFrame(); - builder.verifyFrameAndSet(stateMap.get(null)); + builder.checkFrameAndSet(stateMap.get(null)); } else if (shouldComputeInitialFrame()) { - builder.verifyFrameAndSet( + builder.checkFrameAndSet( new CfFrame( computeInitialLocals(context, method, rewrittenDescription), new ArrayDeque<>())); } @@ -807,7 +816,7 @@ // affect the exceptional transfer (the exception edge is always a singleton stack). if (instruction.canThrow()) { assert !instruction.isStore(); - builder.verifyExceptionEdges(); + builder.checkExceptionEdges(); } instruction.evaluate( builder, context, returnType, appView.dexItemFactory(), appView.initClassLens()); @@ -823,18 +832,16 @@ appView); } } - stackMapStatus = StackMapStatus.VALID; - return true; + return StackMapStatus.VALID; } - private boolean reportStackMapError(CfCodeDiagnostics diagnostics, AppView<?> appView) { + private StackMapStatus reportStackMapError(CfCodeDiagnostics diagnostics, AppView<?> appView) { // Stack maps was required from version V1_6 (50), but the JVM gave a grace-period and only // started enforcing stack maps from 51 in JVM 8. As a consequence, we have different android // libraries that has V1_7 code but has no stack maps. To not fail on compilations we only // report a warning. - stackMapStatus = StackMapStatus.INVALID_OR_NOT_PRESENT; appView.options().reporter.warning(diagnostics); - return false; + return StackMapStatus.INVALID; } private boolean finalAndExitInstruction(CfInstruction instruction) {
diff --git a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java index abeb404..be553ac6 100644 --- a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java +++ b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
@@ -95,7 +95,7 @@ public int getAsDexAccessFlags() { // We unset the super flag here, as it is meaningless in DEX. Furthermore, we add missing // abstract to interfaces to work around a javac bug when generating package-info classes. - int flags = materialize() & ~Constants.ACC_SUPER; + int flags = materialize() & ~Constants.ACC_SUPER & ~Constants.ACC_RECORD; if (isInterface()) { return flags | Constants.ACC_ABSTRACT; } @@ -186,6 +186,10 @@ set(Constants.ACC_RECORD); } + public void unsetRecord() { + unset(Constants.ACC_RECORD); + } + public boolean isSuper() { return isSet(Constants.ACC_SUPER); }
diff --git a/src/main/java/com/android/tools/r8/graph/ClassDefinition.java b/src/main/java/com/android/tools/r8/graph/ClassDefinition.java index f4f1c31..da0f128 100644 --- a/src/main/java/com/android/tools/r8/graph/ClassDefinition.java +++ b/src/main/java/com/android/tools/r8/graph/ClassDefinition.java
@@ -21,6 +21,8 @@ DexType getType(); + boolean isInterface(); + @Override default boolean isClass() { return true;
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java index d5ea16e..d82db4a 100644 --- a/src/main/java/com/android/tools/r8/graph/DexApplication.java +++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -199,6 +199,8 @@ return self(); } + public abstract void addProgramClassPotentiallyOverridingNonProgramClass(DexProgramClass clazz); + public synchronized T addProgramClasses(Collection<DexProgramClass> classes) { programClasses.addAll(classes); return self();
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java index f0745c8..130dbcf 100644 --- a/src/main/java/com/android/tools/r8/graph/DexClass.java +++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -598,6 +598,7 @@ return isFinal(); } + @Override public boolean isInterface() { return accessFlags.isInterface(); }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDefinition.java b/src/main/java/com/android/tools/r8/graph/DexDefinition.java index c3e6998..8eeda6b 100644 --- a/src/main/java/com/android/tools/r8/graph/DexDefinition.java +++ b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
@@ -21,6 +21,10 @@ this.annotations = annotations; } + public boolean hasAnnotations() { + return !annotations().isEmpty(); + } + public DexAnnotationSet annotations() { return annotations; }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java index d15bfc7..1a2b411 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -51,23 +51,6 @@ // TODO(b/171867022): Should the optimization info and member info be part of the definition? } - public DexEncodedField( - DexField field, - FieldAccessFlags accessFlags, - FieldTypeSignature genericSignature, - DexAnnotationSet annotations, - DexValue staticValue, - boolean deprecated) { - super(annotations); - this.field = field; - this.accessFlags = accessFlags; - this.staticValue = staticValue; - this.deprecated = deprecated; - this.genericSignature = genericSignature; - assert genericSignature != null; - assert GenericSignatureUtils.verifyNoDuplicateGenericDefinitions(genericSignature, annotations); - } - public DexEncodedField(DexField field, FieldAccessFlags accessFlags) { this(field, accessFlags, FieldTypeSignature.noSignature(), DexAnnotationSet.empty(), null); } @@ -81,6 +64,34 @@ this(field, accessFlags, genericSignature, annotations, staticValue, false); } + public DexEncodedField( + DexField field, + FieldAccessFlags accessFlags, + FieldTypeSignature genericSignature, + DexAnnotationSet annotations, + DexValue staticValue, + boolean deprecated) { + this(field, accessFlags, genericSignature, annotations, staticValue, deprecated, false); + } + + public DexEncodedField( + DexField field, + FieldAccessFlags accessFlags, + FieldTypeSignature genericSignature, + DexAnnotationSet annotations, + DexValue staticValue, + boolean deprecated, + boolean d8R8Synthesized) { + super(annotations, d8R8Synthesized); + this.field = field; + this.accessFlags = accessFlags; + this.staticValue = staticValue; + this.deprecated = deprecated; + this.genericSignature = genericSignature; + assert genericSignature != null; + assert GenericSignatureUtils.verifyNoDuplicateGenericDefinitions(genericSignature, annotations); + } + @Override public StructuralMapping<DexEncodedField> getStructuralMapping() { return DexEncodedField::specify; @@ -235,10 +246,6 @@ return accessFlags.isVolatile(); } - public boolean hasAnnotation() { - return !annotations().isEmpty(); - } - public boolean hasExplicitStaticValue() { assert accessFlags.isStatic(); return staticValue != null; @@ -356,6 +363,8 @@ private FieldTypeSignature genericSignature; private DexValue staticValue; private FieldOptimizationInfo optimizationInfo; + private boolean deprecated; + private boolean d8R8Synthesized; private Consumer<DexEncodedField> buildConsumer = ConsumerUtils.emptyConsumer(); Builder(DexEncodedField from) { @@ -370,6 +379,8 @@ from.optimizationInfo.isDefaultFieldOptimizationInfo() ? DefaultFieldOptimizationInfo.getInstance() : from.optimizationInfo.mutableCopy(); + deprecated = from.isDeprecated(); + d8R8Synthesized = from.isD8R8Synthesized(); } public Builder apply(Consumer<Builder> consumer) { @@ -397,7 +408,14 @@ DexEncodedField build() { DexEncodedField dexEncodedField = - new DexEncodedField(field, accessFlags, genericSignature, annotations, staticValue); + new DexEncodedField( + field, + accessFlags, + genericSignature, + annotations, + staticValue, + deprecated, + d8R8Synthesized); if (optimizationInfo.isMutableFieldOptimizationInfo()) { dexEncodedField.setOptimizationInfo(optimizationInfo.asMutableFieldOptimizationInfo()); }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java index d5c5135..7d66bb6 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
@@ -8,8 +8,17 @@ public abstract class DexEncodedMember<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> extends DexDefinition { - public DexEncodedMember(DexAnnotationSet annotations) { + // This flag indicates if this member has been synthesized by D8/R8. Such members do not require + // a proguard mapping file entry. This flag is different from the synthesized access flag. When a + // non synthesized member is inlined into a synthesized member, the member no longer has the + // synthesized access flag, but the d8R8Synthesized flag is still there. Members can also have + // the synthesized access flag prior to D8/R8 compilation, in which case d8R8Synthesized is not + // set. + private final boolean d8R8Synthesized; + + public DexEncodedMember(DexAnnotationSet annotations, boolean d8R8Synthesized) { super(annotations); + this.d8R8Synthesized = d8R8Synthesized; } public abstract KotlinMemberLevelInfo getKotlinMemberInfo(); @@ -25,6 +34,10 @@ @Override public abstract R getReference(); + public boolean isD8R8Synthesized() { + return d8R8Synthesized; + } + @Override public boolean isDexEncodedMember() { return true;
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 5c9fc0f..470bee9 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -160,7 +160,7 @@ private CompilationState compilationState = CompilationState.NOT_PROCESSED; private MethodOptimizationInfo optimizationInfo = DefaultMethodOptimizationInfo.DEFAULT_INSTANCE; private CallSiteOptimizationInfo callSiteOptimizationInfo = CallSiteOptimizationInfo.bottom(); - private CfVersion classFileVersion = null; + private CfVersion classFileVersion; private KotlinMethodLevelInfo kotlinMemberInfo = NO_KOTLIN_INFO; /** Generic signature information if the attribute is present in the input */ private MethodTypeSignature genericSignature; @@ -177,18 +177,6 @@ // Any newly added `public` method should check if `this` instance is obsolete. private boolean obsolete = false; - // This flag indicates if the method has been synthesized by D8/R8. Such method do not require - // a proguard mapping file entry. This flag is different from the synthesized access flag. When a - // non synthesized method is inlined into a synthesized method, the method no longer has the - // synthesized access flag, but the d8R8Synthesized flag is still there. Methods can also have - // the synthesized access flag prior to D8/R8 compilation, in which case d8R8Synthesized is not - // set. - private final boolean d8R8Synthesized; - - public boolean isD8R8Synthesized() { - return d8R8Synthesized; - } - private void checkIfObsolete() { assert !obsolete; } @@ -220,6 +208,10 @@ return getReference().getParameter(argumentIndex - 1); } + public int getNumberOfArguments() { + return getReference().getArity() + BooleanUtils.intValue(isInstance()); + } + public CompilationState getCompilationState() { return compilationState; } @@ -319,7 +311,7 @@ boolean d8R8Synthesized, CfVersion classFileVersion, boolean deprecated) { - super(annotations); + super(annotations, d8R8Synthesized); this.method = method; this.accessFlags = accessFlags; this.deprecated = deprecated; @@ -327,7 +319,6 @@ this.parameterAnnotationsList = parameterAnnotationsList; this.code = code; this.classFileVersion = classFileVersion; - this.d8R8Synthesized = d8R8Synthesized; assert accessFlags != null; assert code == null || !shouldNotHaveCode(); assert parameterAnnotationsList != null; @@ -354,15 +345,15 @@ // Visitor specifying the structure of the method with respect to its "synthetic" content. // TODO(b/171867022): Generalize this so that it determines any method in full. private static void syntheticSpecify(StructuralSpecification<DexEncodedMethod, ?> spec) { - spec.withItem(m -> m.method) - .withItem(m -> m.accessFlags) + spec.withItem(DexEncodedMethod::getReference) + .withItem(DexEncodedMethod::getAccessFlags) .withItem(DexDefinition::annotations) .withItem(m -> m.parameterAnnotationsList) .withNullableItem(m -> m.classFileVersion) - .withBool(m -> m.d8R8Synthesized) + .withBool(DexEncodedMember::isD8R8Synthesized) // TODO(b/171867022): Make signatures structural and include it in the definition. .withAssert(m -> m.genericSignature.hasNoSignature()) - .withAssert(m1 -> m1.code != null) + .withAssert(DexEncodedMethod::hasCode) .withCustomItem( DexEncodedMethod::getCode, DexEncodedMethod::compareCodeObject, @@ -1547,7 +1538,7 @@ private Consumer<DexEncodedMethod> buildConsumer = ConsumerUtils.emptyConsumer(); private Builder(DexEncodedMethod from) { - this(from, from.d8R8Synthesized); + this(from, from.isD8R8Synthesized()); } private Builder(DexEncodedMethod from, boolean d8R8Synthesized) {
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 e76409d..2ce742a 100644 --- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java +++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.graph; +import static com.android.tools.r8.horizontalclassmerging.ClassMerger.CLASS_ID_FIELD_NAME; import static com.android.tools.r8.ir.analysis.type.ClassTypeElement.computeLeastUpperBoundOfInterfaces; import static com.android.tools.r8.ir.desugar.LambdaClass.LAMBDA_INSTANCE_FIELD_NAME; @@ -215,6 +216,7 @@ public final DexString stringArrayDescriptor = createString("[Ljava/lang/String;"); public final DexString objectDescriptor = createString("Ljava/lang/Object;"); public final DexString recordDescriptor = createString("Ljava/lang/Record;"); + public final DexString r8RecordDescriptor = createString("Lcom/android/tools/r8/RecordTag;"); public final DexString objectArrayDescriptor = createString("[Ljava/lang/Object;"); public final DexString classDescriptor = createString("Ljava/lang/Class;"); public final DexString classLoaderDescriptor = createString("Ljava/lang/ClassLoader;"); @@ -346,6 +348,7 @@ public final DexType stringArrayType = createStaticallyKnownType(stringArrayDescriptor); public final DexType objectType = createStaticallyKnownType(objectDescriptor); public final DexType recordType = createStaticallyKnownType(recordDescriptor); + public final DexType r8RecordType = createStaticallyKnownType(r8RecordDescriptor); public final DexType objectArrayType = createStaticallyKnownType(objectArrayDescriptor); public final DexType classArrayType = createStaticallyKnownType(classArrayDescriptor); public final DexType enumType = createStaticallyKnownType(enumDescriptor); @@ -509,6 +512,7 @@ public final LongMembers longMembers = new LongMembers(); public final ObjectsMethods objectsMethods = new ObjectsMethods(); public final ObjectMembers objectMembers = new ObjectMembers(); + public final RecordMembers recordMembers = new RecordMembers(); public final ShortMembers shortMembers = new ShortMembers(); public final StringMembers stringMembers = new StringMembers(); public final DoubleMembers doubleMembers = new DoubleMembers(); @@ -1203,9 +1207,23 @@ } } + public class RecordMembers { + public final DexMethod init = createMethod(recordType, createProto(voidType), "<init>"); + public final DexMethod equals = + createMethod(recordType, createProto(booleanType, objectType), "equals"); + public final DexMethod hashCode = createMethod(recordType, createProto(intType), "hashCode"); + public final DexMethod toString = createMethod(recordType, createProto(stringType), "toString"); + } + public class ObjectMembers { /** + * This field is not on {@link Object}, but will be synthesized on horizontally merged classes + * as an instance field. + */ + public final DexField classIdField = createField(objectType, intType, CLASS_ID_FIELD_NAME); + + /** * This field is not on {@link Object}, but will be synthesized on program classes as a static * field, for the compiler to have a principled way to trigger the initialization of a given * class.
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java index 11e5957..a4ccb8e 100644 --- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java +++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -28,9 +28,6 @@ import com.google.common.collect.Iterables; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; @@ -50,7 +47,6 @@ public static final DexProgramClass[] EMPTY_ARRAY = {}; private final ProgramResource.Kind originKind; - private final Collection<DexProgramClass> synthesizedFrom; private CfVersion initialClassFileVersion = null; private boolean deprecated = false; private KotlinClassLevelInfo kotlinInfo = NO_KOTLIN_INFO; @@ -77,50 +73,6 @@ DexEncodedMethod[] virtualMethods, boolean skipNameValidationForTesting, ChecksumSupplier checksumSupplier) { - this( - type, - originKind, - origin, - accessFlags, - superType, - interfaces, - sourceFile, - nestHost, - nestMembers, - enclosingMember, - innerClasses, - classSignature, - classAnnotations, - staticFields, - instanceFields, - directMethods, - virtualMethods, - skipNameValidationForTesting, - checksumSupplier, - Collections.emptyList()); - } - - public DexProgramClass( - DexType type, - Kind originKind, - Origin origin, - ClassAccessFlags accessFlags, - DexType superType, - DexTypeList interfaces, - DexString sourceFile, - NestHostClassAttribute nestHost, - List<NestMemberClassAttribute> nestMembers, - EnclosingMethodAttribute enclosingMember, - List<InnerClassAttribute> innerClasses, - ClassSignature classSignature, - DexAnnotationSet classAnnotations, - DexEncodedField[] staticFields, - DexEncodedField[] instanceFields, - DexEncodedMethod[] directMethods, - DexEncodedMethod[] virtualMethods, - boolean skipNameValidationForTesting, - ChecksumSupplier checksumSupplier, - Collection<DexProgramClass> synthesizedDirectlyFrom) { super( sourceFile, interfaces, @@ -143,8 +95,6 @@ assert classAnnotations != null; this.originKind = originKind; this.checksumSupplier = checksumSupplier; - this.synthesizedFrom = new HashSet<>(); - synthesizedDirectlyFrom.forEach(this::addSynthesizedFrom); } @Override @@ -185,9 +135,7 @@ .withAssert(c -> c.classSignature == ClassSignature.noSignature()) .withItemArray(c -> c.staticFields) .withItemArray(c -> c.instanceFields) - .withItemCollection(DexClass::allMethodsSorted) - // TODO(b/168584485): Synthesized-from is being removed (empty for new synthetics). - .withAssert(c -> c.synthesizedFrom.isEmpty()); + .withItemCollection(DexClass::allMethodsSorted); } public void forEachProgramField(Consumer<? super ProgramField> consumer) { @@ -383,10 +331,6 @@ } } - public Collection<DexProgramClass> getSynthesizedFrom() { - return synthesizedFrom; - } - @Override void collectMixedSectionItems(MixedSectionCollection mixedItems) { assert getEnclosingMethodAttribute() == null; @@ -599,7 +543,7 @@ private boolean hasAnnotations(DexEncodedField[] fields) { synchronized (fields) { - return Arrays.stream(fields).anyMatch(DexEncodedField::hasAnnotation); + return Arrays.stream(fields).anyMatch(DexEncodedField::hasAnnotations); } } @@ -609,14 +553,6 @@ } } - public void addSynthesizedFrom(DexProgramClass clazz) { - if (clazz.synthesizedFrom.isEmpty()) { - synthesizedFrom.add(clazz); - } else { - clazz.synthesizedFrom.forEach(this::addSynthesizedFrom); - } - } - public DexEncodedArray computeStaticValuesArray(NamingLens namingLens) { // Fast path to avoid sorting and collection allocation when no non-default values exist. if (!hasNonDefaultStaticFieldValues()) {
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java index 86377b3..0f7b951 100644 --- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java +++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -213,6 +213,37 @@ } @Override + public void addProgramClassPotentiallyOverridingNonProgramClass(DexProgramClass clazz) { + addProgramClass(clazz); + if (containsType(clazz.type, libraryClasses)) { + replaceLibraryClasses(withoutType(clazz.type, libraryClasses)); + return; + } + if (containsType(clazz.type, classpathClasses)) { + replaceClasspathClasses(withoutType(clazz.type, classpathClasses)); + } + } + + private boolean containsType(DexType type, List<? extends DexClass> classes) { + for (DexClass clazz : classes) { + if (clazz.type == type) { + return true; + } + } + return false; + } + + private <T extends DexClass> ImmutableList<T> withoutType(DexType type, List<T> classes) { + ImmutableList.Builder<T> builder = ImmutableList.builder(); + for (T clazz : classes) { + if (clazz.type != type) { + builder.add(clazz); + } + } + return builder.build(); + } + + @Override Builder self() { return this; }
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java index 4f07887..1697f43 100644 --- a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java +++ b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
@@ -63,6 +63,11 @@ Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC); } + public static FieldAccessFlags createPublicFinalSynthetic() { + return fromSharedAccessFlags( + Constants.ACC_PUBLIC | Constants.ACC_FINAL | Constants.ACC_SYNTHETIC); + } + public static FieldAccessFlags fromSharedAccessFlags(int access) { assert (access & FLAGS) == access; return new FieldAccessFlags(access & FLAGS);
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java index 43e733c..7135cfc 100644 --- a/src/main/java/com/android/tools/r8/graph/GraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -4,7 +4,6 @@ package com.android.tools.r8.graph; import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; -import static com.android.tools.r8.horizontalclassmerging.ClassMerger.CLASS_ID_FIELD_NAME; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.ir.code.Invoke.Type; @@ -364,6 +363,8 @@ return false; } + public abstract String lookupPackageName(String pkg); + public abstract DexType lookupClassType(DexType type); public abstract DexType lookupType(DexType type); @@ -595,7 +596,6 @@ public boolean verifyMappingToOriginalProgram( AppView<?> appView, DexApplication originalApplication) { - DexItemFactory dexItemFactory = appView.dexItemFactory(); Iterable<DexProgramClass> classes = appView.appInfo().classesWithDeterministicOrder(); // Collect all original fields and methods for efficient querying. Set<DexField> originalFields = Sets.newIdentityHashSet(); @@ -616,11 +616,12 @@ continue; } for (DexEncodedField field : clazz.fields()) { - // Fields synthesized by R8 are not present in the input, and therefore we do not require - // that they can be mapped back to the original program. + if (field.isD8R8Synthesized()) { + // Fields synthesized by D8/R8 may not be mapped. + continue; + } DexField originalField = getOriginalFieldSignature(field.getReference()); assert originalFields.contains(originalField) - || isD8R8SynthesizedField(field.getReference(), appView) : "Unable to map field `" + field.getReference().toSourceString() + "` back to original program"; @@ -638,23 +639,6 @@ return true; } - private boolean isD8R8SynthesizedField(DexField field, AppView<?> appView) { - // TODO(b/167947782): Should be a general check to see if the field is D8/R8 synthesized - // instead of relying on field names. - DexItemFactory dexItemFactory = appView.dexItemFactory(); - if (field.match(dexItemFactory.objectMembers.clinitField)) { - return true; - } - if (field.getName().toSourceString().equals(CLASS_ID_FIELD_NAME)) { - return true; - } - if (appView.getSyntheticItems().isNonLegacySynthetic(field.getHolderType()) - && field.getName() == dexItemFactory.lambdaInstanceFieldName) { - return true; - } - return false; - } - public abstract static class NonIdentityGraphLens extends GraphLens { private final DexItemFactory dexItemFactory; @@ -715,6 +699,11 @@ } @Override + public String lookupPackageName(String pkg) { + return pkg; + } + + @Override public final DexType lookupType(DexType type) { if (type.isPrimitiveType() || type.isVoidType() || type.isNullValueType()) { return type; @@ -831,6 +820,11 @@ } @Override + public String lookupPackageName(String pkg) { + return pkg; + } + + @Override public DexType lookupType(DexType type) { return type; } @@ -1051,7 +1045,7 @@ @Override public DexField getRenamedFieldSignature(DexField originalField) { DexField renamedField = getPrevious().getRenamedFieldSignature(originalField); - return fieldMap.getOrDefault(renamedField, renamedField); + return internalGetNextFieldSignature(renamedField); } @Override @@ -1151,6 +1145,10 @@ return prototypeChanges; } + protected DexField internalGetNextFieldSignature(DexField field) { + return fieldMap.getOrDefault(field, field); + } + @Override protected DexMethod internalGetPreviousMethodSignature(DexMethod method) { return originalMethodSignatures.getRepresentativeValueOrDefault(method, method);
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java index 35b4574..d8815c7 100644 --- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java +++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -500,7 +500,7 @@ return; } // TODO(b/169645628): Support records in all compilation. - if (!application.options.canUseRecords()) { + if (!application.options.enableExperimentalRecordDesugaring()) { throw new CompilationError("Records are not supported", origin); } // TODO(b/169645628): Change this logic if we start stripping the record components.
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java index ba22b16..08bc5f0 100644 --- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java +++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -336,6 +336,7 @@ private final DebugParsingOptions debugParsingOptions; private int maxStack; private int maxLocals; + private boolean desugaredVisitMultiANewArrayInstruction; private List<CfInstruction> instructions; private List<CfTryCatch> tryCatchRanges; private List<LocalVariableInfo> localVariables; @@ -386,7 +387,12 @@ } code.setCode( new CfCode( - method.holder, maxStack, maxLocals, instructions, tryCatchRanges, localVariables)); + method.holder, + desugaredVisitMultiANewArrayInstruction ? Integer.MAX_VALUE : maxStack, + maxLocals, + instructions, + tryCatchRanges, + localVariables)); } @Override @@ -948,6 +954,7 @@ // ..., ref visitTypeInsn(Opcodes.CHECKCAST, desc); // ..., arrayref(of type : desc) + desugaredVisitMultiANewArrayInstruction = true; } @Override
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java index 7005e06..509a10d 100644 --- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java +++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -226,6 +226,13 @@ } @Override + public void addProgramClassPotentiallyOverridingNonProgramClass(DexProgramClass clazz) { + addProgramClass(clazz); + classpathClasses.clearType(clazz.type); + libraryClasses.clearType(clazz.type); + } + + @Override public LazyLoadedDexApplication build() { return new LazyLoadedDexApplication( proguardMap,
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java index c0cc708..686d7d5 100644 --- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java +++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -302,8 +302,7 @@ // We already gave up on tracking the allocation sites for `clazz` previously. return false; } - // We currently only use allocation site information for instance field value propagation. - return !clazz.instanceFields().isEmpty(); + return true; } /**
diff --git a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java index 89d0bd7..27f3251 100644 --- a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java +++ b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
@@ -113,8 +113,7 @@ DexEncodedMethod.EMPTY_ARRAY, DexEncodedMethod.EMPTY_ARRAY, dexItemFactory.getSkipNameValidationForTesting(), - clazz.getChecksumSupplier(), - fixupSynthesizedFrom(clazz.getSynthesizedFrom())); + clazz.getChecksumSupplier()); newClass.setInstanceFields(fixupFields(clazz.instanceFields())); newClass.setStaticFields(fixupFields(clazz.staticFields())); newClass.setDirectMethods(
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/CanOnlyMergeIntoClassPolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/CanOnlyMergeIntoClassPolicy.java deleted file mode 100644 index fa9873a..0000000 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/CanOnlyMergeIntoClassPolicy.java +++ /dev/null
@@ -1,17 +0,0 @@ -// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -package com.android.tools.r8.horizontalclassmerging; - -import com.android.tools.r8.graph.DexProgramClass; - -public abstract class CanOnlyMergeIntoClassPolicy extends SingleClassPolicy { - public abstract boolean canOnlyMergeInto(DexProgramClass clazz); - - @Override - public boolean canMerge(DexProgramClass program) { - // TODO(b/165577835): Allow merging of classes that must be the target of their merge group. - return !canOnlyMergeInto(program); - } -}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInitializerSynthesizedCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInitializerSynthesizedCode.java index c919e63..d78d74d 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInitializerSynthesizedCode.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInitializerSynthesizedCode.java
@@ -59,11 +59,14 @@ } public CfCode synthesizeCode(DexType originalHolder) { + // Building the instructions will adjust maxStack and maxLocals. Build it here before invoking + // the CfCode constructor to ensure that the value passed in is the updated values. + List<CfInstruction> instructions = buildInstructions(); return new CfCode( originalHolder, maxStack, maxLocals, - buildInstructions(), + instructions, Collections.emptyList(), Collections.emptyList()); }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java index 11001de..0e7d164 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.graph.CfCode; import com.android.tools.r8.graph.DexAnnotationSet; import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexDefinition; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexItemFactory; @@ -26,6 +27,9 @@ import com.android.tools.r8.graph.MethodAccessFlags; import com.android.tools.r8.graph.ParameterAnnotationsList; import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.analysis.value.NumberFromIntervalValue; +import com.android.tools.r8.ir.optimize.info.OptimizationFeedback; +import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.IterableUtils; import com.android.tools.r8.utils.MethodSignatureEquivalence; @@ -52,6 +56,8 @@ public static final String CLASS_ID_FIELD_NAME = "$r8$classId"; + private static final OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance(); + private final AppView<AppInfoWithLiveness> appView; private final MergeGroup group; private final DexItemFactory dexItemFactory; @@ -131,8 +137,6 @@ CfVersion cfVersion = classInitializerSynthesizedCode.getCfVersion(); if (cfVersion != null) { clinit.upgradeClassFileVersion(cfVersion); - } else { - assert appView.options().isGeneratingDex(); } classMethodsBuilder.addDirectMethod(clinit); } @@ -194,14 +198,29 @@ } void appendClassIdField() { - classInstanceFieldsMerger.setClassIdField( + boolean deprecated = false; + boolean d8R8Synthesized = true; + DexEncodedField classIdField = new DexEncodedField( group.getClassIdField(), - FieldAccessFlags.fromSharedAccessFlags( - Constants.ACC_PUBLIC + Constants.ACC_FINAL + Constants.ACC_SYNTHETIC), + FieldAccessFlags.createPublicFinalSynthetic(), FieldTypeSignature.noSignature(), DexAnnotationSet.empty(), - null)); + null, + deprecated, + d8R8Synthesized); + + // For the $r8$classId synthesized fields, we try to over-approximate the set of values it may + // have. For example, for a merge group of size 4, we may compute the set {0, 2, 3}, if the + // instances with $r8$classId == 1 ends up dead as a result of optimizations). If no instances + // end up being dead, we would compute the set {0, 1, 2, 3}. The latter information does not + // provide any value, and therefore we should not save it in the optimization info. In order to + // be able to recognize that {0, 1, 2, 3} is useless, we record that the value of the field is + // known to be in [0; 3] here. + NumberFromIntervalValue abstractValue = new NumberFromIntervalValue(0, group.size() - 1); + feedback.recordFieldHasAbstractValue(classIdField, appView, abstractValue); + + classInstanceFieldsMerger.setClassIdField(classIdField); } void mergeStaticFields() { @@ -219,6 +238,16 @@ } } + private void mergeAnnotations() { + assert group.getClasses().stream().filter(DexDefinition::hasAnnotations).count() <= 1; + for (DexProgramClass clazz : group.getSources()) { + if (clazz.hasAnnotations()) { + group.getTarget().setAnnotations(clazz.annotations()); + break; + } + } + } + private void mergeInterfaces() { DexTypeList previousInterfaces = group.getTarget().getInterfaces(); Set<DexType> interfaces = Sets.newLinkedHashSet(previousInterfaces); @@ -241,6 +270,7 @@ fixAccessFlags(); appendClassIdField(); + mergeAnnotations(); mergeInterfaces(); mergeVirtualMethods();
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java index d97f12f..05e91a4 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -6,21 +6,20 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexProgramClass; -import com.android.tools.r8.graph.DirectMappedDexApplication; import com.android.tools.r8.horizontalclassmerging.policies.AllInstantiatedOrUninstantiated; import com.android.tools.r8.horizontalclassmerging.policies.CheckAbstractClasses; import com.android.tools.r8.horizontalclassmerging.policies.DontInlinePolicy; import com.android.tools.r8.horizontalclassmerging.policies.DontMergeSynchronizedClasses; -import com.android.tools.r8.horizontalclassmerging.policies.IgnoreSynthetics; import com.android.tools.r8.horizontalclassmerging.policies.LimitGroups; import com.android.tools.r8.horizontalclassmerging.policies.MinimizeFieldCasts; -import com.android.tools.r8.horizontalclassmerging.policies.NoAnnotations; +import com.android.tools.r8.horizontalclassmerging.policies.NoAnnotationClasses; +import com.android.tools.r8.horizontalclassmerging.policies.NoClassAnnotationCollisions; import com.android.tools.r8.horizontalclassmerging.policies.NoClassInitializerWithObservableSideEffects; -import com.android.tools.r8.horizontalclassmerging.policies.NoClassesOrMembersWithAnnotations; import com.android.tools.r8.horizontalclassmerging.policies.NoDirectRuntimeTypeChecks; import com.android.tools.r8.horizontalclassmerging.policies.NoEnums; import com.android.tools.r8.horizontalclassmerging.policies.NoIndirectRuntimeTypeChecks; import com.android.tools.r8.horizontalclassmerging.policies.NoInnerClasses; +import com.android.tools.r8.horizontalclassmerging.policies.NoInstanceFieldAnnotations; import com.android.tools.r8.horizontalclassmerging.policies.NoInterfaces; import com.android.tools.r8.horizontalclassmerging.policies.NoKeepRules; import com.android.tools.r8.horizontalclassmerging.policies.NoKotlinMetadata; @@ -30,13 +29,13 @@ import com.android.tools.r8.horizontalclassmerging.policies.NotVerticallyMergedIntoSubtype; import com.android.tools.r8.horizontalclassmerging.policies.PreserveMethodCharacteristics; import com.android.tools.r8.horizontalclassmerging.policies.PreventMergeIntoDifferentMainDexGroups; -import com.android.tools.r8.horizontalclassmerging.policies.PreventMergeIntoMainDexList; import com.android.tools.r8.horizontalclassmerging.policies.PreventMethodImplementation; import com.android.tools.r8.horizontalclassmerging.policies.RespectPackageBoundaries; import com.android.tools.r8.horizontalclassmerging.policies.SameFeatureSplit; import com.android.tools.r8.horizontalclassmerging.policies.SameInstanceFields; import com.android.tools.r8.horizontalclassmerging.policies.SameNestHost; import com.android.tools.r8.horizontalclassmerging.policies.SameParentClass; +import com.android.tools.r8.horizontalclassmerging.policies.SyntheticItemsPolicy; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier; import com.android.tools.r8.shaking.KeepInfoCollection; @@ -57,9 +56,7 @@ assert appView.options().enableInlining; } - // TODO(b/165577835): replace Collection<DexProgramClass> with MergeGroup public HorizontalClassMergerResult run( - DirectMappedDexApplication.Builder appBuilder, RuntimeTypeCheckInfo runtimeTypeCheckInfo) { MergeGroup initialGroup = new MergeGroup(appView.appInfo().classesWithDeterministicOrder()); @@ -88,7 +85,7 @@ // Merge the classes. SyntheticArgumentClass syntheticArgumentClass = - new SyntheticArgumentClass.Builder(appBuilder, appView).build(allMergeClasses); + new SyntheticArgumentClass.Builder(appView).build(allMergeClasses); applyClassMergers(classMergers, syntheticArgumentClass); // Generate the graph lens. @@ -121,37 +118,44 @@ } private List<Policy> getPolicies(RuntimeTypeCheckInfo runtimeTypeCheckInfo) { - return ImmutableList.of( - new NotMatchedByNoHorizontalClassMerging(appView), - new SameInstanceFields(appView), - new NoInterfaces(), - new NoAnnotations(), - new NoEnums(appView), - new CheckAbstractClasses(appView), - new IgnoreSynthetics(appView), - new NoClassesOrMembersWithAnnotations(appView), - new NoInnerClasses(), - new NoClassInitializerWithObservableSideEffects(), - new NoNativeMethods(), - new NoKeepRules(appView), - new NoKotlinMetadata(), - new NoServiceLoaders(appView), - new NotVerticallyMergedIntoSubtype(appView), - new NoDirectRuntimeTypeChecks(runtimeTypeCheckInfo), - new NoIndirectRuntimeTypeChecks(appView, runtimeTypeCheckInfo), - new PreventMethodImplementation(appView), - new DontInlinePolicy(appView), - new PreventMergeIntoMainDexList(appView), - new PreventMergeIntoDifferentMainDexGroups(appView), - new AllInstantiatedOrUninstantiated(appView), - new SameParentClass(), - new SameNestHost(), - new PreserveMethodCharacteristics(appView), - new SameFeatureSplit(appView), - new RespectPackageBoundaries(appView), - new DontMergeSynchronizedClasses(appView), - new MinimizeFieldCasts(), - new LimitGroups(appView)); + List<SingleClassPolicy> singleClassPolicies = + ImmutableList.of( + new NotMatchedByNoHorizontalClassMerging(appView), + new NoAnnotationClasses(), + new NoEnums(appView), + new NoInnerClasses(), + new NoInstanceFieldAnnotations(), + new NoInterfaces(), + new NoClassInitializerWithObservableSideEffects(), + new NoNativeMethods(), + new NoKeepRules(appView), + new NoKotlinMetadata(), + new NoServiceLoaders(appView), + new NotVerticallyMergedIntoSubtype(appView), + new NoDirectRuntimeTypeChecks(runtimeTypeCheckInfo), + new DontInlinePolicy(appView)); + List<MultiClassPolicy> multiClassPolicies = + ImmutableList.of( + new SameInstanceFields(appView), + new NoClassAnnotationCollisions(), + new CheckAbstractClasses(appView), + new SyntheticItemsPolicy(appView), + new NoIndirectRuntimeTypeChecks(appView, runtimeTypeCheckInfo), + new PreventMethodImplementation(appView), + new PreventMergeIntoDifferentMainDexGroups(appView), + new AllInstantiatedOrUninstantiated(appView), + new SameParentClass(), + new SameNestHost(appView), + new PreserveMethodCharacteristics(appView), + new SameFeatureSplit(appView), + new RespectPackageBoundaries(appView), + new DontMergeSynchronizedClasses(appView), + new MinimizeFieldCasts(), + new LimitGroups(appView)); + return ImmutableList.<Policy>builder() + .addAll(singleClassPolicies) + .addAll(multiClassPolicies) + .build(); } /**
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerUtils.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerUtils.java new file mode 100644 index 0000000..bd83a89 --- /dev/null +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerUtils.java
@@ -0,0 +1,22 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.horizontalclassmerging; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexEncodedField; +import com.android.tools.r8.graph.DexField; + +public class HorizontalClassMergerUtils { + + public static boolean isClassIdField(AppView<?> appView, DexEncodedField field) { + DexField classIdField = appView.dexItemFactory().objectMembers.classIdField; + if (field.isD8R8Synthesized() && field.getType().isIntType()) { + DexField originalField = appView.graphLens().getOriginalFieldSignature(field.getReference()); + return originalField.match(classIdField); + } + assert !appView.graphLens().getOriginalFieldSignature(field.getReference()).match(classIdField); + return false; + } +}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java index 9e4ebf5..4b9ad6a 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
@@ -30,6 +30,11 @@ this.classes = new LinkedList<>(); } + public MergeGroup(DexProgramClass clazz) { + this(); + add(clazz); + } + public MergeGroup(Collection<DexProgramClass> classes) { this(); addAll(classes);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassSameReferencePolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassSameReferencePolicy.java index 3cea5125..aa3ca62 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassSameReferencePolicy.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassSameReferencePolicy.java
@@ -15,11 +15,18 @@ public final Collection<MergeGroup> apply(MergeGroup group) { Map<T, MergeGroup> groups = new LinkedHashMap<>(); for (DexProgramClass clazz : group) { - groups.computeIfAbsent(getMergeKey(clazz), ignore -> new MergeGroup()).add(clazz); + T mergeKey = getMergeKey(clazz); + if (mergeKey != null) { + groups.computeIfAbsent(mergeKey, ignore -> new MergeGroup()).add(clazz); + } } removeTrivialGroups(groups.values()); return groups.values(); } public abstract T getMergeKey(DexProgramClass clazz); + + protected final T ineligibleForClassMerging() { + return null; + } }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java index 60c350a..ca0b752 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
@@ -4,23 +4,12 @@ package com.android.tools.r8.horizontalclassmerging; -import com.android.tools.r8.dex.Constants; import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.ClassAccessFlags; -import com.android.tools.r8.graph.DexAnnotationSet; -import com.android.tools.r8.graph.DexEncodedField; -import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexType; -import com.android.tools.r8.graph.DexTypeList; -import com.android.tools.r8.graph.DirectMappedDexApplication; -import com.android.tools.r8.graph.GenericSignature.ClassSignature; -import com.android.tools.r8.origin.SynthesizedOrigin; import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.android.tools.r8.shaking.MainDexInfo; -import com.google.common.collect.Iterables; +import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind; import java.util.ArrayList; -import java.util.Collections; import java.util.List; /** @@ -41,7 +30,6 @@ * <p>This class generates a synthetic class in the package of the first class to be merged. */ public class SyntheticArgumentClass { - public static final String SYNTHETIC_CLASS_SUFFIX = "$r8$HorizontalClassMergingArgument"; private final List<DexType> syntheticClassTypes; @@ -55,70 +43,28 @@ public static class Builder { - private final DirectMappedDexApplication.Builder appBuilder; private final AppView<AppInfoWithLiveness> appView; - Builder(DirectMappedDexApplication.Builder appBuilder, AppView<AppInfoWithLiveness> appView) { - this.appBuilder = appBuilder; + Builder(AppView<AppInfoWithLiveness> appView) { this.appView = appView; } - private DexType synthesizeClass( - DexProgramClass context, - boolean requiresMainDex, - int index) { - DexType syntheticClassType = - appView - .dexItemFactory() - .createFreshTypeName( - context.type.addSuffix(SYNTHETIC_CLASS_SUFFIX, appView.dexItemFactory()), - type -> appView.appInfo().definitionForWithoutExistenceAssert(type) == null, - index); - - DexProgramClass clazz = - new DexProgramClass( - syntheticClassType, - null, - new SynthesizedOrigin("horizontal class merging", HorizontalClassMerger.class), - ClassAccessFlags.fromCfAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC), - appView.dexItemFactory().objectType, - DexTypeList.empty(), - null, - null, - Collections.emptyList(), - null, - Collections.emptyList(), - ClassSignature.noSignature(), - DexAnnotationSet.empty(), - DexEncodedField.EMPTY_ARRAY, - DexEncodedField.EMPTY_ARRAY, - DexEncodedMethod.EMPTY_ARRAY, - DexEncodedMethod.EMPTY_ARRAY, - appView.dexItemFactory().getSkipNameValidationForTesting(), - DexProgramClass::checksumFromType); - - appBuilder.addSynthesizedClass(clazz); - appView.appInfo().addSynthesizedClass(clazz, requiresMainDex); - return clazz.type; + private DexProgramClass synthesizeClass(DexProgramClass context, SyntheticKind syntheticKind) { + return appView + .getSyntheticItems() + .createFixedClass(syntheticKind, context, appView.dexItemFactory(), builder -> {}); } public SyntheticArgumentClass build(Iterable<DexProgramClass> mergeClasses) { // Find a fresh name in an existing package. DexProgramClass context = mergeClasses.iterator().next(); - - // Add as a root to the main dex tracing result if any of the merged classes is a root. - // This is needed to satisfy an assertion in the inliner that verifies that we do not inline - // methods with references to non-roots into classes that are roots. - MainDexInfo mainDexInfo = appView.appInfo().getMainDexInfo(); - boolean requiresMainDex = Iterables.any(mergeClasses, mainDexInfo::isMainDex); - List<DexType> syntheticArgumentTypes = new ArrayList<>(); - for (int i = 0; - i < appView.options().horizontalClassMergerOptions().getSyntheticArgumentCount(); - i++) { - syntheticArgumentTypes.add(synthesizeClass(context, requiresMainDex, i)); - } - + syntheticArgumentTypes.add( + synthesizeClass(context, SyntheticKind.HORIZONTAL_INIT_TYPE_ARGUMENT_1).getType()); + syntheticArgumentTypes.add( + synthesizeClass(context, SyntheticKind.HORIZONTAL_INIT_TYPE_ARGUMENT_2).getType()); + syntheticArgumentTypes.add( + synthesizeClass(context, SyntheticKind.HORIZONTAL_INIT_TYPE_ARGUMENT_3).getType()); return new SyntheticArgumentClass(syntheticArgumentTypes); } }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/IgnoreSynthetics.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/IgnoreSynthetics.java deleted file mode 100644 index e45e2d3..0000000 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/IgnoreSynthetics.java +++ /dev/null
@@ -1,28 +0,0 @@ -// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -package com.android.tools.r8.horizontalclassmerging.policies; - -import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexProgramClass; -import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy; -import com.android.tools.r8.shaking.AppInfoWithLiveness; - -public class IgnoreSynthetics extends SingleClassPolicy { - - private final AppView<AppInfoWithLiveness> appView; - - public IgnoreSynthetics(AppView<AppInfoWithLiveness> appView) { - this.appView = appView; - } - - @Override - public boolean canMerge(DexProgramClass program) { - if (appView.getSyntheticItems().isSyntheticClass(program)) { - return appView.options().horizontalClassMergerOptions().isJavaLambdaMergingEnabled() - && appView.getSyntheticItems().isLegacySyntheticClass(program); - } - return true; - } -}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotations.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotationClasses.java similarity index 88% rename from src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotations.java rename to src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotationClasses.java index 9190247..1effb8b 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotations.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotationClasses.java
@@ -7,7 +7,7 @@ import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy; -public class NoAnnotations extends SingleClassPolicy { +public class NoAnnotationClasses extends SingleClassPolicy { @Override public boolean canMerge(DexProgramClass program) { return !program.isAnnotation();
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassAnnotationCollisions.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassAnnotationCollisions.java new file mode 100644 index 0000000..9d3d730 --- /dev/null +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassAnnotationCollisions.java
@@ -0,0 +1,44 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.horizontalclassmerging.policies; + +import static com.android.tools.r8.utils.IteratorUtils.createCircularIterator; + +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.horizontalclassmerging.MergeGroup; +import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy; +import com.google.common.collect.ImmutableList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +public class NoClassAnnotationCollisions extends MultiClassPolicy { + + @Override + public Collection<MergeGroup> apply(MergeGroup group) { + // Create a new merge group for each class that has annotations. + List<MergeGroup> newGroups = new LinkedList<>(); + for (DexProgramClass clazz : group) { + if (clazz.hasAnnotations()) { + newGroups.add(new MergeGroup(clazz)); + } + } + + // If there were at most one class with annotations, then just return the original merge group. + if (newGroups.size() <= 1) { + return ImmutableList.of(group); + } + + // Otherwise, fill up the new merge groups with the classes that do not have annotations. + Iterator<MergeGroup> newGroupsIterator = createCircularIterator(newGroups); + for (DexProgramClass clazz : group) { + if (!clazz.hasAnnotations()) { + newGroupsIterator.next().add(clazz); + } + } + return removeTrivialGroups(newGroups); + } +}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassesOrMembersWithAnnotations.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassesOrMembersWithAnnotations.java deleted file mode 100644 index f0b2a6e..0000000 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassesOrMembersWithAnnotations.java +++ /dev/null
@@ -1,31 +0,0 @@ -// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -package com.android.tools.r8.horizontalclassmerging.policies; - -import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexProgramClass; -import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy; -import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions; - -public class NoClassesOrMembersWithAnnotations extends SingleClassPolicy { - - private final HorizontalClassMergerOptions options; - - public NoClassesOrMembersWithAnnotations(AppView<AppInfoWithLiveness> appView) { - this.options = appView.options().horizontalClassMergerOptions(); - } - - @Override - public boolean canMerge(DexProgramClass program) { - return !program.hasClassOrMemberAnnotations(); - } - - @Override - public boolean shouldSkipPolicy() { - // TODO(b/179019716): Add support for merging in presence of annotations. - return options.skipNoClassesOrMembersWithAnnotationsPolicyForTesting; - } -}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotations.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceFieldAnnotations.java similarity index 62% copy from src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotations.java copy to src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceFieldAnnotations.java index 9190247..610af7e 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotations.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceFieldAnnotations.java
@@ -4,12 +4,19 @@ package com.android.tools.r8.horizontalclassmerging.policies; +import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy; -public class NoAnnotations extends SingleClassPolicy { +public class NoInstanceFieldAnnotations extends SingleClassPolicy { + @Override public boolean canMerge(DexProgramClass program) { - return !program.isAnnotation(); + for (DexEncodedField instanceField : program.instanceFields()) { + if (instanceField.hasAnnotations()) { + return false; + } + } + return true; } }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoDifferentMainDexGroups.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoDifferentMainDexGroups.java index 52a1d98..41c240f 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoDifferentMainDexGroups.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoDifferentMainDexGroups.java
@@ -10,19 +10,23 @@ import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.shaking.MainDexInfo; import com.android.tools.r8.shaking.MainDexInfo.MainDexGroup; +import com.android.tools.r8.synthesis.SyntheticItems; public class PreventMergeIntoDifferentMainDexGroups extends MultiClassSameReferencePolicy<MainDexGroup> { private final MainDexInfo mainDexInfo; + private final SyntheticItems synthetics; public PreventMergeIntoDifferentMainDexGroups(AppView<AppInfoWithLiveness> appView) { - this.mainDexInfo = appView.appInfo().getMainDexInfo(); + mainDexInfo = appView.appInfo().getMainDexInfo(); + synthetics = appView.getSyntheticItems(); } @Override public MainDexGroup getMergeKey(DexProgramClass clazz) { - assert !mainDexInfo.isFromList(clazz); - return mainDexInfo.getMergeKey(clazz); + return mainDexInfo.canMerge(clazz, synthetics) + ? mainDexInfo.getMergeKey(clazz, synthetics) + : ineligibleForClassMerging(); } }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoMainDexList.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoMainDexList.java deleted file mode 100644 index 850d02e..0000000 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoMainDexList.java +++ /dev/null
@@ -1,25 +0,0 @@ -// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -package com.android.tools.r8.horizontalclassmerging.policies; - -import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexProgramClass; -import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy; -import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.android.tools.r8.shaking.MainDexInfo; - -public class PreventMergeIntoMainDexList extends SingleClassPolicy { - - private final MainDexInfo mainDexInfo; - - public PreventMergeIntoMainDexList(AppView<AppInfoWithLiveness> appView) { - this.mainDexInfo = appView.appInfo().getMainDexInfo(); - } - - @Override - public boolean canMerge(DexProgramClass program) { - return mainDexInfo.canMerge(program); - } -}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java index 6619878..88d2984 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java
@@ -4,13 +4,23 @@ package com.android.tools.r8.horizontalclassmerging.policies; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy; +import com.android.tools.r8.shaking.AppInfoWithLiveness; public class SameNestHost extends MultiClassSameReferencePolicy<DexType> { + + private final DexItemFactory dexItemFactory; + + public SameNestHost(AppView<AppInfoWithLiveness> appView) { + this.dexItemFactory = appView.dexItemFactory(); + } + @Override public DexType getMergeKey(DexProgramClass clazz) { - return clazz.getNestHost(); + return clazz.isInANest() ? clazz.getNestHost() : dexItemFactory.objectType; } }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java new file mode 100644 index 0000000..b012897 --- /dev/null +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java
@@ -0,0 +1,51 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.horizontalclassmerging.policies; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy; +import com.android.tools.r8.horizontalclassmerging.policies.SyntheticItemsPolicy.ClassKind; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.synthesis.SyntheticItems; +import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind; + +public class SyntheticItemsPolicy extends MultiClassSameReferencePolicy<ClassKind> { + + enum ClassKind { + SYNTHETIC, + NOT_SYNTHETIC + } + + private final AppView<AppInfoWithLiveness> appView; + + public SyntheticItemsPolicy(AppView<AppInfoWithLiveness> appView) { + this.appView = appView; + } + + @Override + public ClassKind getMergeKey(DexProgramClass clazz) { + SyntheticItems syntheticItems = appView.getSyntheticItems(); + + // Allow merging non-synthetics with non-synthetics. + if (!syntheticItems.isSyntheticClass(clazz)) { + return ClassKind.NOT_SYNTHETIC; + } + + // Do not allow merging synthetics that are not lambdas. + if (!syntheticItems.isNonLegacySynthetic(clazz) + || syntheticItems.getNonLegacySyntheticKind(clazz) != SyntheticKind.LAMBDA) { + return ineligibleForClassMerging(); + } + + // Allow merging Java lambdas with Java lambdas. + if (appView.options().horizontalClassMergerOptions().isJavaLambdaMergingEnabled()) { + return ClassKind.SYNTHETIC; + } + + // Java lambda merging is disabled. + return ineligibleForClassMerging(); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java index d0a86ef..2b63089 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
@@ -15,8 +15,10 @@ import com.android.tools.r8.graph.FieldAccessInfoCollection; import com.android.tools.r8.graph.ObjectAllocationInfoCollection; import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerUtils; import com.android.tools.r8.ir.analysis.value.AbstractValue; import com.android.tools.r8.ir.analysis.value.BottomValue; +import com.android.tools.r8.ir.analysis.value.NonConstantNumberValue; import com.android.tools.r8.ir.analysis.value.SingleValue; import com.android.tools.r8.ir.analysis.value.UnknownValue; import com.android.tools.r8.ir.code.FieldInstruction; @@ -173,31 +175,65 @@ while (iterator.hasNext()) { Map.Entry<DexEncodedField, AbstractValue> entry = iterator.next(); DexEncodedField field = entry.getKey(); + AbstractValue abstractValue = entry.getValue(); + + // The power set lattice is an expensive abstraction, so use it with caution. + boolean isClassIdField = HorizontalClassMergerUtils.isClassIdField(appView, field); + InstanceFieldInitializationInfo initializationInfo = initializationInfoCollection.get(field); if (initializationInfo.isArgumentInitializationInfo()) { InstanceFieldArgumentInitializationInfo argumentInitializationInfo = initializationInfo.asArgumentInitializationInfo(); Value argument = invoke.arguments().get(argumentInitializationInfo.getArgumentIndex()); - AbstractValue abstractValue = - entry.getValue().join(argument.getAbstractValue(appView, context)); + AbstractValue argumentAbstractValue = argument.getAbstractValue(appView, context); + abstractValue = + abstractValue.join( + argumentAbstractValue, + appView.abstractValueFactory(), + field.getType(), + isClassIdField); assert !abstractValue.isBottom(); - if (!abstractValue.isUnknown()) { - entry.setValue(abstractValue); - continue; - } } else if (initializationInfo.isSingleValue()) { SingleValue singleValueInitializationInfo = initializationInfo.asSingleValue(); - AbstractValue abstractValue = entry.getValue().join(singleValueInitializationInfo); - assert !abstractValue.isBottom(); - if (!abstractValue.isUnknown()) { - entry.setValue(abstractValue); - continue; - } + abstractValue = + abstractValue.join( + singleValueInitializationInfo, + appView.abstractValueFactory(), + field.getType(), + isClassIdField); } else if (initializationInfo.isTypeInitializationInfo()) { // TODO(b/149732532): Not handled, for now. + abstractValue = UnknownValue.getInstance(); } else { assert initializationInfo.isUnknown(); + abstractValue = UnknownValue.getInstance(); + } + + assert !abstractValue.isBottom(); + + // When approximating the possible values for the $r8$classId fields from horizontal class + // merging, give up if the set of possible values equals the size of the merge group. In + // this case, the information is useless. + if (isClassIdField && abstractValue.isNonConstantNumberValue()) { + NonConstantNumberValue initialAbstractValue = + field.getOptimizationInfo().getAbstractValue().asNonConstantNumberValue(); + if (initialAbstractValue != null) { + if (abstractValue.asNonConstantNumberValue().getAbstractionSize() + >= initialAbstractValue.getAbstractionSize()) { + abstractValue = UnknownValue.getInstance(); + } + } else { + assert false + : "Expected abstract value of " + + field.toSourceString() + + " to be instance of NonConstantNumberValue"; + } + } + + if (!abstractValue.isUnknown()) { + entry.setValue(abstractValue); + continue; } // We just lost track for this field. @@ -236,7 +272,11 @@ .fieldInitializationInfos() .get(field); if (fieldInitializationInfo.isSingleValue()) { - abstractValue = abstractValue.join(fieldInitializationInfo.asSingleValue()); + abstractValue = + abstractValue.join( + fieldInitializationInfo.asSingleValue(), + appView.abstractValueFactory(), + field.getType()); if (abstractValue.isUnknown()) { break; }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java index 899fa2b..05259ca 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
@@ -40,6 +40,12 @@ public class TrivialFieldAccessReprocessor { + enum FieldClassification { + CONSTANT, + NON_CONSTANT, + UNKNOWN + } + private final AppView<AppInfoWithLiveness> appView; private final PostMethodProcessor.Builder postMethodProcessorBuilder; @@ -54,6 +60,9 @@ private final Set<DexEncodedField> constantFields = Sets.newConcurrentHashSet(); /** Updated concurrently from {@link #processClass(DexProgramClass)}. */ + private final Set<DexEncodedField> nonConstantFields = Sets.newConcurrentHashSet(); + + /** Updated concurrently from {@link #processClass(DexProgramClass)}. */ private final ProgramMethodSet methodsToReprocess = ProgramMethodSet.createConcurrent(); public TrivialFieldAccessReprocessor( @@ -72,7 +81,7 @@ assert feedback.noUpdatesLeft(); timing.begin("Compute fields of interest"); - computeConstantFields(); + computeFieldsWithNonTrivialValue(); timing.end(); // Compute fields of interest timing.begin("Enqueue methods for reprocessing"); @@ -89,17 +98,32 @@ writtenFields.keySet().forEach(OptimizationFeedbackSimple.getInstance()::markFieldAsDead); } - private void computeConstantFields() { + private void computeFieldsWithNonTrivialValue() { for (DexProgramClass clazz : appView.appInfo().classes()) { for (DexEncodedField field : clazz.instanceFields()) { - if (canOptimizeField(field, appView)) { - constantFields.add(field); + FieldClassification fieldClassification = classifyField(field, appView); + switch (fieldClassification) { + case CONSTANT: + // Reprocess reads and writes. + constantFields.add(field); + break; + case NON_CONSTANT: + // Only reprocess writes, to allow branch pruning. + nonConstantFields.add(field); + break; + default: + assert fieldClassification == FieldClassification.UNKNOWN; + break; } } if (appView.canUseInitClass() || !clazz.classInitializationMayHaveSideEffects(appView)) { for (DexEncodedField field : clazz.staticFields()) { - if (canOptimizeField(field, appView)) { + FieldClassification fieldClassification = classifyField(field, appView); + if (fieldClassification == FieldClassification.CONSTANT) { constantFields.add(field); + } else { + assert fieldClassification == FieldClassification.NON_CONSTANT + || fieldClassification == FieldClassification.UNKNOWN; } } } @@ -138,30 +162,39 @@ method -> method.registerCodeReferences(new TrivialFieldAccessUseRegistry(method))); } - private static boolean canOptimizeField( + private static FieldClassification classifyField( DexEncodedField field, AppView<AppInfoWithLiveness> appView) { FieldAccessInfo fieldAccessInfo = appView.appInfo().getFieldAccessInfoCollection().get(field.field); - if (fieldAccessInfo == null || fieldAccessInfo.isAccessedFromMethodHandle()) { - return false; + if (fieldAccessInfo == null + || fieldAccessInfo.hasReflectiveAccess() + || fieldAccessInfo.isAccessedFromMethodHandle() + || fieldAccessInfo.isReadFromAnnotation()) { + return FieldClassification.UNKNOWN; } AbstractValue abstractValue = field.getOptimizationInfo().getAbstractValue(); if (abstractValue.isSingleValue()) { SingleValue singleValue = abstractValue.asSingleValue(); if (!singleValue.isMaterializableInAllContexts(appView)) { - return false; + return FieldClassification.UNKNOWN; } if (singleValue.isSingleConstValue()) { - return true; + return FieldClassification.CONSTANT; } if (singleValue.isSingleFieldValue()) { SingleFieldValue singleFieldValue = singleValue.asSingleFieldValue(); DexField singleField = singleFieldValue.getField(); - return singleField != field.field - && !singleFieldValue.mayHaveFinalizeMethodDirectlyOrIndirectly(appView); + if (singleField != field.field + && !singleFieldValue.mayHaveFinalizeMethodDirectlyOrIndirectly(appView)) { + return FieldClassification.CONSTANT; + } } + return FieldClassification.UNKNOWN; } - return false; + if (abstractValue.isNonConstantNumberValue()) { + return FieldClassification.NON_CONSTANT; + } + return FieldClassification.UNKNOWN; } private void processFieldsNeverRead(AppInfoWithLiveness appInfo) { @@ -262,6 +295,13 @@ DexClassAndField field = resolutionResult.getResolutionPair(); DexEncodedField definition = field.getDefinition(); + if (definition.isStatic() != isStatic + || appView.isCfByteCodePassThrough(method.getDefinition()) + || resolutionResult.isAccessibleFrom(method, appView).isPossiblyFalse()) { + recordAccessThatCannotBeOptimized(field, definition); + return; + } + // Record access. if (field.isProgramField() && appView.appInfo().mayPropagateValueFor(field)) { if (field.getAccessFlags().isStatic() == isStatic) { @@ -275,19 +315,17 @@ } } - // We cannot remove references from pass through functions. - if (appView.isCfByteCodePassThrough(method.getDefinition())) { - constantFields.remove(definition); - return; + if (constantFields.contains(definition) + || (!isWrite && nonConstantFields.contains(definition))) { + methodsToReprocess.add(method); } + } - if (definition.isStatic() == isStatic) { - if (constantFields.contains(definition)) { - methodsToReprocess.add(method); - } - } else { - // Should generally not happen. - constantFields.remove(definition); + private void recordAccessThatCannotBeOptimized( + DexClassAndField field, DexEncodedField definition) { + constantFields.remove(definition); + if (field.isProgramField() && appView.appInfo().mayPropagateValueFor(field)) { + destroyFieldAccessContexts(definition); } }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunction.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractTransferFunction.java similarity index 89% rename from src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunction.java rename to src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractTransferFunction.java index 73c9470..af7a836 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunction.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractTransferFunction.java
@@ -11,7 +11,7 @@ * A transfer function that defines the abstract semantics of the instructions in the program * according to some abstract state {@link StateType}. */ -public interface TransferFunction<StateType extends AbstractState<StateType>> { +public interface AbstractTransferFunction<StateType extends AbstractState<StateType>> { TransferFunctionResult<StateType> apply(Instruction instruction, StateType state);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java index 701df38..a8a1832 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java
@@ -42,6 +42,14 @@ this.blockExitStates = blockExitStates; } + public StateType join() { + StateType result = null; + for (StateType blockExitState : blockExitStates.values()) { + result = result != null ? result.join(blockExitState) : blockExitState; + } + return result; + } + @Override public boolean isSuccessfulAnalysisResult() { return true;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/FailedTransferFunctionResult.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/FailedTransferFunctionResult.java index 4341265..fd0a761 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/FailedTransferFunctionResult.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/FailedTransferFunctionResult.java
@@ -6,7 +6,10 @@ import com.android.tools.r8.errors.Unreachable; -/** Used by the {@link TransferFunction} to signal that the dataflow analysis should be aborted. */ +/** + * Used by the {@link AbstractTransferFunction} to signal that the dataflow analysis should be + * aborted. + */ public class FailedTransferFunctionResult<StateType extends AbstractState<StateType>> implements TransferFunctionResult<StateType> {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java index c990bdb..60136ce 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java
@@ -16,11 +16,11 @@ * This defines a simple fixpoint solver for running an intraprocedural dataflow analysis. * * <p>The solver computes an {@link AbstractState} for each {@link BasicBlock} using the {@link - * TransferFunction} which defines the abstract semantics for each instruction. + * AbstractTransferFunction} which defines the abstract semantics for each instruction. * * <p>Once the fixpoint is reached the analysis returns a {@link SuccessfulDataflowAnalysisResult}. - * If the supplied {@link TransferFunction} returns a {@link FailedTransferFunctionResult} for a - * given instruction and abstract state, then the analysis return a {@link + * If the supplied {@link AbstractTransferFunction} returns a {@link FailedTransferFunctionResult} + * for a given instruction and abstract state, then the analysis return a {@link * FailedDataflowAnalysisResult}. */ public class IntraproceduralDataflowAnalysis<StateType extends AbstractState<StateType>> { @@ -28,12 +28,13 @@ private final StateType bottom; // The transfer function that defines the abstract semantics for each instruction. - private final TransferFunction<StateType> transfer; + private final AbstractTransferFunction<StateType> transfer; // The state of the analysis. private final Map<BasicBlock, StateType> blockExitStates = new IdentityHashMap<>(); - public IntraproceduralDataflowAnalysis(StateType bottom, TransferFunction<StateType> transfer) { + public IntraproceduralDataflowAnalysis( + StateType bottom, AbstractTransferFunction<StateType> transfer) { this.bottom = bottom; this.transfer = transfer; }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunctionResult.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunctionResult.java index 183d79a..96a17ab 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunctionResult.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunctionResult.java
@@ -7,8 +7,8 @@ import com.android.tools.r8.ir.code.Instruction; /** - * The result of applying the {@link TransferFunction} to an {@link Instruction} and an {@link - * AbstractState}. + * The result of applying the {@link AbstractTransferFunction} to an {@link Instruction} and an + * {@link AbstractState}. * * <p>The result can either be a new {@link AbstractState} or a failure, in which case the dataflow * analysis is aborted.
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java index d45bf07..a512bd4 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
@@ -5,6 +5,7 @@ package com.android.tools.r8.ir.analysis.value; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.GraphLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -52,6 +53,10 @@ return false; } + public SingleConstValue asSingleConstValue() { + return null; + } + public boolean isSingleConstClassValue() { return false; } @@ -104,7 +109,47 @@ return null; } - public AbstractValue join(AbstractValue other) { + public boolean isConstantOrNonConstantNumberValue() { + return false; + } + + public ConstantOrNonConstantNumberValue asConstantOrNonConstantNumberValue() { + return null; + } + + public boolean isNonConstantNumberValue() { + return false; + } + + public NonConstantNumberValue asNonConstantNumberValue() { + return null; + } + + public boolean isNumberFromIntervalValue() { + return false; + } + + public NumberFromIntervalValue asNumberFromIntervalValue() { + return null; + } + + public boolean isNumberFromSetValue() { + return false; + } + + public NumberFromSetValue asNumberFromSetValue() { + return null; + } + + public AbstractValue join(AbstractValue other, AbstractValueFactory factory, DexType type) { + return join(other, factory, type, false); + } + + public AbstractValue join( + AbstractValue other, + AbstractValueFactory factory, + DexType type, + boolean allowNonConstantNumbers) { if (isBottom() || other.isUnknown()) { return other; } @@ -114,11 +159,31 @@ if (equals(other)) { return this; } - if (isNull()) { - return NullOrAbstractValue.create(other); + if (type.isReferenceType()) { + if (isNull()) { + return NullOrAbstractValue.create(other); + } + if (other.isNull()) { + return NullOrAbstractValue.create(this); + } } - if (other.isNull()) { - return NullOrAbstractValue.create(this); + if (allowNonConstantNumbers + && isConstantOrNonConstantNumberValue() + && other.isConstantOrNonConstantNumberValue()) { + NumberFromSetValue.Builder numberFromSetValueBuilder; + if (isSingleNumberValue()) { + numberFromSetValueBuilder = NumberFromSetValue.builder(asSingleNumberValue()); + } else { + assert isNumberFromSetValue(); + numberFromSetValueBuilder = asNumberFromSetValue().instanceBuilder(); + } + if (other.isSingleNumberValue()) { + numberFromSetValueBuilder.addInt(other.asSingleNumberValue().getIntValue()); + } else { + assert other.isNumberFromSetValue(); + numberFromSetValueBuilder.addInts(other.asNumberFromSetValue()); + } + return numberFromSetValueBuilder.build(factory); } return UnknownValue.getInstance(); }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/ConstantOrNonConstantNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/ConstantOrNonConstantNumberValue.java new file mode 100644 index 0000000..4c6cf04 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/analysis/value/ConstantOrNonConstantNumberValue.java
@@ -0,0 +1,34 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.analysis.value; + +import com.android.tools.r8.utils.OptionalBool; + +public interface ConstantOrNonConstantNumberValue { + + boolean containsInt(int value); + + OptionalBool isSubsetOf(int[] values); + + ConstantOrNonConstantNumberValue asConstantOrNonConstantNumberValue(); + + boolean isSingleNumberValue(); + + SingleNumberValue asSingleNumberValue(); + + boolean isNonConstantNumberValue(); + + NonConstantNumberValue asNonConstantNumberValue(); + + boolean isNumberFromIntervalValue(); + + NumberFromIntervalValue asNumberFromIntervalValue(); + + boolean isNumberFromSetValue(); + + NumberFromSetValue asNumberFromSetValue(); + + boolean mayOverlapWith(ConstantOrNonConstantNumberValue other); +}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/NonConstantNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/NonConstantNumberValue.java new file mode 100644 index 0000000..e233568 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/analysis/value/NonConstantNumberValue.java
@@ -0,0 +1,31 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.analysis.value; + +public abstract class NonConstantNumberValue extends AbstractValue + implements ConstantOrNonConstantNumberValue { + + @Override + public boolean isNonConstantNumberValue() { + return true; + } + + @Override + public NonConstantNumberValue asNonConstantNumberValue() { + return this; + } + + @Override + public boolean isConstantOrNonConstantNumberValue() { + return true; + } + + @Override + public ConstantOrNonConstantNumberValue asConstantOrNonConstantNumberValue() { + return this; + } + + public abstract long getAbstractionSize(); +}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromIntervalValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromIntervalValue.java new file mode 100644 index 0000000..f100893 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromIntervalValue.java
@@ -0,0 +1,101 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.analysis.value; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.GraphLens; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.OptionalBool; +import java.util.Objects; + +public class NumberFromIntervalValue extends NonConstantNumberValue { + + private final long minInclusive; + private final long maxInclusive; + + public NumberFromIntervalValue(long minInclusive, long maxInclusive) { + assert maxInclusive > minInclusive; + this.minInclusive = minInclusive; + this.maxInclusive = maxInclusive; + } + + @Override + public boolean containsInt(int value) { + return minInclusive <= value && value <= maxInclusive; + } + + @Override + public long getAbstractionSize() { + return maxInclusive - minInclusive + 1; + } + + @Override + public boolean isNumberFromIntervalValue() { + return true; + } + + @Override + public NumberFromIntervalValue asNumberFromIntervalValue() { + return this; + } + + @Override + public boolean isNonTrivial() { + return true; + } + + @Override + public OptionalBool isSubsetOf(int[] values) { + // Not implemented. + return OptionalBool.unknown(); + } + + @Override + public boolean mayOverlapWith(ConstantOrNonConstantNumberValue other) { + if (other.isSingleNumberValue()) { + return containsInt(other.asSingleNumberValue().getIntValue()); + } + if (other.isNumberFromIntervalValue()) { + return mayOverlapWith(other.asNumberFromIntervalValue()); + } + assert other.isNumberFromSetValue(); + return mayOverlapWith(other.asNumberFromSetValue()); + } + + public boolean mayOverlapWith(NumberFromIntervalValue other) { + return minInclusive <= other.maxInclusive && maxInclusive >= other.minInclusive; + } + + public boolean mayOverlapWith(NumberFromSetValue other) { + return other.mayOverlapWith(this); + } + + @Override + public AbstractValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens lens) { + return this; + } + + @Override + public boolean equals(Object o) { + if (o == null || o.getClass() != getClass()) { + return false; + } + NumberFromIntervalValue numberFromIntervalValue = (NumberFromIntervalValue) o; + return minInclusive == numberFromIntervalValue.minInclusive + && maxInclusive == numberFromIntervalValue.maxInclusive; + } + + @Override + public int hashCode() { + int hash = 31 * (31 * (31 + Long.hashCode(minInclusive)) + Long.hashCode(maxInclusive)); + assert hash == Objects.hash(minInclusive, maxInclusive); + return hash; + } + + @Override + public String toString() { + return "NumberFromIntervalValue([" + minInclusive + "; " + maxInclusive + "])"; + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromSetValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromSetValue.java new file mode 100644 index 0000000..e6062f6 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromSetValue.java
@@ -0,0 +1,163 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.analysis.value; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.GraphLens; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.ArrayUtils; +import com.android.tools.r8.utils.OptionalBool; +import it.unimi.dsi.fastutil.ints.IntArraySet; +import it.unimi.dsi.fastutil.ints.IntIterator; +import it.unimi.dsi.fastutil.ints.IntSet; +import java.util.Arrays; + +public class NumberFromSetValue extends NonConstantNumberValue { + + private static final int MAX_SIZE = 30; + + private final IntSet numbers; + + private NumberFromSetValue(IntSet numbers) { + this.numbers = numbers; + } + + static Builder builder() { + return new Builder(); + } + + static Builder builder(SingleNumberValue singleNumberValue) { + return new Builder().addInt(singleNumberValue.getIntValue()); + } + + Builder instanceBuilder() { + return new Builder(this); + } + + @Override + public boolean containsInt(int value) { + return numbers.contains(value); + } + + @Override + public long getAbstractionSize() { + return numbers.size(); + } + + @Override + public boolean isNumberFromSetValue() { + return true; + } + + @Override + public NumberFromSetValue asNumberFromSetValue() { + return this; + } + + @Override + public boolean isNonTrivial() { + return true; + } + + @Override + public OptionalBool isSubsetOf(int[] values) { + assert ArrayUtils.isSorted(values); + for (int number : numbers) { + if (Arrays.binarySearch(values, number) < 0) { + return OptionalBool.FALSE; + } + } + return OptionalBool.TRUE; + } + + @Override + public boolean mayOverlapWith(ConstantOrNonConstantNumberValue other) { + if (other.isSingleNumberValue()) { + return containsInt(other.asSingleNumberValue().getIntValue()); + } + assert other.isNonConstantNumberValue(); + for (int number : numbers) { + if (other.containsInt(number)) { + return true; + } + } + return false; + } + + @Override + public AbstractValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens lens) { + return this; + } + + @Override + public boolean equals(Object o) { + if (o == null || o.getClass() != getClass()) { + return false; + } + NumberFromSetValue numberFromSetValue = (NumberFromSetValue) o; + return numbers.equals(numberFromSetValue.numbers); + } + + @Override + public int hashCode() { + return numbers.hashCode(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("NumberFromSetValue("); + IntIterator iterator = numbers.iterator(); + builder.append(iterator.nextInt()); + while (iterator.hasNext()) { + builder.append(", ").append(iterator.nextInt()); + } + return builder.append(")").toString(); + } + + static class Builder { + + private IntSet numbers; + + Builder() { + numbers = new IntArraySet(); + } + + Builder(NumberFromSetValue numberFromSetValue) { + numbers = new IntArraySet(numberFromSetValue.numbers); + } + + Builder addInt(int number) { + if (numbers != null) { + assert numbers.size() <= MAX_SIZE; + if (numbers.add(number) && numbers.size() > MAX_SIZE) { + numbers = null; + } + } + return this; + } + + Builder addInts(NumberFromSetValue numberFromSetValue) { + if (numbers != null) { + assert numbers.size() <= MAX_SIZE; + if (numbers.addAll(numberFromSetValue.numbers) && numbers.size() > MAX_SIZE) { + numbers = null; + } + } + return this; + } + + AbstractValue build(AbstractValueFactory abstractValueFactory) { + if (numbers != null) { + assert !numbers.isEmpty(); + assert numbers.size() <= MAX_SIZE; + if (numbers.size() == 1) { + return abstractValueFactory.createSingleNumberValue(numbers.iterator().nextInt()); + } + return new NumberFromSetValue(numbers); + } + return UnknownValue.getInstance(); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java index f0af9b4..4db63a6 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java
@@ -8,10 +8,12 @@ import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.GraphLens; +import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.shaking.AppInfoWithLiveness; import java.util.IdentityHashMap; import java.util.Map; import java.util.function.BiConsumer; +import java.util.function.Predicate; public abstract class ObjectState { @@ -25,6 +27,22 @@ public abstract void forEachAbstractFieldValue(BiConsumer<DexField, AbstractValue> consumer); + public final boolean hasMaterializableFieldValueThatMatches( + AppView<AppInfoWithLiveness> appView, + DexEncodedField field, + ProgramMethod context, + Predicate<SingleValue> predicate) { + AbstractValue abstractValue = getAbstractFieldValue(field); + if (!abstractValue.isSingleValue()) { + return false; + } + SingleValue singleValue = abstractValue.asSingleValue(); + if (!singleValue.isMaterializableInContext(appView, context)) { + return false; + } + return predicate.test(singleValue); + } + public abstract AbstractValue getAbstractFieldValue(DexEncodedField field); public abstract boolean isEmpty();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstValue.java index ecf9838..11d238a 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstValue.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstValue.java
@@ -10,4 +10,9 @@ public boolean isSingleConstValue() { return true; } + + @Override + public SingleConstValue asSingleConstValue() { + return this; + } }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java index 76e6ac8..d51b94a 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
@@ -16,8 +16,11 @@ import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.ArrayUtils; +import com.android.tools.r8.utils.OptionalBool; -public class SingleNumberValue extends SingleConstValue { +public class SingleNumberValue extends SingleConstValue + implements ConstantOrNonConstantNumberValue { private final long value; @@ -27,6 +30,16 @@ } @Override + public boolean containsInt(int value) { + return value == getIntValue(); + } + + @Override + public OptionalBool isSubsetOf(int[] values) { + return OptionalBool.of(ArrayUtils.containsInt(values, getIntValue())); + } + + @Override public boolean isSingleBoolean() { return isFalse() || isTrue(); } @@ -51,6 +64,16 @@ return this; } + @Override + public boolean isConstantOrNonConstantNumberValue() { + return true; + } + + @Override + public ConstantOrNonConstantNumberValue asConstantOrNonConstantNumberValue() { + return this; + } + public boolean getBooleanValue() { assert value == 0 || value == 1; return value != 0; @@ -77,6 +100,15 @@ } @Override + public boolean mayOverlapWith(ConstantOrNonConstantNumberValue other) { + if (other.isSingleNumberValue()) { + return equals(other.asSingleNumberValue()); + } + assert other.isNonConstantNumberValue(); + return other.asNonConstantNumberValue().containsInt(getIntValue()); + } + + @Override public boolean equals(Object o) { return this == o; }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java index cbc8c6b..506e8af 100644 --- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java +++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -1096,6 +1096,14 @@ return arguments; } + public Argument getLastArgument() { + InstructionIterator instructionIterator = entryBlock().iterator(getNumberOfArguments() - 1); + Argument lastArgument = instructionIterator.next().asArgument(); + assert lastArgument != null; + assert !instructionIterator.peekNext().isArgument(); + return lastArgument; + } + public Value getThis() { if (method().accessFlags.isStatic()) { return null;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java index 689514f..4c1d942 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java +++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -309,7 +309,11 @@ } public void replace(Instruction newInstruction, IRCode code) { - getBlock().listIterator(code, this).replaceCurrentInstruction(newInstruction); + replace(newInstruction, code, null); + } + + public void replace(Instruction newInstruction, IRCode code, Set<Value> affectedValues) { + getBlock().listIterator(code, this).replaceCurrentInstruction(newInstruction, affectedValues); } /**
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java index fdaf8f0..94ab740 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java +++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -24,6 +24,7 @@ import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo; import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.google.common.collect.Iterables; import java.util.List; public abstract class InvokeMethodWithReceiver extends InvokeMethod { @@ -32,6 +33,10 @@ super(target, result, arguments); } + public Iterable<Value> getNonReceiverArguments() { + return Iterables.skip(arguments(), 1); + } + public boolean hasRefinedReceiverLowerBoundType(AppView<AppInfoWithLiveness> appView) { assert isInvokeMethodWithDynamicDispatch(); return getReceiver().getDynamicLowerBoundType(appView) != null;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java index e0841f7..8a63d63 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -164,7 +164,7 @@ DexBuilder.removeRedundantDebugPositions(code); CfCode code = buildCfCode(); assert verifyInvokeInterface(code, appView); - assert code.verifyFrames(method, appView, this.code.origin, false); + assert code.verifyFrames(method, appView, this.code.origin, false).isValid(); return code; }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java index 50ba68b..1d7f619 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -671,7 +671,7 @@ public DexType getPhiTypeForBlock( int register, int blockOffset, ValueTypeConstraint constraint, RegisterReadType readType) { assert code.getStackMapStatus() != StackMapStatus.NOT_VERIFIED; - if (code.getStackMapStatus() == StackMapStatus.INVALID_OR_NOT_PRESENT) { + if (code.getStackMapStatus().isInvalidOrNotPresent()) { return null; } // We should be able to find the a snapshot at the block-offset:
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java index 6d79c9c..8f83dca 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
@@ -9,6 +9,8 @@ import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.desugar.CfClassDesugaringEventConsumer; +import com.android.tools.r8.ir.desugar.CfClassDesugaringEventConsumer.D8CfClassDesugaringEventConsumer; import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer; import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer.D8CfInstructionDesugaringEventConsumer; import com.android.tools.r8.utils.ThreadUtils; @@ -50,6 +52,11 @@ ClassConverterResult.Builder resultBuilder, ExecutorService executorService) throws ExecutionException { List<DexProgramClass> classes = appView.appInfo().classes(); + + D8CfClassDesugaringEventConsumer classDesugaringEventConsumer = + CfClassDesugaringEventConsumer.createForD8(methodProcessor); + converter.desugarClassesForD8(classes, classDesugaringEventConsumer, executorService); + while (!classes.isEmpty()) { Set<DexType> seenNestHosts = Sets.newIdentityHashSet(); List<DexProgramClass> deferred = new ArrayList<>(classes.size() / 2); @@ -65,18 +72,19 @@ } } - // Process the wave and wait for all IR processing to complete. - D8CfInstructionDesugaringEventConsumer desugaringEventConsumer = + D8CfInstructionDesugaringEventConsumer instructionDesugaringEventConsumer = CfInstructionDesugaringEventConsumer.createForD8(methodProcessor); + + // Process the wave and wait for all IR processing to complete. methodProcessor.newWave(); ThreadUtils.processItems( - wave, clazz -> convertClass(clazz, desugaringEventConsumer), executorService); + wave, clazz -> convertClass(clazz, instructionDesugaringEventConsumer), executorService); methodProcessor.awaitMethodProcessing(); // Finalize the desugaring of the processed classes. This may require processing (and // reprocessing) of some methods. List<ProgramMethod> needsProcessing = - desugaringEventConsumer.finalizeDesugaring(appView, resultBuilder); + instructionDesugaringEventConsumer.finalizeDesugaring(appView, resultBuilder); if (!needsProcessing.isEmpty()) { // Create a new processor context to ensure unique method processing contexts. methodProcessor.newWave(); @@ -90,13 +98,13 @@ if (definition.isProcessed()) { definition.markNotProcessed(); } - methodProcessor.processMethod(method, desugaringEventConsumer); + methodProcessor.processMethod(method, instructionDesugaringEventConsumer); }, executorService); // Verify there is nothing to finalize once method processing finishes. methodProcessor.awaitMethodProcessing(); - assert desugaringEventConsumer.verifyNothingToFinalize(); + assert instructionDesugaringEventConsumer.verifyNothingToFinalize(); } classes = deferred;
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 72f9ac7..54b0a66 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
@@ -42,6 +42,8 @@ import com.android.tools.r8.ir.code.InvokeStatic; import com.android.tools.r8.ir.code.NumericType; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection; +import com.android.tools.r8.ir.desugar.CfClassDesugaringEventConsumer.D8CfClassDesugaringEventConsumer; import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection; import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer; import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer.D8CfInstructionDesugaringEventConsumer; @@ -125,7 +127,8 @@ private final Timing timing; private final Outliner outliner; private final ClassInitializerDefaultsOptimization classInitializerDefaultsOptimization; - private final CfInstructionDesugaringCollection desugaring; + private final CfClassDesugaringCollection classDesugaring; + private final CfInstructionDesugaringCollection instructionDesugaring; private final FieldAccessAnalysis fieldAccessAnalysis; private final LibraryMethodOverrideAnalysis libraryMethodOverrideAnalysis; private final StringOptimizer stringOptimizer; @@ -217,7 +220,8 @@ // - nest based access desugaring, // - invoke-special desugaring. assert options.desugarState.isOn(); - this.desugaring = CfInstructionDesugaringCollection.create(appView); + this.instructionDesugaring = CfInstructionDesugaringCollection.create(appView); + this.classDesugaring = instructionDesugaring.createClassDesugaringCollection(); this.desugaredLibraryRetargeter = options.desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty() ? null @@ -249,10 +253,11 @@ this.assumeInserter = null; return; } - this.desugaring = + this.instructionDesugaring = appView.enableWholeProgramOptimizations() ? CfInstructionDesugaringCollection.empty() : CfInstructionDesugaringCollection.create(appView); + this.classDesugaring = instructionDesugaring.createClassDesugaringCollection(); this.interfaceMethodRewriter = options.isInterfaceMethodDesugaringEnabled() ? new InterfaceMethodRewriter(appView, this) @@ -349,7 +354,7 @@ private void synthesizeBridgesForNestBasedAccessesOnClasspath( D8MethodProcessor methodProcessor, ExecutorService executorService) throws ExecutionException { - desugaring.withD8NestBasedAccessDesugaring( + instructionDesugaring.withD8NestBasedAccessDesugaring( d8NestBasedAccessDesugaring -> d8NestBasedAccessDesugaring.synthesizeBridgesForNestBasedAccessesOnClasspath( methodProcessor, executorService)); @@ -357,14 +362,15 @@ } private void reportNestDesugarDependencies() { - desugaring.withD8NestBasedAccessDesugaring( + instructionDesugaring.withD8NestBasedAccessDesugaring( D8NestBasedAccessDesugaring::reportDesugarDependencies); } - private void staticizeClasses(OptimizationFeedback feedback, ExecutorService executorService) + private void staticizeClasses( + OptimizationFeedback feedback, ExecutorService executorService, GraphLens applied) throws ExecutionException { if (classStaticizer != null) { - classStaticizer.staticizeCandidates(feedback, executorService); + classStaticizer.staticizeCandidates(feedback, executorService, applied); } } @@ -459,6 +465,26 @@ appView, classConverterResult.getForcefullyMovedLambdaMethods()); } + public void desugarClassesForD8( + List<DexProgramClass> classes, + D8CfClassDesugaringEventConsumer desugaringEventConsumer, + ExecutorService executorService) + throws ExecutionException { + if (classDesugaring.isEmpty()) { + return; + } + // Currently the classes can be processed in any order and do not require to be sorted. + ThreadUtils.processItems( + classes, clazz -> desugarClassForD8(clazz, desugaringEventConsumer), executorService); + } + + public void desugarClassForD8( + DexProgramClass clazz, D8CfClassDesugaringEventConsumer desugaringEventConsumer) { + if (classDesugaring.needsDesugaring(clazz)) { + classDesugaring.desugar(clazz, desugaringEventConsumer); + } + } + void convertMethods( DexProgramClass clazz, D8CfInstructionDesugaringEventConsumer desugaringEventConsumer, @@ -544,7 +570,7 @@ if (!options.cfToCfDesugar) { return true; } - if (desugaring.needsDesugaring(method)) { + if (instructionDesugaring.needsDesugaring(method)) { return true; } if (desugaredLibraryAPIConverter != null @@ -620,7 +646,7 @@ AppView<AppInfoWithLiveness> appView, ExecutorService executorService) throws ExecutionException { // Desugaring happens in the enqueuer. - assert desugaring.isEmpty(); + assert instructionDesugaring.isEmpty(); DexApplication application = appView.appInfo().app(); @@ -723,7 +749,7 @@ if (!options.isGeneratingClassFiles()) { printPhase("Class staticizer post processing"); // TODO(b/127694949): Adapt to PostOptimization. - staticizeClasses(feedback, executorService); + staticizeClasses(feedback, executorService, initialGraphLensForIR); feedback.updateVisibleOptimizationInfo(); // The class staticizer lens shall not be applied through lens code rewriting or it breaks // the lambda merger. @@ -1109,13 +1135,15 @@ ProgramMethod method, CfInstructionDesugaringEventConsumer desugaringEventConsumer, MethodProcessingContext methodProcessingContext) { - if (options.desugarState.isOff() - || !method.getDefinition().getCode().isCfCode() - || !desugaring.needsDesugaring(method)) { + if (options.desugarState.isOff() || !method.getDefinition().getCode().isCfCode()) { return false; } - desugaring.desugar(method, methodProcessingContext, desugaringEventConsumer); - return true; + instructionDesugaring.scan(method, desugaringEventConsumer); + if (instructionDesugaring.needsDesugaring(method)) { + instructionDesugaring.desugar(method, methodProcessingContext, desugaringEventConsumer); + return true; + } + return false; } // TODO(b/140766440): Convert all sub steps an implementer of CodeOptimization
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 0cdf967..9dfe2e3 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
@@ -13,9 +13,7 @@ import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.analysis.value.AbstractValue; import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; -import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo; import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint; -import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo; import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo; import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -64,15 +62,11 @@ void setClassInlinerMethodConstraint( ProgramMethod method, ClassInlinerMethodConstraint classInlinerConstraint); - void setClassInlinerEligibility(DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility); - void setInstanceInitializerInfoCollection( DexEncodedMethod method, InstanceInitializerInfoCollection instanceInitializerInfoCollection); void setInitializerEnablingJavaVmAssertions(DexEncodedMethod method); - void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo); - void setNonNullParamOrThrow(DexEncodedMethod method, BitSet facts); void setNonNullParamOnNormalExits(DexEncodedMethod method, BitSet facts);
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 bd5c0b3..5cd32f2 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
@@ -78,7 +78,8 @@ CfInvoke invoke = instruction.asInvoke(); MethodProvider methodProvider = getMethodProviderOrNull(invoke.getMethod()); return methodProvider != null - ? methodProvider.rewriteInvoke(invoke, appView, eventConsumer, methodProcessingContext) + ? methodProvider.rewriteInvoke( + invoke, appView, eventConsumer, methodProcessingContext, localStackAllocator) : null; } @@ -194,6 +195,7 @@ } // These are currently not implemented at any API level in Android. + initializeJava9MethodProviders(factory); initializeJava10MethodProviders(factory); initializeJava11MethodProviders(factory); } @@ -1043,6 +1045,60 @@ } } + private void initializeJava9MethodProviders(DexItemFactory factory) { + // Integer + DexType type = factory.boxedIntType; + // long Long.parseLong(CharSequence s, int beginIndex, int endIndex, int radix) + DexString name = factory.createString("parseInt"); + DexProto proto = + factory.createProto( + factory.intType, + factory.charSequenceType, + factory.intType, + factory.intType, + factory.intType); + DexMethod method = factory.createMethod(type, proto, name); + addProvider( + new MethodGenerator( + method, + BackportedMethods::IntegerMethods_parseIntSubsequenceWithRadix, + "parseIntSubsequenceWithRadix")); + + // Long + type = factory.boxedLongType; + // long Long.parseLong(CharSequence s, int beginIndex, int endIndex, int radix) + name = factory.createString("parseLong"); + proto = + factory.createProto( + factory.longType, + factory.charSequenceType, + factory.intType, + factory.intType, + factory.intType); + method = factory.createMethod(type, proto, name); + addProvider( + new MethodGenerator( + method, + BackportedMethods::LongMethods_parseLongSubsequenceWithRadix, + "parseLongSubsequenceWithRadix")); + + // long Long.parseUnsignedLong(CharSequence s, int beginIndex, int endIndex, int radix) + name = factory.createString("parseUnsignedLong"); + proto = + factory.createProto( + factory.longType, + factory.charSequenceType, + factory.intType, + factory.intType, + factory.intType); + method = factory.createMethod(type, proto, name); + addProvider( + new MethodGenerator( + method, + BackportedMethods::LongMethods_parseUnsignedLongSubsequenceWithRadix, + "parseUnsignedLongSubsequenceWithRadix")); + } + private void initializeJava10MethodProviders(DexItemFactory factory) { // List DexType type = factory.listType; @@ -1309,7 +1365,8 @@ CfInvoke invoke, AppView<?> appView, BackportedMethodDesugaringEventConsumer eventConsumer, - MethodProcessingContext methodProcessingContext); + MethodProcessingContext methodProcessingContext, + LocalStackAllocator localStackAllocator); } private static final class InvokeRewriter extends MethodProvider { @@ -1326,8 +1383,9 @@ CfInvoke invoke, AppView<?> appView, BackportedMethodDesugaringEventConsumer eventConsumer, - MethodProcessingContext methodProcessingContext) { - return rewriter.rewrite(invoke, appView.dexItemFactory()); + MethodProcessingContext methodProcessingContext, + LocalStackAllocator localStackAllocator) { + return rewriter.rewrite(invoke, appView.dexItemFactory(), localStackAllocator); } } @@ -1351,7 +1409,8 @@ CfInvoke invoke, AppView<?> appView, BackportedMethodDesugaringEventConsumer eventConsumer, - MethodProcessingContext methodProcessingContext) { + MethodProcessingContext methodProcessingContext, + LocalStackAllocator localStackAllocator) { ProgramMethod method = getSyntheticMethod(appView, methodProcessingContext); eventConsumer.acceptBackportedMethod(method, methodProcessingContext.getMethodContext()); return ImmutableList.of(new CfInvoke(Opcodes.INVOKESTATIC, method.getReference(), false)); @@ -1410,7 +1469,8 @@ CfInstruction rewriteSingle(CfInvoke invoke, DexItemFactory factory); // Convenience wrapper since most rewrites are to a single instruction. - default Collection<CfInstruction> rewrite(CfInvoke invoke, DexItemFactory factory) { + default Collection<CfInstruction> rewrite( + CfInvoke invoke, DexItemFactory factory, LocalStackAllocator localStackAllocator) { return ImmutableList.of(rewriteSingle(invoke, factory)); } } @@ -1423,6 +1483,7 @@ } @Override - public abstract Collection<CfInstruction> rewrite(CfInvoke invoke, DexItemFactory factory); + public abstract Collection<CfInstruction> rewrite( + CfInvoke invoke, DexItemFactory factory, LocalStackAllocator localStackAllocator); } }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaring.java new file mode 100644 index 0000000..a885344 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaring.java
@@ -0,0 +1,16 @@ +// Copyright (c) 2021, 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.desugar; + +import com.android.tools.r8.graph.DexProgramClass; + +/** Interface for desugaring a class. */ +public interface CfClassDesugaring { + + void desugar(DexProgramClass clazz, CfClassDesugaringEventConsumer eventConsumer); + + /** Returns true if the given class needs desugaring. */ + boolean needsDesugaring(DexProgramClass clazz); +}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringCollection.java new file mode 100644 index 0000000..9b7c709 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringCollection.java
@@ -0,0 +1,58 @@ +// Copyright (c) 2021, 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.desugar; + +import com.android.tools.r8.graph.DexProgramClass; + +/** Interface for desugaring a class. */ +public abstract class CfClassDesugaringCollection { + + public abstract void desugar(DexProgramClass clazz, CfClassDesugaringEventConsumer eventConsumer); + + /** Returns true if the given class needs desugaring. */ + public abstract boolean needsDesugaring(DexProgramClass clazz); + + public abstract boolean isEmpty(); + + public static class NonEmptyCfClassDesugaringCollection extends CfClassDesugaringCollection { + private final RecordRewriter recordRewriter; + + NonEmptyCfClassDesugaringCollection(RecordRewriter recordRewriter) { + this.recordRewriter = recordRewriter; + } + + @Override + public void desugar(DexProgramClass clazz, CfClassDesugaringEventConsumer eventConsumer) { + recordRewriter.desugar(clazz, eventConsumer); + } + + @Override + public boolean needsDesugaring(DexProgramClass clazz) { + return recordRewriter.needsDesugaring(clazz); + } + + @Override + public boolean isEmpty() { + return false; + } + } + + public static class EmptyCfClassDesugaringCollection extends CfClassDesugaringCollection { + @Override + public void desugar(DexProgramClass clazz, CfClassDesugaringEventConsumer eventConsumer) { + // Intentionally empty. + } + + @Override + public boolean needsDesugaring(DexProgramClass clazz) { + return false; + } + + @Override + public boolean isEmpty() { + return true; + } + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringEventConsumer.java new file mode 100644 index 0000000..d5acaa7 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringEventConsumer.java
@@ -0,0 +1,31 @@ +// Copyright (c) 2021, 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.desugar; + +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.ir.conversion.D8MethodProcessor; + +public abstract class CfClassDesugaringEventConsumer implements RecordDesugaringEventConsumer { + + public static D8CfClassDesugaringEventConsumer createForD8(D8MethodProcessor methodProcessor) { + return new D8CfClassDesugaringEventConsumer(methodProcessor); + } + + public static class D8CfClassDesugaringEventConsumer extends CfClassDesugaringEventConsumer { + + private final D8MethodProcessor methodProcessor; + + public D8CfClassDesugaringEventConsumer(D8MethodProcessor methodProcessor) { + this.methodProcessor = methodProcessor; + } + + @Override + public void acceptRecordClass(DexProgramClass recordClass) { + methodProcessor.scheduleDesugaredMethodsForProcessing(recordClass.programMethods()); + } + } + + // TODO(b/): Implement R8CfClassDesugaringEventConsumer +}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java index 21ac7ae..3fb9329 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
@@ -9,6 +9,7 @@ import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring; import com.android.tools.r8.utils.ThrowingConsumer; +import java.util.function.Consumer; /** * Abstracts a collection of low-level desugarings (i.e., mappings from class-file instructions to @@ -34,6 +35,9 @@ return EmptyCfInstructionDesugaringCollection.getInstance(); } + public abstract void scan( + ProgramMethod method, CfInstructionDesugaringEventConsumer eventConsumer); + /** Desugars the instructions in the given method. */ public abstract void desugar( ProgramMethod method, @@ -44,9 +48,13 @@ return false; } + public abstract CfClassDesugaringCollection createClassDesugaringCollection(); + /** Returns true if the given method needs desugaring. */ public abstract boolean needsDesugaring(ProgramMethod method); public abstract <T extends Throwable> void withD8NestBasedAccessDesugaring( ThrowingConsumer<D8NestBasedAccessDesugaring, T> consumer) throws T; + + public abstract void withRecordRewriter(Consumer<RecordRewriter> consumer); }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java index d765537..a95569e 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -39,6 +39,7 @@ InvokeSpecialToSelfDesugaringEventConsumer, LambdaDesugaringEventConsumer, NestBasedAccessDesugaringEventConsumer, + RecordDesugaringEventConsumer, TwrCloseResourceDesugaringEventConsumer { public static D8CfInstructionDesugaringEventConsumer createForD8( @@ -58,6 +59,11 @@ return new CfInstructionDesugaringEventConsumer() { @Override + public void acceptRecordClass(DexProgramClass recordClass) { + assert false; + } + + @Override public void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context) { assert false; } @@ -121,6 +127,11 @@ } @Override + public void acceptRecordClass(DexProgramClass recordClass) { + methodProcessor.scheduleDesugaredMethodsForProcessing(recordClass.programMethods()); + } + + @Override public void acceptLambdaClass(LambdaClass lambdaClass, ProgramMethod context) { synchronized (synthesizedLambdaClasses) { synthesizedLambdaClasses.add(lambdaClass); @@ -225,6 +236,12 @@ } @Override + public void acceptRecordClass(DexProgramClass recordClass) { + // This is called each time an instruction or a class is found to require the record class. + assert false : "TODO(b/179146128): To be implemented"; + } + + @Override public void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context) { // Intentionally empty. The backported method will be hit by the tracing in R8 as if it was // present in the input code, and thus nothing needs to be done.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java index 6dbd525..8a6624e 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -256,7 +256,7 @@ : new DexEncodedMethod[] {uniqueMethod}, factory.getSkipNameValidationForTesting(), getChecksumSupplier(uniqueMethod, appView)); - appView.appInfo().addSynthesizedClass(newClass, false); + appView.appInfo().addSynthesizedClassForLibraryDesugaring(newClass); builder.addSynthesizedClass(newClass); } @@ -409,6 +409,21 @@ itemFactory.createMethod( itemFactory.createType("Ljava/util/DesugarArrays;"), proto, name); retargetLibraryMember.put(source, target); + + // TODO(b/181629049): This is only a workaround rewriting invokes of + // j.u.TimeZone.getTimeZone taking a java.time.ZoneId. + // to j.u.DesugarArrays.deepEquals0. + name = itemFactory.createString("getTimeZone"); + proto = + itemFactory.createProto( + itemFactory.createType("Ljava/util/TimeZone;"), + itemFactory.createType("Ljava/time/ZoneId;")); + source = + itemFactory.createMethod(itemFactory.createType("Ljava/util/TimeZone;"), proto, name); + target = + itemFactory.createMethod( + itemFactory.createType("Ljava/util/DesugarTimeZone;"), proto, name); + retargetLibraryMember.put(source, target); } }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java index eddbf6d..ce4f61a 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
@@ -537,7 +537,7 @@ throws ExecutionException { for (DexProgramClass wrapper : wrappers) { builder.addSynthesizedClass(wrapper); - appView.appInfo().addSynthesizedClass(wrapper, false); + appView.appInfo().addSynthesizedClassForLibraryDesugaring(wrapper); } irConverter.optimizeSynthesizedClasses(wrappers, executorService); }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java index 60b3537..c8b6147 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
@@ -6,8 +6,10 @@ import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext; import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection.EmptyCfClassDesugaringCollection; import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring; import com.android.tools.r8.utils.ThrowingConsumer; +import java.util.function.Consumer; public class EmptyCfInstructionDesugaringCollection extends CfInstructionDesugaringCollection { @@ -22,6 +24,11 @@ } @Override + public void scan(ProgramMethod method, CfInstructionDesugaringEventConsumer eventConsumer) { + // Intentionally empty. + } + + @Override public void desugar( ProgramMethod method, MethodProcessingContext methodProcessingContext, @@ -35,6 +42,11 @@ } @Override + public CfClassDesugaringCollection createClassDesugaringCollection() { + return new EmptyCfClassDesugaringCollection(); + } + + @Override public boolean needsDesugaring(ProgramMethod method) { return false; } @@ -44,4 +56,9 @@ ThrowingConsumer<D8NestBasedAccessDesugaring, T> consumer) { // Intentionally empty. } + + @Override + public void withRecordRewriter(Consumer<RecordRewriter> consumer) { + // Intentionally empty. + } }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java index 7d041f6..a1c0d58 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -979,8 +979,7 @@ emulationMethods.toArray(DexEncodedMethod.EMPTY_ARRAY), DexEncodedMethod.EMPTY_ARRAY, factory.getSkipNameValidationForTesting(), - DexProgramClass::checksumFromType, - Collections.singletonList(theInterface)); + DexProgramClass::checksumFromType); clazz.forEachProgramMethod(synthesizedMethods::add); return clazz; } @@ -1467,7 +1466,7 @@ // At this point we likely have a non-library type that may depend on default method information // from its interfaces and the dependency should be reported. if (implementing.isProgramClass() && !definedInterface.isLibraryClass()) { - reportDependencyEdge(implementing.asProgramClass(), definedInterface, appView.options()); + reportDependencyEdge(implementing.asProgramClass(), definedInterface, appView.appInfo()); } // Merge information from all superinterfaces. @@ -1491,19 +1490,19 @@ } public static void reportDependencyEdge( - DexClass dependent, DexClass dependency, InternalOptions options) { + DexClass dependent, DexClass dependency, AppInfo appInfo) { assert !dependent.isLibraryClass(); assert !dependency.isLibraryClass(); - DesugarGraphConsumer consumer = options.desugarGraphConsumer; + DesugarGraphConsumer consumer = appInfo.app().options.desugarGraphConsumer; if (consumer != null) { Origin dependencyOrigin = dependency.getOrigin(); - java.util.Collection<DexProgramClass> dependents = - dependent.isProgramClass() ? dependent.asProgramClass().getSynthesizedFrom() : null; - if (dependents == null || dependents.isEmpty()) { + java.util.Collection<DexType> dependents = + appInfo.getSyntheticItems().getSynthesizingContexts(dependent.getType()); + if (dependents.isEmpty()) { reportDependencyEdge(consumer, dependencyOrigin, dependent); } else { - for (DexClass clazz : dependents) { - reportDependencyEdge(consumer, dependencyOrigin, clazz); + for (DexType type : dependents) { + reportDependencyEdge(consumer, dependencyOrigin, appInfo.definitionFor(type)); } } }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java index e40251c..d20eee7 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -135,8 +135,7 @@ companionMethods.toArray(DexEncodedMethod.EMPTY_ARRAY), DexEncodedMethod.EMPTY_ARRAY, rewriter.factory.getSkipNameValidationForTesting(), - getChecksumSupplier(iface), - Collections.singletonList(iface)); + getChecksumSupplier(iface)); syntheticClasses.put(iface, companionClass); if (companionClass.hasClassInitializer()) { newSynthesizedMethodConsumer.accept(companionClass.getProgramClassInitializer());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java index 076c0c4..d54264c 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -230,16 +230,17 @@ int fieldCount = fieldTypes.length; List<DexEncodedField> fields = new ArrayList<>(fieldCount); for (int i = 0; i < fieldCount; i++) { - FieldAccessFlags accessFlags = - FieldAccessFlags.fromSharedAccessFlags( - Constants.ACC_FINAL | Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC); + boolean deprecated = false; + boolean d8R8Synthesized = true; fields.add( new DexEncodedField( getCaptureField(i), - accessFlags, + FieldAccessFlags.createPublicFinalSynthetic(), FieldTypeSignature.noSignature(), DexAnnotationSet.empty(), - null)); + null, + deprecated, + d8R8Synthesized)); } builder.setInstanceFields(fields); } @@ -249,6 +250,8 @@ if (isStateless()) { // Create instance field for stateless lambda. assert this.lambdaField != null; + boolean deprecated = false; + boolean d8R8Synthesized = true; builder.setStaticFields( Collections.singletonList( new DexEncodedField( @@ -260,7 +263,9 @@ | Constants.ACC_STATIC), FieldTypeSignature.noSignature(), DexAnnotationSet.empty(), - DexValueNull.NULL))); + DexValueNull.NULL, + deprecated, + d8R8Synthesized))); } }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java index cbab081..6975252 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
@@ -361,9 +361,18 @@ Builder<CfInstruction> instructions, DexItemFactory factory) { internalAdjustType(fromType, toType, returnType, instructions, factory); + if (fromType == toType) { + return ValueType.fromDexType(fromType).requiredRegisters(); + } + // Account for the potential unboxing of a wide type. + DexType fromTypeAsPrimitive = factory.getPrimitiveFromBoxed(fromType); return Math.max( ValueType.fromDexType(fromType).requiredRegisters(), - ValueType.fromDexType(toType).requiredRegisters()); + Math.max( + fromTypeAsPrimitive == null + ? 0 + : ValueType.fromDexType(fromTypeAsPrimitive).requiredRegisters(), + ValueType.fromDexType(toType).requiredRegisters())); } private static void internalAdjustType(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java index 5815053..3a7a195 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -12,6 +12,8 @@ import com.android.tools.r8.graph.CfCode; import com.android.tools.r8.graph.Code; import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection.EmptyCfClassDesugaringCollection; +import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection.NonEmptyCfClassDesugaringCollection; import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialToSelfDesugaring; import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring; import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring; @@ -28,6 +30,7 @@ import java.util.Collection; import java.util.Iterator; import java.util.List; +import java.util.function.Consumer; public class NonEmptyCfInstructionDesugaringCollection extends CfInstructionDesugaringCollection { @@ -35,6 +38,7 @@ private final List<CfInstructionDesugaring> desugarings = new ArrayList<>(); private final NestBasedAccessDesugaring nestBasedAccessDesugaring; + private final RecordRewriter recordRewriter; NonEmptyCfInstructionDesugaringCollection(AppView<?> appView) { this.appView = appView; @@ -54,6 +58,11 @@ if (nestBasedAccessDesugaring != null) { desugarings.add(nestBasedAccessDesugaring); } + this.recordRewriter = RecordRewriter.create(appView); + if (recordRewriter != null) { + assert !appView.enableWholeProgramOptimizations() : "To be implemented"; + desugarings.add(recordRewriter); + } } // TODO(b/145775365): special constructor for cf-to-cf compilations with desugaring disabled. @@ -62,6 +71,7 @@ AppView<?> appView, InvokeSpecialToSelfDesugaring invokeSpecialToSelfDesugaring) { this.appView = appView; this.nestBasedAccessDesugaring = null; + this.recordRewriter = null; desugarings.add(invokeSpecialToSelfDesugaring); } @@ -72,13 +82,8 @@ appView, new InvokeSpecialToSelfDesugaring(appView)); } - @Override - public void desugar( - ProgramMethod method, - MethodProcessingContext methodProcessingContext, - CfInstructionDesugaringEventConsumer eventConsumer) { - Code code = method.getDefinition().getCode(); - if (!code.isCfCode()) { + private void ensureCfCode(ProgramMethod method) { + if (!method.getDefinition().getCode().isCfCode()) { appView .options() .reporter @@ -87,10 +92,24 @@ "Unsupported attempt to desugar non-CF code", method.getOrigin(), method.getPosition())); - return; } + } - CfCode cfCode = code.asCfCode(); + @Override + public void scan(ProgramMethod method, CfInstructionDesugaringEventConsumer eventConsumer) { + ensureCfCode(method); + if (recordRewriter != null) { + recordRewriter.scan(method, eventConsumer); + } + } + + @Override + public void desugar( + ProgramMethod method, + MethodProcessingContext methodProcessingContext, + CfInstructionDesugaringEventConsumer eventConsumer) { + ensureCfCode(method); + CfCode cfCode = method.getDefinition().getCode().asCfCode(); // Tracking of temporary locals used for instruction desugaring. The desugaring of each // instruction is assumed to use locals only for the duration of the instruction, such that any @@ -114,12 +133,14 @@ method, methodProcessingContext); if (replacement != null) { - // Record if we increased the max number of locals for the method, and reset the - // next temporary locals register. + // Record if we increased the max number of locals and stack height for the method, + // and reset the next temporary locals register. maxLocalsForCode.setMax(maxLocalsForInstruction.getAndSet(cfCode.getMaxLocals())); + maxStackForCode.setMax(maxStackForInstruction.getAndSet(cfCode.getMaxStack())); } else { // The next temporary locals register should be unchanged. assert maxLocalsForInstruction.get() == cfCode.getMaxLocals(); + assert maxStackForInstruction.get() == cfCode.getMaxStack(); } return replacement; }, @@ -135,6 +156,14 @@ } } + @Override + public CfClassDesugaringCollection createClassDesugaringCollection() { + if (recordRewriter == null) { + return new EmptyCfClassDesugaringCollection(); + } + return new NonEmptyCfClassDesugaringCollection(recordRewriter); + } + private Collection<CfInstruction> desugarInstruction( CfInstruction instruction, FreshLocalProvider freshLocalProvider, @@ -220,4 +249,11 @@ consumer.accept((D8NestBasedAccessDesugaring) nestBasedAccessDesugaring); } } + + @Override + public void withRecordRewriter(Consumer<RecordRewriter> consumer) { + if (recordRewriter != null) { + consumer.accept(recordRewriter); + } + } }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/RecordDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/RecordDesugaringEventConsumer.java new file mode 100644 index 0000000..19d9ae3 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/desugar/RecordDesugaringEventConsumer.java
@@ -0,0 +1,12 @@ +// Copyright (c) 2021, 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.desugar; + +import com.android.tools.r8.graph.DexProgramClass; + +public interface RecordDesugaringEventConsumer { + + void acceptRecordClass(DexProgramClass recordClass); +}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java new file mode 100644 index 0000000..67e2c90 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java
@@ -0,0 +1,272 @@ +// Copyright (c) 2021, 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.desugar; + +import com.android.tools.r8.cf.code.CfConstNull; +import com.android.tools.r8.cf.code.CfConstNumber; +import com.android.tools.r8.cf.code.CfFieldInstruction; +import com.android.tools.r8.cf.code.CfInstruction; +import com.android.tools.r8.cf.code.CfInvoke; +import com.android.tools.r8.cf.code.CfStackInstruction; +import com.android.tools.r8.cf.code.CfTypeInstruction; +import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext; +import com.android.tools.r8.dex.Constants; +import com.android.tools.r8.errors.CompilationError; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.CfCode; +import com.android.tools.r8.graph.DexAnnotationSet; +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.DexItemFactory; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.DexProto; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature; +import com.android.tools.r8.graph.MethodAccessFlags; +import com.android.tools.r8.graph.ParameterAnnotationsList; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.code.ValueType; +import com.android.tools.r8.ir.synthetic.CallObjectInitCfCodeProvider; +import com.android.tools.r8.synthesis.SyntheticNaming; +import com.google.common.collect.ImmutableList; +import java.util.Collection; +import java.util.Collections; + +public class RecordRewriter implements CfInstructionDesugaring, CfClassDesugaring { + + private final AppView<?> appView; + private final DexItemFactory factory; + + public static RecordRewriter create(AppView<?> appView) { + return appView.options().shouldDesugarRecords() ? new RecordRewriter(appView) : null; + } + + private RecordRewriter(AppView<?> appView) { + this.appView = appView; + factory = appView.dexItemFactory(); + } + + public void scan( + ProgramMethod programMethod, CfInstructionDesugaringEventConsumer eventConsumer) { + CfCode cfCode = programMethod.getDefinition().getCode().asCfCode(); + for (CfInstruction instruction : cfCode.getInstructions()) { + scanInstruction(instruction, eventConsumer); + } + } + + // The record rewriter scans the cf instructions to figure out if the record class needs to + // be added in the output. the analysis cannot be done in desugarInstruction because the analysis + // does not rewrite any instruction, and desugarInstruction is expected to rewrite at least one + // instruction for assertions to be valid. + private void scanInstruction( + CfInstruction instruction, CfInstructionDesugaringEventConsumer eventConsumer) { + assert !instruction.isInitClass(); + if (instruction.isInvoke()) { + CfInvoke cfInvoke = instruction.asInvoke(); + if (refersToRecord(cfInvoke.getMethod())) { + requiresRecordClass(eventConsumer); + } + return; + } + if (instruction.isFieldInstruction()) { + CfFieldInstruction fieldInstruction = instruction.asFieldInstruction(); + if (refersToRecord(fieldInstruction.getField())) { + requiresRecordClass(eventConsumer); + } + return; + } + if (instruction.isTypeInstruction()) { + CfTypeInstruction typeInstruction = instruction.asTypeInstruction(); + if (refersToRecord(typeInstruction.getType())) { + requiresRecordClass(eventConsumer); + } + return; + } + // TODO(b/179146128): Analyse MethodHandle and MethodType. + } + + @Override + public Collection<CfInstruction> desugarInstruction( + CfInstruction instruction, + FreshLocalProvider freshLocalProvider, + LocalStackAllocator localStackAllocator, + CfInstructionDesugaringEventConsumer eventConsumer, + ProgramMethod context, + MethodProcessingContext methodProcessingContext) { + + // TODO(b/179146128): This is a temporary work-around to test desugaring of records + // without rewriting the record invoke-custom. This should be removed when the record support + // is complete. + if (instruction.isInvokeDynamic() + && context.getHolder().superType == factory.recordType + && (context.getReference().match(factory.recordMembers.toString) + || context.getReference().match(factory.recordMembers.hashCode) + || context.getReference().match(factory.recordMembers.equals))) { + requiresRecordClass(eventConsumer); + CfInstruction constant = + context.getReference().match(factory.recordMembers.toString) + ? new CfConstNull() + : new CfConstNumber(0, ValueType.INT); + return ImmutableList.of(new CfStackInstruction(CfStackInstruction.Opcode.Pop), constant); + } + + CfInstruction desugaredInstruction = desugarInstruction(instruction, context); + return desugaredInstruction == null ? null : Collections.singletonList(desugaredInstruction); + } + + private CfInstruction desugarInstruction(CfInstruction instruction, ProgramMethod context) { + assert !instruction.isInitClass(); + // TODO(b/179146128): Rewrite record invoke-dynamic here. + if (instruction.isInvoke()) { + CfInvoke cfInvoke = instruction.asInvoke(); + DexMethod newMethod = + rewriteMethod(cfInvoke.getMethod(), cfInvoke.isInvokeSuper(context.getHolderType())); + if (newMethod != cfInvoke.getMethod()) { + return new CfInvoke(cfInvoke.getOpcode(), newMethod, cfInvoke.isInterface()); + } + } + return null; + } + + @Override + public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) { + assert !instruction.isInitClass(); + // TODO(b/179146128): This is a temporary work-around to test desugaring of records + // without rewriting the record invoke-custom. This should be removed when the record support + // is complete. + if (instruction.isInvokeDynamic() + && context.getHolder().superType == factory.recordType + && (context.getName() == factory.toStringMethodName + || context.getName() == factory.hashCodeMethodName + || context.getName() == factory.equalsMethodName)) { + return true; + } + if (instruction.isInvoke()) { + CfInvoke cfInvoke = instruction.asInvoke(); + return needsDesugaring(cfInvoke.getMethod(), cfInvoke.isInvokeSuper(context.getHolderType())); + } + return false; + } + + private void requiresRecordClass(RecordDesugaringEventConsumer eventConsumer) { + DexProgramClass recordClass = synthesizeR8Record(); + if (recordClass != null) { + eventConsumer.acceptRecordClass(recordClass); + } + } + + @Override + public boolean needsDesugaring(DexProgramClass clazz) { + assert clazz.isRecord() || clazz.superType != factory.recordType; + return clazz.isRecord(); + } + + @Override + public void desugar(DexProgramClass clazz, CfClassDesugaringEventConsumer eventConsumer) { + if (clazz.isRecord()) { + assert clazz.superType == factory.recordType; + requiresRecordClass(eventConsumer); + clazz.accessFlags.unsetRecord(); + } + } + + private boolean refersToRecord(DexField field) { + assert !refersToRecord(field.holder) : "The java.lang.Record class has no fields."; + return refersToRecord(field.type); + } + + private boolean refersToRecord(DexMethod method) { + if (refersToRecord(method.holder)) { + return true; + } + return refersToRecord(method.proto); + } + + private boolean refersToRecord(DexProto proto) { + if (refersToRecord(proto.returnType)) { + return true; + } + return refersToRecord(proto.parameters.values); + } + + private boolean refersToRecord(DexType[] types) { + for (DexType type : types) { + if (refersToRecord(type)) { + return true; + } + } + return false; + } + + private boolean refersToRecord(DexType type) { + return type == factory.recordType; + } + + private boolean needsDesugaring(DexMethod method, boolean isSuper) { + return rewriteMethod(method, isSuper) != method; + } + + @SuppressWarnings("ConstantConditions") + private DexMethod rewriteMethod(DexMethod method, boolean isSuper) { + if (method.holder != factory.recordType || method.isInstanceInitializer(factory)) { + return method; + } + assert method == factory.recordMembers.equals + || method == factory.recordMembers.hashCode + || method == factory.recordMembers.toString; + if (isSuper) { + // TODO(b/179146128): Support rewriting invoke-super to a Record method. + throw new CompilationError("Rewrite invoke-super to abstract method error."); + } + if (method == factory.recordMembers.equals) { + return factory.objectMembers.equals; + } + if (method == factory.recordMembers.toString) { + return factory.objectMembers.toString; + } + assert method == factory.recordMembers.hashCode; + return factory.objectMembers.toString; + } + + private DexProgramClass synthesizeR8Record() { + DexItemFactory factory = appView.dexItemFactory(); + DexClass recordClass = + appView.appInfo().definitionForWithoutExistenceAssert(factory.recordType); + if (recordClass != null && recordClass.isProgramClass()) { + return null; + } + assert recordClass == null || recordClass.isLibraryClass(); + DexEncodedMethod init = synthesizeRecordInitMethod(); + // TODO(b/179146128): We may want to remove here the class from the library classes if present + // in cf to cf. + return appView + .getSyntheticItems() + .createFixedClassFromType( + SyntheticNaming.SyntheticKind.RECORD_TAG, + factory.recordType, + factory, + builder -> builder.setAbstract().setDirectMethods(Collections.singletonList(init))); + } + + private DexEncodedMethod synthesizeRecordInitMethod() { + MethodAccessFlags methodAccessFlags = + MethodAccessFlags.fromSharedAccessFlags( + Constants.ACC_SYNTHETIC | Constants.ACC_PROTECTED, true); + DexEncodedMethod init = + new DexEncodedMethod( + factory.recordMembers.init, + methodAccessFlags, + MethodTypeSignature.noSignature(), + DexAnnotationSet.empty(), + ParameterAnnotationsList.empty(), + null, + true); + init.setCode( + new CallObjectInitCfCodeProvider(appView, factory.r8RecordType).generateCfCode(), appView); + return init; + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java index da56b69..b7b0db0 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -2115,6 +2115,53 @@ ImmutableList.of()); } + public static CfCode IntegerMethods_parseIntSubsequenceWithRadix( + InternalOptions options, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + return new CfCode( + method.holder, + 3, + 4, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 2), + new CfInvoke( + 185, + options.itemFactory.createMethod( + options.itemFactory.charSequenceType, + options.itemFactory.createProto( + options.itemFactory.charSequenceType, + options.itemFactory.intType, + options.itemFactory.intType), + options.itemFactory.createString("subSequence")), + true), + new CfInvoke( + 185, + options.itemFactory.createMethod( + options.itemFactory.charSequenceType, + options.itemFactory.createProto(options.itemFactory.stringType), + options.itemFactory.createString("toString")), + true), + new CfLoad(ValueType.INT, 3), + new CfInvoke( + 184, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/Integer;"), + options.itemFactory.createProto( + options.itemFactory.intType, + options.itemFactory.stringType, + options.itemFactory.intType), + options.itemFactory.createString("parseInt")), + false), + new CfReturn(ValueType.INT), + label1), + ImmutableList.of(), + ImmutableList.of()); + } + public static CfCode IntegerMethods_parseUnsignedInt(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); @@ -2643,6 +2690,53 @@ ImmutableList.of()); } + public static CfCode LongMethods_parseLongSubsequenceWithRadix( + InternalOptions options, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + return new CfCode( + method.holder, + 3, + 4, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 2), + new CfInvoke( + 185, + options.itemFactory.createMethod( + options.itemFactory.charSequenceType, + options.itemFactory.createProto( + options.itemFactory.charSequenceType, + options.itemFactory.intType, + options.itemFactory.intType), + options.itemFactory.createString("subSequence")), + true), + new CfInvoke( + 185, + options.itemFactory.createMethod( + options.itemFactory.charSequenceType, + options.itemFactory.createProto(options.itemFactory.stringType), + options.itemFactory.createString("toString")), + true), + new CfLoad(ValueType.INT, 3), + new CfInvoke( + 184, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/Long;"), + options.itemFactory.createProto( + options.itemFactory.longType, + options.itemFactory.stringType, + options.itemFactory.intType), + options.itemFactory.createString("parseLong")), + false), + new CfReturn(ValueType.LONG), + label1), + ImmutableList.of(), + ImmutableList.of()); + } + public static CfCode LongMethods_parseUnsignedLong(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); @@ -2670,7 +2764,7 @@ ImmutableList.of()); } - public static CfCode LongMethods_parseUnsignedLongWithRadix( + public static CfCode LongMethods_parseUnsignedLongSubsequenceWithRadix( InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); @@ -2697,20 +2791,15 @@ return new CfCode( method.holder, 5, - 10, + 12, ImmutableList.of( label0, - new CfLoad(ValueType.OBJECT, 0), - new CfInvoke( - 182, - options.itemFactory.createMethod( - options.itemFactory.stringType, - options.itemFactory.createProto(options.itemFactory.intType), - options.itemFactory.createString("length")), - false), - new CfStore(ValueType.INT, 2), - label1, new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.INT, 1), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfStore(ValueType.INT, 4), + label1, + new CfLoad(ValueType.INT, 4), new CfIf(If.Type.NE, ValueType.INT, label3), label2, new CfNew(options.itemFactory.createType("Ljava/lang/NumberFormatException;")), @@ -2728,25 +2817,29 @@ label3, new CfFrame( new Int2ReferenceAVLTreeMap<>( - new int[] {0, 1, 2}, + new int[] {0, 1, 2, 3, 4}, new FrameType[] { - FrameType.initialized(options.itemFactory.stringType), + FrameType.initialized(options.itemFactory.charSequenceType), + FrameType.initialized(options.itemFactory.intType), + FrameType.initialized(options.itemFactory.intType), FrameType.initialized(options.itemFactory.intType), FrameType.initialized(options.itemFactory.intType) }), new ArrayDeque<>(Arrays.asList())), - new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 3), new CfConstNumber(2, ValueType.INT), new CfIfCmp(If.Type.LT, ValueType.INT, label4), - new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 3), new CfConstNumber(36, ValueType.INT), new CfIfCmp(If.Type.LE, ValueType.INT, label5), label4, new CfFrame( new Int2ReferenceAVLTreeMap<>( - new int[] {0, 1, 2}, + new int[] {0, 1, 2, 3, 4}, new FrameType[] { - FrameType.initialized(options.itemFactory.stringType), + FrameType.initialized(options.itemFactory.charSequenceType), + FrameType.initialized(options.itemFactory.intType), + FrameType.initialized(options.itemFactory.intType), FrameType.initialized(options.itemFactory.intType), FrameType.initialized(options.itemFactory.intType) }), @@ -2754,7 +2847,7 @@ new CfNew(options.itemFactory.createType("Ljava/lang/NumberFormatException;")), new CfStackInstruction(CfStackInstruction.Opcode.Dup), new CfConstString(options.itemFactory.createString("illegal radix: ")), - new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 3), new CfInvoke( 184, options.itemFactory.createMethod( @@ -2783,15 +2876,17 @@ label5, new CfFrame( new Int2ReferenceAVLTreeMap<>( - new int[] {0, 1, 2}, + new int[] {0, 1, 2, 3, 4}, new FrameType[] { - FrameType.initialized(options.itemFactory.stringType), + FrameType.initialized(options.itemFactory.charSequenceType), + FrameType.initialized(options.itemFactory.intType), + FrameType.initialized(options.itemFactory.intType), FrameType.initialized(options.itemFactory.intType), FrameType.initialized(options.itemFactory.intType) }), new ArrayDeque<>(Arrays.asList())), new CfConstNumber(-1, ValueType.LONG), - new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 3), new CfNumberConversion(NumericType.INT, NumericType.LONG), new CfInvoke( 184, @@ -2803,62 +2898,70 @@ options.itemFactory.longType), options.itemFactory.createString("divideUnsigned")), false), - new CfStore(ValueType.LONG, 3), + new CfStore(ValueType.LONG, 5), label6, new CfLoad(ValueType.OBJECT, 0), - new CfConstNumber(0, ValueType.INT), + new CfLoad(ValueType.INT, 1), new CfInvoke( - 182, + 185, options.itemFactory.createMethod( - options.itemFactory.stringType, + options.itemFactory.charSequenceType, options.itemFactory.createProto( options.itemFactory.charType, options.itemFactory.intType), options.itemFactory.createString("charAt")), - false), + true), new CfConstNumber(43, ValueType.INT), new CfIfCmp(If.Type.NE, ValueType.INT, label7), - new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.INT, 4), new CfConstNumber(1, ValueType.INT), new CfIfCmp(If.Type.LE, ValueType.INT, label7), + new CfLoad(ValueType.INT, 1), new CfConstNumber(1, ValueType.INT), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), new CfGoto(label8), label7, new CfFrame( new Int2ReferenceAVLTreeMap<>( - new int[] {0, 1, 2, 3}, + new int[] {0, 1, 2, 3, 4, 5}, new FrameType[] { - FrameType.initialized(options.itemFactory.stringType), + FrameType.initialized(options.itemFactory.charSequenceType), + FrameType.initialized(options.itemFactory.intType), + FrameType.initialized(options.itemFactory.intType), FrameType.initialized(options.itemFactory.intType), FrameType.initialized(options.itemFactory.intType), FrameType.initialized(options.itemFactory.longType) }), new ArrayDeque<>(Arrays.asList())), - new CfConstNumber(0, ValueType.INT), + new CfLoad(ValueType.INT, 1), label8, new CfFrame( new Int2ReferenceAVLTreeMap<>( - new int[] {0, 1, 2, 3}, + new int[] {0, 1, 2, 3, 4, 5}, new FrameType[] { - FrameType.initialized(options.itemFactory.stringType), + FrameType.initialized(options.itemFactory.charSequenceType), + FrameType.initialized(options.itemFactory.intType), + FrameType.initialized(options.itemFactory.intType), FrameType.initialized(options.itemFactory.intType), FrameType.initialized(options.itemFactory.intType), FrameType.initialized(options.itemFactory.longType) }), new ArrayDeque<>( Arrays.asList(FrameType.initialized(options.itemFactory.intType)))), - new CfStore(ValueType.INT, 5), + new CfStore(ValueType.INT, 7), label9, new CfConstNumber(0, ValueType.LONG), - new CfStore(ValueType.LONG, 6), + new CfStore(ValueType.LONG, 8), label10, - new CfLoad(ValueType.INT, 5), - new CfStore(ValueType.INT, 8), + new CfLoad(ValueType.INT, 7), + new CfStore(ValueType.INT, 10), label11, new CfFrame( new Int2ReferenceAVLTreeMap<>( - new int[] {0, 1, 2, 3, 5, 6, 8}, + new int[] {0, 1, 2, 3, 4, 5, 7, 8, 10}, new FrameType[] { - FrameType.initialized(options.itemFactory.stringType), + FrameType.initialized(options.itemFactory.charSequenceType), + FrameType.initialized(options.itemFactory.intType), + FrameType.initialized(options.itemFactory.intType), FrameType.initialized(options.itemFactory.intType), FrameType.initialized(options.itemFactory.intType), FrameType.initialized(options.itemFactory.longType), @@ -2867,21 +2970,21 @@ FrameType.initialized(options.itemFactory.intType) }), new ArrayDeque<>(Arrays.asList())), - new CfLoad(ValueType.INT, 8), + new CfLoad(ValueType.INT, 10), new CfLoad(ValueType.INT, 2), new CfIfCmp(If.Type.GE, ValueType.INT, label20), label12, new CfLoad(ValueType.OBJECT, 0), - new CfLoad(ValueType.INT, 8), + new CfLoad(ValueType.INT, 10), new CfInvoke( - 182, + 185, options.itemFactory.createMethod( - options.itemFactory.stringType, + options.itemFactory.charSequenceType, options.itemFactory.createProto( options.itemFactory.charType, options.itemFactory.intType), options.itemFactory.createString("charAt")), - false), - new CfLoad(ValueType.INT, 1), + true), + new CfLoad(ValueType.INT, 3), new CfInvoke( 184, options.itemFactory.createMethod( @@ -2892,9 +2995,9 @@ options.itemFactory.intType), options.itemFactory.createString("digit")), false), - new CfStore(ValueType.INT, 9), + new CfStore(ValueType.INT, 11), label13, - new CfLoad(ValueType.INT, 9), + new CfLoad(ValueType.INT, 11), new CfConstNumber(-1, ValueType.INT), new CfIfCmp(If.Type.NE, ValueType.INT, label15), label14, @@ -2902,6 +3005,13 @@ new CfStackInstruction(CfStackInstruction.Opcode.Dup), new CfLoad(ValueType.OBJECT, 0), new CfInvoke( + 185, + options.itemFactory.createMethod( + options.itemFactory.charSequenceType, + options.itemFactory.createProto(options.itemFactory.stringType), + options.itemFactory.createString("toString")), + true), + new CfInvoke( 183, options.itemFactory.createMethod( options.itemFactory.createType("Ljava/lang/NumberFormatException;"), @@ -2913,9 +3023,11 @@ label15, new CfFrame( new Int2ReferenceAVLTreeMap<>( - new int[] {0, 1, 2, 3, 5, 6, 8, 9}, + new int[] {0, 1, 2, 3, 4, 5, 7, 8, 10, 11}, new FrameType[] { - FrameType.initialized(options.itemFactory.stringType), + FrameType.initialized(options.itemFactory.charSequenceType), + FrameType.initialized(options.itemFactory.intType), + FrameType.initialized(options.itemFactory.intType), FrameType.initialized(options.itemFactory.intType), FrameType.initialized(options.itemFactory.intType), FrameType.initialized(options.itemFactory.longType), @@ -2925,21 +3037,21 @@ FrameType.initialized(options.itemFactory.intType) }), new ArrayDeque<>(Arrays.asList())), - new CfLoad(ValueType.LONG, 6), + new CfLoad(ValueType.LONG, 8), new CfConstNumber(0, ValueType.LONG), new CfCmp(Cmp.Bias.NONE, NumericType.LONG), new CfIf(If.Type.LT, ValueType.INT, label17), - new CfLoad(ValueType.LONG, 6), - new CfLoad(ValueType.LONG, 3), + new CfLoad(ValueType.LONG, 8), + new CfLoad(ValueType.LONG, 5), new CfCmp(Cmp.Bias.NONE, NumericType.LONG), new CfIf(If.Type.GT, ValueType.INT, label17), - new CfLoad(ValueType.LONG, 6), - new CfLoad(ValueType.LONG, 3), + new CfLoad(ValueType.LONG, 8), + new CfLoad(ValueType.LONG, 5), new CfCmp(Cmp.Bias.NONE, NumericType.LONG), new CfIf(If.Type.NE, ValueType.INT, label18), - new CfLoad(ValueType.INT, 9), + new CfLoad(ValueType.INT, 11), new CfConstNumber(-1, ValueType.LONG), - new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 3), new CfNumberConversion(NumericType.INT, NumericType.LONG), label16, new CfInvoke( @@ -2957,9 +3069,11 @@ label17, new CfFrame( new Int2ReferenceAVLTreeMap<>( - new int[] {0, 1, 2, 3, 5, 6, 8, 9}, + new int[] {0, 1, 2, 3, 4, 5, 7, 8, 10, 11}, new FrameType[] { - FrameType.initialized(options.itemFactory.stringType), + FrameType.initialized(options.itemFactory.charSequenceType), + FrameType.initialized(options.itemFactory.intType), + FrameType.initialized(options.itemFactory.intType), FrameType.initialized(options.itemFactory.intType), FrameType.initialized(options.itemFactory.intType), FrameType.initialized(options.itemFactory.longType), @@ -2974,6 +3088,13 @@ new CfConstString(options.itemFactory.createString("Too large for unsigned long: ")), new CfLoad(ValueType.OBJECT, 0), new CfInvoke( + 185, + options.itemFactory.createMethod( + options.itemFactory.charSequenceType, + options.itemFactory.createProto(options.itemFactory.stringType), + options.itemFactory.createString("toString")), + true), + new CfInvoke( 182, options.itemFactory.createMethod( options.itemFactory.stringType, @@ -2993,9 +3114,11 @@ label18, new CfFrame( new Int2ReferenceAVLTreeMap<>( - new int[] {0, 1, 2, 3, 5, 6, 8, 9}, + new int[] {0, 1, 2, 3, 4, 5, 7, 8, 10, 11}, new FrameType[] { - FrameType.initialized(options.itemFactory.stringType), + FrameType.initialized(options.itemFactory.charSequenceType), + FrameType.initialized(options.itemFactory.intType), + FrameType.initialized(options.itemFactory.intType), FrameType.initialized(options.itemFactory.intType), FrameType.initialized(options.itemFactory.intType), FrameType.initialized(options.itemFactory.longType), @@ -3005,23 +3128,25 @@ FrameType.initialized(options.itemFactory.intType) }), new ArrayDeque<>(Arrays.asList())), - new CfLoad(ValueType.LONG, 6), - new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.LONG, 8), + new CfLoad(ValueType.INT, 3), new CfNumberConversion(NumericType.INT, NumericType.LONG), new CfArithmeticBinop(CfArithmeticBinop.Opcode.Mul, NumericType.LONG), - new CfLoad(ValueType.INT, 9), + new CfLoad(ValueType.INT, 11), new CfNumberConversion(NumericType.INT, NumericType.LONG), new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.LONG), - new CfStore(ValueType.LONG, 6), + new CfStore(ValueType.LONG, 8), label19, - new CfIinc(8, 1), + new CfIinc(10, 1), new CfGoto(label11), label20, new CfFrame( new Int2ReferenceAVLTreeMap<>( - new int[] {0, 1, 2, 3, 5, 6}, + new int[] {0, 1, 2, 3, 4, 5, 7, 8}, new FrameType[] { - FrameType.initialized(options.itemFactory.stringType), + FrameType.initialized(options.itemFactory.charSequenceType), + FrameType.initialized(options.itemFactory.intType), + FrameType.initialized(options.itemFactory.intType), FrameType.initialized(options.itemFactory.intType), FrameType.initialized(options.itemFactory.intType), FrameType.initialized(options.itemFactory.longType), @@ -3029,13 +3154,52 @@ FrameType.initialized(options.itemFactory.longType) }), new ArrayDeque<>(Arrays.asList())), - new CfLoad(ValueType.LONG, 6), + new CfLoad(ValueType.LONG, 8), new CfReturn(ValueType.LONG), label21), ImmutableList.of(), ImmutableList.of()); } + public static CfCode LongMethods_parseUnsignedLongWithRadix( + InternalOptions options, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + return new CfCode( + method.holder, + 4, + 2, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfConstNumber(0, ValueType.INT), + new CfLoad(ValueType.OBJECT, 0), + new CfInvoke( + 182, + options.itemFactory.createMethod( + options.itemFactory.stringType, + options.itemFactory.createProto(options.itemFactory.intType), + options.itemFactory.createString("length")), + false), + new CfLoad(ValueType.INT, 1), + new CfInvoke( + 184, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/Long;"), + options.itemFactory.createProto( + options.itemFactory.longType, + options.itemFactory.charSequenceType, + options.itemFactory.intType, + options.itemFactory.intType, + options.itemFactory.intType), + options.itemFactory.createString("parseUnsignedLong")), + false), + new CfReturn(ValueType.LONG), + label1), + ImmutableList.of(), + ImmutableList.of()); + } + public static CfCode LongMethods_remainderUnsigned(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java index 51949c9..8b928aa 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java
@@ -8,6 +8,7 @@ import com.android.tools.r8.ir.code.NumericType; import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.FullMethodInvokeRewriter; import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.MethodInvokeRewriter; +import com.android.tools.r8.ir.desugar.LocalStackAllocator; import java.util.Collection; import java.util.Collections; import org.objectweb.asm.Opcodes; @@ -34,7 +35,8 @@ public static MethodInvokeRewriter rewriteAsIdentity() { return new FullMethodInvokeRewriter() { @Override - public Collection<CfInstruction> rewrite(CfInvoke invoke, DexItemFactory factory) { + public Collection<CfInstruction> rewrite( + CfInvoke invoke, DexItemFactory factory, LocalStackAllocator localStackAllocator) { // The invoke consumes the stack value and pushes another assumed to be the same. return Collections.emptyList(); }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java index f8ab4a7..20998db 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.FullMethodInvokeRewriter; import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.MethodInvokeRewriter; +import com.android.tools.r8.ir.desugar.LocalStackAllocator; import com.google.common.collect.ImmutableList; import java.util.Collection; import org.objectweb.asm.Opcodes; @@ -32,8 +33,10 @@ return new FullMethodInvokeRewriter() { @Override - public Collection<CfInstruction> rewrite(CfInvoke invoke, DexItemFactory factory) { + public Collection<CfInstruction> rewrite( + CfInvoke invoke, DexItemFactory factory, LocalStackAllocator localStackAllocator) { // requireNonNull returns the operand, so dup top-of-stack, do getClass and pop the class. + localStackAllocator.allocateLocalStack(1); return ImmutableList.of( new CfStackInstruction(Opcode.Dup), new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.objectMembers.getClass, false),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java index 2518219..44a37b0 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java
@@ -45,8 +45,8 @@ DexClass hostClass = nest.getHostClass(); for (DexClass memberClass : nest.getMembers()) { if (hostClass.isProgramClass() || memberClass.isProgramClass()) { - reportDependencyEdge(hostClass, memberClass, appView.options()); - reportDependencyEdge(memberClass, hostClass, appView.options()); + reportDependencyEdge(hostClass, memberClass, appView.appInfo()); + reportDependencyEdge(memberClass, hostClass, appView.appInfo()); } } },
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 d01470e..d3dc297 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
@@ -27,11 +27,14 @@ import com.android.tools.r8.graph.DexProto; import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult; import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerUtils; import com.android.tools.r8.ir.analysis.equivalence.BasicBlockBehavioralSubsumption; import com.android.tools.r8.ir.analysis.type.TypeAnalysis; import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.analysis.value.AbstractValue; +import com.android.tools.r8.ir.analysis.value.ConstantOrNonConstantNumberValue; import com.android.tools.r8.ir.analysis.value.SingleConstClassValue; import com.android.tools.r8.ir.analysis.value.SingleFieldValue; import com.android.tools.r8.ir.code.AlwaysMaterializingNop; @@ -54,6 +57,7 @@ import com.android.tools.r8.ir.code.If; import com.android.tools.r8.ir.code.If.Type; import com.android.tools.r8.ir.code.InstanceFieldInstruction; +import com.android.tools.r8.ir.code.InstanceGet; import com.android.tools.r8.ir.code.InstanceOf; import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.Instruction.SideEffectAssumption; @@ -1092,6 +1096,10 @@ BasicBlock block, InstructionListIterator iterator, IntSwitch theSwitch) { + if (disableSwitchToIfRewritingForClassIdComparisons(theSwitch)) { + return; + } + if (theSwitch.numberOfKeys() == 1) { rewriteSingleKeySwitchToIf(code, block, iterator, theSwitch); return; @@ -1186,6 +1194,27 @@ } } + // TODO(b/181732463): We currently disable switch-to-if rewritings for switches on $r8$classId + // field values (from horizontal class merging. See bug for more details. + private boolean disableSwitchToIfRewritingForClassIdComparisons(IntSwitch theSwitch) { + Value switchValue = theSwitch.value().getAliasedValue(); + if (!switchValue.isDefinedByInstructionSatisfying(Instruction::isInstanceGet)) { + return false; + } + AppInfoWithLiveness appInfo = appView.appInfoWithLiveness(); + if (appInfo == null) { + return false; + } + InstanceGet instanceGet = switchValue.getDefinition().asInstanceGet(); + SuccessfulFieldResolutionResult resolutionResult = + appInfo.resolveField(instanceGet.getField()).asSuccessfulResolution(); + if (resolutionResult == null) { + return false; + } + DexEncodedField resolvedField = resolutionResult.getResolvedField(); + return HorizontalClassMergerUtils.isClassIdField(appView, resolvedField); + } + private SwitchCaseEliminator removeUnnecessarySwitchCases( IRCode code, Switch theSwitch, @@ -1197,6 +1226,7 @@ new BasicBlockBehavioralSubsumption(appView, code); // Compute the set of switch cases that can be removed. + AbstractValue switchAbstractValue = theSwitch.value().getAbstractValue(appView, code.context()); for (int i = 0; i < theSwitch.numberOfKeys(); i++) { BasicBlock targetBlock = theSwitch.targetBlock(i); @@ -1210,7 +1240,7 @@ // This switch case can be removed if the behavior of the target block is equivalent to the // behavior of the default block, or if the switch case is unreachable. - if (switchCaseAnalyzer.switchCaseIsUnreachable(theSwitch, i) + if (switchCaseAnalyzer.switchCaseIsUnreachable(theSwitch, switchAbstractValue, i) || behavioralSubsumption.isSubsumedBy(targetBlock, defaultTarget)) { if (eliminator == null) { eliminator = new SwitchCaseEliminator(theSwitch, iterator); @@ -1218,6 +1248,16 @@ eliminator.markSwitchCaseForRemoval(i); } } + + if (eliminator == null || eliminator.isFallthroughLive()) { + if (switchCaseAnalyzer.switchFallthroughIsNeverHit(theSwitch, switchAbstractValue)) { + if (eliminator == null) { + eliminator = new SwitchCaseEliminator(theSwitch, iterator); + } + eliminator.markSwitchFallthroughAsNeverHit(); + } + } + if (eliminator != null) { eliminator.optimize(); } @@ -2619,6 +2659,16 @@ } } + if (theIf.getType() == Type.EQ || theIf.getType() == Type.NE) { + AbstractValue lhsAbstractValue = lhs.getAbstractValue(appView, code.context()); + if (lhsAbstractValue.isConstantOrNonConstantNumberValue() + && !lhsAbstractValue.asConstantOrNonConstantNumberValue().containsInt(0)) { + // Value doesn't contain zero at all. + simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromCondition(1)); + return true; + } + } + if (lhs.hasValueRange()) { LongInterval interval = lhs.getValueRange(); if (!interval.containsValue(0)) { @@ -2691,6 +2741,23 @@ return true; } + if (theIf.getType() == Type.EQ || theIf.getType() == Type.NE) { + AbstractValue lhsAbstractValue = lhs.getAbstractValue(appView, code.context()); + AbstractValue rhsAbstractValue = rhs.getAbstractValue(appView, code.context()); + if (lhsAbstractValue.isConstantOrNonConstantNumberValue() + && rhsAbstractValue.isConstantOrNonConstantNumberValue()) { + ConstantOrNonConstantNumberValue lhsNumberValue = + lhsAbstractValue.asConstantOrNonConstantNumberValue(); + ConstantOrNonConstantNumberValue rhsNumberValue = + rhsAbstractValue.asConstantOrNonConstantNumberValue(); + if (!lhsNumberValue.mayOverlapWith(rhsNumberValue)) { + // No overlap. + simplifyIfWithKnownCondition(code, block, theIf, 1); + return true; + } + } + } + if (lhs.hasValueRange() && rhs.hasValueRange()) { // Zero test with a value range, or comparison between between two values, // each with a value ranges. @@ -3372,6 +3439,7 @@ Instruction arrayDefinition = array.getDefinition(); assert arrayDefinition != null; + Set<Phi> phiUsers = arrayLength.outValue().uniquePhiUsers(); if (arrayDefinition.isNewArrayEmpty()) { Value size = arrayDefinition.asNewArrayEmpty().size(); arrayLength.outValue().replaceUsers(size); @@ -3380,7 +3448,11 @@ int size = (int) arrayDefinition.asNewArrayFilledData().size; ConstNumber constSize = code.createIntConstant(size); iterator.replaceCurrentInstruction(constSize); + } else { + continue; } + + phiUsers.forEach(Phi::removeTrivialPhi); // TODO(139489070): static-get of constant array } assert code.isConsistentSSA();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java index 33e414f..8e66601 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -201,13 +201,13 @@ // If we do this it can increase the size of the main dex dependent classes. if (reason != Reason.FORCE && inliner.mainDexInfo.disallowInliningIntoContext( - appView.appInfo(), method, singleTarget)) { + appView.appInfo(), method, singleTarget, appView.getSyntheticItems())) { whyAreYouNotInliningReporter.reportInlineeRefersToClassesNotInMainDex(); return false; } assert reason != Reason.FORCE || !inliner.mainDexInfo.disallowInliningIntoContext( - appView.appInfo(), method, singleTarget); + appView.appInfo(), method, singleTarget, appView.getSyntheticItems()); return true; }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java index f5f4d70..fa3886e 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -391,6 +391,9 @@ } private boolean isRebindingNewClassIntoMainDex(ProgramMethod context, DexMethod reboundMethod) { - return !appView.appInfo().getMainDexInfo().canRebindReference(context, reboundMethod); + return !appView + .appInfo() + .getMainDexInfo() + .canRebindReference(context, reboundMethod, appView.getSyntheticItems()); } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java index 289b2ba..802fdb3 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -92,7 +92,8 @@ && !appView .appInfo() .getMainDexInfo() - .canRebindReference(code.context(), baseClass.getType())) { + .canRebindReference( + code.context(), baseClass.getType(), appView.getSyntheticItems())) { return; }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java b/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java index 6c9bfbb..11e0b51 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java
@@ -11,6 +11,7 @@ import com.android.tools.r8.ir.code.IntSwitch; import com.android.tools.r8.ir.code.StringSwitch; import com.android.tools.r8.ir.code.Switch; +import com.android.tools.r8.utils.BooleanUtils; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; @@ -28,6 +29,7 @@ private int alwaysHitCase = -1; private BasicBlock alwaysHitTarget; + private boolean liveFallthrough = true; private boolean mayHaveIntroducedUnreachableBlocks = false; private IntSet switchCasesToBeRemoved; @@ -39,13 +41,13 @@ } private boolean allSwitchCasesMarkedForRemoval() { - assert switchCasesToBeRemoved != null; - return switchCasesToBeRemoved.size() == theSwitch.numberOfKeys(); + return switchCasesToBeRemoved != null + && switchCasesToBeRemoved.size() == theSwitch.numberOfKeys(); } private boolean canBeOptimized() { assert switchCasesToBeRemoved == null || !switchCasesToBeRemoved.isEmpty(); - return switchCasesToBeRemoved != null || hasAlwaysHitCase(); + return switchCasesToBeRemoved != null || hasAlwaysHitCase() || !isFallthroughLive(); } boolean mayHaveIntroducedUnreachableBlocks() { @@ -56,11 +58,19 @@ if (hasAlwaysHitCase()) { return index == alwaysHitCase; } - return !switchCasesToBeRemoved.contains(index); + if (switchCasesToBeRemoved != null) { + return !switchCasesToBeRemoved.contains(index); + } + assert !isFallthroughLive(); + return true; + } + + public boolean isFallthroughDead() { + return !isFallthroughLive(); } public boolean isFallthroughLive() { - return !hasAlwaysHitCase(); + return liveFallthrough; } public boolean hasAlwaysHitCase() { @@ -71,6 +81,7 @@ assert alwaysHitCase < 0; alwaysHitCase = i; alwaysHitTarget = theSwitch.targetBlock(i); + liveFallthrough = false; } void markSwitchCaseForRemoval(int i) { @@ -80,6 +91,11 @@ switchCasesToBeRemoved.add(i); } + void markSwitchFallthroughAsNeverHit() { + assert !hasAlwaysHitCase(); + liveFallthrough = false; + } + boolean optimize() { if (canBeOptimized()) { int originalNumberOfSuccessors = block.getSuccessors().size(); @@ -147,53 +163,70 @@ targetBlockIndexOffset[i] += targetBlockIndexOffset[i - 1]; } - int newNumberOfKeys = theSwitch.numberOfKeys() - switchCasesToBeRemoved.size(); + int newFallthrough = theSwitch.numberOfKeys(); + if (isFallthroughDead()) { + for (int i = theSwitch.numberOfKeys() - 1; i >= 0; i--) { + if (isSwitchCaseLive(i)) { + newFallthrough = i; + break; + } + } + } + + // Compute the number of removed switch cases. If the fallthrough is dead, we promote one of the + // live switch cases to being the fallthrough instead. + int numberOfRemovedSwitchCases = + switchCasesToBeRemoved != null ? switchCasesToBeRemoved.size() : 0; + numberOfRemovedSwitchCases += BooleanUtils.intValue(isFallthroughDead()); + + int newNumberOfKeys = theSwitch.numberOfKeys() - numberOfRemovedSwitchCases; int[] newTargetBlockIndices = new int[newNumberOfKeys]; - for (int i = 0, j = 0; i < theSwitch.numberOfKeys(); i++) { - if (!switchCasesToBeRemoved.contains(i)) { + for (int i = 0, j = 0; i < newFallthrough; i++) { + if (isSwitchCaseLive(i)) { newTargetBlockIndices[j] = theSwitch.getTargetBlockIndex(i) - targetBlockIndexOffset[theSwitch.getTargetBlockIndex(i)]; assert newTargetBlockIndices[j] < block.getSuccessors().size(); - assert newTargetBlockIndices[j] != theSwitch.getFallthroughBlockIndex(); j++; } } + int fallthroughBlockIndex; + if (isFallthroughLive()) { + fallthroughBlockIndex = + theSwitch.getFallthroughBlockIndex() + - targetBlockIndexOffset[theSwitch.getFallthroughBlockIndex()]; + } else { + fallthroughBlockIndex = + theSwitch.getTargetBlockIndex(newFallthrough) + - targetBlockIndexOffset[theSwitch.getTargetBlockIndex(newFallthrough)]; + } + Switch replacement; if (theSwitch.isIntSwitch()) { IntSwitch intSwitch = theSwitch.asIntSwitch(); int[] newKeys = new int[newNumberOfKeys]; - for (int i = 0, j = 0; i < theSwitch.numberOfKeys(); i++) { - if (!switchCasesToBeRemoved.contains(i)) { + for (int i = 0, j = 0; i < newFallthrough; i++) { + if (isSwitchCaseLive(i)) { newKeys[j] = intSwitch.getKey(i); j++; } } replacement = - new IntSwitch( - theSwitch.value(), - newKeys, - newTargetBlockIndices, - theSwitch.getFallthroughBlockIndex() - - targetBlockIndexOffset[theSwitch.getFallthroughBlockIndex()]); + new IntSwitch(theSwitch.value(), newKeys, newTargetBlockIndices, fallthroughBlockIndex); } else { assert theSwitch.isStringSwitch(); StringSwitch stringSwitch = theSwitch.asStringSwitch(); DexString[] newKeys = new DexString[newNumberOfKeys]; - for (int i = 0, j = 0; i < theSwitch.numberOfKeys(); i++) { - if (!switchCasesToBeRemoved.contains(i)) { + for (int i = 0, j = 0; i < newFallthrough; i++) { + if (isSwitchCaseLive(i)) { newKeys[j] = stringSwitch.getKey(i); j++; } } replacement = new StringSwitch( - theSwitch.value(), - newKeys, - newTargetBlockIndices, - theSwitch.getFallthroughBlockIndex() - - targetBlockIndexOffset[theSwitch.getFallthroughBlockIndex()]); + theSwitch.value(), newKeys, newTargetBlockIndices, fallthroughBlockIndex); } iterator.replaceCurrentInstruction(replacement); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java index 78dc973..4857999 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -183,14 +183,11 @@ continue; } - assert processor.getReceivers().verifyReceiverSetsAreDisjoint(); - // Is inlining allowed. InliningIRProvider inliningIRProvider = new InliningIRProvider(appView, method, code, methodProcessor); ClassInlinerCostAnalysis costAnalysis = - new ClassInlinerCostAnalysis( - appView, inliningIRProvider, processor.getReceivers().getDefiniteReceiverAliases()); + new ClassInlinerCostAnalysis(appView, inliningIRProvider, processor.getReceivers()); if (costAnalysis.willExceedInstructionBudget( code, processor.getEligibleClass(), @@ -213,8 +210,7 @@ // Inline the class instance. Set<Value> affectedValues = Sets.newIdentityHashSet(); try { - anyInlinedMethods |= - processor.processInlining(code, affectedValues, defaultOracle, inliningIRProvider); + anyInlinedMethods |= processor.processInlining(code, affectedValues, inliningIRProvider); } catch (IllegalClassInlinerStateException e) { // We introduced a user that we cannot handle in the class inliner as a result of force // inlining. Abort gracefully from class inlining without removing the instance. @@ -227,8 +223,6 @@ anyInlinedMethods = true; } - assert inliningIRProvider.verifyIRCacheIsEmpty(); - // Restore normality. code.removeAllDeadAndTrivialPhis(affectedValues); if (!affectedValues.isEmpty()) {
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 3feac87..33fa6fe 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
@@ -28,17 +28,17 @@ private final AppView<AppInfoWithLiveness> appView; private final InliningIRProvider inliningIRProvider; - private final Set<Value> definiteReceiverAliases; + private final ClassInlinerReceiverSet receivers; private int estimatedCost = 0; ClassInlinerCostAnalysis( AppView<AppInfoWithLiveness> appView, InliningIRProvider inliningIRProvider, - Set<Value> definiteReceiverAliases) { + ClassInlinerReceiverSet receivers) { this.appView = appView; this.inliningIRProvider = inliningIRProvider; - this.definiteReceiverAliases = definiteReceiverAliases; + this.receivers = receivers; } boolean willExceedInstructionBudget( @@ -145,10 +145,10 @@ Set<Value> receiverAliasesInInlinee = Sets.newIdentityHashSet(); for (int i = 0; i < invoke.inValues().size(); i++) { Value inValue = invoke.inValues().get(i); - if (definiteReceiverAliases.contains(inValue)) { + if (receivers.isReceiverAlias(inValue)) { receiverAliasesInInlinee.add(arguments.get(i)); } else { - assert !definiteReceiverAliases.contains(inValue.getAliasedValue()); + assert !receivers.isReceiverAlias(inValue.getAliasedValue()); } } return receiverAliasesInInlinee;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java deleted file mode 100644 index 78e756d..0000000 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java +++ /dev/null
@@ -1,37 +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.classinliner; - -import com.android.tools.r8.graph.DexMethod; -import com.android.tools.r8.ir.code.Invoke; -import com.android.tools.r8.utils.OptionalBool; -import com.android.tools.r8.utils.Pair; -import java.util.List; - -public class ClassInlinerEligibilityInfo { - - final List<Pair<Invoke.Type, DexMethod>> callsReceiver; - - /** - * Set to {@link OptionalBool#TRUE} if the method is guaranteed to return the receiver, {@link - * OptionalBool#FALSE} if the method is guaranteed not to return the receiver, and {@link - * OptionalBool#UNKNOWN} if the method may return the receiver. - */ - final OptionalBool returnsReceiver; - - public final boolean hasMonitorOnReceiver; - public final boolean modifiesInstanceFields; - - public ClassInlinerEligibilityInfo( - List<Pair<Invoke.Type, DexMethod>> callsReceiver, - OptionalBool returnsReceiver, - boolean hasMonitorOnReceiver, - boolean modifiesInstanceFields) { - this.callsReceiver = callsReceiver; - this.returnsReceiver = returnsReceiver; - this.hasMonitorOnReceiver = hasMonitorOnReceiver; - this.modifiesInstanceFields = modifiesInstanceFields; - } -}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverAnalysis.java deleted file mode 100644 index e0f991d..0000000 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverAnalysis.java +++ /dev/null
@@ -1,135 +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.classinliner; - -import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexEncodedMethod; -import com.android.tools.r8.ir.analysis.type.ClassTypeElement; -import com.android.tools.r8.ir.code.BasicBlock; -import com.android.tools.r8.ir.code.IRCode; -import com.android.tools.r8.ir.code.Instruction; -import com.android.tools.r8.ir.code.InvokeMethod; -import com.android.tools.r8.ir.code.Value; -import com.android.tools.r8.utils.BooleanLatticeElement; -import com.android.tools.r8.utils.OptionalBool; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; - -/** - * This analysis determines whether a method returns the receiver. The analysis is specific to the - * class inliner, and the result is therefore not sound in general. - * - * <p>The analysis makes the following assumptions. - * - * <ul> - * <li>None of the given method's arguments is an alias of the receiver (except for the receiver - * itself). - * <li>The receiver is not stored in the heap. Thus, it is guaranteed that (i) all field-get - * instructions do not return an alias of the receiver, and (ii) invoke instructions can only - * return an alias of the receiver if the receiver is given as an argument. - * </ul> - */ -public class ClassInlinerReceiverAnalysis { - - private final AppView<?> appView; - private final DexEncodedMethod method; - private final IRCode code; - private final Value receiver; - - private final Map<Value, OptionalBool> isReceiverAliasCache = new IdentityHashMap<>(); - - public ClassInlinerReceiverAnalysis(AppView<?> appView, DexEncodedMethod method, IRCode code) { - this.appView = appView; - this.method = method; - this.code = code; - this.receiver = code.getThis(); - assert !receiver.hasAliasedValue(); - assert receiver.getType().isClassType(); - } - - public OptionalBool computeReturnsReceiver() { - if (method.method.proto.returnType.isVoidType()) { - return OptionalBool.FALSE; - } - - List<BasicBlock> normalExitBlocks = code.computeNormalExitBlocks(); - if (normalExitBlocks.isEmpty()) { - return OptionalBool.FALSE; - } - - BooleanLatticeElement result = OptionalBool.BOTTOM; - for (BasicBlock block : normalExitBlocks) { - Value returnValue = block.exit().asReturn().returnValue(); - result = result.join(getOrComputeIsReceiverAlias(returnValue)); - - // Stop as soon as we reach unknown. - if (result.isUnknown()) { - return OptionalBool.UNKNOWN; - } - } - assert !result.isBottom(); - return result.asOptionalBool(); - } - - private OptionalBool getOrComputeIsReceiverAlias(Value value) { - Value root = value.getAliasedValue(); - return isReceiverAliasCache.computeIfAbsent(root, this::computeIsReceiverAlias); - } - - private OptionalBool computeIsReceiverAlias(Value value) { - assert !value.hasAliasedValue(); - - if (value == receiver) { - // Guaranteed to return the receiver. - return OptionalBool.TRUE; - } - - ClassTypeElement valueType = value.getType().asClassType(); - if (valueType == null) { - return OptionalBool.FALSE; - } - - ClassTypeElement receiverType = receiver.getType().asClassType(); - if (!valueType.isRelatedTo(receiverType, appView)) { - // Guaranteed not to return the receiver. - return OptionalBool.FALSE; - } - - if (value.isPhi()) { - // Not sure what is returned. - return OptionalBool.UNKNOWN; - } - - Instruction definition = value.definition; - if (definition.isArrayGet() || definition.isFieldGet()) { - // Guaranteed not to return the receiver, since the class inliner does not allow the - // receiver to flow into any arrays or fields. - return OptionalBool.FALSE; - } - - if (definition.isConstInstruction() || definition.isCreatingInstanceOrArray()) { - // Guaranteed not to return the receiver. - return OptionalBool.FALSE; - } - - if (definition.isInvokeMethod()) { - // Since the class inliner does not allow the receiver to flow into the heap, the only way for - // the invoked method to return the receiver is if one of the given arguments is an alias of - // the receiver. - InvokeMethod invoke = definition.asInvokeMethod(); - for (Value argument : invoke.arguments()) { - if (getOrComputeIsReceiverAlias(argument).isPossiblyTrue()) { - return OptionalBool.UNKNOWN; - } - } - - return OptionalBool.FALSE; - } - - // Not sure what is returned. - return OptionalBool.UNKNOWN; - } -}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverSet.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverSet.java index 5a8c59d..1d73fdc 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverSet.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverSet.java
@@ -5,7 +5,6 @@ package com.android.tools.r8.ir.optimize.classinliner; import com.android.tools.r8.ir.code.Value; -import com.android.tools.r8.ir.optimize.classinliner.InlineCandidateProcessor.AliasKind; import com.android.tools.r8.utils.SetUtils; import com.google.common.collect.Sets; import java.util.ArrayList; @@ -19,8 +18,7 @@ private final Value root; - private final Set<Value> definiteReceiverAliases; - private final Set<Value> maybeReceiverAliases = Sets.newIdentityHashSet(); + private final Set<Value> receiverAliases; // Set of values that are not allowed to become an alias of the receiver. private final Set<Value> illegalReceiverAliases = Sets.newIdentityHashSet(); @@ -30,32 +28,18 @@ new IdentityHashMap<>(); ClassInlinerReceiverSet(Value root) { - this.definiteReceiverAliases = SetUtils.newIdentityHashSet(root); + this.receiverAliases = SetUtils.newIdentityHashSet(root); this.root = root; } - Set<Value> getDefiniteReceiverAliases() { - return definiteReceiverAliases; - } - - Set<Value> getMaybeReceiverAliases() { - return maybeReceiverAliases; - } - - boolean addReceiverAlias(Value alias, AliasKind kind) { + boolean addReceiverAlias(Value alias) { if (isIllegalReceiverAlias(alias)) { return false; // Not allowed. } // All checks passed. deferredAliasValidityChecks.remove(alias); boolean changed; - if (kind == AliasKind.DEFINITE) { - assert !maybeReceiverAliases.contains(alias); - changed = definiteReceiverAliases.add(alias); - } else { - assert !definiteReceiverAliases.contains(alias); - changed = maybeReceiverAliases.add(alias); - } + changed = receiverAliases.add(alias); // Verify that the state changed. Otherwise, we are analyzing the same instruction more than // once. assert changed : alias.toString() + " already added as an alias"; @@ -87,15 +71,11 @@ } boolean isReceiverAlias(Value value) { - return isDefiniteReceiverAlias(value) || isMaybeReceiverAlias(value); + return isDefiniteReceiverAlias(value); } boolean isDefiniteReceiverAlias(Value value) { - return definiteReceiverAliases.contains(value); - } - - private boolean isMaybeReceiverAlias(Value value) { - return maybeReceiverAliases.contains(value); + return receiverAliases.contains(value); } private boolean isIllegalReceiverAlias(Value value) { @@ -115,13 +95,7 @@ void reset() { deferredAliasValidityChecks.clear(); - definiteReceiverAliases.clear(); - definiteReceiverAliases.add(root); - maybeReceiverAliases.clear(); - } - - boolean verifyReceiverSetsAreDisjoint() { - assert Sets.intersection(getMaybeReceiverAliases(), getDefiniteReceiverAliases()).isEmpty(); - return true; + receiverAliases.clear(); + receiverAliases.add(root); } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java index 32e25f1..854f6e9 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -4,13 +4,13 @@ package com.android.tools.r8.ir.optimize.classinliner; -import static com.android.tools.r8.graph.DexEncodedMethod.asProgramMethodOrNull; import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; import com.android.tools.r8.errors.InternalCompilerError; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AccessControl; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; @@ -23,23 +23,23 @@ import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult; -import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis; import com.android.tools.r8.ir.analysis.type.ClassTypeElement; import com.android.tools.r8.ir.analysis.type.Nullability; import com.android.tools.r8.ir.analysis.type.TypeAnalysis; +import com.android.tools.r8.ir.analysis.value.AbstractValue; +import com.android.tools.r8.ir.analysis.value.ObjectState; +import com.android.tools.r8.ir.analysis.value.SingleConstValue; import com.android.tools.r8.ir.code.AliasedValueConfiguration; import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.CheckCast; -import com.android.tools.r8.ir.code.ConstNumber; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.If; import com.android.tools.r8.ir.code.InstanceGet; import com.android.tools.r8.ir.code.InstancePut; import com.android.tools.r8.ir.code.Instruction; +import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.InstructionOrPhi; -import com.android.tools.r8.ir.code.Invoke; -import com.android.tools.r8.ir.code.Invoke.Type; import com.android.tools.r8.ir.code.InvokeDirect; import com.android.tools.r8.ir.code.InvokeMethod; import com.android.tools.r8.ir.code.InvokeMethodWithReceiver; @@ -48,29 +48,25 @@ import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.MethodProcessor; import com.android.tools.r8.ir.optimize.Inliner; -import com.android.tools.r8.ir.optimize.Inliner.InlineAction; import com.android.tools.r8.ir.optimize.Inliner.InliningInfo; import com.android.tools.r8.ir.optimize.Inliner.Reason; import com.android.tools.r8.ir.optimize.InliningOracle; import com.android.tools.r8.ir.optimize.classinliner.ClassInliner.EligibilityStatus; +import com.android.tools.r8.ir.optimize.classinliner.analysis.NonEmptyParameterUsage; +import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsage; import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint; import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo; import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo; -import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage; import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo; import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider; import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter; import com.android.tools.r8.kotlin.KotlinClassLevelInfo; import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.android.tools.r8.utils.Pair; +import com.android.tools.r8.utils.OptionalBool; import com.android.tools.r8.utils.SetUtils; -import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.Timing; import com.android.tools.r8.utils.WorkList; import com.android.tools.r8.utils.collections.ProgramMethodSet; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; import com.google.common.collect.Sets; import java.util.ArrayList; import java.util.Comparator; @@ -80,18 +76,10 @@ import java.util.Set; import java.util.TreeSet; import java.util.function.Function; -import java.util.function.Predicate; import java.util.function.Supplier; final class InlineCandidateProcessor { - enum AliasKind { - DEFINITE, - MAYBE - } - - private static final ImmutableSet<If.Type> ALLOWED_ZERO_TEST_TYPES = - ImmutableSet.of(If.Type.EQ, If.Type.NE); private static final AliasedValueConfiguration aliasesThroughAssumeAndCheckCasts = AssumeAndCheckCastAliasedValueConfiguration.getInstance(); @@ -105,15 +93,11 @@ private Value eligibleInstance; private DexProgramClass eligibleClass; + private ObjectState objectState; - private final Map<InvokeMethodWithReceiver, InliningInfo> methodCallsOnInstance = - new IdentityHashMap<>(); + private final Map<InvokeMethod, InliningInfo> directMethodCalls = new IdentityHashMap<>(); private final ProgramMethodSet indirectMethodCallsOnInstance = ProgramMethodSet.create(); - private final Map<InvokeMethod, InliningInfo> extraMethodCalls - = new IdentityHashMap<>(); - private final List<Pair<InvokeMethod, Integer>> unusedArguments - = new ArrayList<>(); private final Map<InvokeMethod, ProgramMethod> directInlinees = new IdentityHashMap<>(); private final List<ProgramMethod> indirectInlinees = new ArrayList<>(); @@ -203,6 +187,11 @@ if (eligibleClass == null) { return EligibilityStatus.NOT_ELIGIBLE; } + AbstractValue abstractValue = optimizationInfo.getAbstractValue(); + objectState = + abstractValue.isSingleFieldValue() + ? abstractValue.asSingleFieldValue().getState() + : ObjectState.empty(); return EligibilityStatus.ELIGIBLE; } @@ -255,7 +244,7 @@ if (alias.hasPhiUsers()) { return alias.firstPhiUser(); // Not eligible. } - if (!receivers.addReceiverAlias(alias, AliasKind.DEFINITE)) { + if (!receivers.addReceiverAlias(alias)) { return user; // Not eligible. } indirectUsers.addAll(alias.uniqueUsers()); @@ -263,10 +252,6 @@ } if (user.isInstanceGet()) { - if (root.isStaticGet()) { - // We don't have a replacement for this field read. - return user; // Not eligible. - } DexEncodedField field = appView .appInfo() @@ -275,6 +260,11 @@ if (field == null || field.isStatic()) { return user; // Not eligible. } + if (root.isStaticGet() + && !objectState.hasMaterializableFieldValueThatMatches( + appView, field, method, AbstractValue::isSingleConstValue)) { + return user; // Not eligible. + } continue; } @@ -298,11 +288,11 @@ } if (user.isInvokeMethod()) { - InvokeMethod invokeMethod = user.asInvokeMethod(); + InvokeMethod invoke = user.asInvokeMethod(); SingleResolutionResult resolutionResult = appView .appInfo() - .resolveMethod(invokeMethod.getInvokedMethod(), invokeMethod.getInterfaceBit()) + .resolveMethod(invoke.getInvokedMethod(), invoke.getInterfaceBit()) .asSingleResolution(); if (resolutionResult == null || resolutionResult.isAccessibleFrom(method, appView).isPossiblyFalse()) { @@ -310,13 +300,13 @@ } // TODO(b/156853206): Avoid duplicating resolution. - DexClassAndMethod singleTarget = invokeMethod.lookupSingleTarget(appView, method); + DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, method); if (singleTarget == null) { return user; // Not eligible. } if (singleTarget.isLibraryMethod() - && isEligibleLibraryMethodCall(invokeMethod, singleTarget.asLibraryMethod())) { + && isEligibleLibraryMethodCall(invoke, singleTarget.asLibraryMethod())) { continue; } @@ -331,44 +321,25 @@ } // Eligible constructor call (for new instance roots only). - if (user.isInvokeDirect()) { - InvokeDirect invoke = user.asInvokeDirect(); - if (dexItemFactory.isConstructor(invoke.getInvokedMethod())) { - boolean isCorrespondingConstructorCall = - root.isNewInstance() - && !invoke.inValues().isEmpty() - && root.outValue() == invoke.getReceiver(); - if (isCorrespondingConstructorCall) { - InliningInfo inliningInfo = isEligibleConstructorCall(invoke, singleProgramTarget); - if (inliningInfo != null) { - methodCallsOnInstance.put(invoke, inliningInfo); - continue; - } + if (user.isInvokeConstructor(dexItemFactory)) { + InvokeDirect invokeDirect = user.asInvokeDirect(); + boolean isCorrespondingConstructorCall = + root.isNewInstance() && root.outValue() == invokeDirect.getReceiver(); + if (isCorrespondingConstructorCall) { + InliningInfo inliningInfo = + isEligibleConstructorCall(invokeDirect, singleProgramTarget); + if (inliningInfo != null) { + directMethodCalls.put(invoke, inliningInfo); + continue; } - assert !isExtraMethodCall(invoke); - return user; // Not eligible. } + return user; // Not eligible. } - // Eligible virtual method call on the instance as a receiver. - if (user.isInvokeVirtual() || user.isInvokeInterface()) { - InvokeMethodWithReceiver invoke = user.asInvokeMethodWithReceiver(); - InliningInfo inliningInfo = - isEligibleDirectVirtualMethodCall( - invoke, resolutionResult, singleProgramTarget, indirectUsers, defaultOracle); - if (inliningInfo != null) { - methodCallsOnInstance.put(invoke, inliningInfo); - continue; - } - } - - // Eligible usage as an invocation argument. - if (isExtraMethodCall(invokeMethod)) { - assert !invokeMethod.isInvokeSuper(); - assert !invokeMethod.isInvokePolymorphic(); - if (isExtraMethodCallEligible(invokeMethod, singleProgramTarget, defaultOracle)) { - continue; - } + // Eligible non-constructor method call. + if (isEligibleDirectMethodCall( + invoke, resolutionResult, singleProgramTarget, defaultOracle, indirectUsers)) { + continue; } return user; // Not eligible. @@ -395,7 +366,6 @@ // Process inlining, includes the following steps: // // * remove linked assume instructions if any so that users of the eligible field are up-to-date. - // * replace unused instance usages as arguments which are never used // * inline extra methods if any, collect new direct method calls // * inline direct methods if any // * remove superclass initializer call and field reads @@ -406,30 +376,12 @@ boolean processInlining( IRCode code, Set<Value> affectedValues, - Supplier<InliningOracle> defaultOracle, InliningIRProvider inliningIRProvider) throws IllegalClassInlinerStateException { // Verify that `eligibleInstance` is not aliased. assert eligibleInstance == eligibleInstance.getAliasedValue(); - replaceUsagesAsUnusedArgument(code); - boolean anyInlinedMethods = forceInlineExtraMethodInvocations(code, inliningIRProvider); - if (anyInlinedMethods) { - // Reset the collections. - clear(); - - // Repeat user analysis - InstructionOrPhi ineligibleUser = areInstanceUsersEligible(defaultOracle); - if (ineligibleUser != null) { - throw new IllegalClassInlinerStateException(); - } - assert extraMethodCalls.isEmpty() - : "Remaining extra method calls: " + StringUtils.join(extraMethodCalls.entrySet(), ", "); - assert unusedArguments.isEmpty() - : "Remaining unused arg: " + StringUtils.join(unusedArguments, ", "); - } - - anyInlinedMethods |= forceInlineDirectMethodInvocations(code, inliningIRProvider); + boolean anyInlinedMethods = forceInlineDirectMethodInvocations(code, inliningIRProvider); anyInlinedMethods |= forceInlineIndirectMethodInvocations(code, inliningIRProvider); rebindIndirectEligibleInstanceUsersFromPhis(); @@ -440,60 +392,21 @@ return anyInlinedMethods; } - private void clear() { - methodCallsOnInstance.clear(); - indirectMethodCallsOnInstance.clear(); - extraMethodCalls.clear(); - unusedArguments.clear(); - receivers.reset(); - } - - private void replaceUsagesAsUnusedArgument(IRCode code) { - for (Pair<InvokeMethod, Integer> unusedArgument : unusedArguments) { - InvokeMethod invoke = unusedArgument.getFirst(); - BasicBlock block = invoke.getBlock(); - - ConstNumber nullValue = code.createConstNull(); - nullValue.setPosition(invoke.getPosition()); - block.listIterator(code, invoke).add(nullValue); - assert nullValue.getBlock() == block; - - int argIndex = unusedArgument.getSecond(); - invoke.replaceValue(argIndex, nullValue.outValue()); - } - unusedArguments.clear(); - } - - private boolean forceInlineExtraMethodInvocations( - IRCode code, InliningIRProvider inliningIRProvider) { - if (extraMethodCalls.isEmpty()) { - return false; - } - // Inline extra methods. - inliner.performForcedInlining( - method, code, extraMethodCalls, inliningIRProvider, Timing.empty()); - return true; - } - private boolean forceInlineDirectMethodInvocations( IRCode code, InliningIRProvider inliningIRProvider) throws IllegalClassInlinerStateException { - if (methodCallsOnInstance.isEmpty()) { + if (directMethodCalls.isEmpty()) { return false; } - assert methodCallsOnInstance.keySet().stream() - .map(InvokeMethodWithReceiver::getReceiver) - .allMatch(receivers::isReceiverAlias); - inliner.performForcedInlining( - method, code, methodCallsOnInstance, inliningIRProvider, Timing.empty()); + method, code, directMethodCalls, inliningIRProvider, Timing.empty()); // In case we are class inlining an object allocation that does not inherit directly from // java.lang.Object, we need keep force inlining the constructor until we reach // java.lang.Object.<init>(). if (root.isNewInstance()) { do { - methodCallsOnInstance.clear(); + directMethodCalls.clear(); for (Instruction instruction : eligibleInstance.uniqueUsers()) { if (instruction.isInvokeDirect()) { InvokeDirect invoke = instruction.asInvokeDirect(); @@ -512,7 +425,7 @@ } DexProgramClass holder = - asProgramClassOrNull(appView.definitionForHolder(invokedMethod)); + asProgramClassOrNull(appView.definitionForHolder(invokedMethod, method)); if (holder == null) { throw new IllegalClassInlinerStateException(); } @@ -523,21 +436,21 @@ .getDefinition() .isInliningCandidate( method, - Reason.SIMPLE, + Reason.ALWAYS, appView.appInfo(), NopWhyAreYouNotInliningReporter.getInstance())) { throw new IllegalClassInlinerStateException(); } - methodCallsOnInstance.put(invoke, new InliningInfo(singleTarget, eligibleClass.type)); + directMethodCalls.put(invoke, new InliningInfo(singleTarget, eligibleClass.type)); break; } } - if (!methodCallsOnInstance.isEmpty()) { + if (!directMethodCalls.isEmpty()) { inliner.performForcedInlining( - method, code, methodCallsOnInstance, inliningIRProvider, Timing.empty()); + method, code, directMethodCalls, inliningIRProvider, Timing.empty()); } - } while (!methodCallsOnInstance.isEmpty()); + } while (!directMethodCalls.isEmpty()); } return true; @@ -746,11 +659,24 @@ // Replace field reads with appropriate values, insert phis when needed. private void removeFieldReads(IRCode code) { + Set<Value> affectedValues = Sets.newIdentityHashSet(); + if (root.isNewInstance()) { + removeFieldReadsFromNewInstance(code, affectedValues); + } else { + assert root.isStaticGet(); + removeFieldReadsFromStaticGet(code, affectedValues); + } + if (!affectedValues.isEmpty()) { + new TypeAnalysis(appView).narrowing(affectedValues); + } + } + + private void removeFieldReadsFromNewInstance(IRCode code, Set<Value> affectedValues) { TreeSet<InstanceGet> uniqueInstanceGetUsersWithDeterministicOrder = new TreeSet<>(Comparator.comparingInt(x -> x.outValue().getNumber())); for (Instruction user : eligibleInstance.uniqueUsers()) { if (user.isInstanceGet()) { - if (user.outValue().hasAnyUsers()) { + if (user.hasUsedOutValue()) { uniqueInstanceGetUsersWithDeterministicOrder.add(user.asInstanceGet()); } else { removeInstruction(user); @@ -761,6 +687,7 @@ if (user.isInstancePut()) { // Skip in this iteration since these instructions are needed to properly calculate what // value should field reads be replaced with. + assert root.isNewInstance(); continue; } @@ -774,12 +701,15 @@ Map<DexField, FieldValueHelper> fieldHelpers = new IdentityHashMap<>(); for (InstanceGet user : uniqueInstanceGetUsersWithDeterministicOrder) { // Replace a field read with appropriate value. - replaceFieldRead(code, user, fieldHelpers); + removeFieldReadFromNewInstance(code, user, affectedValues, fieldHelpers); } } - private void replaceFieldRead( - IRCode code, InstanceGet fieldRead, Map<DexField, FieldValueHelper> fieldHelpers) { + private void removeFieldReadFromNewInstance( + IRCode code, + InstanceGet fieldRead, + Set<Value> affectedValues, + Map<DexField, FieldValueHelper> fieldHelpers) { Value value = fieldRead.outValue(); if (value != null) { FieldValueHelper helper = @@ -794,12 +724,89 @@ // `newValue` could be a phi introduced by FieldValueHelper. Its initial type is set as the // type of read field, but it could be more precise than that due to (multiple) inlining. // In addition to values affected by `newValue`, it's necessary to revisit `newValue` itself. - new TypeAnalysis(appView).narrowing( - Iterables.concat(ImmutableSet.of(newValue), newValue.affectedValues())); + affectedValues.add(newValue); + affectedValues.addAll(newValue.affectedValues()); } removeInstruction(fieldRead); } + private void removeFieldReadsFromStaticGet(IRCode code, Set<Value> affectedValues) { + Set<BasicBlock> seen = Sets.newIdentityHashSet(); + Set<Instruction> users = eligibleInstance.uniqueUsers(); + for (Instruction user : users) { + BasicBlock block = user.getBlock(); + if (!seen.add(block)) { + continue; + } + + InstructionListIterator instructionIterator = block.listIterator(code); + while (instructionIterator.hasNext()) { + Instruction instruction = instructionIterator.next(); + if (!users.contains(instruction)) { + continue; + } + + if (user.isInstanceGet()) { + if (user.hasUsedOutValue()) { + replaceFieldReadFromStaticGet( + code, instructionIterator, user.asInstanceGet(), affectedValues); + } else { + instructionIterator.removeOrReplaceByDebugLocalRead(); + } + continue; + } + + if (user.isInstancePut()) { + instructionIterator.removeOrReplaceByDebugLocalRead(); + continue; + } + + throw new Unreachable( + "Unexpected usage left in method `" + + method.toSourceString() + + "` after inlining: " + + user); + } + } + } + + private void replaceFieldReadFromStaticGet( + IRCode code, + InstructionListIterator instructionIterator, + InstanceGet fieldRead, + Set<Value> affectedValues) { + DexField fieldReference = fieldRead.getField(); + DexClass holder = appView.definitionFor(fieldReference.getHolderType(), method); + DexEncodedField field = fieldReference.lookupOnClass(holder); + if (field == null) { + throw reportUnknownFieldReadFromSingleton(fieldRead); + } + + AbstractValue abstractValue = objectState.getAbstractFieldValue(field); + if (!abstractValue.isSingleConstValue()) { + throw reportUnknownFieldReadFromSingleton(fieldRead); + } + + SingleConstValue singleConstValue = abstractValue.asSingleConstValue(); + if (!singleConstValue.isMaterializableInContext(appView, method)) { + throw reportUnknownFieldReadFromSingleton(fieldRead); + } + + Instruction replacement = + singleConstValue.createMaterializingInstruction(appView, code, fieldRead); + instructionIterator.replaceCurrentInstruction(replacement, affectedValues); + } + + private RuntimeException reportUnknownFieldReadFromSingleton(InstanceGet fieldRead) { + throw appView + .reporter() + .fatalError( + "Unexpected usage left in method `" + + method.toSourceString() + + "` after inlining: " + + fieldRead.toString()); + } + private void removeFieldWrites() { for (Instruction user : eligibleInstance.uniqueUsers()) { if (!user.isInstancePut()) { @@ -809,6 +816,9 @@ + "` after field reads removed: " + user); } + + assert root.isNewInstance(); + InstancePut instancePut = user.asInstancePut(); DexEncodedField field = appView @@ -864,7 +874,8 @@ if (parent == null) { return null; } - DexProgramClass parentClass = asProgramClassOrNull(appView.definitionForHolder(parent)); + DexProgramClass parentClass = + asProgramClassOrNull(appView.definitionForHolder(parent, method)); if (parentClass == null) { return null; } @@ -878,7 +889,7 @@ DexEncodedMethod encodedParentMethod = encodedParent.getDefinition(); if (!encodedParentMethod.isInliningCandidate( method, - Reason.SIMPLE, + Reason.ALWAYS, appView.appInfo(), NopWhyAreYouNotInliningReporter.getInstance())) { return null; @@ -900,11 +911,29 @@ // - if it is a regular chaining pattern where the only users of the out value are receivers to // other invocations. In that case, we should add all indirect users of the out value to ensure // they can also be inlined. - private boolean isEligibleInvokeWithAllUsersAsReceivers( - ClassInlinerEligibilityInfo eligibility, - InvokeMethodWithReceiver invoke, + private boolean scheduleNewUsersForAnalysis( + InvokeMethod invoke, + ProgramMethod singleTarget, + int parameter, Set<Instruction> indirectUsers) { - if (eligibility.returnsReceiver.isFalse()) { + ClassInlinerMethodConstraint classInlinerMethodConstraint = + singleTarget.getDefinition().getOptimizationInfo().getClassInlinerMethodConstraint(); + ParameterUsage usage = classInlinerMethodConstraint.getParameterUsage(parameter); + + OptionalBool returnsParameter; + if (usage.isParameterReturned()) { + if (singleTarget.getDefinition().getOptimizationInfo().returnsArgument()) { + assert singleTarget.getDefinition().getOptimizationInfo().getReturnedArgument() + == parameter; + returnsParameter = OptionalBool.TRUE; + } else { + returnsParameter = OptionalBool.UNKNOWN; + } + } else { + returnsParameter = OptionalBool.FALSE; + } + + if (returnsParameter.isFalse()) { return true; } @@ -922,168 +951,58 @@ // We cannot guarantee the invoke returns the receiver or another instance and since the // return value is used we have to bail out. - if (eligibility.returnsReceiver.isUnknown()) { + if (returnsParameter.isUnknown()) { return false; } // Add the out-value as a definite-alias if the invoke instruction is guaranteed to return the // receiver. Otherwise, the out-value may be an alias of the receiver, and it is added to the // may-alias set. - AliasKind kind = eligibility.returnsReceiver.isTrue() ? AliasKind.DEFINITE : AliasKind.MAYBE; - if (!receivers.addReceiverAlias(outValue, kind)) { + assert returnsParameter.isTrue(); + if (!receivers.addReceiverAlias(outValue)) { return false; } - Set<Instruction> currentUsers = outValue.uniqueUsers(); - while (!currentUsers.isEmpty()) { - Set<Instruction> indirectOutValueUsers = Sets.newIdentityHashSet(); - for (Instruction instruction : currentUsers) { - if (instruction.isAssume() || instruction.isCheckCast()) { - if (instruction.isCheckCast()) { - CheckCast checkCast = instruction.asCheckCast(); - if (!appView.appInfo().isSubtype(eligibleClass.type, checkCast.getType())) { - return false; // Unsafe cast. - } - } - Value outValueAlias = instruction.outValue(); - if (outValueAlias.hasPhiUsers() || outValueAlias.hasDebugUsers()) { - return false; - } - if (!receivers.addReceiverAlias(outValueAlias, kind)) { - return false; - } - indirectOutValueUsers.addAll(outValueAlias.uniqueUsers()); - continue; - } - - if (instruction.isInvokeMethodWithReceiver()) { - InvokeMethodWithReceiver user = instruction.asInvokeMethodWithReceiver(); - if (user.getReceiver().getAliasedValue(aliasesThroughAssumeAndCheckCasts) != outValue) { - return false; - } - for (int i = 1; i < user.inValues().size(); i++) { - Value inValue = user.inValues().get(i); - if (inValue.getAliasedValue(aliasesThroughAssumeAndCheckCasts) == outValue) { - return false; - } - } - indirectUsers.add(user); - continue; - } - - return false; - } - currentUsers = indirectOutValueUsers; - } - + indirectUsers.addAll(outValue.uniqueUsers()); return true; } - private InliningInfo isEligibleDirectVirtualMethodCall( - InvokeMethodWithReceiver invoke, - SingleResolutionResult resolutionResult, - ProgramMethod singleTarget, - Set<Instruction> indirectUsers, - Supplier<InliningOracle> defaultOracle) { - assert isEligibleSingleTarget(singleTarget); - - // None of the none-receiver arguments may be an alias of the receiver. - List<Value> inValues = invoke.inValues(); - for (int i = 1; i < inValues.size(); i++) { - if (!receivers.addIllegalReceiverAlias(inValues.get(i))) { - return null; - } - } - - // TODO(b/141719453): Should not constrain library overrides if all instantiations are inlined. - if (singleTarget.getDefinition().isLibraryMethodOverride().isTrue()) { - InliningOracle inliningOracle = defaultOracle.get(); - if (!inliningOracle.passesInliningConstraints( - invoke, - resolutionResult, - singleTarget, - Reason.SIMPLE, - NopWhyAreYouNotInliningReporter.getInstance())) { - return null; - } - } - - if (!isEligibleVirtualMethodCall( - invoke, - invoke.getInvokedMethod(), - singleTarget, - eligibility -> isEligibleInvokeWithAllUsersAsReceivers(eligibility, invoke, indirectUsers), - invoke.getType())) { - return null; - } - - ClassInlinerEligibilityInfo eligibility = - singleTarget.getDefinition().getOptimizationInfo().getClassInlinerEligibility(); - if (eligibility.callsReceiver.size() > 1) { - return null; - } - if (!eligibility.callsReceiver.isEmpty()) { - assert eligibility.callsReceiver.get(0).getFirst() == Invoke.Type.VIRTUAL; - Pair<Type, DexMethod> invokeInfo = eligibility.callsReceiver.get(0); - Type invokeType = invokeInfo.getFirst(); - DexMethod indirectlyInvokedMethod = invokeInfo.getSecond(); - SingleResolutionResult indirectResolutionResult = - appView - .appInfo() - .resolveMethodOn(eligibleClass, indirectlyInvokedMethod) - .asSingleResolution(); - if (indirectResolutionResult == null) { - return null; - } - ProgramMethod indirectSingleTarget = - indirectResolutionResult.getResolutionPair().asProgramMethod(); - if (!isEligibleIndirectVirtualMethodCall( - indirectlyInvokedMethod, invokeType, indirectSingleTarget)) { - return null; - } - indirectMethodCallsOnInstance.add(indirectSingleTarget); - } - - return new InliningInfo(singleTarget, eligibleClass.type); - } - - private boolean isEligibleIndirectVirtualMethodCall(DexMethod invokedMethod, Type type) { - ProgramMethod singleTarget = - asProgramMethodOrNull( - appView.appInfo().resolveMethodOn(eligibleClass, invokedMethod).getSingleTarget(), - appView); - return isEligibleIndirectVirtualMethodCall(invokedMethod, type, singleTarget); - } private boolean isEligibleIndirectVirtualMethodCall( - DexMethod invokedMethod, Type type, ProgramMethod singleTarget) { + DexMethod invokedMethod, ProgramMethod singleTarget) { if (!isEligibleSingleTarget(singleTarget)) { return false; } if (singleTarget.getDefinition().isLibraryMethodOverride().isTrue()) { return false; } - return isEligibleVirtualMethodCall( - null, - invokedMethod, - singleTarget, - eligibility -> eligibility.callsReceiver.isEmpty() && eligibility.returnsReceiver.isFalse(), - type); + if (!isEligibleVirtualMethodCall(invokedMethod, singleTarget)) { + return false; + } + + ParameterUsage usage = + singleTarget + .getDefinition() + .getOptimizationInfo() + .getClassInlinerMethodConstraint() + .getParameterUsage(0); + assert !usage.isTop(); + if (usage.isBottom()) { + return true; + } + NonEmptyParameterUsage nonEmptyUsage = usage.asNonEmpty(); + return nonEmptyUsage.getMethodCallsWithParameterAsReceiver().isEmpty() + && !nonEmptyUsage.isParameterReturned(); } - private boolean isEligibleVirtualMethodCall( - InvokeMethodWithReceiver invoke, - DexMethod callee, - ProgramMethod singleTarget, - Predicate<ClassInlinerEligibilityInfo> eligibilityAcceptanceCheck, - Type type) { + private boolean isEligibleVirtualMethodCall(DexMethod callee, ProgramMethod singleTarget) { assert isEligibleSingleTarget(singleTarget); // We should not inline a method if the invocation has type interface or virtual and the // signature of the invocation resolves to a private or static method. // TODO(b/147212189): Why not inline private methods? If access is permitted it is valid. ResolutionResult resolutionResult = - appView.appInfo().resolveMethod(callee, type == Type.INTERFACE); + appView.appInfo().resolveMethodOnClass(callee, eligibleClass); if (resolutionResult.isSingleResolution() && !resolutionResult.getSingleTarget().isNonPrivateVirtualMethod()) { return false; @@ -1099,53 +1018,20 @@ MethodOptimizationInfo optimizationInfo = singleTarget.getDefinition().getOptimizationInfo(); ClassInlinerMethodConstraint classInlinerMethodConstraint = optimizationInfo.getClassInlinerMethodConstraint(); + int parameter = 0; if (root.isNewInstance()) { - if (!classInlinerMethodConstraint.isEligibleForNewInstanceClassInlining(singleTarget)) { - return false; - } - } else { - assert root.isStaticGet(); - if (!classInlinerMethodConstraint.isEligibleForStaticGetClassInlining(singleTarget)) { - return false; - } + return classInlinerMethodConstraint.isEligibleForNewInstanceClassInlining( + singleTarget, parameter); } - // If the method returns receiver and the return value is actually - // used in the code we need to make some additional checks. - if (!eligibilityAcceptanceCheck.test(optimizationInfo.getClassInlinerEligibility())) { - return false; - } - - markSizeForInlining(invoke, singleTarget); - return true; - } - - private boolean isExtraMethodCall(InvokeMethod invoke) { - if (invoke.isInvokeDirect() && dexItemFactory.isConstructor(invoke.getInvokedMethod())) { - return false; - } - if (invoke.isInvokeMethodWithReceiver()) { - Value receiver = invoke.asInvokeMethodWithReceiver().getReceiver(); - if (!receivers.addIllegalReceiverAlias(receiver)) { - return false; - } - } - if (invoke.isInvokeSuper()) { - return false; - } - if (invoke.isInvokePolymorphic()) { - return false; - } - return true; + assert root.isStaticGet(); + return classInlinerMethodConstraint.isEligibleForStaticGetClassInlining( + appView, parameter, objectState, method); } // Analyzes if a method invoke the eligible instance is passed to is eligible. In short, // it can be eligible if: // - // -- eligible instance is passed as argument #N which is not used in the method, - // such cases are collected in 'unusedArguments' parameter and later replaced - // with 'null' value - // // -- eligible instance is passed as argument #N which is only used in the method to // call a method on this object (we call it indirect method call), and method is // eligible according to the same rules defined for direct method call eligibility @@ -1157,52 +1043,48 @@ // // -- method itself can be inlined // - private boolean isExtraMethodCallEligible( - InvokeMethod invoke, ProgramMethod singleTarget, Supplier<InliningOracle> defaultOracle) { - // Don't consider constructor invocations and super calls, since we don't want to forcibly - // inline them. - assert isExtraMethodCall(invoke); - assert isEligibleSingleTarget(singleTarget); - - List<Value> arguments = Lists.newArrayList(invoke.inValues()); - - // If we got here with invocation on receiver the user is ineligible. - if (invoke.isInvokeMethodWithReceiver()) { - InvokeMethodWithReceiver invokeMethodWithReceiver = invoke.asInvokeMethodWithReceiver(); - Value receiver = invokeMethodWithReceiver.getReceiver(); - if (!receivers.addIllegalReceiverAlias(receiver)) { - return false; - } - - // A definitely null receiver will throw an error on call site. - if (receiver.getType().nullability().isDefinitelyNull()) { - return false; - } - } - - MethodOptimizationInfo optimizationInfo = singleTarget.getDefinition().getOptimizationInfo(); - - // Go through all arguments, see if all usages of eligibleInstance are good. - if (!isEligibleParameterUsages( - invoke, arguments, singleTarget.getDefinition(), defaultOracle)) { + private boolean isEligibleDirectMethodCall( + InvokeMethod invoke, + SingleResolutionResult resolutionResult, + ProgramMethod singleTarget, + Supplier<InliningOracle> defaultOracle, + Set<Instruction> indirectUsers) { + if (!((invoke.isInvokeDirect() && !invoke.isInvokeConstructor(dexItemFactory)) + || invoke.isInvokeInterface() + || invoke.isInvokeStatic() + || invoke.isInvokeVirtual())) { return false; } - for (int argIndex = 0; argIndex < arguments.size(); argIndex++) { - Value argument = arguments.get(argIndex).getAliasedValue(); - ParameterUsage parameterUsage = optimizationInfo.getParameterUsages(argIndex); - if (receivers.isDefiniteReceiverAlias(argument) - && parameterUsage != null - && parameterUsage.notUsed()) { - // Reference can be removed since it's not used. - unusedArguments.add(new Pair<>(invoke, argIndex)); + // If we got here with invocation on receiver the user is ineligible. + if (invoke.isInvokeMethodWithReceiver()) { + // A definitely null receiver will throw an error on call site. + Value receiver = invoke.asInvokeMethodWithReceiver().getReceiver(); + if (receiver.getType().isDefinitelyNull()) { + return false; } } - extraMethodCalls.put(invoke, new InliningInfo(singleTarget, null)); + // Check if the method is inline-able by standard inliner. + InliningOracle oracle = defaultOracle.get(); + if (!oracle.passesInliningConstraints( + invoke, + resolutionResult, + singleTarget, + Reason.ALWAYS, + NopWhyAreYouNotInliningReporter.getInstance())) { + return false; + } + + // Go through all arguments, see if all usages of eligibleInstance are good. + if (!isEligibleParameterUsages(invoke, singleTarget, indirectUsers)) { + return false; + } + + directMethodCalls.put(invoke, new InliningInfo(singleTarget, null)); // Looks good. - markSizeForInlining(invoke, singleTarget); + markSizeOfDirectTargetForInlining(invoke, singleTarget); return true; } @@ -1219,147 +1101,90 @@ } private boolean isEligibleParameterUsages( - InvokeMethod invoke, - List<Value> arguments, - DexEncodedMethod singleTarget, - Supplier<InliningOracle> defaultOracle) { + InvokeMethod invoke, ProgramMethod singleTarget, Set<Instruction> indirectUsers) { // Go through all arguments, see if all usages of eligibleInstance are good. - for (int argIndex = 0; argIndex < arguments.size(); argIndex++) { - MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo(); - ParameterUsage parameterUsage = optimizationInfo.getParameterUsages(argIndex); - - Value argument = arguments.get(argIndex); + for (int parameter = 0; parameter < invoke.arguments().size(); parameter++) { + Value argument = invoke.getArgument(parameter); if (receivers.isReceiverAlias(argument)) { // Have parameter usage info? - if (!isEligibleParameterUsage(parameterUsage, invoke, defaultOracle)) { + if (!isEligibleParameterUsage(invoke, singleTarget, parameter, indirectUsers)) { return false; } } else { // Nothing to worry about, unless `argument` becomes an alias of the receiver later. + int finalParameter = parameter; receivers.addDeferredAliasValidityCheck( - argument, () -> isEligibleParameterUsage(parameterUsage, invoke, defaultOracle)); + argument, + () -> isEligibleParameterUsage(invoke, singleTarget, finalParameter, indirectUsers)); } } return true; } private boolean isEligibleParameterUsage( - ParameterUsage parameterUsage, InvokeMethod invoke, Supplier<InliningOracle> defaultOracle) { - if (parameterUsage == null) { - return false; // Don't know anything. - } - - if (parameterUsage.notUsed()) { - return true; - } - - if (parameterUsage.isAssignedToField) { - return false; - } - - if (root.isStaticGet()) { - // If we are class inlining a singleton instance from a static-get, then we don't know - // the value of the fields, and we also can't optimize away instance-field assignments, as - // they have global side effects. - if (parameterUsage.hasFieldAssignment || parameterUsage.hasFieldRead) { + InvokeMethod invoke, + ProgramMethod singleTarget, + int parameter, + Set<Instruction> indirectUsers) { + ClassInlinerMethodConstraint classInlinerMethodConstraint = + singleTarget.getDefinition().getOptimizationInfo().getClassInlinerMethodConstraint(); + if (root.isNewInstance()) { + if (!classInlinerMethodConstraint.isEligibleForNewInstanceClassInlining( + singleTarget, parameter)) { + return false; + } + } else { + assert root.isStaticGet(); + if (!classInlinerMethodConstraint.isEligibleForStaticGetClassInlining( + appView, parameter, objectState, method)) { return false; } } - if (parameterUsage.isReturned) { - if (invoke.outValue() != null && invoke.outValue().hasAnyUsers()) { - // Used as return value which is not ignored. - return false; - } - } - - if (parameterUsage.isUsedInMonitor) { - return !root.isStaticGet(); - } - - if (!Sets.difference(parameterUsage.ifZeroTest, ALLOWED_ZERO_TEST_TYPES).isEmpty()) { - // Used in unsupported zero-check-if kinds. + ParameterUsage usage = classInlinerMethodConstraint.getParameterUsage(parameter); + if (!scheduleNewUsersForAnalysis(invoke, singleTarget, parameter, indirectUsers)) { return false; } - for (Pair<Type, DexMethod> call : parameterUsage.callsReceiver) { - Type type = call.getFirst(); - DexMethod target = call.getSecond(); + if (!usage.isBottom()) { + NonEmptyParameterUsage nonEmptyUsage = usage.asNonEmpty(); + for (DexMethod invokedMethod : nonEmptyUsage.getMethodCallsWithParameterAsReceiver()) { + SingleResolutionResult resolutionResult = + appView.appInfo().resolveMethodOn(eligibleClass, invokedMethod).asSingleResolution(); + if (resolutionResult == null || !resolutionResult.getResolvedHolder().isProgramClass()) { + return false; + } - if (type == Type.VIRTUAL || type == Type.INTERFACE) { // Is the method called indirectly still eligible? - if (!isEligibleIndirectVirtualMethodCall(target, type)) { + ProgramMethod indirectSingleTarget = resolutionResult.getResolutionPair().asProgramMethod(); + if (!isEligibleIndirectVirtualMethodCall(invokedMethod, indirectSingleTarget)) { return false; } - } else if (type == Type.DIRECT) { - if (!isInstanceInitializerEligibleForClassInlining(target)) { - // Only calls to trivial instance initializers are supported at this point. - return false; - } - } else { - // Static and super calls are not yet supported. - return false; - } - - SingleResolutionResult resolutionResult = - appView - .appInfo() - .resolveMethod(invoke.getInvokedMethod(), invoke.getInterfaceBit()) - .asSingleResolution(); - if (resolutionResult == null) { - return false; - } - - // Check if the method is inline-able by standard inliner. - // TODO(b/156853206): Should not duplicate resolution. - ProgramMethod singleTarget = invoke.lookupSingleProgramTarget(appView, method); - if (singleTarget == null) { - return false; - } - - InliningOracle oracle = defaultOracle.get(); - InlineAction inlineAction = - oracle.computeInlining( - invoke, - resolutionResult, - singleTarget, - method, - ClassInitializationAnalysis.trivial(), - NopWhyAreYouNotInliningReporter.getInstance()); - if (inlineAction == null) { - return false; + markSizeOfIndirectTargetForInlining(indirectSingleTarget); } } + return true; } - private boolean isInstanceInitializerEligibleForClassInlining(DexMethod method) { - if (method == dexItemFactory.objectMembers.constructor) { - return true; - } - DexProgramClass holder = asProgramClassOrNull(appView.definitionForHolder(method)); - DexEncodedMethod definition = method.lookupOnClass(holder); - if (definition == null) { - return false; - } - InstanceInitializerInfo initializerInfo = - definition.getOptimizationInfo().getContextInsensitiveInstanceInitializerInfo(); - return initializerInfo.receiverNeverEscapesOutsideConstructorChain(); - } - private boolean exemptFromInstructionLimit(ProgramMethod inlinee) { KotlinClassLevelInfo kotlinInfo = inlinee.getHolder().getKotlinInfo(); return kotlinInfo.isSyntheticClass() && kotlinInfo.asSyntheticClass().isLambda(); } - private void markSizeForInlining(InvokeMethod invoke, ProgramMethod inlinee) { + private void markSizeOfIndirectTargetForInlining(ProgramMethod inlinee) { assert !methodProcessor.isProcessedConcurrently(inlinee); if (!exemptFromInstructionLimit(inlinee)) { - if (invoke != null) { - directInlinees.put(invoke, inlinee); - } else { - indirectInlinees.add(inlinee); - } + indirectInlinees.add(inlinee); + } + indirectMethodCallsOnInstance.add(inlinee); + } + + private void markSizeOfDirectTargetForInlining(InvokeMethod invoke, ProgramMethod inlinee) { + assert invoke != null; + assert !methodProcessor.isProcessedConcurrently(inlinee); + if (!exemptFromInstructionLimit(inlinee)) { + directInlinees.put(invoke, inlinee); } } @@ -1374,7 +1199,7 @@ .getDefinition() .isInliningCandidate( method, - Reason.SIMPLE, + Reason.ALWAYS, appView.appInfo(), NopWhyAreYouNotInliningReporter.getInstance())) { // If `singleTarget` is not an inlining candidate, we won't be able to inline it here. @@ -1394,5 +1219,10 @@ instruction.getBlock().removeInstruction(instruction); } - static class IllegalClassInlinerStateException extends Exception {} + static class IllegalClassInlinerStateException extends Exception { + + IllegalClassInlinerStateException() { + assert false; + } + } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/AnalysisContext.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/AnalysisContext.java new file mode 100644 index 0000000..5f63f4c --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/AnalysisContext.java
@@ -0,0 +1,19 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.optimize.classinliner.analysis; + +/** + * The context used for the {@link ParameterUsages} lattice. + * + * <p>By only using {@link DefaultAnalysisContext} a context-insensitive result is achieved. + * Context-sensitive results can be achieved by forking different analysis contexts during the class + * inliner constraint analysis. + */ +public abstract class AnalysisContext { + + public static DefaultAnalysisContext getDefaultContext() { + return DefaultAnalysisContext.getInstance(); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsage.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsage.java new file mode 100644 index 0000000..930edc5 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsage.java
@@ -0,0 +1,71 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.optimize.classinliner.analysis; + +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.ir.code.InvokeMethodWithReceiver; + +class BottomParameterUsage extends ParameterUsage { + + private static final BottomParameterUsage BOTTOM = new BottomParameterUsage(); + + private BottomParameterUsage() {} + + static BottomParameterUsage getInstance() { + return BOTTOM; + } + + @Override + ParameterUsage addFieldReadFromParameter(DexField field) { + return InternalNonEmptyParameterUsage.builder().addFieldReadFromParameter(field).build(); + } + + @Override + ParameterUsage addMethodCallWithParameterAsReceiver(InvokeMethodWithReceiver invoke) { + return InternalNonEmptyParameterUsage.builder() + .addMethodCallWithParameterAsReceiver(invoke) + .build(); + } + + @Override + ParameterUsage externalize() { + return this; + } + + @Override + public boolean isBottom() { + return true; + } + + @Override + public boolean isParameterMutated() { + return false; + } + + @Override + public boolean isParameterReturned() { + return false; + } + + @Override + public boolean isParameterUsedAsLock() { + return false; + } + + @Override + ParameterUsage setParameterMutated() { + return InternalNonEmptyParameterUsage.builder().setParameterMutated().build(); + } + + @Override + ParameterUsage setParameterReturned() { + return InternalNonEmptyParameterUsage.builder().setParameterReturned().build(); + } + + @Override + ParameterUsage setParameterUsedAsLock() { + return InternalNonEmptyParameterUsage.builder().setParameterUsedAsLock().build(); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsagePerContext.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsagePerContext.java new file mode 100644 index 0000000..b535038 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsagePerContext.java
@@ -0,0 +1,40 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.optimize.classinliner.analysis; + +import java.util.function.BiFunction; + +class BottomParameterUsagePerContext extends ParameterUsagePerContext { + + private static final BottomParameterUsagePerContext INSTANCE = + new BottomParameterUsagePerContext(); + + private BottomParameterUsagePerContext() {} + + static BottomParameterUsagePerContext getInstance() { + return INSTANCE; + } + + @Override + ParameterUsagePerContext externalize() { + return this; + } + + @Override + public ParameterUsage get(AnalysisContext context) { + return ParameterUsage.bottom(); + } + + @Override + public boolean isBottom() { + return true; + } + + @Override + ParameterUsagePerContext rebuild( + BiFunction<AnalysisContext, ParameterUsage, ParameterUsage> transformation) { + return this; + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsages.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsages.java new file mode 100644 index 0000000..84bae0e --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsages.java
@@ -0,0 +1,50 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.optimize.classinliner.analysis; + +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +public class BottomParameterUsages extends ParameterUsages { + + private static final BottomParameterUsages INSTANCE = new BottomParameterUsages(); + + private BottomParameterUsages() {} + + static BottomParameterUsages getInstance() { + return INSTANCE; + } + + @Override + ParameterUsages externalize() { + return this; + } + + @Override + public ParameterUsagePerContext get(int parameter) { + return ParameterUsagePerContext.bottom(); + } + + @Override + public boolean isBottom() { + return true; + } + + @Override + ParameterUsages put(int parameter, ParameterUsagePerContext usagePerContext) { + Int2ObjectOpenHashMap<ParameterUsagePerContext> backing = new Int2ObjectOpenHashMap<>(); + backing.put(parameter, usagePerContext); + return NonEmptyParameterUsages.create(backing); + } + + @Override + public boolean equals(Object other) { + return other == INSTANCE; + } + + @Override + public int hashCode() { + return System.identityHashCode(this); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ClassInlinerMethodConstraintAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ClassInlinerMethodConstraintAnalysis.java new file mode 100644 index 0000000..b8f406a --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ClassInlinerMethodConstraintAnalysis.java
@@ -0,0 +1,43 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.optimize.classinliner.analysis; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult.SuccessfulDataflowAnalysisResult; +import com.android.tools.r8.ir.analysis.framework.intraprocedural.IntraproceduralDataflowAnalysis; +import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint; +import com.android.tools.r8.ir.optimize.classinliner.constraint.ConditionalClassInlinerMethodConstraint; +import com.android.tools.r8.shaking.AppInfoWithLiveness; + +public class ClassInlinerMethodConstraintAnalysis { + + public static ClassInlinerMethodConstraint analyze( + AppView<AppInfoWithLiveness> appView, ProgramMethod method, IRCode code) { + if (method.getDefinition().isClassInitializer() + || method.getDefinition().getNumberOfArguments() == 0) { + return ClassInlinerMethodConstraint.alwaysFalse(); + } + + // Analyze code. + IntraproceduralDataflowAnalysis<ParameterUsages> analysis = + new IntraproceduralDataflowAnalysis<>( + ParameterUsages.bottom(), new TransferFunction(appView, method, code)); + SuccessfulDataflowAnalysisResult<ParameterUsages> result = + analysis.run(code.entryBlock()).asSuccessfulAnalysisResult(); + if (result == null) { + return ClassInlinerMethodConstraint.alwaysFalse(); + } + ParameterUsages usages = result.join().externalize(); + if (usages.isBottom()) { + return ClassInlinerMethodConstraint.alwaysTrue(); + } + if (usages.isTop()) { + return ClassInlinerMethodConstraint.alwaysFalse(); + } + return new ConditionalClassInlinerMethodConstraint(usages); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/DefaultAnalysisContext.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/DefaultAnalysisContext.java new file mode 100644 index 0000000..5378648 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/DefaultAnalysisContext.java
@@ -0,0 +1,16 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.optimize.classinliner.analysis; + +class DefaultAnalysisContext extends AnalysisContext { + + private static final DefaultAnalysisContext INSTANCE = new DefaultAnalysisContext(); + + private DefaultAnalysisContext() {} + + static DefaultAnalysisContext getInstance() { + return INSTANCE; + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/InternalNonEmptyParameterUsage.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/InternalNonEmptyParameterUsage.java new file mode 100644 index 0000000..4bba181 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/InternalNonEmptyParameterUsage.java
@@ -0,0 +1,282 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.optimize.classinliner.analysis; + +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.ir.code.InvokeMethodWithReceiver; +import com.android.tools.r8.utils.BooleanUtils; +import com.google.common.collect.ImmutableMultiset; +import com.google.common.collect.ImmutableSet; +import java.util.Collection; +import java.util.Objects; +import java.util.Set; + +/** + * Non-trivial information (neither BOTTOM nor TOP) about a method's usage of a given parameter. + * + * <p>This is internal to the analysis, since {@link #methodCallsWithParameterAsReceiver} references + * instructions from {@link com.android.tools.r8.ir.code.IRCode}, which makes it unsuited for being + * stored in {@link com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo}. + */ +public class InternalNonEmptyParameterUsage extends ParameterUsage { + + private Set<DexField> fieldsReadFromParameter; + private Set<InvokeMethodWithReceiver> methodCallsWithParameterAsReceiver; + + private boolean isParameterMutated; + private boolean isParameterReturned; + private boolean isParameterUsedAsLock; + + InternalNonEmptyParameterUsage( + Set<DexField> fieldsReadFromParameter, + Set<InvokeMethodWithReceiver> methodCallsWithParameterAsReceiver, + boolean isParameterMutated, + boolean isParameterReturned, + boolean isParameterUsedAsLock) { + assert !fieldsReadFromParameter.isEmpty() + || !methodCallsWithParameterAsReceiver.isEmpty() + || isParameterMutated + || isParameterReturned + || isParameterUsedAsLock; + this.fieldsReadFromParameter = fieldsReadFromParameter; + this.methodCallsWithParameterAsReceiver = methodCallsWithParameterAsReceiver; + this.isParameterMutated = isParameterMutated; + this.isParameterReturned = isParameterReturned; + this.isParameterUsedAsLock = isParameterUsedAsLock; + } + + static Builder builder() { + return new Builder(); + } + + Builder builderFromInstance() { + return new Builder(this); + } + + @Override + InternalNonEmptyParameterUsage addFieldReadFromParameter(DexField field) { + ImmutableSet.Builder<DexField> newFieldsReadFromParameterBuilder = ImmutableSet.builder(); + newFieldsReadFromParameterBuilder.addAll(fieldsReadFromParameter); + newFieldsReadFromParameterBuilder.add(field); + return new InternalNonEmptyParameterUsage( + newFieldsReadFromParameterBuilder.build(), + methodCallsWithParameterAsReceiver, + isParameterMutated, + isParameterReturned, + isParameterUsedAsLock); + } + + @Override + InternalNonEmptyParameterUsage addMethodCallWithParameterAsReceiver( + InvokeMethodWithReceiver invoke) { + ImmutableSet.Builder<InvokeMethodWithReceiver> newMethodCallsWithParameterAsReceiverBuilder = + ImmutableSet.builder(); + newMethodCallsWithParameterAsReceiverBuilder.addAll(methodCallsWithParameterAsReceiver); + newMethodCallsWithParameterAsReceiverBuilder.add(invoke); + return new InternalNonEmptyParameterUsage( + fieldsReadFromParameter, + newMethodCallsWithParameterAsReceiverBuilder.build(), + isParameterMutated, + isParameterReturned, + isParameterUsedAsLock); + } + + @Override + public InternalNonEmptyParameterUsage asInternalNonEmpty() { + return this; + } + + @Override + ParameterUsage externalize() { + ImmutableMultiset.Builder<DexMethod> methodCallsWithParameterAsReceiverBuilder = + ImmutableMultiset.builder(); + methodCallsWithParameterAsReceiver.forEach( + invoke -> methodCallsWithParameterAsReceiverBuilder.add(invoke.getInvokedMethod())); + return new NonEmptyParameterUsage( + fieldsReadFromParameter, + methodCallsWithParameterAsReceiverBuilder.build(), + isParameterMutated, + isParameterReturned, + isParameterUsedAsLock); + } + + @Override + public boolean isParameterMutated() { + return isParameterMutated; + } + + @Override + public boolean isParameterReturned() { + return isParameterReturned; + } + + @Override + public boolean isParameterUsedAsLock() { + return isParameterUsedAsLock; + } + + InternalNonEmptyParameterUsage join(InternalNonEmptyParameterUsage other) { + return builderFromInstance() + .addFieldsReadFromParameter(other.fieldsReadFromParameter) + .addMethodCallsWithParameterAsReceiver(other.methodCallsWithParameterAsReceiver) + .joinIsReceiverMutated(other.isParameterMutated) + .joinIsReceiverReturned(other.isParameterReturned) + .joinIsReceiverUsedAsLock(other.isParameterUsedAsLock) + .build(); + } + + @Override + InternalNonEmptyParameterUsage setParameterMutated() { + if (isParameterMutated) { + return this; + } + return new InternalNonEmptyParameterUsage( + fieldsReadFromParameter, + methodCallsWithParameterAsReceiver, + true, + isParameterReturned, + isParameterUsedAsLock); + } + + @Override + InternalNonEmptyParameterUsage setParameterReturned() { + if (isParameterReturned) { + return this; + } + return new InternalNonEmptyParameterUsage( + fieldsReadFromParameter, + methodCallsWithParameterAsReceiver, + isParameterMutated, + true, + isParameterUsedAsLock); + } + + @Override + InternalNonEmptyParameterUsage setParameterUsedAsLock() { + if (isParameterUsedAsLock) { + return this; + } + return new InternalNonEmptyParameterUsage( + fieldsReadFromParameter, + methodCallsWithParameterAsReceiver, + isParameterMutated, + isParameterReturned, + true); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != getClass()) { + return false; + } + InternalNonEmptyParameterUsage knownParameterUsage = (InternalNonEmptyParameterUsage) obj; + return isParameterMutated == knownParameterUsage.isParameterMutated + && isParameterReturned == knownParameterUsage.isParameterReturned + && isParameterUsedAsLock == knownParameterUsage.isParameterUsedAsLock + && fieldsReadFromParameter.equals(knownParameterUsage.fieldsReadFromParameter) + && methodCallsWithParameterAsReceiver.equals( + knownParameterUsage.methodCallsWithParameterAsReceiver); + } + + @Override + public int hashCode() { + int hash = + 31 * (31 + fieldsReadFromParameter.hashCode()) + + methodCallsWithParameterAsReceiver.hashCode(); + assert hash == Objects.hash(fieldsReadFromParameter, methodCallsWithParameterAsReceiver); + hash = (hash << 1) | BooleanUtils.intValue(isParameterMutated); + hash = (hash << 1) | BooleanUtils.intValue(isParameterReturned); + hash = (hash << 1) | BooleanUtils.intValue(isParameterUsedAsLock); + return hash; + } + + static class Builder { + + private ImmutableSet.Builder<DexField> fieldsReadFromParameterBuilder; + private ImmutableSet.Builder<InvokeMethodWithReceiver> + methodCallsWithParameterAsReceiverBuilder; + private boolean isParameterMutated; + private boolean isParameterReturned; + private boolean isParameterUsedAsLock; + + Builder() { + fieldsReadFromParameterBuilder = ImmutableSet.builder(); + methodCallsWithParameterAsReceiverBuilder = ImmutableSet.builder(); + } + + Builder(InternalNonEmptyParameterUsage methodBehavior) { + fieldsReadFromParameterBuilder = + ImmutableSet.<DexField>builder().addAll(methodBehavior.fieldsReadFromParameter); + methodCallsWithParameterAsReceiverBuilder = + ImmutableSet.<InvokeMethodWithReceiver>builder() + .addAll(methodBehavior.methodCallsWithParameterAsReceiver); + isParameterMutated = methodBehavior.isParameterMutated; + isParameterReturned = methodBehavior.isParameterReturned; + isParameterUsedAsLock = methodBehavior.isParameterUsedAsLock; + } + + Builder addFieldReadFromParameter(DexField fieldReadFromParameter) { + fieldsReadFromParameterBuilder.add(fieldReadFromParameter); + return this; + } + + Builder addFieldsReadFromParameter(Collection<DexField> fieldsReadFromParameter) { + fieldsReadFromParameterBuilder.addAll(fieldsReadFromParameter); + return this; + } + + Builder addMethodCallWithParameterAsReceiver( + InvokeMethodWithReceiver methodCallWithParameterAsReceiver) { + methodCallsWithParameterAsReceiverBuilder.add(methodCallWithParameterAsReceiver); + return this; + } + + Builder addMethodCallsWithParameterAsReceiver( + Set<InvokeMethodWithReceiver> methodCallsWithParameterAsReceiver) { + methodCallsWithParameterAsReceiverBuilder.addAll(methodCallsWithParameterAsReceiver); + return this; + } + + Builder joinIsReceiverMutated(boolean isParameterMutated) { + this.isParameterMutated |= isParameterMutated; + return this; + } + + Builder joinIsReceiverReturned(boolean isParameterReturned) { + this.isParameterReturned |= isParameterReturned; + return this; + } + + Builder joinIsReceiverUsedAsLock(boolean isParameterUsedAsLock) { + this.isParameterUsedAsLock |= isParameterUsedAsLock; + return this; + } + + Builder setParameterMutated() { + this.isParameterMutated = true; + return this; + } + + Builder setParameterReturned() { + this.isParameterReturned = true; + return this; + } + + Builder setParameterUsedAsLock() { + this.isParameterUsedAsLock = true; + return this; + } + + InternalNonEmptyParameterUsage build() { + return new InternalNonEmptyParameterUsage( + fieldsReadFromParameterBuilder.build(), + methodCallsWithParameterAsReceiverBuilder.build(), + isParameterMutated, + isParameterReturned, + isParameterUsedAsLock); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsage.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsage.java new file mode 100644 index 0000000..d377305 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsage.java
@@ -0,0 +1,130 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.optimize.classinliner.analysis; + +import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.ir.code.InvokeMethodWithReceiver; +import com.android.tools.r8.utils.BooleanUtils; +import com.google.common.collect.Multiset; +import java.util.Objects; +import java.util.Set; + +public class NonEmptyParameterUsage extends ParameterUsage { + + private Set<DexField> fieldsReadFromParameter; + private Multiset<DexMethod> methodCallsWithParameterAsReceiver; + + private boolean isParameterMutated; + private boolean isParameterReturned; + private boolean isParameterUsedAsLock; + + NonEmptyParameterUsage( + Set<DexField> fieldsReadFromParameter, + Multiset<DexMethod> methodCallsWithParameterAsReceiver, + boolean isParameterMutated, + boolean isParameterReturned, + boolean isParameterUsedAsLock) { + assert !fieldsReadFromParameter.isEmpty() + || !methodCallsWithParameterAsReceiver.isEmpty() + || isParameterMutated + || isParameterReturned + || isParameterUsedAsLock; + this.fieldsReadFromParameter = fieldsReadFromParameter; + this.methodCallsWithParameterAsReceiver = methodCallsWithParameterAsReceiver; + this.isParameterMutated = isParameterMutated; + this.isParameterReturned = isParameterReturned; + this.isParameterUsedAsLock = isParameterUsedAsLock; + } + + @Override + ParameterUsage addFieldReadFromParameter(DexField field) { + throw new Unreachable(); + } + + @Override + ParameterUsage addMethodCallWithParameterAsReceiver(InvokeMethodWithReceiver invoke) { + throw new Unreachable(); + } + + @Override + public NonEmptyParameterUsage asNonEmpty() { + return this; + } + + @Override + ParameterUsage externalize() { + throw new Unreachable(); + } + + public boolean hasFieldsReadFromParameter() { + return !getFieldsReadFromParameter().isEmpty(); + } + + public Set<DexField> getFieldsReadFromParameter() { + return fieldsReadFromParameter; + } + + public Multiset<DexMethod> getMethodCallsWithParameterAsReceiver() { + return methodCallsWithParameterAsReceiver; + } + + @Override + public boolean isParameterMutated() { + return isParameterMutated; + } + + @Override + public boolean isParameterReturned() { + return isParameterReturned; + } + + @Override + public boolean isParameterUsedAsLock() { + return isParameterUsedAsLock; + } + + @Override + ParameterUsage setParameterMutated() { + throw new Unreachable(); + } + + @Override + ParameterUsage setParameterReturned() { + throw new Unreachable(); + } + + @Override + ParameterUsage setParameterUsedAsLock() { + throw new Unreachable(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != getClass()) { + return false; + } + NonEmptyParameterUsage knownParameterUsage = (NonEmptyParameterUsage) obj; + return isParameterMutated == knownParameterUsage.isParameterMutated + && isParameterReturned == knownParameterUsage.isParameterReturned + && isParameterUsedAsLock == knownParameterUsage.isParameterUsedAsLock + && fieldsReadFromParameter.equals(knownParameterUsage.fieldsReadFromParameter) + && methodCallsWithParameterAsReceiver.equals( + knownParameterUsage.methodCallsWithParameterAsReceiver); + } + + @Override + public int hashCode() { + int hash = + 31 * (31 + fieldsReadFromParameter.hashCode()) + + methodCallsWithParameterAsReceiver.hashCode(); + assert hash == Objects.hash(fieldsReadFromParameter, methodCallsWithParameterAsReceiver); + hash = (hash << 1) | BooleanUtils.intValue(isParameterMutated); + hash = (hash << 1) | BooleanUtils.intValue(isParameterReturned); + hash = (hash << 1) | BooleanUtils.intValue(isParameterUsedAsLock); + return hash; + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsagePerContext.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsagePerContext.java new file mode 100644 index 0000000..9460e7e --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsagePerContext.java
@@ -0,0 +1,131 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.optimize.classinliner.analysis; + +import com.google.common.collect.ImmutableMap; +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; + +class NonEmptyParameterUsagePerContext extends ParameterUsagePerContext { + + private final Map<AnalysisContext, ParameterUsage> backing; + + private NonEmptyParameterUsagePerContext(Map<AnalysisContext, ParameterUsage> backing) { + assert !backing.isEmpty(); + this.backing = backing; + } + + static ParameterUsagePerContext create(Map<AnalysisContext, ParameterUsage> backing) { + return backing.isEmpty() ? bottom() : new NonEmptyParameterUsagePerContext(backing); + } + + static NonEmptyParameterUsagePerContext createInitial() { + return new NonEmptyParameterUsagePerContext( + ImmutableMap.of(DefaultAnalysisContext.getInstance(), ParameterUsage.bottom())); + } + + void forEach(BiConsumer<AnalysisContext, ParameterUsage> consumer) { + backing.forEach(consumer); + } + + ParameterUsagePerContext join(NonEmptyParameterUsagePerContext parameterUsagePerContext) { + if (isBottom()) { + return parameterUsagePerContext; + } + if (parameterUsagePerContext.isBottom()) { + return this; + } + Map<AnalysisContext, ParameterUsage> newBacking = new HashMap<>(backing); + parameterUsagePerContext.forEach( + (context, parameterUsage) -> + newBacking.put( + context, + parameterUsage.join(newBacking.getOrDefault(context, ParameterUsage.bottom())))); + return create(newBacking); + } + + @Override + NonEmptyParameterUsagePerContext asKnown() { + return this; + } + + @Override + ParameterUsagePerContext externalize() { + boolean allBottom = true; + boolean allTop = true; + for (ParameterUsage usage : backing.values()) { + if (!usage.isBottom()) { + allBottom = false; + } + if (!usage.isTop()) { + allTop = false; + } + } + if (allBottom) { + return bottom(); + } + if (allTop) { + return top(); + } + // Remove mappings to top. These mappings represent unknown information, which there is no point + // in storing. After the removal of these mappings, the result should still be non-empty. + ParameterUsagePerContext rebuilt = + rebuild((context, usage) -> usage.isTop() ? null : usage.externalize()); + assert !rebuilt.isBottom(); + assert !rebuilt.isTop(); + return rebuilt; + } + + @Override + public ParameterUsage get(AnalysisContext context) { + return backing.getOrDefault(context, ParameterUsage.top()); + } + + @Override + ParameterUsagePerContext rebuild( + BiFunction<AnalysisContext, ParameterUsage, ParameterUsage> transformation) { + ImmutableMap.Builder<AnalysisContext, ParameterUsage> builder = null; + for (Map.Entry<AnalysisContext, ParameterUsage> entry : backing.entrySet()) { + AnalysisContext context = entry.getKey(); + ParameterUsage usage = entry.getValue(); + ParameterUsage newUsage = transformation.apply(context, usage); + if (newUsage != null) { + if (newUsage != usage) { + if (builder == null) { + builder = ImmutableMap.builder(); + for (Map.Entry<AnalysisContext, ParameterUsage> previousEntry : backing.entrySet()) { + AnalysisContext previousContext = previousEntry.getKey(); + if (previousContext == context) { + break; + } + builder.put(previousContext, previousEntry.getValue()); + } + } + builder.put(context, newUsage); + } else if (builder != null) { + builder.put(context, newUsage); + } + } + } + return builder != null ? create(builder.build()) : this; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != getClass()) { + return false; + } + NonEmptyParameterUsagePerContext knownParameterUsagePerContext = + (NonEmptyParameterUsagePerContext) obj; + return backing.equals(knownParameterUsagePerContext.backing); + } + + @Override + public int hashCode() { + return backing.hashCode(); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsages.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsages.java new file mode 100644 index 0000000..f7b2cae --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsages.java
@@ -0,0 +1,238 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.optimize.classinliner.analysis; + +import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration; +import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.utils.ArrayUtils; +import com.android.tools.r8.utils.Int2ObjectMapUtils; +import com.android.tools.r8.utils.IntObjConsumer; +import com.android.tools.r8.utils.IntObjPredicate; +import com.android.tools.r8.utils.IntObjToObjFunction; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Predicate; + +/** + * This implements the lattice for the dataflow analysis used to determine if a given method is + * eligible for class inlining. + * + * <p>Given a value that is subject to class inlining (e.g., `A a = new A()`), we need to determine + * if the value `a` will be eligible for class inlining if it flows into a method call. The value + * `a` may flow into any argument position, therefore we need to be able to determine if calls where + * `a` is used as a receiver are eligible for class inlining {@code a.foo(...)}, as well as calls + * where `a` is not used as a receiver: {@code other.foo(a)} or {@code Clazz.foo(a)}. + * + * <p>To answer such questions, this lattice contains information about the way a method uses its + * parameters. For a given parameter, this information is encoded in {@link ParameterUsage}. + * + * <p>To facilitate context sensitive information, {@link ParameterUsagePerContext} gives the + * parameter usage information for a given parameter in a given context. As a simple example, + * consider the following method: + * + * <pre> + * int x; + * void foo() { + * if (this.x == 0) { + * // Do nothing. + * } else { + * System.out.println(x); + * } + * } + * </pre> + * + * <p>In the above example, the parameter `this` is not eligible for class inlining if `this.x != + * 0`. However, when `this.x == 0`, the parameter usage information is bottom. This piece of + * information is encoded as a map lattice from contexts to parameter usage information: + * + * <pre> + * ParameterUsagePerContext[ + * Context[this.x == 0] -> BottomParameterUsage (BOTTOM), + * DefaultContext -> UnknownParameterUsage (TOP) + * ] + * </pre> + * + * <p>Finally, to provide the information for each method parameter, this class provides a mapping + * from parameters to {@link ParameterUsagePerContext}. + */ +public class NonEmptyParameterUsages extends ParameterUsages { + + private static final AssumeAndCheckCastAliasedValueConfiguration aliasedValueConfiguration = + AssumeAndCheckCastAliasedValueConfiguration.getInstance(); + + private final Int2ObjectMap<ParameterUsagePerContext> backing; + + private NonEmptyParameterUsages(Int2ObjectMap<ParameterUsagePerContext> backing) { + assert !backing.isEmpty() : "Should use bottom() instead"; + this.backing = backing; + } + + public static ParameterUsages create(Int2ObjectMap<ParameterUsagePerContext> backing) { + return backing.isEmpty() ? bottom() : new NonEmptyParameterUsages(backing); + } + + public boolean allMatch(IntObjPredicate<ParameterUsagePerContext> predicate) { + for (Int2ObjectMap.Entry<ParameterUsagePerContext> entry : backing.int2ObjectEntrySet()) { + if (!predicate.test(entry.getIntKey(), entry.getValue())) { + return false; + } + } + return true; + } + + @Override + public NonEmptyParameterUsages asNonEmpty() { + return this; + } + + @Override + ParameterUsages externalize() { + NonEmptyParameterUsages rebuilt = + rebuildParameters((parameter, usagePerContext) -> usagePerContext.externalize()); + boolean allBottom = true; + boolean allTop = true; + for (ParameterUsagePerContext usagePerContext : rebuilt.backing.values()) { + if (!usagePerContext.isBottom()) { + allBottom = false; + } + if (!usagePerContext.isTop()) { + allTop = false; + } + } + if (allBottom) { + return bottom(); + } + if (allTop) { + return top(); + } + return rebuilt; + } + + @Override + ParameterUsages put(int parameter, ParameterUsagePerContext parameterUsagePerContext) { + Int2ObjectOpenHashMap<ParameterUsagePerContext> newBacking = + new Int2ObjectOpenHashMap<>(backing); + newBacking.put(parameter, parameterUsagePerContext); + return create(newBacking); + } + + public void forEach(IntObjConsumer<ParameterUsagePerContext> consumer) { + Int2ObjectMapUtils.forEach(backing, consumer); + } + + @Override + public ParameterUsagePerContext get(int parameter) { + return backing.getOrDefault(parameter, ParameterUsagePerContext.top()); + } + + NonEmptyParameterUsages abandonClassInliningInCurrentContexts(Value value) { + return rebuildParameter(value, (context, usage) -> ParameterUsage.top()); + } + + NonEmptyParameterUsages abandonClassInliningInCurrentContexts(Collection<Value> values) { + if (values.isEmpty()) { + return this; + } + int[] parametersToRebuild = new int[values.size()]; + Iterator<Value> iterator = values.iterator(); + for (int i = 0; i < values.size(); i++) { + parametersToRebuild[i] = iterator.next().getDefinition().asArgument().getIndex(); + } + return rebuildParameters( + (currentParameter, usagePerContext) -> + ArrayUtils.containsInt(parametersToRebuild, currentParameter) + ? usagePerContext.rebuild((context, usage) -> ParameterUsage.top()) + : usagePerContext); + } + + NonEmptyParameterUsages abandonClassInliningInCurrentContexts( + Iterable<Value> values, Predicate<Value> predicate) { + List<Value> filtered = new ArrayList<>(); + for (Value value : values) { + Value root = value.getAliasedValue(aliasedValueConfiguration); + if (predicate.test(root)) { + filtered.add(root); + } + } + return abandonClassInliningInCurrentContexts(filtered); + } + + NonEmptyParameterUsages rebuildParameter( + Value value, BiFunction<AnalysisContext, ParameterUsage, ParameterUsage> transformation) { + Value valueRoot = value.getAliasedValue(aliasedValueConfiguration); + assert valueRoot.isArgument(); + int parameter = valueRoot.getDefinition().asArgument().getIndex(); + return rebuildParameters( + (currentParameter, usagePerContext) -> + currentParameter == parameter + ? usagePerContext.rebuild(transformation) + : usagePerContext); + } + + NonEmptyParameterUsages rebuildParameters( + IntObjToObjFunction<ParameterUsagePerContext, ParameterUsagePerContext> transformation) { + Int2ObjectMap<ParameterUsagePerContext> rebuiltBacking = null; + for (Int2ObjectMap.Entry<ParameterUsagePerContext> entry : backing.int2ObjectEntrySet()) { + int parameter = entry.getIntKey(); + ParameterUsagePerContext usagePerContext = entry.getValue(); + ParameterUsagePerContext newUsagePerContext = + transformation.apply(parameter, entry.getValue()); + if (newUsagePerContext != usagePerContext) { + if (rebuiltBacking == null) { + rebuiltBacking = new Int2ObjectOpenHashMap<>(); + for (Int2ObjectMap.Entry<ParameterUsagePerContext> previousEntry : + backing.int2ObjectEntrySet()) { + int previousParameter = previousEntry.getIntKey(); + if (previousParameter == parameter) { + break; + } + rebuiltBacking.put(previousParameter, previousEntry.getValue()); + } + } + rebuiltBacking.put(parameter, newUsagePerContext); + } else if (rebuiltBacking != null) { + rebuiltBacking.put(parameter, newUsagePerContext); + } + } + return rebuiltBacking != null ? new NonEmptyParameterUsages(rebuiltBacking) : this; + } + + public NonEmptyParameterUsages join(NonEmptyParameterUsages otherAnalysisState) { + if (isBottom()) { + return otherAnalysisState; + } + if (otherAnalysisState.isBottom()) { + return this; + } + Int2ObjectMap<ParameterUsagePerContext> newBacking = new Int2ObjectOpenHashMap<>(backing); + otherAnalysisState.forEach( + (parameter, parameterUsagePerContext) -> + newBacking.put( + parameter, + parameterUsagePerContext.join( + Int2ObjectMapUtils.getOrDefault( + newBacking, parameter, ParameterUsagePerContext.bottom())))); + return new NonEmptyParameterUsages(newBacking); + } + + @Override + public boolean equals(Object other) { + if (other == null || getClass() != other.getClass()) { + return false; + } + NonEmptyParameterUsages analysisState = (NonEmptyParameterUsages) other; + return backing.equals(analysisState.backing); + } + + @Override + public int hashCode() { + return backing.hashCode(); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsage.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsage.java new file mode 100644 index 0000000..e2ad8da --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsage.java
@@ -0,0 +1,90 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.optimize.classinliner.analysis; + +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.ir.code.InvokeMethodWithReceiver; + +public abstract class ParameterUsage { + + abstract ParameterUsage addFieldReadFromParameter(DexField field); + + abstract ParameterUsage addMethodCallWithParameterAsReceiver(InvokeMethodWithReceiver invoke); + + public NonEmptyParameterUsage asNonEmpty() { + return null; + } + + InternalNonEmptyParameterUsage asInternalNonEmpty() { + return null; + } + + abstract ParameterUsage externalize(); + + /** + * Returns true if this is an instanceof {@link BottomParameterUsage}. + * + * <p>Note that this does NOT imply that the parameter is <i>unused</i>, but only that it is + * always eligible for class inlining. + */ + public boolean isBottom() { + return false; + } + + /** + * Returns true if the method may mutate the state of this parameter (i.e., mutate the value of + * one of its instance fields). + */ + public abstract boolean isParameterMutated(); + + /** + * Returns true if the method <i>may</i> return the parameter. + * + * <p>Note that this does NOT imply that the method <i>always</i> returns the method. + */ + public abstract boolean isParameterReturned(); + + /** + * Returns true if the parameter may be used as a lock (i.e., may flow into a monitor + * instruction). + */ + public abstract boolean isParameterUsedAsLock(); + + /** + * Returns true if this is an instance of {@link UnknownParameterUsage}. + * + * <p>In this case, the parameter is never eligible for class inlining. + */ + public boolean isTop() { + return false; + } + + ParameterUsage join(ParameterUsage parameterUsage) { + if (isBottom()) { + return parameterUsage; + } + if (parameterUsage.isBottom()) { + return this; + } + if (isTop() || parameterUsage.isTop()) { + return top(); + } + return asInternalNonEmpty().join(parameterUsage.asInternalNonEmpty()); + } + + abstract ParameterUsage setParameterMutated(); + + abstract ParameterUsage setParameterReturned(); + + abstract ParameterUsage setParameterUsedAsLock(); + + public static BottomParameterUsage bottom() { + return BottomParameterUsage.getInstance(); + } + + public static UnknownParameterUsage top() { + return UnknownParameterUsage.getInstance(); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsagePerContext.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsagePerContext.java new file mode 100644 index 0000000..f294ea2 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsagePerContext.java
@@ -0,0 +1,55 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.optimize.classinliner.analysis; + +import java.util.function.BiFunction; + +public abstract class ParameterUsagePerContext { + + NonEmptyParameterUsagePerContext asKnown() { + return null; + } + + abstract ParameterUsagePerContext externalize(); + + /** Returns the usage information for this parameter in the given context. */ + public abstract ParameterUsage get(AnalysisContext context); + + /** + * Returns true if this is an instance of {@link BottomParameterUsagePerContext}. + * + * <p>In this case, the given parameter is always eligible for class inlining. + */ + public boolean isBottom() { + return false; + } + + /** + * Returns true if this is an instance of {@link UnknownParameterUsagePerContext}. + * + * <p>In this case, the given parameter is never eligible for class inlining. + */ + public boolean isTop() { + return false; + } + + ParameterUsagePerContext join(ParameterUsagePerContext parameterUsagePerContext) { + if (isTop() || parameterUsagePerContext.isTop()) { + return top(); + } + return asKnown().join(parameterUsagePerContext.asKnown()); + } + + abstract ParameterUsagePerContext rebuild( + BiFunction<AnalysisContext, ParameterUsage, ParameterUsage> transformation); + + static BottomParameterUsagePerContext bottom() { + return BottomParameterUsagePerContext.getInstance(); + } + + static UnknownParameterUsagePerContext top() { + return UnknownParameterUsagePerContext.getInstance(); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsages.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsages.java new file mode 100644 index 0000000..0cb3a5e --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsages.java
@@ -0,0 +1,66 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.optimize.classinliner.analysis; + +import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractState; + +public abstract class ParameterUsages extends AbstractState<ParameterUsages> { + + @Override + public ParameterUsages asAbstractState() { + return this; + } + + public NonEmptyParameterUsages asNonEmpty() { + return null; + } + + /** + * Prepares this instance for being stored in the optimization info. This converts instances + * inside this {@link ParameterUsages} instance that are not suitable for being stored in + * optimization info into instances that can be stored in the optimization info. + * + * <p>For example, converts instances of {@link InternalNonEmptyParameterUsage} to {@link + * NonEmptyParameterUsage}. This is needed because {@link InternalNonEmptyParameterUsage} is not + * suitable for being stored in {@link + * com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo}, since it contains references to + * IR instructions. + */ + abstract ParameterUsages externalize(); + + public abstract ParameterUsagePerContext get(int parameter); + + public boolean isBottom() { + return false; + } + + public boolean isTop() { + return false; + } + + @Override + public ParameterUsages join(ParameterUsages state) { + if (isBottom()) { + return state; + } + if (state.isBottom()) { + return this; + } + if (isTop() || state.isTop()) { + return top(); + } + return asNonEmpty().join(state.asNonEmpty()); + } + + abstract ParameterUsages put(int parameter, ParameterUsagePerContext usagePerContext); + + public static BottomParameterUsages bottom() { + return BottomParameterUsages.getInstance(); + } + + public static UnknownParameterUsages top() { + return UnknownParameterUsages.getInstance(); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java new file mode 100644 index 0000000..8cbade6 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java
@@ -0,0 +1,386 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.optimize.classinliner.analysis; + +import static com.android.tools.r8.ir.code.Opcodes.ASSUME; +import static com.android.tools.r8.ir.code.Opcodes.CHECK_CAST; +import static com.android.tools.r8.ir.code.Opcodes.IF; +import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET; +import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT; +import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT; +import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE; +import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC; +import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL; +import static com.android.tools.r8.ir.code.Opcodes.MONITOR; +import static com.android.tools.r8.ir.code.Opcodes.RETURN; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.ClasspathOrLibraryClass; +import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.FieldResolutionResult; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult; +import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractTransferFunction; +import com.android.tools.r8.ir.analysis.framework.intraprocedural.FailedTransferFunctionResult; +import com.android.tools.r8.ir.analysis.framework.intraprocedural.TransferFunctionResult; +import com.android.tools.r8.ir.analysis.type.TypeElement; +import com.android.tools.r8.ir.code.AliasedValueConfiguration; +import com.android.tools.r8.ir.code.Argument; +import com.android.tools.r8.ir.code.Assume; +import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration; +import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.CheckCast; +import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.If; +import com.android.tools.r8.ir.code.InstanceGet; +import com.android.tools.r8.ir.code.InstancePut; +import com.android.tools.r8.ir.code.Instruction; +import com.android.tools.r8.ir.code.InvokeDirect; +import com.android.tools.r8.ir.code.InvokeInterface; +import com.android.tools.r8.ir.code.InvokeStatic; +import com.android.tools.r8.ir.code.InvokeVirtual; +import com.android.tools.r8.ir.code.Monitor; +import com.android.tools.r8.ir.code.Return; +import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.google.common.collect.Sets; +import java.util.Set; + +class TransferFunction implements AbstractTransferFunction<ParameterUsages> { + + private static final AliasedValueConfiguration aliasedValueConfiguration = + AssumeAndCheckCastAliasedValueConfiguration.getInstance(); + + private final AppView<AppInfoWithLiveness> appView; + private final DexItemFactory dexItemFactory; + private final ProgramMethod method; + + // The last argument instruction. + private final Argument lastArgument; + + // Caches the parent or forwarding constructor call (only used in constructors). + private InvokeDirect constructorInvoke; + + // The arguments that are considered by the analysis. We don't consider primitive arguments since + // they cannot be class inlined. + private Set<Value> argumentsOfInterest = Sets.newIdentityHashSet(); + + // Instructions that use one of the arguments. Instructions that don't use any of the arguments + // do not have any impact on the ability to class inline the arguments, therefore they are + // skipped. + private Set<Instruction> instructionsOfInterest = Sets.newIdentityHashSet(); + + TransferFunction(AppView<AppInfoWithLiveness> appView, ProgramMethod method, IRCode code) { + this.appView = appView; + this.dexItemFactory = appView.dexItemFactory(); + this.method = method; + this.lastArgument = code.getLastArgument(); + } + + @Override + public TransferFunctionResult<ParameterUsages> apply( + Instruction instruction, ParameterUsages state) { + if (instruction.isArgument()) { + Argument argument = instruction.asArgument(); + ParameterUsages result = analyzeArgument(argument, state); + // After analyzing the last argument instruction, only proceed if there is at least one + // argument that may be eligible for class inlining. + if (argument == lastArgument + && result.asNonEmpty().allMatch((context, usagePerContext) -> usagePerContext.isTop())) { + return new FailedTransferFunctionResult<>(); + } + return result; + } + if (!instructionsOfInterest.contains(instruction)) { + // The instruction does not use any of the argument values that we are analyzing, so for the + // purpose of class inlining we can ignore this instruction. + return state; + } + assert !state.isBottom(); + assert !state.isTop(); + return apply(instruction, state.asNonEmpty()); + } + + private ParameterUsages apply(Instruction instruction, NonEmptyParameterUsages state) { + switch (instruction.opcode()) { + case ASSUME: + return analyzeAssume(instruction.asAssume(), state); + case CHECK_CAST: + return analyzeCheckCast(instruction.asCheckCast(), state); + case IF: + return analyzeIf(instruction.asIf(), state); + case INSTANCE_GET: + return analyzeInstanceGet(instruction.asInstanceGet(), state); + case INSTANCE_PUT: + return analyzeInstancePut(instruction.asInstancePut(), state); + case INVOKE_DIRECT: + return analyzeInvokeDirect(instruction.asInvokeDirect(), state); + case INVOKE_INTERFACE: + return analyzeInvokeInterface(instruction.asInvokeInterface(), state); + case INVOKE_STATIC: + return analyzeInvokeStatic(instruction.asInvokeStatic(), state); + case INVOKE_VIRTUAL: + return analyzeInvokeVirtual(instruction.asInvokeVirtual(), state); + case MONITOR: + return analyzeMonitor(instruction.asMonitor(), state); + case RETURN: + return analyzeReturn(instruction.asReturn(), state); + default: + return fail(instruction, state); + } + } + + @Override + public ParameterUsages computeBlockEntryState( + BasicBlock block, BasicBlock predecessor, ParameterUsages predecessorExitState) { + // TODO(b/173337498): Fork a new `FIELD=x` analysis context for the successor block if the + // predecessor ends with an if or switch instruction, and the successor block is the + // `FIELD=x` target of the predecessor. To avoid an excessive number of contexts being + // created, only allow forking new contexts for $r8$classId fields synthesized by the + // horizontal class merger. + return predecessorExitState; + } + + private ParameterUsages analyzeArgument(Argument argument, ParameterUsages state) { + // Only consider arguments that could store an instance eligible for class inlining. Note that + // we can't ignore parameters with a library type, since instances of program classes could + // still flow into such parameters. + Value value = argument.outValue(); + if (!isMaybeEligibleForClassInlining(value.getType()) || value.hasPhiUsers()) { + return state.put(argument.getIndex(), ParameterUsagePerContext.top()); + } + + // Mark the users of this argument for analysis, and fork the analysis of this argument in the + // default analysis context. + argumentsOfInterest.add(value); + instructionsOfInterest.addAll(value.aliasedUsers(aliasedValueConfiguration)); + return state.put(argument.getIndex(), NonEmptyParameterUsagePerContext.createInitial()); + } + + private ParameterUsages analyzeAssume(Assume assume, NonEmptyParameterUsages state) { + // Mark the value as ineligible for class inlining if it has phi users. + return assume.outValue().hasPhiUsers() ? fail(assume, state) : state; + } + + private ParameterUsages analyzeCheckCast(CheckCast checkCast, NonEmptyParameterUsages state) { + // Mark the value as ineligible for class inlining if it has phi users. + return checkCast.outValue().hasPhiUsers() ? fail(checkCast, state) : state; + } + + private ParameterUsages analyzeIf(If theIf, NonEmptyParameterUsages state) { + // Null/not-null tests are ok. + if (theIf.isZeroTest()) { + assert argumentsOfInterest.contains(theIf.lhs().getAliasedValue(aliasedValueConfiguration)); + return state; + } + + // For non-null tests, mark the inputs as ineligible for class inlining. + return fail(theIf, state); + } + + private ParameterUsages analyzeInstanceGet( + InstanceGet instanceGet, NonEmptyParameterUsages state) { + // Instance field reads are OK, as long as the field resolves, since the class inliner will + // just replace the field read by the value of the field. + FieldResolutionResult resolutionResult = appView.appInfo().resolveField(instanceGet.getField()); + if (resolutionResult.isSuccessfulResolution()) { + // Record that the field is read from the parameter. For class inlining of singletons, this + // parameter is only eligible for class inlining if the singleton's field value is known. + return state.rebuildParameter( + instanceGet.object(), + (context, usage) -> usage.addFieldReadFromParameter(instanceGet.getField())); + } + + return fail(instanceGet, state); + } + + private ParameterUsages analyzeInstancePut( + InstancePut instancePut, NonEmptyParameterUsages state) { + // Instance field writes are OK, as long as the field resolves and the receiver is not being + // assigned (in that case the receiver escapes, and thus it is not eligible for class + // inlining). + Value valueRoot = instancePut.value().getAliasedValue(aliasedValueConfiguration); + if (isArgumentOfInterest(valueRoot)) { + state = state.abandonClassInliningInCurrentContexts(valueRoot); + } + + Value objectRoot = instancePut.object().getAliasedValue(aliasedValueConfiguration); + if (!isArgumentOfInterest(objectRoot)) { + return state; + } + + FieldResolutionResult resolutionResult = appView.appInfo().resolveField(instancePut.getField()); + if (resolutionResult.isSuccessfulResolution()) { + return state.rebuildParameter(objectRoot, (context, usage) -> usage.setParameterMutated()); + } else { + return state.abandonClassInliningInCurrentContexts(objectRoot); + } + } + + private ParameterUsages analyzeInvokeDirect(InvokeDirect invoke, NonEmptyParameterUsages state) { + // We generally don't class inline instances that escape through invoke-direct calls, but we + // make an exception for forwarding/parent constructor calls that does not leak the receiver. + state = + state.abandonClassInliningInCurrentContexts( + invoke.getNonReceiverArguments(), this::isArgumentOfInterest); + + Value receiverRoot = invoke.getReceiver().getAliasedValue(aliasedValueConfiguration); + if (!isArgumentOfInterest(receiverRoot)) { + return state; + } + + if (!receiverRoot.isThis() + || !method.getDefinition().isInstanceInitializer() + || !invoke.isInvokeConstructor(dexItemFactory)) { + return state.abandonClassInliningInCurrentContexts(receiverRoot); + } + + SingleResolutionResult resolutionResult = + appView.appInfo().resolveMethodOnClass(invoke.getInvokedMethod()).asSingleResolution(); + if (resolutionResult == null) { + return state.abandonClassInliningInCurrentContexts(receiverRoot); + } + + InstanceInitializerInfo instanceInitializerInfo = + resolutionResult + .getResolvedMethod() + .getOptimizationInfo() + .getInstanceInitializerInfo(invoke); + if (instanceInitializerInfo.receiverMayEscapeOutsideConstructorChain()) { + return state.abandonClassInliningInCurrentContexts(receiverRoot); + } + + // We require that there is exactly one forwarding/parent constructor call. + if (constructorInvoke != null && constructorInvoke != invoke) { + return state.abandonClassInliningInCurrentContexts(receiverRoot); + } + + constructorInvoke = invoke; + return state; + } + + private ParameterUsages analyzeInvokeInterface( + InvokeInterface invoke, NonEmptyParameterUsages state) { + // We only allow invoke-interface instructions where the parameter is in the receiver position. + state = + state.abandonClassInliningInCurrentContexts( + invoke.getNonReceiverArguments(), this::isArgumentOfInterest); + + Value receiverRoot = invoke.getReceiver().getAliasedValue(aliasedValueConfiguration); + if (!isArgumentOfInterest(receiverRoot)) { + return state; + } + + SingleResolutionResult resolutionResult = + appView.appInfo().resolveMethodOnInterface(invoke.getInvokedMethod()).asSingleResolution(); + if (resolutionResult == null) { + return state.abandonClassInliningInCurrentContexts(receiverRoot); + } + + return state.rebuildParameter( + receiverRoot, (context, usage) -> usage.addMethodCallWithParameterAsReceiver(invoke)); + } + + private ParameterUsages analyzeInvokeStatic(InvokeStatic invoke, NonEmptyParameterUsages state) { + // We generally don't class inline instances that escape through invoke-static calls, but we + // make an exception for calls to Objects.requireNonNull(). + SingleResolutionResult resolutionResult = + appView + .appInfo() + .unsafeResolveMethodDueToDexFormat(invoke.getInvokedMethod()) + .asSingleResolution(); + if (resolutionResult != null + && resolutionResult.getResolvedMethod().getReference() + == dexItemFactory.objectsMethods.requireNonNull) { + return state; + } + + return fail(invoke, state); + } + + private ParameterUsages analyzeInvokeVirtual( + InvokeVirtual invoke, NonEmptyParameterUsages state) { + // We only allow invoke-virtual instructions where the parameter is in the receiver position. + state = + state.abandonClassInliningInCurrentContexts( + invoke.getNonReceiverArguments(), this::isArgumentOfInterest); + + Value receiverRoot = invoke.getReceiver().getAliasedValue(aliasedValueConfiguration); + if (!isArgumentOfInterest(receiverRoot)) { + return state; + } + + SingleResolutionResult resolutionResult = + appView.appInfo().resolveMethodOnClass(invoke.getInvokedMethod()).asSingleResolution(); + if (resolutionResult == null) { + return state.abandonClassInliningInCurrentContexts(receiverRoot); + } + + return state.rebuildParameter( + receiverRoot, (context, usage) -> usage.addMethodCallWithParameterAsReceiver(invoke)); + } + + private ParameterUsages analyzeMonitor(Monitor monitor, NonEmptyParameterUsages state) { + // Record that the receiver is used as a lock in each context that may reach this monitor + // instruction. + return state.rebuildParameter( + monitor.object(), (context, usage) -> usage.setParameterUsedAsLock()); + } + + private ParameterUsages analyzeReturn(Return theReturn, NonEmptyParameterUsages state) { + return state.rebuildParameter( + theReturn.returnValue(), (context, usage) -> usage.setParameterReturned()); + } + + private ParameterUsages fail(Instruction instruction, NonEmptyParameterUsages state) { + return state.abandonClassInliningInCurrentContexts( + instruction.inValues(), this::isArgumentOfInterest); + } + + private boolean isArgumentOfInterest(Value value) { + assert value.getAliasedValue(aliasedValueConfiguration) == value; + return value.isArgument() && argumentsOfInterest.contains(value); + } + + private boolean isMaybeEligibleForClassInlining(TypeElement type) { + if (!type.isClassType()) { + // Primitives and arrays will never be class inlined. + return false; + } + DexClass clazz = appView.definitionFor(type.asClassType().getClassType()); + if (clazz == null) { + // We cannot class inline in presence of missing classes. + return false; + } + return clazz.isProgramClass() + ? isMaybeEligibleForClassInlining(clazz.asProgramClass()) + : isMaybeEligibleForClassInlining(clazz.asClasspathOrLibraryClass()); + } + + private boolean isMaybeEligibleForClassInlining(DexProgramClass clazz) { + // We can only class inline parameters that does not inherit from other classpath or library + // classes than java.lang.Object. + DexType superType = clazz.getSuperType(); + do { + DexClass superClass = appView.definitionFor(superType); + if (superClass == null) { + return false; + } + if (!superClass.isProgramClass()) { + return superClass.getType() == dexItemFactory.objectType; + } + superType = superClass.getSuperType(); + } while (true); + } + + private boolean isMaybeEligibleForClassInlining(ClasspathOrLibraryClass clazz) { + // We can only class inline a parameter that is either java.lang.Object or an interface type. + return clazz.getType() == dexItemFactory.objectType || clazz.isInterface(); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsage.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsage.java new file mode 100644 index 0000000..93577ad --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsage.java
@@ -0,0 +1,69 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.optimize.classinliner.analysis; + +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.ir.code.InvokeMethodWithReceiver; + +class UnknownParameterUsage extends ParameterUsage { + + private static final UnknownParameterUsage TOP = new UnknownParameterUsage(); + + private UnknownParameterUsage() {} + + public static UnknownParameterUsage getInstance() { + return TOP; + } + + @Override + UnknownParameterUsage addFieldReadFromParameter(DexField field) { + return this; + } + + @Override + UnknownParameterUsage addMethodCallWithParameterAsReceiver(InvokeMethodWithReceiver invoke) { + return this; + } + + @Override + ParameterUsage externalize() { + return this; + } + + @Override + public boolean isParameterMutated() { + return true; + } + + @Override + public boolean isParameterReturned() { + return true; + } + + @Override + public boolean isParameterUsedAsLock() { + return true; + } + + @Override + public boolean isTop() { + return true; + } + + @Override + UnknownParameterUsage setParameterMutated() { + return this; + } + + @Override + UnknownParameterUsage setParameterReturned() { + return this; + } + + @Override + UnknownParameterUsage setParameterUsedAsLock() { + return this; + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsagePerContext.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsagePerContext.java new file mode 100644 index 0000000..4ead51d --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsagePerContext.java
@@ -0,0 +1,40 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.optimize.classinliner.analysis; + +import java.util.function.BiFunction; + +class UnknownParameterUsagePerContext extends ParameterUsagePerContext { + + private static final UnknownParameterUsagePerContext INSTANCE = + new UnknownParameterUsagePerContext(); + + private UnknownParameterUsagePerContext() {} + + static UnknownParameterUsagePerContext getInstance() { + return INSTANCE; + } + + @Override + ParameterUsagePerContext externalize() { + return this; + } + + @Override + public ParameterUsage get(AnalysisContext context) { + return ParameterUsage.top(); + } + + @Override + public boolean isTop() { + return true; + } + + @Override + ParameterUsagePerContext rebuild( + BiFunction<AnalysisContext, ParameterUsage, ParameterUsage> transformation) { + return this; + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsages.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsages.java new file mode 100644 index 0000000..13e32a7 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsages.java
@@ -0,0 +1,46 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.optimize.classinliner.analysis; + +public class UnknownParameterUsages extends ParameterUsages { + + private static final UnknownParameterUsages INSTANCE = new UnknownParameterUsages(); + + private UnknownParameterUsages() {} + + static UnknownParameterUsages getInstance() { + return INSTANCE; + } + + @Override + ParameterUsages externalize() { + return this; + } + + @Override + public ParameterUsagePerContext get(int parameter) { + return ParameterUsagePerContext.top(); + } + + @Override + public boolean isTop() { + return true; + } + + @Override + ParameterUsages put(int parameter, ParameterUsagePerContext usagePerContext) { + return this; + } + + @Override + public boolean equals(Object other) { + return other == INSTANCE; + } + + @Override + public int hashCode() { + return System.identityHashCode(this); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java index c441134..6ddae8d 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java
@@ -4,7 +4,11 @@ package com.android.tools.r8.ir.optimize.classinliner.constraint; +import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.analysis.value.ObjectState; +import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsage; +import com.android.tools.r8.shaking.AppInfoWithLiveness; public class AlwaysFalseClassInlinerMethodConstraint implements ClassInlinerMethodConstraint { @@ -13,17 +17,31 @@ private AlwaysFalseClassInlinerMethodConstraint() {} - public static AlwaysFalseClassInlinerMethodConstraint getInstance() { + static AlwaysFalseClassInlinerMethodConstraint getInstance() { return INSTANCE; } @Override - public boolean isEligibleForNewInstanceClassInlining(ProgramMethod method) { + public ClassInlinerMethodConstraint fixupAfterRemovingThisParameter() { + return this; + } + + @Override + public ParameterUsage getParameterUsage(int parameter) { + return ParameterUsage.top(); + } + + @Override + public boolean isEligibleForNewInstanceClassInlining(ProgramMethod method, int parameter) { return false; } @Override - public boolean isEligibleForStaticGetClassInlining(ProgramMethod method) { + public boolean isEligibleForStaticGetClassInlining( + AppView<AppInfoWithLiveness> appView, + int parameter, + ObjectState objectState, + ProgramMethod context) { return false; } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysTrueClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysTrueClassInlinerMethodConstraint.java index 3488111..dc89cf4 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysTrueClassInlinerMethodConstraint.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysTrueClassInlinerMethodConstraint.java
@@ -4,7 +4,11 @@ package com.android.tools.r8.ir.optimize.classinliner.constraint; +import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.analysis.value.ObjectState; +import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsage; +import com.android.tools.r8.shaking.AppInfoWithLiveness; public class AlwaysTrueClassInlinerMethodConstraint implements ClassInlinerMethodConstraint { @@ -13,17 +17,31 @@ private AlwaysTrueClassInlinerMethodConstraint() {} - public static AlwaysTrueClassInlinerMethodConstraint getInstance() { + static AlwaysTrueClassInlinerMethodConstraint getInstance() { return INSTANCE; } @Override - public boolean isEligibleForNewInstanceClassInlining(ProgramMethod method) { + public ClassInlinerMethodConstraint fixupAfterRemovingThisParameter() { + return this; + } + + @Override + public ParameterUsage getParameterUsage(int parameter) { + return ParameterUsage.bottom(); + } + + @Override + public boolean isEligibleForNewInstanceClassInlining(ProgramMethod method, int parameter) { return true; } @Override - public boolean isEligibleForStaticGetClassInlining(ProgramMethod method) { + public boolean isEligibleForStaticGetClassInlining( + AppView<AppInfoWithLiveness> appView, + int parameter, + ObjectState objectState, + ProgramMethod context) { return true; } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java index e8ab3ab..22079fe 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java
@@ -4,11 +4,31 @@ package com.android.tools.r8.ir.optimize.classinliner.constraint; +import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.analysis.value.ObjectState; +import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsage; +import com.android.tools.r8.shaking.AppInfoWithLiveness; public interface ClassInlinerMethodConstraint { - boolean isEligibleForNewInstanceClassInlining(ProgramMethod method); + ClassInlinerMethodConstraint fixupAfterRemovingThisParameter(); - boolean isEligibleForStaticGetClassInlining(ProgramMethod method); + ParameterUsage getParameterUsage(int parameter); + + boolean isEligibleForNewInstanceClassInlining(ProgramMethod method, int parameter); + + boolean isEligibleForStaticGetClassInlining( + AppView<AppInfoWithLiveness> appView, + int parameter, + ObjectState objectState, + ProgramMethod context); + + static AlwaysFalseClassInlinerMethodConstraint alwaysFalse() { + return AlwaysFalseClassInlinerMethodConstraint.getInstance(); + } + + static AlwaysTrueClassInlinerMethodConstraint alwaysTrue() { + return AlwaysTrueClassInlinerMethodConstraint.getInstance(); + } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraintAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraintAnalysis.java deleted file mode 100644 index fea67a4..0000000 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraintAnalysis.java +++ /dev/null
@@ -1,73 +0,0 @@ -// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -package com.android.tools.r8.ir.optimize.classinliner.constraint; - -import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo; -import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo; -import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage; - -public class ClassInlinerMethodConstraintAnalysis { - - public static ClassInlinerMethodConstraint analyze( - ClassInlinerEligibilityInfo classInlinerEligibilityInfo, - ParameterUsagesInfo parameterUsagesInfo) { - boolean isEligibleForNewInstanceClassInlining = - isEligibleForNewInstanceClassInlining(classInlinerEligibilityInfo); - boolean isEligibleForStaticGetClassInlining = - isEligibleForStaticGetClassInlining(classInlinerEligibilityInfo, parameterUsagesInfo); - if (isEligibleForNewInstanceClassInlining) { - if (isEligibleForStaticGetClassInlining) { - return alwaysTrue(); - } - return onlyNewInstanceClassInlining(); - } - assert !isEligibleForStaticGetClassInlining; - return alwaysFalse(); - } - - private static boolean isEligibleForNewInstanceClassInlining( - ClassInlinerEligibilityInfo classInlinerEligibilityInfo) { - return classInlinerEligibilityInfo != null; - } - - private static boolean isEligibleForStaticGetClassInlining( - ClassInlinerEligibilityInfo classInlinerEligibilityInfo, - ParameterUsagesInfo parameterUsagesInfo) { - if (classInlinerEligibilityInfo == null || parameterUsagesInfo == null) { - return false; - } - if (classInlinerEligibilityInfo.hasMonitorOnReceiver) { - // We will not be able to remove the monitor instruction afterwards. - return false; - } - if (classInlinerEligibilityInfo.modifiesInstanceFields) { - // The static instance could be accessed from elsewhere. Therefore, we cannot allow - // side-effects to be removed and therefore cannot class inline method calls that modifies the - // instance. - return false; - } - ParameterUsage receiverUsage = parameterUsagesInfo.getParameterUsage(0); - if (receiverUsage == null) { - return false; - } - if (receiverUsage.hasFieldRead) { - // We don't know the value of the field. - return false; - } - return true; - } - - private static AlwaysFalseClassInlinerMethodConstraint alwaysFalse() { - return AlwaysFalseClassInlinerMethodConstraint.getInstance(); - } - - private static AlwaysTrueClassInlinerMethodConstraint alwaysTrue() { - return AlwaysTrueClassInlinerMethodConstraint.getInstance(); - } - - private static OnlyNewInstanceClassInlinerMethodConstraint onlyNewInstanceClassInlining() { - return OnlyNewInstanceClassInlinerMethodConstraint.getInstance(); - } -}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java new file mode 100644 index 0000000..eb743cb --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java
@@ -0,0 +1,107 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.optimize.classinliner.constraint; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexEncodedField; +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.analysis.value.AbstractValue; +import com.android.tools.r8.ir.analysis.value.ObjectState; +import com.android.tools.r8.ir.analysis.value.SingleConstValue; +import com.android.tools.r8.ir.optimize.classinliner.analysis.AnalysisContext; +import com.android.tools.r8.ir.optimize.classinliner.analysis.NonEmptyParameterUsage; +import com.android.tools.r8.ir.optimize.classinliner.analysis.NonEmptyParameterUsages; +import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsage; +import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsagePerContext; +import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsages; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +public class ConditionalClassInlinerMethodConstraint implements ClassInlinerMethodConstraint { + + private final ParameterUsages usages; + + public ConditionalClassInlinerMethodConstraint(ParameterUsages usages) { + assert !usages.isTop(); + this.usages = usages; + } + + @Override + public ClassInlinerMethodConstraint fixupAfterRemovingThisParameter() { + if (usages.isBottom()) { + return this; + } + Int2ObjectMap<ParameterUsagePerContext> backing = new Int2ObjectOpenHashMap<>(); + usages + .asNonEmpty() + .forEach( + (parameter, usagePerContext) -> { + if (parameter > 0) { + backing.put(parameter - 1, usagePerContext); + } + }); + return new ConditionalClassInlinerMethodConstraint(NonEmptyParameterUsages.create(backing)); + } + + @Override + public ParameterUsage getParameterUsage(int parameter) { + AnalysisContext defaultContext = AnalysisContext.getDefaultContext(); + return usages.get(parameter).get(defaultContext); + } + + @Override + public boolean isEligibleForNewInstanceClassInlining(ProgramMethod method, int parameter) { + AnalysisContext defaultContext = AnalysisContext.getDefaultContext(); + ParameterUsage usage = usages.get(parameter).get(defaultContext); + return !usage.isTop(); + } + + @Override + public boolean isEligibleForStaticGetClassInlining( + AppView<AppInfoWithLiveness> appView, + int parameter, + ObjectState objectState, + ProgramMethod context) { + AnalysisContext defaultContext = AnalysisContext.getDefaultContext(); + ParameterUsage usage = usages.get(parameter).get(defaultContext); + if (usage.isBottom()) { + return true; + } + if (usage.isTop()) { + return false; + } + + NonEmptyParameterUsage knownUsage = usage.asNonEmpty(); + if (knownUsage.isParameterMutated()) { + // The static instance could be accessed from elsewhere. Therefore, we cannot allow + // side-effects to be removed and therefore cannot class inline method calls that modifies the + // instance. + return false; + } + if (knownUsage.isParameterUsedAsLock()) { + // We will not be able to remove the monitor instruction afterwards. + return false; + } + for (DexField fieldReadFromParameter : knownUsage.getFieldsReadFromParameter()) { + DexClass holder = appView.definitionFor(fieldReadFromParameter.getHolderType()); + DexEncodedField definition = fieldReadFromParameter.lookupOnClass(holder); + if (definition == null) { + return false; + } + AbstractValue abstractValue = objectState.getAbstractFieldValue(definition); + if (!abstractValue.isSingleConstValue()) { + return false; + } + SingleConstValue singleConstValue = abstractValue.asSingleConstValue(); + if (!singleConstValue.isMaterializableInContext(appView, context)) { + return false; + } + } + return true; + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/OnlyNewInstanceClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/OnlyNewInstanceClassInlinerMethodConstraint.java deleted file mode 100644 index d3f35d4..0000000 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/OnlyNewInstanceClassInlinerMethodConstraint.java +++ /dev/null
@@ -1,29 +0,0 @@ -// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -package com.android.tools.r8.ir.optimize.classinliner.constraint; - -import com.android.tools.r8.graph.ProgramMethod; - -public class OnlyNewInstanceClassInlinerMethodConstraint implements ClassInlinerMethodConstraint { - - private static final OnlyNewInstanceClassInlinerMethodConstraint INSTANCE = - new OnlyNewInstanceClassInlinerMethodConstraint(); - - private OnlyNewInstanceClassInlinerMethodConstraint() {} - - public static OnlyNewInstanceClassInlinerMethodConstraint getInstance() { - return INSTANCE; - } - - @Override - public boolean isEligibleForNewInstanceClassInlining(ProgramMethod method) { - return true; - } - - @Override - public boolean isEligibleForStaticGetClassInlining(ProgramMethod method) { - return false; - } -}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/controlflow/SwitchCaseAnalyzer.java b/src/main/java/com/android/tools/r8/ir/optimize/controlflow/SwitchCaseAnalyzer.java index 76f63ac..4e01bc5 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/controlflow/SwitchCaseAnalyzer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/controlflow/SwitchCaseAnalyzer.java
@@ -5,7 +5,9 @@ package com.android.tools.r8.ir.optimize.controlflow; import com.android.tools.r8.graph.DexString; +import com.android.tools.r8.ir.analysis.value.AbstractValue; import com.android.tools.r8.ir.code.Instruction; +import com.android.tools.r8.ir.code.IntSwitch; import com.android.tools.r8.ir.code.Switch; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.utils.LongInterval; @@ -37,11 +39,17 @@ && key == rootSwitchValue.definition.asConstString().getValue(); } - public boolean switchCaseIsUnreachable(Switch theSwitch, int index) { + public boolean switchCaseIsUnreachable( + Switch theSwitch, AbstractValue switchAbstractValue, int index) { Value switchValue = theSwitch.value(); if (theSwitch.isIntSwitch()) { - return switchValue.hasValueRange() - && !switchValue.getValueRange().containsValue(theSwitch.asIntSwitch().getKey(index)); + int key = theSwitch.asIntSwitch().getKey(index); + if (switchAbstractValue.isConstantOrNonConstantNumberValue() + && !switchAbstractValue.asConstantOrNonConstantNumberValue().containsInt(key)) { + return true; + } + // TODO(b/150836439): Reimplement using AbstractValue. + return switchValue.hasValueRange() && !switchValue.getValueRange().containsValue(key); } assert theSwitch.isStringSwitch(); @@ -51,4 +59,21 @@ return rootSwitchValue.isDefinedByInstructionSatisfying(Instruction::isConstString) && key != rootSwitchValue.definition.asConstString().getValue(); } + + public boolean switchFallthroughIsNeverHit(Switch theSwitch, AbstractValue switchAbstractValue) { + if (theSwitch.isIntSwitch()) { + IntSwitch intSwitch = theSwitch.asIntSwitch(); + if (switchAbstractValue.isConstantOrNonConstantNumberValue()) { + return switchAbstractValue + .asConstantOrNonConstantNumberValue() + .isSubsetOf(intSwitch.getKeys()) + .isTrue(); + } + return false; + } + + assert theSwitch.isStringSwitch(); + + return false; + } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java index 48e545c..c29dd86 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java
@@ -5,7 +5,6 @@ package com.android.tools.r8.ir.optimize.enums; import static com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter.createValuesField; -import static com.google.common.base.Predicates.alwaysTrue; import com.android.tools.r8.dex.Constants; import com.android.tools.r8.graph.AppView; @@ -24,7 +23,6 @@ import com.android.tools.r8.graph.ProgramPackageCollection; import com.android.tools.r8.origin.SynthesizedOrigin; import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier; -import com.android.tools.r8.shaking.MainDexInfo; import com.android.tools.r8.utils.SetUtils; import com.google.common.collect.ImmutableMap; import java.util.ArrayList; @@ -34,7 +32,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.function.Predicate; public class UnboxedEnumMemberRelocator { @@ -132,7 +129,7 @@ Set<DexProgramClass> relocatedEnums, DirectMappedDexApplication.Builder appBuilder, FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) { - DexProgramClass deterministicContext = findDeterministicContextType(contexts, alwaysTrue()); + DexProgramClass deterministicContext = findDeterministicContextType(contexts); String descriptorString = deterministicContext.getType().toDescriptorString(); String descriptorPrefix = descriptorString.substring(0, descriptorString.length() - 1); String syntheticClassDescriptor = descriptorPrefix + ENUM_UNBOXING_UTILITY_CLASS_SUFFIX + ";"; @@ -182,21 +179,13 @@ appView.dexItemFactory().getSkipNameValidationForTesting(), DexProgramClass::checksumFromType); appBuilder.addSynthesizedClass(syntheticClass); - MainDexInfo mainDexInfo = appView.appInfo().getMainDexInfo(); - appView - .appInfo() - .addSynthesizedClass( - syntheticClass, findDeterministicContextType(contexts, mainDexInfo::isMainDex)); + appView.appInfo().addSynthesizedClass(syntheticClass, contexts); return syntheticClass; } - private DexProgramClass findDeterministicContextType( - Set<DexProgramClass> contexts, Predicate<DexProgramClass> predicate) { + private DexProgramClass findDeterministicContextType(Set<DexProgramClass> contexts) { DexProgramClass deterministicContext = null; for (DexProgramClass context : contexts) { - if (!predicate.test(context)) { - continue; - } if (deterministicContext == null) { deterministicContext = context; } else if (context.type.compareTo(deterministicContext.type) < 0) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java index ded60a3..ab2b0ac 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
@@ -15,7 +15,6 @@ import com.android.tools.r8.ir.analysis.value.AbstractValue; import com.android.tools.r8.ir.analysis.value.UnknownValue; import com.android.tools.r8.ir.code.Value; -import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage; import com.android.tools.r8.shaking.AppInfoWithLiveness; import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap; import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; @@ -40,16 +39,21 @@ CallSiteOptimizationInfo join( ConcreteCallSiteOptimizationInfo other, AppView<?> appView, DexEncodedMethod method) { - assert this.size == other.size; + assert size == other.size; + assert size == method.getNumberOfArguments(); boolean allowConstantPropagation = appView.options().callSiteOptimizationOptions().isConstantPropagationEnabled(); ConcreteCallSiteOptimizationInfo result = - new ConcreteCallSiteOptimizationInfo(this.size, allowConstantPropagation); + new ConcreteCallSiteOptimizationInfo(size, allowConstantPropagation); for (int i = 0; i < result.size; i++) { if (allowConstantPropagation) { assert result.constants != null; AbstractValue abstractValue = - getAbstractArgumentValue(i).join(other.getAbstractArgumentValue(i)); + getAbstractArgumentValue(i) + .join( + other.getAbstractArgumentValue(i), + appView.abstractValueFactory(), + method.getArgumentType(i)); if (abstractValue.isNonTrivial()) { result.constants.put(i, abstractValue); } @@ -94,11 +98,6 @@ public boolean hasUsefulOptimizationInfo(AppView<?> appView, DexEncodedMethod method) { TypeElement[] staticTypes = getStaticTypes(appView, method); for (int i = 0; i < size; i++) { - ParameterUsage parameterUsage = method.getOptimizationInfo().getParameterUsages(i); - // If the parameter is not used, passing accurate argument info doesn't matter. - if (parameterUsage != null && parameterUsage.notUsed()) { - continue; - } AbstractValue abstractValue = getAbstractArgumentValue(i); if (abstractValue.isNonTrivial()) { assert appView.options().callSiteOptimizationOptions().isConstantPropagationEnabled();
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 45764df..d49189f 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
@@ -12,10 +12,7 @@ import com.android.tools.r8.ir.analysis.value.AbstractValue; import com.android.tools.r8.ir.analysis.value.UnknownValue; import com.android.tools.r8.ir.code.InvokeDirect; -import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo; -import com.android.tools.r8.ir.optimize.classinliner.constraint.AlwaysFalseClassInlinerMethodConstraint; import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint; -import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage; import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo; import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo; import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo; @@ -36,9 +33,7 @@ static ClassTypeElement UNKNOWN_CLASS_TYPE = null; static boolean UNKNOWN_CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT = false; static boolean UNKNOWN_TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT = false; - static ClassInlinerEligibilityInfo UNKNOWN_CLASS_INLINER_ELIGIBILITY = null; static boolean UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS = false; - static ParameterUsagesInfo UNKNOWN_PARAMETER_USAGE_INFO = null; static boolean UNKNOWN_MAY_HAVE_SIDE_EFFECTS = true; static boolean UNKNOWN_RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS = false; static BitSet NO_NULL_PARAMETER_OR_THROW_FACTS = null; @@ -77,7 +72,7 @@ @Override public ClassInlinerMethodConstraint getClassInlinerMethodConstraint() { - return AlwaysFalseClassInlinerMethodConstraint.getInstance(); + return ClassInlinerMethodConstraint.alwaysFalse(); } @Override @@ -106,12 +101,6 @@ } @Override - public ParameterUsage getParameterUsages(int parameter) { - assert UNKNOWN_PARAMETER_USAGE_INFO == null; - return null; - } - - @Override public BitSet getNonNullParamOrThrow() { return NO_NULL_PARAMETER_OR_THROW_FACTS; } @@ -153,11 +142,6 @@ } @Override - public ClassInlinerEligibilityInfo getClassInlinerEligibility() { - return UNKNOWN_CLASS_INLINER_ELIGIBILITY; - } - - @Override public AbstractValue getAbstractReturnValue() { return UNKNOWN_ABSTRACT_RETURN_VALUE; }
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 7cced9d..bdc35f3 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
@@ -10,9 +10,7 @@ import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.analysis.value.AbstractValue; import com.android.tools.r8.ir.code.InvokeDirect; -import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo; import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint; -import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage; import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo; import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo; import java.util.BitSet; @@ -47,8 +45,6 @@ public abstract ClassTypeElement getDynamicLowerBoundType(); - public abstract ParameterUsage getParameterUsages(int parameter); - public final boolean hasNonNullParamOrThrow() { return getNonNullParamOrThrow() != null; } @@ -73,8 +69,6 @@ public abstract BridgeInfo getBridgeInfo(); - public abstract ClassInlinerEligibilityInfo getClassInlinerEligibility(); - public abstract Set<DexType> getInitializedClassesOnNormalExit(); public abstract InstanceInitializerInfo getContextInsensitiveInstanceInitializerInfo();
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 804b6b3..aa28923 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
@@ -29,7 +29,6 @@ import static com.android.tools.r8.ir.code.Opcodes.INVOKE_NEW_ARRAY; import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC; import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL; -import static com.android.tools.r8.ir.code.Opcodes.MONITOR; import static com.android.tools.r8.ir.code.Opcodes.MUL; import static com.android.tools.r8.ir.code.Opcodes.NEW_ARRAY_EMPTY; import static com.android.tools.r8.ir.code.Opcodes.NEW_INSTANCE; @@ -47,10 +46,8 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; -import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; -import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProgramClass; @@ -77,23 +74,17 @@ import com.android.tools.r8.ir.code.InstancePut; import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.InstructionIterator; -import com.android.tools.r8.ir.code.Invoke; import com.android.tools.r8.ir.code.InvokeDirect; import com.android.tools.r8.ir.code.InvokeMethod; import com.android.tools.r8.ir.code.InvokeNewArray; import com.android.tools.r8.ir.code.InvokeStatic; -import com.android.tools.r8.ir.code.InvokeVirtual; import com.android.tools.r8.ir.code.NewInstance; import com.android.tools.r8.ir.code.Return; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.IRConverter; import com.android.tools.r8.ir.optimize.DynamicTypeOptimization; -import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo; -import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerReceiverAnalysis; +import com.android.tools.r8.ir.optimize.classinliner.analysis.ClassInlinerMethodConstraintAnalysis; import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint; -import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraintAnalysis; -import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage; -import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsageBuilder; import com.android.tools.r8.ir.optimize.info.bridge.BridgeAnalyzer; import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection; import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo; @@ -103,19 +94,14 @@ import com.android.tools.r8.kotlin.Kotlin; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.InternalOptions; -import com.android.tools.r8.utils.ListUtils; -import com.android.tools.r8.utils.Pair; import com.android.tools.r8.utils.Timing; -import com.android.tools.r8.utils.WorkList; import com.google.common.collect.Sets; import java.util.ArrayDeque; -import java.util.ArrayList; import java.util.BitSet; import java.util.Deque; import java.util.List; import java.util.Set; import java.util.function.BiFunction; -import java.util.function.Predicate; public class MethodOptimizationInfoCollector { @@ -145,16 +131,11 @@ Timing timing) { DexEncodedMethod definition = method.getDefinition(); identifyBridgeInfo(definition, code, feedback, timing); - ClassInlinerEligibilityInfo classInlinerEligibilityInfo = - identifyClassInlinerEligibility(code, feedback, timing); - ParameterUsagesInfo parameterUsagesInfo = - identifyParameterUsages(definition, code, feedback, timing); analyzeReturns(code, feedback, timing); if (options.enableInlining) { identifyInvokeSemanticsForInlining(definition, code, feedback, timing); } - computeClassInlinerMethodConstraint( - method, code, feedback, classInlinerEligibilityInfo, parameterUsagesInfo, timing); + computeClassInlinerMethodConstraint(method, code, feedback, timing); computeSimpleInliningConstraint(method, code, feedback, timing); computeDynamicReturnType(dynamicTypeOptimization, feedback, definition, code, timing); computeInitializedClassesOnNormalExit(feedback, definition, code, timing); @@ -173,201 +154,6 @@ timing.end(); } - private ClassInlinerEligibilityInfo identifyClassInlinerEligibility( - IRCode code, OptimizationFeedback feedback, Timing timing) { - timing.begin("Identify class inliner eligibility"); - ClassInlinerEligibilityInfo classInlinerEligibilityInfo = - identifyClassInlinerEligibility(code, feedback); - timing.end(); - return classInlinerEligibilityInfo; - } - - private ClassInlinerEligibilityInfo identifyClassInlinerEligibility( - IRCode code, OptimizationFeedback feedback) { - // Method eligibility is calculated in similar way for regular method - // and for the constructor. To be eligible method should only be using its - // receiver in the following ways: - // - // (1) as a receiver of reads/writes of instance fields of the holder class, - // (2) as a return value, - // (3) as a receiver of a call to the superclass initializer. Note that we don't - // check what is passed to superclass initializer as arguments, only check - // that it is not the instance being initialized, - // (4) as argument to a monitor instruction. - // - // Note that (4) can safely be removed as the receiver is guaranteed not to escape when we class - // inline it, and hence any monitor instructions are no-ops. - ProgramMethod context = code.context(); - DexEncodedMethod definition = context.getDefinition(); - boolean instanceInitializer = definition.isInstanceInitializer(); - if (definition.isNative() - || (!definition.isNonAbstractVirtualMethod() && !instanceInitializer)) { - return null; - } - - feedback.setClassInlinerEligibility(definition, null); // To allow returns below. - - Value receiver = code.getThis(); - if (receiver.numberOfPhiUsers() > 0) { - return null; - } - - List<Pair<Invoke.Type, DexMethod>> callsReceiver = new ArrayList<>(); - boolean seenSuperInitCall = false; - boolean seenMonitor = false; - boolean modifiesInstanceFields = false; - - AliasedValueConfiguration configuration = - AssumeAndCheckCastAliasedValueConfiguration.getInstance(); - Predicate<Value> isReceiverAlias = value -> value.getAliasedValue(configuration) == receiver; - for (Instruction insn : receiver.aliasedUsers(configuration)) { - switch (insn.opcode()) { - case ASSUME: - case CHECK_CAST: - case RETURN: - break; - - case MONITOR: - seenMonitor = true; - break; - - case INSTANCE_GET: - case INSTANCE_PUT: - { - if (insn.isInstancePut()) { - InstancePut instancePutInstruction = insn.asInstancePut(); - // Only allow field writes to the receiver. - if (!isReceiverAlias.test(instancePutInstruction.object())) { - return null; - } - // Do not allow the receiver to escape via a field write. - if (isReceiverAlias.test(instancePutInstruction.value())) { - return null; - } - modifiesInstanceFields = true; - } - DexField field = insn.asFieldInstruction().getField(); - if (appView.appInfo().resolveField(field).isFailedOrUnknownResolution()) { - return null; - } - break; - } - - case INVOKE_DIRECT: - { - InvokeDirect invoke = insn.asInvokeDirect(); - DexMethod invokedMethod = invoke.getInvokedMethod(); - if (dexItemFactory.isConstructor(invokedMethod) - && invokedMethod.holder == context.getHolder().superType - && ListUtils.lastIndexMatching(invoke.arguments(), isReceiverAlias) == 0 - && !seenSuperInitCall - && instanceInitializer) { - seenSuperInitCall = true; - break; - } - // We don't support other direct calls yet. - return null; - } - - case INVOKE_STATIC: - { - InvokeStatic invoke = insn.asInvokeStatic(); - DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context); - if (singleTarget == null) { - return null; // Not allowed. - } - if (singleTarget.getReference() == dexItemFactory.objectsMethods.requireNonNull) { - if (!invoke.hasOutValue() || !invoke.outValue().hasAnyUsers()) { - continue; - } - } - return null; - } - - case INVOKE_VIRTUAL: - { - InvokeVirtual invoke = insn.asInvokeVirtual(); - if (ListUtils.lastIndexMatching(invoke.arguments(), isReceiverAlias) != 0) { - return null; // Not allowed. - } - DexMethod invokedMethod = invoke.getInvokedMethod(); - DexType returnType = invokedMethod.proto.returnType; - if (returnType.isClassType() - && appView.appInfo().inSameHierarchy(returnType, context.getHolderType())) { - return null; // Not allowed, could introduce an alias of the receiver. - } - callsReceiver.add(new Pair<>(Invoke.Type.VIRTUAL, invokedMethod)); - } - break; - - default: - // Other receiver usages make the method not eligible. - return null; - } - } - - if (instanceInitializer && !seenSuperInitCall) { - // Call to super constructor not found? - return null; - } - - boolean synchronizedVirtualMethod = definition.isSynchronized() && definition.isVirtualMethod(); - ClassInlinerEligibilityInfo classInlinerEligibilityInfo = - new ClassInlinerEligibilityInfo( - callsReceiver, - new ClassInlinerReceiverAnalysis(appView, definition, code).computeReturnsReceiver(), - seenMonitor || synchronizedVirtualMethod, - modifiesInstanceFields); - feedback.setClassInlinerEligibility(definition, classInlinerEligibilityInfo); - return classInlinerEligibilityInfo; - } - - private ParameterUsagesInfo identifyParameterUsages( - DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) { - timing.begin("Identify parameter usages"); - ParameterUsagesInfo parameterUsagesInfo = identifyParameterUsages(method, code, feedback); - timing.end(); - return parameterUsagesInfo; - } - - private ParameterUsagesInfo identifyParameterUsages( - DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) { - List<ParameterUsage> usages = new ArrayList<>(); - List<Value> values = code.collectArguments(); - for (int i = 0; i < values.size(); i++) { - Value value = values.get(i); - ParameterUsage usage = collectParameterUsages(i, value); - if (usage != null) { - usages.add(usage); - } - } - ParameterUsagesInfo parameterUsagesInfo = - !usages.isEmpty() ? new ParameterUsagesInfo(usages) : null; - feedback.setParameterUsages(method, parameterUsagesInfo); - return parameterUsagesInfo; - } - - private ParameterUsage collectParameterUsages(int i, Value root) { - ParameterUsageBuilder builder = new ParameterUsageBuilder(root, i, dexItemFactory); - WorkList<Value> worklist = WorkList.newIdentityWorkList(); - worklist.addIfNotSeen(root); - while (worklist.hasNext()) { - Value value = worklist.next(); - if (value.hasPhiUsers()) { - return null; - } - for (Instruction user : value.uniqueUsers()) { - if (!builder.note(user)) { - return null; - } - if (user.isAssume()) { - worklist.addIfNotSeen(user.outValue()); - } - } - } - return builder.build(); - } - private void analyzeReturns(IRCode code, OptimizationFeedback feedback, Timing timing) { timing.begin("Identify returns argument"); analyzeReturns(code, feedback); @@ -982,24 +768,16 @@ ProgramMethod method, IRCode code, OptimizationFeedback feedback, - ClassInlinerEligibilityInfo classInlinerEligibilityInfo, - ParameterUsagesInfo parameterUsagesInfo, Timing timing) { timing.begin("Compute class inlining constraint"); - computeClassInlinerMethodConstraint( - method, code, feedback, classInlinerEligibilityInfo, parameterUsagesInfo); + computeClassInlinerMethodConstraint(method, code, feedback); timing.end(); } private void computeClassInlinerMethodConstraint( - ProgramMethod method, - IRCode code, - OptimizationFeedback feedback, - ClassInlinerEligibilityInfo classInlinerEligibilityInfo, - ParameterUsagesInfo parameterUsagesInfo) { + ProgramMethod method, IRCode code, OptimizationFeedback feedback) { ClassInlinerMethodConstraint classInlinerMethodConstraint = - ClassInlinerMethodConstraintAnalysis.analyze( - classInlinerEligibilityInfo, parameterUsagesInfo); + ClassInlinerMethodConstraintAnalysis.analyze(appView, method, code); feedback.setClassInlinerMethodConstraint(method, classInlinerMethodConstraint); }
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 39f725d..df06045 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
@@ -14,7 +14,6 @@ import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.analysis.value.AbstractValue; import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; -import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo; import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint; import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo; import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection; @@ -263,12 +262,6 @@ } @Override - public synchronized void setClassInlinerEligibility( - DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility) { - getMethodOptimizationInfoForUpdating(method).setClassInlinerEligibility(eligibility); - } - - @Override public synchronized void setInstanceInitializerInfoCollection( DexEncodedMethod method, InstanceInitializerInfoCollection instanceInitializerInfoCollection) { @@ -282,12 +275,6 @@ } @Override - public synchronized void setParameterUsages( - DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo) { - getMethodOptimizationInfoForUpdating(method).setParameterUsages(parameterUsagesInfo); - } - - @Override public synchronized void setNonNullParamOrThrow(DexEncodedMethod method, BitSet facts) { getMethodOptimizationInfoForUpdating(method).setNonNullParamOrThrow(facts); }
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 3b46f4e..e862824 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
@@ -14,7 +14,6 @@ import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.analysis.value.AbstractValue; import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; -import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo; import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint; import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo; import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection; @@ -118,10 +117,6 @@ ProgramMethod method, ClassInlinerMethodConstraint classInlinerConstraint) {} @Override - public void setClassInlinerEligibility( - DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility) {} - - @Override public void setInstanceInitializerInfoCollection( DexEncodedMethod method, InstanceInitializerInfoCollection instanceInitializerInfoCollection) {} @@ -130,10 +125,6 @@ public void setInitializerEnablingJavaVmAssertions(DexEncodedMethod method) {} @Override - public void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo) { - } - - @Override public void setNonNullParamOrThrow(DexEncodedMethod method, BitSet facts) {} @Override
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 2624486..3fb77d9 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
@@ -14,7 +14,6 @@ import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.analysis.value.AbstractValue; import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; -import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo; import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint; import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo; import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection; @@ -170,12 +169,6 @@ } @Override - public void setClassInlinerEligibility( - DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility) { - // Ignored. - } - - @Override public void setInstanceInitializerInfoCollection( DexEncodedMethod method, InstanceInitializerInfoCollection instanceInitializerInfoCollection) { @@ -190,11 +183,6 @@ } @Override - public void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo) { - // Ignored. - } - - @Override public void setNonNullParamOrThrow(DexEncodedMethod method, BitSet facts) { method.getMutableOptimizationInfo().setNonNullParamOrThrow(facts); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java deleted file mode 100644 index 4389193..0000000 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java +++ /dev/null
@@ -1,273 +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.info; - -import com.android.tools.r8.graph.DexItemFactory; -import com.android.tools.r8.graph.DexMethod; -import com.android.tools.r8.ir.code.If; -import com.android.tools.r8.ir.code.If.Type; -import com.android.tools.r8.ir.code.InstanceGet; -import com.android.tools.r8.ir.code.InstancePut; -import com.android.tools.r8.ir.code.Instruction; -import com.android.tools.r8.ir.code.Invoke; -import com.android.tools.r8.ir.code.InvokeMethodWithReceiver; -import com.android.tools.r8.ir.code.InvokeStatic; -import com.android.tools.r8.ir.code.Monitor; -import com.android.tools.r8.ir.code.Return; -import com.android.tools.r8.ir.code.Value; -import com.android.tools.r8.utils.ListUtils; -import com.android.tools.r8.utils.Pair; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -public final class ParameterUsagesInfo { - - private ImmutableList<ParameterUsage> parametersUsages; - - public ParameterUsagesInfo(List<ParameterUsage> usages) { - assert !usages.isEmpty(); - parametersUsages = ImmutableList.copyOf(usages); - assert parametersUsages.size() == - parametersUsages.stream().map(usage -> usage.index).collect(Collectors.toSet()).size(); - } - - public ParameterUsage getParameterUsage(int index) { - for (ParameterUsage usage : parametersUsages) { - if (usage.index == index) { - return usage; - } - } - return null; - } - - ParameterUsagesInfo remove(int index) { - assert parametersUsages.size() > 0; - assert 0 <= index && index <= ListUtils.last(parametersUsages).index; - ImmutableList.Builder<ParameterUsage> builder = ImmutableList.builder(); - for (ParameterUsage usage : parametersUsages) { - // Once we remove or pass the designated index, copy-n-shift the remaining usages. - if (index < usage.index) { - builder.add(ParameterUsage.copyAndShift(usage, 1)); - } else if (index == usage.index) { - // Do not add the parameter usage with the matched index. - } else { - // Until we meet the `parameter` of interest, keep copying. - assert usage.index < index; - builder.add(usage); - } - } - ImmutableList<ParameterUsage> adjustedUsages = builder.build(); - if (adjustedUsages.isEmpty()) { - return DefaultMethodOptimizationInfo.UNKNOWN_PARAMETER_USAGE_INFO; - } - return new ParameterUsagesInfo(adjustedUsages); - } - - public final static class ParameterUsage { - - public final int index; - public final Set<Type> ifZeroTest; - public final List<Pair<Invoke.Type, DexMethod>> callsReceiver; - - // If a field of this argument is assigned: arg.f = x. - public final boolean hasFieldAssignment; - - // If a field of this argument is read: x = arg.f. - public final boolean hasFieldRead; - - // If this argument is assigned to a field: x.f = arg. - public final boolean isAssignedToField; - - // If this argument is returned: return arg. - public final boolean isReturned; - - // If this argument is used in a monitor instruction. - public final boolean isUsedInMonitor; - - ParameterUsage( - int index, - Set<Type> ifZeroTest, - List<Pair<Invoke.Type, DexMethod>> callsReceiver, - boolean hasFieldAssignment, - boolean hasFieldRead, - boolean isAssignedToField, - boolean isReturned, - boolean isUsedInMonitor) { - this.index = index; - this.ifZeroTest = - ifZeroTest.isEmpty() ? Collections.emptySet() : ImmutableSet.copyOf(ifZeroTest); - this.callsReceiver = - callsReceiver.isEmpty() ? Collections.emptyList() : ImmutableList.copyOf(callsReceiver); - this.hasFieldAssignment = hasFieldAssignment; - this.hasFieldRead = hasFieldRead; - this.isAssignedToField = isAssignedToField; - this.isReturned = isReturned; - this.isUsedInMonitor = isUsedInMonitor; - } - - static ParameterUsage copyAndShift(ParameterUsage original, int shift) { - assert original.index >= shift; - return new ParameterUsage( - original.index - shift, - original.ifZeroTest, - original.callsReceiver, - original.hasFieldAssignment, - original.hasFieldRead, - original.isAssignedToField, - original.isReturned, - original.isUsedInMonitor); - } - - public boolean notUsed() { - return (ifZeroTest == null || ifZeroTest.isEmpty()) - && (callsReceiver == null || callsReceiver.isEmpty()) - && !hasFieldAssignment - && !hasFieldRead - && !isAssignedToField - && !isReturned - && !isUsedInMonitor; - } - } - - public static class ParameterUsageBuilder { - - private final int index; - private final Value arg; - private final DexItemFactory dexItemFactory; - - private final Set<Type> ifZeroTestTypes = new HashSet<>(); - private final List<Pair<Invoke.Type, DexMethod>> callsOnReceiver = new ArrayList<>(); - - private boolean hasFieldAssignment = false; - private boolean hasFieldRead = false; - private boolean isAssignedToField = false; - private boolean isReturned = false; - private boolean isUsedInMonitor = false; - - ParameterUsageBuilder(Value arg, int index, DexItemFactory dexItemFactory) { - this.arg = arg; - this.index = index; - this.dexItemFactory = dexItemFactory; - } - - // Returns false if the instruction is not supported. - public boolean note(Instruction instruction) { - if (instruction.isAssume()) { - // Keep examining other users if there are no phi users, but the param usage builder should - // consider aliased users. - return !instruction.outValue().hasPhiUsers(); - } - if (instruction.isIf()) { - return note(instruction.asIf()); - } - if (instruction.isInstanceGet()) { - return note(instruction.asInstanceGet()); - } - if (instruction.isInstancePut()) { - return note(instruction.asInstancePut()); - } - if (instruction.isInvokeMethodWithReceiver()) { - return note(instruction.asInvokeMethodWithReceiver()); - } - if (instruction.isInvokeStatic()) { - return note(instruction.asInvokeStatic()); - } - if (instruction.isReturn()) { - return note(instruction.asReturn()); - } - if (instruction.isMonitor()) { - return note(instruction.asMonitor()); - } - return false; - } - - public ParameterUsage build() { - return new ParameterUsage( - index, - ifZeroTestTypes, - callsOnReceiver, - hasFieldAssignment, - hasFieldRead, - isAssignedToField, - isReturned, - isUsedInMonitor); - } - - private boolean note(If ifInstruction) { - if (ifInstruction.asIf().isZeroTest()) { - assert ifInstruction.inValues().size() == 1 - && ifInstruction.inValues().get(0).getAliasedValue() == arg; - ifZeroTestTypes.add(ifInstruction.asIf().getType()); - return true; - } - return false; - } - - private boolean note(InstanceGet instanceGetInstruction) { - assert arg != instanceGetInstruction.outValue(); - if (instanceGetInstruction.object().getAliasedValue() == arg) { - hasFieldRead = true; - return true; - } - return false; - } - - private boolean note(InstancePut instancePutInstruction) { - assert arg != instancePutInstruction.outValue(); - if (instancePutInstruction.object().getAliasedValue() == arg) { - hasFieldAssignment = true; - isAssignedToField |= instancePutInstruction.value().getAliasedValue() == arg; - return true; - } - if (instancePutInstruction.value().getAliasedValue() == arg) { - isAssignedToField = true; - return true; - } - return false; - } - - private boolean note(InvokeMethodWithReceiver invokeInstruction) { - if (ListUtils.lastIndexMatching( - invokeInstruction.inValues(), v -> v.getAliasedValue() == arg) == 0) { - callsOnReceiver.add( - new Pair<>( - invokeInstruction.asInvokeMethodWithReceiver().getType(), - invokeInstruction.asInvokeMethodWithReceiver().getInvokedMethod())); - return true; - } - return false; - } - - private boolean note(InvokeStatic invokeInstruction) { - if (invokeInstruction.getInvokedMethod() == dexItemFactory.objectsMethods.requireNonNull) { - if (!invokeInstruction.hasOutValue() || !invokeInstruction.outValue().hasAnyUsers()) { - ifZeroTestTypes.add(Type.EQ); - return true; - } - } - return false; - } - - private boolean note(Return returnInstruction) { - assert returnInstruction.inValues().size() == 1 - && returnInstruction.inValues().get(0).getAliasedValue() == arg; - isReturned = true; - return true; - } - - private boolean note(Monitor monitorInstruction) { - assert monitorInstruction.inValues().size() == 1; - assert monitorInstruction.inValues().get(0).getAliasedValue() == arg; - isUsedInMonitor = true; - return true; - } - } -}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java index 43667c8..d6bdd6b 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
@@ -15,10 +15,7 @@ import com.android.tools.r8.ir.analysis.value.AbstractValue; import com.android.tools.r8.ir.analysis.value.UnknownValue; import com.android.tools.r8.ir.code.InvokeDirect; -import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo; -import com.android.tools.r8.ir.optimize.classinliner.constraint.AlwaysFalseClassInlinerMethodConstraint; import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint; -import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage; import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo; import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo; import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection; @@ -36,7 +33,7 @@ private AbstractValue abstractReturnValue = DefaultMethodOptimizationInfo.UNKNOWN_ABSTRACT_RETURN_VALUE; private ClassInlinerMethodConstraint classInlinerConstraint = - AlwaysFalseClassInlinerMethodConstraint.getInstance(); + ClassInlinerMethodConstraint.alwaysFalse(); private TypeElement returnsObjectWithUpperBoundType = DefaultMethodOptimizationInfo.UNKNOWN_TYPE; private ClassTypeElement returnsObjectWithLowerBoundType = DefaultMethodOptimizationInfo.UNKNOWN_CLASS_TYPE; @@ -44,12 +41,8 @@ // Stores information about instance methods and constructors for // class inliner, null value indicates that the method is not eligible. private BridgeInfo bridgeInfo = null; - private ClassInlinerEligibilityInfo classInlinerEligibility = - DefaultMethodOptimizationInfo.UNKNOWN_CLASS_INLINER_ELIGIBILITY; private InstanceInitializerInfoCollection instanceInitializerInfoCollection = InstanceInitializerInfoCollection.empty(); - private ParameterUsagesInfo parametersUsages = - DefaultMethodOptimizationInfo.UNKNOWN_PARAMETER_USAGE_INFO; // Stores information about nullability hint per parameter. If set, that means, the method // somehow (e.g., null check, such as arg != null, or using checkParameterIsNotNull) ensures // the corresponding parameter is not null, or throws NPE before any other side effects. @@ -148,9 +141,7 @@ inlining = template.inlining; simpleInliningConstraint = template.simpleInliningConstraint; bridgeInfo = template.bridgeInfo; - classInlinerEligibility = template.classInlinerEligibility; instanceInitializerInfoCollection = template.instanceInitializerInfoCollection; - parametersUsages = template.parametersUsages; nonNullParamOrThrow = template.nonNullParamOrThrow; nonNullParamOnNormalExits = template.nonNullParamOnNormalExits; } @@ -277,11 +268,6 @@ } @Override - public ParameterUsage getParameterUsages(int parameter) { - return parametersUsages == null ? null : parametersUsages.getParameterUsage(parameter); - } - - @Override public BitSet getNonNullParamOrThrow() { return nonNullParamOrThrow; } @@ -331,11 +317,6 @@ } @Override - public ClassInlinerEligibilityInfo getClassInlinerEligibility() { - return classInlinerEligibility; - } - - @Override public AbstractValue getAbstractReturnValue() { return abstractReturnValue; } @@ -380,10 +361,6 @@ return isFlagSet(RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG); } - void setParameterUsages(ParameterUsagesInfo parametersUsages) { - this.parametersUsages = parametersUsages; - } - void setNonNullParamOrThrow(BitSet facts) { this.nonNullParamOrThrow = facts; } @@ -400,10 +377,6 @@ this.simpleInliningConstraint = constraint; } - void setClassInlinerEligibility(ClassInlinerEligibilityInfo eligibility) { - this.classInlinerEligibility = eligibility; - } - void setInstanceInitializerInfoCollection( InstanceInitializerInfoCollection instanceInitializerInfoCollection) { this.instanceInitializerInfoCollection = instanceInitializerInfoCollection; @@ -518,6 +491,7 @@ } public void adjustOptimizationInfoAfterRemovingThisParameter() { + classInlinerConstraint = classInlinerConstraint.fixupAfterRemovingThisParameter(); // cannotBeKept: doesn't depend on `this` // classInitializerMayBePostponed: `this` could trigger <clinit> of the previous holder. clearFlag(CLASS_INITIALIZER_MAY_BE_POSTPONED_FLAG); @@ -550,18 +524,12 @@ // triggersClassInitBeforeAnySideEffect: code is not changed. markTriggerClassInitBeforeAnySideEffect( DefaultMethodOptimizationInfo.UNKNOWN_TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT); - // classInlinerEligibility: chances are the method is not an instance method anymore. - classInlinerEligibility = DefaultMethodOptimizationInfo.UNKNOWN_CLASS_INLINER_ELIGIBILITY; // initializerInfo: the computed initializer info may become invalid. instanceInitializerInfoCollection = InstanceInitializerInfoCollection.empty(); // initializerEnablingJavaAssertions: `this` could trigger <clinit> of the previous holder. setFlag( INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG, DefaultMethodOptimizationInfo.UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS); - parametersUsages = - parametersUsages == DefaultMethodOptimizationInfo.UNKNOWN_PARAMETER_USAGE_INFO - ? DefaultMethodOptimizationInfo.UNKNOWN_PARAMETER_USAGE_INFO - : parametersUsages.remove(0); nonNullParamOrThrow = nonNullParamOrThrow == DefaultMethodOptimizationInfo.NO_NULL_PARAMETER_OR_THROW_FACTS ? DefaultMethodOptimizationInfo.NO_NULL_PARAMETER_OR_THROW_FACTS
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java index 1141303..2de1eb0 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.ir.analysis.value.AbstractValueFactory; import com.android.tools.r8.ir.analysis.value.ObjectState; import com.android.tools.r8.ir.optimize.info.LibraryOptimizationInfoInitializerFeedback; +import com.android.tools.r8.ir.optimize.info.field.EmptyInstanceFieldInitializationInfoCollection; import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection; import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory; import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection; @@ -57,6 +58,16 @@ } private void modelInstanceInitializers() { + DexEncodedMethod objectConstructor = lookupMethod(dexItemFactory.objectMembers.constructor); + if (objectConstructor != null) { + InstanceFieldInitializationInfoCollection fieldInitializationInfos = + EmptyInstanceFieldInitializationInfoCollection.getInstance(); + feedback.setInstanceInitializerInfoCollection( + objectConstructor, + InstanceInitializerInfoCollection.of( + NonTrivialInstanceInitializerInfo.builder(fieldInitializationInfos).build())); + } + EnumMembers enumMembers = dexItemFactory.enumMembers; DexEncodedMethod enumConstructor = lookupMethod(enumMembers.constructor); if (enumConstructor != null) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java index d5ef586..5a2f63a 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
@@ -16,6 +16,7 @@ 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.GraphLens; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.graph.UseRegistry; @@ -686,8 +687,9 @@ // 3. Rewrite methods referencing staticized members, also remove instance creation // public final void staticizeCandidates( - OptimizationFeedback feedback, ExecutorService executorService) throws ExecutionException { - new StaticizingProcessor(appView, this, converter).run(feedback, executorService); + OptimizationFeedback feedback, ExecutorService executorService, GraphLens applied) + throws ExecutionException { + new StaticizingProcessor(appView, this, converter, applied).run(feedback, executorService); } private class CallSiteReferencesInvalidator extends UseRegistry {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java index 5f54ab6..27806d5 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -19,6 +19,7 @@ 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.GraphLens; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.code.IRCode; @@ -85,14 +86,17 @@ private final Map<DexField, CandidateInfo> singletonFields = new IdentityHashMap<>(); private final Map<DexMethod, CandidateInfo> singletonGetters = new IdentityHashMap<>(); private final Map<DexType, DexType> candidateToHostMapping = new IdentityHashMap<>(); + private final GraphLens applied; StaticizingProcessor( AppView<AppInfoWithLiveness> appView, ClassStaticizer classStaticizer, - IRConverter converter) { + IRConverter converter, + GraphLens applied) { this.appView = appView; this.classStaticizer = classStaticizer; this.converter = converter; + this.applied = applied; } final void run(OptimizationFeedback feedback, ExecutorService executorService) @@ -233,7 +237,7 @@ LongLivedProgramMethodSetBuilder<?> referencedFromBuilder = classStaticizer.referencedFrom.remove(info); assert referencedFromBuilder != null; - referencedFrom = referencedFromBuilder.build(appView); + referencedFrom = referencedFromBuilder.build(appView, applied); materializedReferencedFromCollections.put(info, referencedFrom); } else { referencedFrom = ProgramMethodSet.empty();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java index 0cafbcd..80066f3 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java
@@ -6,10 +6,10 @@ import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractState; +import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractTransferFunction; import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult; import com.android.tools.r8.ir.analysis.framework.intraprocedural.FailedTransferFunctionResult; import com.android.tools.r8.ir.analysis.framework.intraprocedural.IntraproceduralDataflowAnalysis; -import com.android.tools.r8.ir.analysis.framework.intraprocedural.TransferFunction; import com.android.tools.r8.ir.analysis.framework.intraprocedural.TransferFunctionResult; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.Instruction; @@ -42,7 +42,7 @@ Value builder, StringBuilderOptimizationConfiguration configuration) { IntraproceduralDataflowAnalysis<AbstractStateImpl> analysis = new IntraproceduralDataflowAnalysis<>( - AbstractStateImpl.bottom(), new TransferFunctionImpl(builder, configuration)); + AbstractStateImpl.bottom(), new TransferFunction(builder, configuration)); DataflowAnalysisResult result = analysis.run(builder.definition.getBlock()); return result.isFailedAnalysisResult(); } @@ -121,13 +121,12 @@ * <p>If a call to {@code toString()} on the builder i seen, then the abstract state is reset to * bottom. */ - private static class TransferFunctionImpl implements TransferFunction<AbstractStateImpl> { + private static class TransferFunction implements AbstractTransferFunction<AbstractStateImpl> { private final Value builder; private final StringBuilderOptimizationConfiguration configuration; - private TransferFunctionImpl( - Value builder, StringBuilderOptimizationConfiguration configuration) { + private TransferFunction(Value builder, StringBuilderOptimizationConfiguration configuration) { this.builder = builder; this.configuration = configuration; }
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/CallObjectInitCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/CallObjectInitCfCodeProvider.java new file mode 100644 index 0000000..77462a4 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/synthetic/CallObjectInitCfCodeProvider.java
@@ -0,0 +1,35 @@ +// 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.synthetic; + +import com.android.tools.r8.cf.code.CfInstruction; +import com.android.tools.r8.cf.code.CfInvoke; +import com.android.tools.r8.cf.code.CfLoad; +import com.android.tools.r8.cf.code.CfReturnVoid; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.CfCode; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.ir.code.ValueType; +import java.util.ArrayList; +import java.util.List; +import org.objectweb.asm.Opcodes; + +public class CallObjectInitCfCodeProvider extends SyntheticCfCodeProvider { + + public CallObjectInitCfCodeProvider(AppView<?> appView, DexType holder) { + super(appView, holder); + } + + @Override + public CfCode generateCfCode() { + DexItemFactory factory = appView.dexItemFactory(); + List<CfInstruction> instructions = new ArrayList<>(); + instructions.add(new CfLoad(ValueType.OBJECT, 0)); + instructions.add(new CfInvoke(Opcodes.INVOKESPECIAL, factory.objectMembers.constructor, false)); + instructions.add(new CfReturnVoid()); + return standardCfCodeFromInstructions(instructions); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorBuilder.java b/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorBuilder.java index b02544d..1f3c6d0 100644 --- a/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorBuilder.java +++ b/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorBuilder.java
@@ -89,6 +89,7 @@ // Load the argument. ValueType fieldType = ValueType.fromDexType(field.getType()); instructions.add(new CfLoad(fieldType, maxLocals)); + maxStack += fieldType.requiredRegisters(); maxLocals += fieldType.requiredRegisters(); } @@ -102,6 +103,7 @@ instructions.add(new CfReturnVoid()); } else { ValueType fieldType = ValueType.fromDexType(field.getType()); + maxStack = Math.max(fieldType.requiredRegisters(), maxStack); instructions.add(new CfReturn(fieldType)); }
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java index 84acf70..0c6b359 100644 --- a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java +++ b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
@@ -182,6 +182,13 @@ maybeInsertArgumentCast(i, parameter, instructions); } instructions.add(new CfInvoke(getInvokeOpcode(), targetMethod, isInterface)); + if (!targetMethod.getReturnType().isVoidType()) { + // If the return type is not void, it will push a value on the stack. We subtract the + // arguments pushed by the invoke to see if bumping the stack height is necessary. + maxStack = + Math.max( + maxStack, ValueType.fromDexType(targetMethod.getReturnType()).requiredRegisters()); + } if (isSourceReturnVoid()) { assert !isConstructorDelegate; instructions.add(new CfReturnVoid());
diff --git a/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java b/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java new file mode 100644 index 0000000..83fa84e --- /dev/null +++ b/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java
@@ -0,0 +1,94 @@ +// 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.naming; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexString; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.InnerClassAttribute; +import com.android.tools.r8.naming.NamingLens.NonIdentityNamingLens; +import com.android.tools.r8.utils.InternalOptions; +import java.util.IdentityHashMap; + +// Naming lens for rewriting java.lang.Record to the internal RecordTag type. +public class RecordRewritingNamingLens extends NonIdentityNamingLens { + + final NamingLens namingLens; + private final DexItemFactory factory; + + public static NamingLens createRecordRewritingNamingLens(AppView<?> appView) { + return createRecordRewritingNamingLens(appView, NamingLens.getIdentityLens()); + } + + public static NamingLens createRecordRewritingNamingLens( + AppView<?> appView, NamingLens namingLens) { + if (!appView.options().shouldDesugarRecords()) { + return namingLens; + } + return new RecordRewritingNamingLens(namingLens, appView); + } + + public RecordRewritingNamingLens(NamingLens namingLens, AppView<?> appView) { + super(appView.dexItemFactory(), new IdentityHashMap<>()); + this.namingLens = namingLens; + factory = appView.dexItemFactory(); + } + + private boolean isRenamed(DexType type) { + return getRenaming(type) != null; + } + + private DexString getRenaming(DexType type) { + if (type == factory.recordType) { + return factory.r8RecordType.descriptor; + } + return null; + } + + @Override + protected DexString internalLookupClassDescriptor(DexType type) { + DexString renaming = getRenaming(type); + return renaming != null ? renaming : namingLens.lookupDescriptor(type); + } + + @Override + public DexString lookupInnerName(InnerClassAttribute attribute, InternalOptions options) { + assert !isRenamed(attribute.getInner()); + return namingLens.lookupInnerName(attribute, options); + } + + @Override + public DexString lookupName(DexMethod method) { + // Record rewriting does not influence method name. + return namingLens.lookupName(method); + } + + @Override + public DexString lookupName(DexField field) { + // Record rewriting does not influence field name. + return namingLens.lookupName(field); + } + + @Override + public DexString lookupDescriptorForJavaTypeName(String typeName) { + if (typeName.equals(factory.recordType.toSourceString())) { + return factory.r8RecordType.descriptor; + } + return namingLens.lookupDescriptorForJavaTypeName(typeName); + } + + @Override + public String lookupPackageName(String packageName) { + return namingLens.lookupPackageName(packageName); + } + + @Override + public boolean verifyRenamingConsistentWithResolution(DexMethod item) { + return namingLens.verifyRenamingConsistentWithResolution(item); + } +}
diff --git a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java index cdfa649..056abce 100644 --- a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java +++ b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
@@ -33,9 +33,11 @@ import com.google.common.collect.HashBiMap; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -130,11 +132,13 @@ } BiMap<DexType, DexType> mappings = HashBiMap.create(); + Map<String, String> packageMappings = new HashMap<>(); Set<String> seenPackageDescriptors = new HashSet<>(); ProgramPackageCollection packages = SortedProgramPackageCollection.createWithAllProgramClasses(appView); - processPackagesInDesiredLocation(packages, mappings, seenPackageDescriptors); - processRemainingPackages(packages, mappings, seenPackageDescriptors, executorService); + processPackagesInDesiredLocation(packages, mappings, packageMappings, seenPackageDescriptors); + processRemainingPackages( + packages, mappings, packageMappings, seenPackageDescriptors, executorService); mappings.entrySet().removeIf(entry -> entry.getKey() == entry.getValue()); if (mappings.isEmpty()) { return null; @@ -146,7 +150,7 @@ new ArrayList<>( repackagingTreeFixer.fixupClasses(appView.appInfo().classesWithDeterministicOrder())); appBuilder.replaceProgramClasses(newProgramClasses); - RepackagingLens lens = lensBuilder.build(appView); + RepackagingLens lens = lensBuilder.build(appView, packageMappings); new AnnotationFixer(lens).run(appBuilder.getProgramClasses()); return lens; } @@ -191,6 +195,7 @@ private void processPackagesInDesiredLocation( ProgramPackageCollection packages, BiMap<DexType, DexType> mappings, + Map<String, String> packageMappings, Set<String> seenPackageDescriptors) { // For each package that is already in the desired location, record all the classes from the // package in the mapping for collision detection. @@ -208,6 +213,7 @@ for (DexProgramClass alreadyRepackagedClass : pkg) { processClass(alreadyRepackagedClass, pkg, newPackageDescriptor, mappings); } + packageMappings.put(pkg.getPackageDescriptor(), newPackageDescriptor); seenPackageDescriptors.add(newPackageDescriptor); iterator.remove(); } @@ -217,6 +223,7 @@ private void processRemainingPackages( ProgramPackageCollection packages, BiMap<DexType, DexType> mappings, + Map<String, String> packageMappings, Set<String> seenPackageDescriptors, ExecutorService executorService) throws ExecutionException { @@ -228,13 +235,20 @@ repackagingConfiguration.getNewPackageDescriptor(pkg, seenPackageDescriptors); assert !pkg.getPackageDescriptor().equals(newPackageDescriptor); - Iterable<DexProgramClass> classesToRepackage = + Collection<DexProgramClass> classesToRepackage = computeClassesToRepackage(pkg, executorService); for (DexProgramClass classToRepackage : classesToRepackage) { processClass(classToRepackage, pkg, newPackageDescriptor, mappings); } - seenPackageDescriptors.add(newPackageDescriptor); + // Package remapping is used for adapting resources. If we cannot repackage all classes in + // a package then we put in the original descriptor to ensure that resources are not + // rewritten. + packageMappings.put( + pkg.getPackageDescriptor(), + classesToRepackage.size() == pkg.classesInPackage().size() + ? newPackageDescriptor + : pkg.getPackageDescriptor()); // TODO(b/165783399): Investigate if repackaging can lead to different dynamic dispatch. See, // for example, CrossPackageInvokeSuperToPackagePrivateMethodTest. } @@ -269,12 +283,12 @@ classToRepackage, outerClass, newPackageDescriptor, mappings)); } - private Iterable<DexProgramClass> computeClassesToRepackage( + private Collection<DexProgramClass> computeClassesToRepackage( ProgramPackage pkg, ExecutorService executorService) throws ExecutionException { RepackagingConstraintGraph constraintGraph = new RepackagingConstraintGraph(appView, pkg); boolean canRepackageAllClasses = constraintGraph.initializeGraph(); if (canRepackageAllClasses) { - return pkg; + return pkg.classesInPackage(); } constraintGraph.populateConstraints(executorService); return constraintGraph.computeClassesToRepackage();
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java index ab5b01b..dd6af4a 100644 --- a/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java +++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.utils.WorkList; import com.google.common.collect.Sets; import java.util.ArrayList; +import java.util.Collection; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; @@ -172,7 +173,7 @@ } } - public Iterable<DexProgramClass> computeClassesToRepackage() { + public Collection<DexProgramClass> computeClassesToRepackage() { WorkList<Node> worklist = WorkList.newIdentityWorkList(pinnedNodes); while (worklist.hasNext()) { Node pinnedNode = worklist.next();
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 63b8d06..e6d16eb 100644 --- a/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java +++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java
@@ -18,16 +18,19 @@ 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> originalTypes; + private final Map<String, String> packageRenamings; private RepackagingLens( AppView<AppInfoWithLiveness> appView, BidirectionalOneToOneMap<DexField, DexField> newFieldSignatures, BidirectionalOneToOneMap<DexMethod, DexMethod> originalMethodSignatures, - BiMap<DexType, DexType> originalTypes) { + BiMap<DexType, DexType> originalTypes, + Map<String, String> packageRenamings) { super( originalTypes.inverse(), originalMethodSignatures.getInverseOneToOneMap().getForwardMap(), @@ -36,6 +39,12 @@ appView.graphLens(), appView.dexItemFactory()); this.originalTypes = originalTypes; + this.packageRenamings = packageRenamings; + } + + @Override + public String lookupPackageName(String pkg) { + return packageRenamings.getOrDefault(getPrevious().lookupPackageName(pkg), pkg); } @Override @@ -97,10 +106,11 @@ originalTypes.put(to, from); } - public RepackagingLens build(AppView<AppInfoWithLiveness> appView) { + public RepackagingLens build( + AppView<AppInfoWithLiveness> appView, Map<String, String> packageRenamings) { assert !originalTypes.isEmpty(); return new RepackagingLens( - appView, newFieldSignatures, originalMethodSignatures, originalTypes); + appView, newFieldSignatures, originalMethodSignatures, originalTypes, packageRenamings); } } }
diff --git a/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java b/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java index 3c83acd..f032832 100644 --- a/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java +++ b/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
@@ -77,13 +77,17 @@ | Constants.ACC_FINAL | Constants.ACC_PUBLIC | Constants.ACC_STATIC); + boolean deprecated = false; + boolean d8R8Synthesized = true; encodedClinitField = new DexEncodedField( appView.dexItemFactory().createField(clazz.type, clinitField.type, clinitField.name), accessFlags, FieldTypeSignature.noSignature(), DexAnnotationSet.empty(), - null); + null, + deprecated, + d8R8Synthesized); clazz.appendStaticField(encodedClinitField); } lensBuilder.map(type, encodedClinitField.field);
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java index fd3c069..e818526 100644 --- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java +++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1695,7 +1695,7 @@ assert !mode.isFinalMainDexTracing() || !options.testing.checkForNotExpandingMainDexTracingResult - || appView.appInfo().getMainDexInfo().isTracedRoot(clazz) + || appView.appInfo().getMainDexInfo().isTracedRoot(clazz, appView.getSyntheticItems()) || clazz.toSourceString().contains(ENUM_UNBOXING_UTILITY_CLASS_SUFFIX) : "Class " + clazz.toSourceString() + " was not a main dex root in the first round";
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java b/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java index a60967f..96e263c 100644 --- a/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java +++ b/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java
@@ -6,7 +6,6 @@ import static com.android.tools.r8.shaking.MainDexInfo.MainDexGroup.MAIN_DEX_ROOT; import static com.android.tools.r8.utils.LensUtils.rewriteAndApplyIfNotPrimitiveType; -import static com.android.tools.r8.utils.PredicateUtils.not; import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.DexMethod; @@ -17,13 +16,11 @@ import com.android.tools.r8.graph.ProgramDefinition; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.graph.PrunedItems; +import com.android.tools.r8.synthesis.SyntheticItems; import com.android.tools.r8.utils.ConsumerUtils; -import com.android.tools.r8.utils.SetUtils; import com.google.common.collect.Sets; import java.util.Collections; -import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; public class MainDexInfo { @@ -34,7 +31,6 @@ Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), - Collections.emptySet(), false); public enum MainDexGroup { @@ -46,7 +42,6 @@ // Specific set of classes specified to be in main-dex private final Set<DexType> classList; - private final Map<DexType, DexType> synthesizedClassesMap; // Traced roots are traced main dex references. private final Set<DexType> tracedRoots; // Traced method roots are the methods visited from an initial main dex root set. The set is @@ -64,7 +59,6 @@ Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), - /* synthesized classes - cannot be emptyset */ Sets.newIdentityHashSet(), false); } @@ -73,17 +67,18 @@ Set<DexType> tracedRoots, Set<DexMethod> tracedMethodRoots, Set<DexType> tracedDependencies, - Set<DexType> synthesizedClasses, boolean tracedMethodRootsCleared) { this.classList = classList; this.tracedRoots = tracedRoots; this.tracedMethodRoots = tracedMethodRoots; this.tracedDependencies = tracedDependencies; - this.synthesizedClassesMap = new ConcurrentHashMap<>(); this.tracedMethodRootsCleared = tracedMethodRootsCleared; - synthesizedClasses.forEach(type -> synthesizedClassesMap.put(type, type)); assert tracedDependencies.stream().noneMatch(tracedRoots::contains); - assert tracedRoots.containsAll(synthesizedClasses); + } + + // TODO(b/181858113): Remove once deprecated main-dex-list is removed. + public boolean isSyntheticContextOnMainDexList(DexType syntheticContextType) { + return classList.contains(syntheticContextType); } public boolean isNone() { @@ -91,25 +86,16 @@ return this == NONE; } - public boolean isMainDexTypeThatShouldIncludeDependencies(DexType type) { - // Dependencies of 'type' are only needed if 'type' is a direct/executed main-dex type. - return classList.contains(type) || tracedRoots.contains(type); + public boolean isFromList(ProgramDefinition definition, SyntheticItems synthetics) { + return isFromList(definition.getContextType(), synthetics); } - public boolean isMainDex(ProgramDefinition definition) { - return isFromList(definition) || isTracedRoot(definition) || isDependency(definition); + private boolean isFromList(DexReference reference, SyntheticItems synthetics) { + return isContainedOrHasContainedContext(reference, classList, synthetics); } - public boolean isFromList(ProgramDefinition definition) { - return isFromList(definition.getContextType()); - } - - private boolean isFromList(DexReference reference) { - return classList.contains(reference.getContextType()); - } - - public boolean isTracedRoot(ProgramDefinition definition) { - return isTracedRoot(definition.getContextType()); + public boolean isTracedRoot(ProgramDefinition definition, SyntheticItems synthetics) { + return isTracedRoot(definition.getContextType(), synthetics); } public boolean isTracedMethodRoot(DexMethod method) { @@ -117,8 +103,22 @@ return tracedMethodRoots.contains(method); } - private boolean isTracedRoot(DexReference reference) { - return tracedRoots.contains(reference.getContextType()); + private boolean isTracedRoot(DexReference reference, SyntheticItems synthetics) { + return isContainedOrHasContainedContext(reference, tracedRoots, synthetics); + } + + private boolean isContainedOrHasContainedContext( + DexReference reference, Set<DexType> items, SyntheticItems synthetics) { + if (items.isEmpty()) { + return false; + } + DexType type = reference.getContextType(); + for (DexType context : synthetics.getSynthesizingContexts(type)) { + if (items.contains(context)) { + return true; + } + } + return items.contains(type); } private boolean isDependency(ProgramDefinition definition) { @@ -138,8 +138,9 @@ this.tracedMethodRoots = Sets.newIdentityHashSet(); } - public boolean canRebindReference(ProgramMethod context, DexReference referenceToTarget) { - MainDexGroup holderGroup = getMainDexGroupInternal(context); + public boolean canRebindReference( + ProgramMethod context, DexReference referenceToTarget, SyntheticItems synthetics) { + MainDexGroup holderGroup = getMainDexGroupInternal(context, synthetics); if (holderGroup == MainDexGroup.NOT_IN_MAIN_DEX || holderGroup == MainDexGroup.MAIN_DEX_DEPENDENCY) { // We are always free to rebind/inline into something not in main-dex or traced dependencies. @@ -152,20 +153,21 @@ } assert holderGroup == MAIN_DEX_ROOT; // Otherwise we allow if either is both root. - return getMainDexGroupInternal(referenceToTarget) == MAIN_DEX_ROOT; + return getMainDexGroupInternal(referenceToTarget, synthetics) == MAIN_DEX_ROOT; } - public boolean canMerge(ProgramDefinition candidate) { - return !isFromList(candidate); + public boolean canMerge(ProgramDefinition candidate, SyntheticItems synthetics) { + return !isFromList(candidate, synthetics); } - public boolean canMerge(ProgramDefinition source, ProgramDefinition target) { - return canMerge(source.getContextType(), target.getContextType()); + public boolean canMerge( + ProgramDefinition source, ProgramDefinition target, SyntheticItems synthetics) { + return canMerge(source.getContextType(), target.getContextType(), synthetics); } - private boolean canMerge(DexReference source, DexReference target) { - MainDexGroup sourceGroup = getMainDexGroupInternal(source); - MainDexGroup targetGroup = getMainDexGroupInternal(target); + private boolean canMerge(DexReference source, DexReference target, SyntheticItems synthetics) { + MainDexGroup sourceGroup = getMainDexGroupInternal(source, synthetics); + MainDexGroup targetGroup = getMainDexGroupInternal(target, synthetics); if (sourceGroup != targetGroup) { return false; } @@ -174,20 +176,22 @@ return sourceGroup != MainDexGroup.MAIN_DEX_LIST; } - public MainDexGroup getMergeKey(ProgramDefinition mergeCandidate) { - MainDexGroup mainDexGroupInternal = getMainDexGroupInternal(mergeCandidate); + public MainDexGroup getMergeKey(ProgramDefinition mergeCandidate, SyntheticItems synthetics) { + assert canMerge(mergeCandidate, synthetics); + MainDexGroup mainDexGroupInternal = getMainDexGroupInternal(mergeCandidate, synthetics); return mainDexGroupInternal == MainDexGroup.MAIN_DEX_LIST ? null : mainDexGroupInternal; } - private MainDexGroup getMainDexGroupInternal(ProgramDefinition definition) { - return getMainDexGroupInternal(definition.getReference()); + private MainDexGroup getMainDexGroupInternal( + ProgramDefinition definition, SyntheticItems synthetics) { + return getMainDexGroupInternal(definition.getReference(), synthetics); } - private MainDexGroup getMainDexGroupInternal(DexReference reference) { - if (isFromList(reference)) { + private MainDexGroup getMainDexGroupInternal(DexReference reference, SyntheticItems synthetics) { + if (isFromList(reference, synthetics)) { return MainDexGroup.MAIN_DEX_LIST; } - if (isTracedRoot(reference)) { + if (isTracedRoot(reference, synthetics)) { return MAIN_DEX_ROOT; } if (isDependency(reference)) { @@ -197,22 +201,25 @@ } public boolean disallowInliningIntoContext( - AppInfoWithClassHierarchy appInfo, ProgramDefinition context, ProgramMethod method) { + AppInfoWithClassHierarchy appInfo, + ProgramDefinition context, + ProgramMethod method, + SyntheticItems synthetics) { if (context.getContextType() == method.getContextType()) { return false; } - MainDexGroup mainDexGroupInternal = getMainDexGroupInternal(context); + MainDexGroup mainDexGroupInternal = getMainDexGroupInternal(context, synthetics); if (mainDexGroupInternal == MainDexGroup.NOT_IN_MAIN_DEX || mainDexGroupInternal == MainDexGroup.MAIN_DEX_DEPENDENCY) { return false; } if (mainDexGroupInternal == MainDexGroup.MAIN_DEX_LIST) { return MainDexDirectReferenceTracer.hasReferencesOutsideMainDexClasses( - appInfo, method, not(this::isFromList)); + appInfo, method, t -> !isFromList(t, synthetics)); } assert mainDexGroupInternal == MAIN_DEX_ROOT; return MainDexDirectReferenceTracer.hasReferencesOutsideMainDexClasses( - appInfo, method, not(this::isTracedRoot)); + appInfo, method, t -> !isTracedRoot(t, synthetics)); } public boolean isEmpty() { @@ -224,35 +231,6 @@ return NONE; } - // TODO(b/178127572): This mutates the MainDexClasses which otherwise should be immutable. - public void addSyntheticClass(DexProgramClass clazz) { - // TODO(b/178127572): This will add a synthesized type as long as the initial set is not empty. - // A better approach would be to use the context for the synthetic with a containment check. - assert !isNone(); - if (!classList.isEmpty()) { - synthesizedClassesMap.computeIfAbsent( - clazz.type, - type -> { - classList.add(type); - return type; - }); - } - if (!tracedRoots.isEmpty()) { - synthesizedClassesMap.computeIfAbsent( - clazz.type, - type -> { - tracedRoots.add(type); - return type; - }); - } - } - - public void addLegacySyntheticClass(DexProgramClass clazz, ProgramDefinition context) { - if (isTracedRoot(context) || isFromList(context) || isDependency(context)) { - addSyntheticClass(clazz); - } - } - public int size() { return classList.size() + tracedRoots.size() + tracedDependencies.size(); } @@ -278,11 +256,7 @@ } Set<DexType> removedClasses = prunedItems.getRemovedClasses(); Set<DexType> modifiedClassList = Sets.newIdentityHashSet(); - Set<DexType> modifiedSynthesized = Sets.newIdentityHashSet(); classList.forEach(type -> ifNotRemoved(type, removedClasses, modifiedClassList::add)); - synthesizedClassesMap - .keySet() - .forEach(type -> ifNotRemoved(type, removedClasses, modifiedSynthesized::add)); MainDexInfo.Builder builder = builder(); tracedRoots.forEach(type -> ifNotRemoved(type, removedClasses, builder::addRoot)); // TODO(b/169927809): Methods could be pruned without the holder being pruned, however, one has @@ -292,7 +266,7 @@ ifNotRemoved( method.getHolderType(), removedClasses, ignored -> builder.addRoot(method))); tracedDependencies.forEach(type -> ifNotRemoved(type, removedClasses, builder::addDependency)); - return builder.build(modifiedClassList, modifiedSynthesized); + return builder.build(modifiedClassList); } private void ifNotRemoved( @@ -304,10 +278,8 @@ public MainDexInfo rewrittenWithLens(GraphLens lens) { Set<DexType> modifiedClassList = Sets.newIdentityHashSet(); - Set<DexType> modifiedSynthesized = Sets.newIdentityHashSet(); classList.forEach( type -> rewriteAndApplyIfNotPrimitiveType(lens, type, modifiedClassList::add)); - synthesizedClassesMap.keySet().forEach(type -> modifiedSynthesized.add(lens.lookupType(type))); MainDexInfo.Builder builder = builder(); tracedRoots.forEach(type -> rewriteAndApplyIfNotPrimitiveType(lens, type, builder::addRoot)); tracedMethodRoots.forEach(method -> builder.addRoot(lens.getRenamedMethodSignature(method))); @@ -321,7 +293,7 @@ rewriteAndApplyIfNotPrimitiveType(lens, type, builder::addDependency); } }); - return builder.build(modifiedClassList, modifiedSynthesized); + return builder.build(modifiedClassList); } public Builder builder() { @@ -413,20 +385,27 @@ return new MainDexInfo(list); } - public MainDexInfo build(Set<DexType> classList, Set<DexType> synthesizedClasses) { + public MainDexInfo build(Set<DexType> classList) { // Class can contain dependencies which we should not regard as roots. assert list.isEmpty(); - return new MainDexInfo( - classList, - SetUtils.unionIdentityHashSet(roots, synthesizedClasses), - methodRoots, - Sets.difference(dependencies, synthesizedClasses), - synthesizedClasses, - tracedMethodRootsCleared); + return new MainDexInfo(classList, roots, methodRoots, dependencies, tracedMethodRootsCleared); } public MainDexInfo build(MainDexInfo previous) { - return build(previous.classList, previous.synthesizedClassesMap.keySet()); + return build(previous.classList); } + + public MainDexInfo build() { + return new MainDexInfo(list, roots, methodRoots, dependencies, tracedMethodRootsCleared); + } + } + + public Builder builderFromCopy() { + Builder builder = new Builder(tracedMethodRootsCleared); + builder.list.addAll(classList); + builder.roots.addAll(tracedRoots); + builder.methodRoots.addAll(tracedMethodRoots); + builder.dependencies.addAll(tracedDependencies); + return builder; } }
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 afc9e50..761b314 100644 --- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java +++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -826,7 +826,7 @@ } // Check with main dex classes to see if we are allowed to merge. - if (!mainDexInfo.canMerge(clazz, targetClass)) { + if (!mainDexInfo.canMerge(clazz, targetClass, appView.getSyntheticItems())) { return; } @@ -1657,7 +1657,8 @@ } // Constructors can have references beyond the root main dex classes. This can increase the // size of the main dex dependent classes and we should bail out. - if (mainDexInfo.disallowInliningIntoContext(appView.appInfo(), context, method)) { + if (mainDexInfo.disallowInliningIntoContext( + appView.appInfo(), context, method, appView.getSyntheticItems())) { return AbortReason.MAIN_DEX_ROOT_OUTSIDE_REFERENCE; } return null;
diff --git a/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java b/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java index 2681a90..adc1298 100644 --- a/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java +++ b/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java
@@ -56,7 +56,7 @@ @Deprecated public Collection<DexType> getLegacySyntheticTypes() { - return committed.getLegacyTypes(); + return committed.getLegacyTypes().keySet(); } @Override
diff --git a/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java b/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java index 053b20b..7dbdec5 100644 --- a/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java +++ b/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
@@ -4,12 +4,11 @@ package com.android.tools.r8.synthesis; import com.android.tools.r8.graph.DexApplication; -import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens; import com.android.tools.r8.graph.PrunedItems; +import com.android.tools.r8.utils.BooleanBox; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import java.util.Collection; import java.util.Map; @@ -29,7 +28,7 @@ private ImmutableMap.Builder<DexType, SyntheticProgramClassReference> newNonLegacyClasses = null; private ImmutableMap.Builder<DexType, SyntheticMethodReference> newNonLegacyMethods = null; - private ImmutableSet.Builder<DexType> newLegacyClasses = null; + private ImmutableMap.Builder<DexType, LegacySyntheticReference> newLegacyClasses = null; public Builder(CommittedSyntheticsCollection parent) { this.parent = parent; @@ -66,19 +65,19 @@ return this; } - public Builder addLegacyClasses(Collection<DexProgramClass> classes) { + public Builder addLegacyClasses(Map<DexType, LegacySyntheticDefinition> classes) { if (newLegacyClasses == null) { - newLegacyClasses = ImmutableSet.builder(); + newLegacyClasses = ImmutableMap.builder(); } - classes.forEach(c -> newLegacyClasses.add(c.getType())); + classes.forEach((type, item) -> newLegacyClasses.put(type, item.toReference())); return this; } - public Builder addLegacyClass(DexType type) { + public Builder addLegacyClass(LegacySyntheticReference item) { if (newLegacyClasses == null) { - newLegacyClasses = ImmutableSet.builder(); + newLegacyClasses = ImmutableMap.builder(); } - newLegacyClasses.add(type); + newLegacyClasses.put(item.getHolder(), item); return this; } @@ -94,24 +93,24 @@ newNonLegacyMethods == null ? parent.nonLegacyMethods : newNonLegacyMethods.putAll(parent.nonLegacyMethods).build(); - ImmutableSet<DexType> allLegacyClasses = + ImmutableMap<DexType, LegacySyntheticReference> allLegacyClasses = newLegacyClasses == null ? parent.legacyTypes - : newLegacyClasses.addAll(parent.legacyTypes).build(); + : newLegacyClasses.putAll(parent.legacyTypes).build(); return new CommittedSyntheticsCollection( allLegacyClasses, allNonLegacyMethods, allNonLegacyClasses); } } private static final CommittedSyntheticsCollection EMPTY = - new CommittedSyntheticsCollection(ImmutableSet.of(), ImmutableMap.of(), ImmutableMap.of()); + new CommittedSyntheticsCollection(ImmutableMap.of(), ImmutableMap.of(), ImmutableMap.of()); /** * Immutable set of synthetic types in the application (eg, committed). * * <p>TODO(b/158159959): Remove legacy support. */ - private final ImmutableSet<DexType> legacyTypes; + private final ImmutableMap<DexType, LegacySyntheticReference> legacyTypes; /** Mapping from synthetic type to its synthetic method item description. */ private final ImmutableMap<DexType, SyntheticMethodReference> nonLegacyMethods; @@ -120,14 +119,16 @@ private final ImmutableMap<DexType, SyntheticProgramClassReference> nonLegacyClasses; public CommittedSyntheticsCollection( - ImmutableSet<DexType> legacyTypes, + ImmutableMap<DexType, LegacySyntheticReference> legacyTypes, ImmutableMap<DexType, SyntheticMethodReference> nonLegacyMethods, ImmutableMap<DexType, SyntheticProgramClassReference> nonLegacyClasses) { this.legacyTypes = legacyTypes; this.nonLegacyMethods = nonLegacyMethods; this.nonLegacyClasses = nonLegacyClasses; assert legacyTypes.size() + nonLegacyMethods.size() + nonLegacyClasses.size() - == Sets.union(Sets.union(nonLegacyMethods.keySet(), nonLegacyClasses.keySet()), legacyTypes) + == Sets.union( + Sets.union(nonLegacyMethods.keySet(), nonLegacyClasses.keySet()), + legacyTypes.keySet()) .size(); } @@ -148,14 +149,14 @@ } public boolean containsLegacyType(DexType type) { - return legacyTypes.contains(type); + return legacyTypes.containsKey(type); } public boolean containsNonLegacyType(DexType type) { return nonLegacyMethods.containsKey(type) || nonLegacyClasses.containsKey(type); } - public ImmutableSet<DexType> getLegacyTypes() { + public ImmutableMap<DexType, LegacySyntheticReference> getLegacyTypes() { return legacyTypes; } @@ -186,39 +187,40 @@ return this; } Builder builder = CommittedSyntheticsCollection.empty().builder(); - boolean changed = false; - for (DexType type : legacyTypes) { - if (removed.contains(type)) { - changed = true; - } else { - builder.addLegacyClass(type); - } - } + BooleanBox changed = new BooleanBox(false); + legacyTypes.forEach( + (type, item) -> { + if (removed.contains(type)) { + changed.set(); + } else { + builder.addLegacyClass(item); + } + }); for (SyntheticMethodReference reference : nonLegacyMethods.values()) { if (removed.contains(reference.getHolder())) { - changed = true; + changed.set(); } else { builder.addNonLegacyMethod(reference); } } for (SyntheticProgramClassReference reference : nonLegacyClasses.values()) { if (removed.contains(reference.getHolder())) { - changed = true; + changed.set(); } else { builder.addNonLegacyClass(reference); } } - return changed ? builder.build() : this; + return changed.isTrue() ? builder.build() : this; } CommittedSyntheticsCollection rewriteWithLens(NonIdentityGraphLens lens) { return new CommittedSyntheticsCollection( - lens.rewriteTypes(legacyTypes), + rewriteItems(legacyTypes, lens), rewriteItems(nonLegacyMethods, lens), rewriteItems(nonLegacyClasses, lens)); } - private static <R extends SyntheticReference<R, ?, ?>> ImmutableMap<DexType, R> rewriteItems( + private static <R extends Rewritable<R>> ImmutableMap<DexType, R> rewriteItems( Map<DexType, R> items, NonIdentityGraphLens lens) { ImmutableMap.Builder<DexType, R> rewrittenItems = ImmutableMap.builder(); for (R reference : items.values()) { @@ -231,7 +233,7 @@ } boolean verifyTypesAreInApp(DexApplication application) { - assert verifyTypesAreInApp(application, legacyTypes); + assert verifyTypesAreInApp(application, legacyTypes.keySet()); assert verifyTypesAreInApp(application, nonLegacyMethods.keySet()); assert verifyTypesAreInApp(application, nonLegacyClasses.keySet()); return true;
diff --git a/src/main/java/com/android/tools/r8/synthesis/LegacySyntheticDefinition.java b/src/main/java/com/android/tools/r8/synthesis/LegacySyntheticDefinition.java new file mode 100644 index 0000000..47a9556 --- /dev/null +++ b/src/main/java/com/android/tools/r8/synthesis/LegacySyntheticDefinition.java
@@ -0,0 +1,38 @@ +// Copyright (c) 2021, 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.synthesis; + +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.ProgramDefinition; +import com.google.common.collect.ImmutableSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public class LegacySyntheticDefinition { + private final DexProgramClass clazz; + private final Map<DexType, DexType> contexts = new ConcurrentHashMap<>(); + + public LegacySyntheticDefinition(DexProgramClass clazz) { + this.clazz = clazz; + } + + public void addContext(ProgramDefinition clazz) { + DexType type = clazz.getContextType(); + contexts.put(type, type); + } + + public Set<DexType> getContexts() { + return contexts.keySet(); + } + + public LegacySyntheticReference toReference() { + return new LegacySyntheticReference(clazz.getType(), ImmutableSet.copyOf(contexts.keySet())); + } + + public DexProgramClass getDefinition() { + return clazz; + } +}
diff --git a/src/main/java/com/android/tools/r8/synthesis/LegacySyntheticReference.java b/src/main/java/com/android/tools/r8/synthesis/LegacySyntheticReference.java new file mode 100644 index 0000000..06d2e2c --- /dev/null +++ b/src/main/java/com/android/tools/r8/synthesis/LegacySyntheticReference.java
@@ -0,0 +1,37 @@ +// Copyright (c) 2021, 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.synthesis; + +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens; +import java.util.Set; + +public class LegacySyntheticReference implements Rewritable<LegacySyntheticReference> { + private final DexType type; + private final Set<DexType> contexts; + + public LegacySyntheticReference(DexType type, Set<DexType> contexts) { + this.type = type; + this.contexts = contexts; + } + + @Override + public DexType getHolder() { + return type; + } + + public Set<DexType> getContexts() { + return contexts; + } + + @Override + public LegacySyntheticReference rewrite(NonIdentityGraphLens lens) { + DexType rewrittenType = lens.lookupType(type); + Set<DexType> rewrittenContexts = lens.rewriteTypes(getContexts()); + if (type == rewrittenType && contexts.equals(rewrittenContexts)) { + return this; + } + return new LegacySyntheticReference(rewrittenType, rewrittenContexts); + } +}
diff --git a/src/main/java/com/android/tools/r8/synthesis/Rewritable.java b/src/main/java/com/android/tools/r8/synthesis/Rewritable.java new file mode 100644 index 0000000..408b3a3 --- /dev/null +++ b/src/main/java/com/android/tools/r8/synthesis/Rewritable.java
@@ -0,0 +1,14 @@ +// Copyright (c) 2021, 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.synthesis; + +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens; + +public interface Rewritable<R extends Rewritable<R>> { + + DexType getHolder(); + + R rewrite(NonIdentityGraphLens lens); +}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java index bff9202..04ec40a 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java +++ b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
@@ -44,6 +44,10 @@ context.getContextType(), context.getContextType(), context.getOrigin()); } + static SynthesizingContext fromType(DexType type) { + return new SynthesizingContext(type, type, Origin.unknown()); + } + static SynthesizingContext fromNonSyntheticInputContext(ProgramDefinition context) { // A context that is itself non-synthetic is the single context, thus both the input context // and synthesizing context coincide. @@ -122,21 +126,13 @@ appView.rewritePrefix.rewriteType(hygienicType, rewrittenType); } - // TODO(b/180074885): Remove this once main-dex is traced at the end of of compilation. - void addIfDerivedFromMainDexClass( - DexProgramClass externalSyntheticClass, MainDexInfo mainDexInfo) { - if (mainDexInfo.isMainDex(externalSyntheticClass)) { - return; - } - // The input context type (not the annotated context) determines if the derived class is to be - // in main dex, as it is the input context type that is traced as part of main-dex tracing. - if (mainDexInfo.isMainDexTypeThatShouldIncludeDependencies(inputContextType)) { - mainDexInfo.addSyntheticClass(externalSyntheticClass); - } - } - @Override public String toString() { return "SynthesizingContext{" + getSynthesizingContextType() + "}"; } + + // TODO(b/181858113): Remove once deprecated main-dex-list is removed. + boolean isDerivedFromMainDexList(MainDexInfo mainDexInfo) { + return mainDexInfo.isSyntheticContextOnMainDexList(inputContextType); + } }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java index 01e6c75..126355f 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java +++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
@@ -34,6 +34,7 @@ private final DexType type; private final Origin origin; + private boolean isAbstract = false; private Kind originKind; private DexType superType; private DexTypeList interfaces = DexTypeList.empty(); @@ -70,6 +71,11 @@ return self(); } + public B setAbstract() { + isAbstract = true; + return self(); + } + public B setOriginKind(Kind originKind) { this.originKind = originKind; return self(); @@ -107,9 +113,10 @@ } public C build() { + int flag = isAbstract ? Constants.ACC_ABSTRACT : Constants.ACC_FINAL; ClassAccessFlags accessFlags = ClassAccessFlags.fromSharedAccessFlags( - Constants.ACC_FINAL | Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC); + flag | Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC); DexString sourceFile = null; NestHostClassAttribute nestHost = null; List<NestMemberClassAttribute> nestMembers = Collections.emptyList();
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassReference.java index 69f87fe..92ac183 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassReference.java +++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassReference.java
@@ -27,7 +27,7 @@ } @Override - DexType getHolder() { + public DexType getHolder() { return type; }
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 2e70db6..d610175 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java +++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -3,6 +3,8 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.synthesis; +import static com.google.common.base.Predicates.alwaysTrue; + import com.android.tools.r8.features.ClassToFeatureSplitMap; import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppInfoWithClassHierarchy; @@ -29,7 +31,8 @@ import com.android.tools.r8.utils.collections.BidirectionalManyToManyRepresentativeMap; import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap; import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap; -import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap; +import com.android.tools.r8.utils.collections.BidirectionalOneToManyRepresentativeHashMap; +import com.android.tools.r8.utils.collections.MutableBidirectionalOneToManyRepresentativeMap; import com.android.tools.r8.utils.structural.RepresentativeMap; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; @@ -43,10 +46,8 @@ import java.util.IdentityHashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import java.util.function.BiConsumer; -import java.util.function.Function; public class SyntheticFinalization { @@ -54,129 +55,76 @@ public final CommittedItems commit; public final NonIdentityGraphLens lens; public final PrunedItems prunedItems; + public final MainDexInfo mainDexInfo; public Result( - CommittedItems commit, SyntheticFinalizationGraphLens lens, PrunedItems prunedItems) { + CommittedItems commit, + SyntheticFinalizationGraphLens lens, + PrunedItems prunedItems, + MainDexInfo mainDexInfo) { this.commit = commit; this.lens = lens; this.prunedItems = prunedItems; + this.mainDexInfo = mainDexInfo; } } public static class SyntheticFinalizationGraphLens extends NestedGraphLens { - private final Map<DexType, DexType> syntheticTypeMap; - private final Map<DexMethod, DexMethod> syntheticMethodsMap; - private SyntheticFinalizationGraphLens( GraphLens previous, - Map<DexType, DexType> syntheticClassesMap, - Map<DexMethod, DexMethod> syntheticMethodsMap, Map<DexType, DexType> typeMap, BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap, Map<DexMethod, DexMethod> methodMap, BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod> originalMethodSignatures, DexItemFactory factory) { super(typeMap, methodMap, fieldMap, originalMethodSignatures, previous, factory); - this.syntheticTypeMap = syntheticClassesMap; - this.syntheticMethodsMap = syntheticMethodsMap; } @Override public boolean isSyntheticFinalizationGraphLens() { return true; } - - // The mapping is many to one, so the inverse is only defined up to equivalence groups. - // Override the access to renamed signatures to first check for synthetic mappings before - // using the original item mappings of the - - @Override - public DexField getRenamedFieldSignature(DexField originalField) { - if (syntheticTypeMap.containsKey(originalField.holder)) { - DexField renamed = fieldMap.get(originalField); - if (renamed != null) { - return renamed; - } - } - return super.getRenamedFieldSignature(originalField); - } - - @Override - public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) { - if (syntheticTypeMap.containsKey(originalMethod.holder)) { - DexMethod renamed = methodMap.get(originalMethod); - if (renamed != null) { - return renamed; - } - } - DexMethod renamed = syntheticMethodsMap.get(originalMethod); - return renamed != null ? renamed : super.getRenamedMethodSignature(originalMethod, applied); - } } private static class Builder { - // Forward mapping of internal to external synthetics. - Map<DexType, DexType> syntheticClassesMap = new IdentityHashMap<>(); - Map<DexMethod, DexMethod> syntheticMethodsMap = new IdentityHashMap<>(); - Map<DexType, DexType> typeMap = new IdentityHashMap<>(); BidirectionalManyToOneRepresentativeHashMap<DexField, DexField> fieldMap = new BidirectionalManyToOneRepresentativeHashMap<>(); Map<DexMethod, DexMethod> methodMap = new IdentityHashMap<>(); - protected final BidirectionalOneToOneHashMap<DexMethod, DexMethod> originalMethodSignatures = - new BidirectionalOneToOneHashMap<>(); - - void moveSyntheticClass(DexType from, DexType to) { - assert !syntheticClassesMap.containsKey(from); - syntheticClassesMap.put(from, to); - typeMap.put(from, to); - } - - void moveSyntheticMethod(DexMethod from, DexMethod to) { - assert !syntheticMethodsMap.containsKey(from); - syntheticMethodsMap.put(from, to); - methodMap.put(from, to); - typeMap.put(from.getHolderType(), to.getHolderType()); - } + protected final MutableBidirectionalOneToManyRepresentativeMap<DexMethod, DexMethod> + originalMethodSignatures = new BidirectionalOneToManyRepresentativeHashMap<>(); void move(DexType from, DexType to) { - typeMap.put(from, to); + DexType old = typeMap.put(from, to); + assert old == null || old == to; } void move(DexField from, DexField to) { - fieldMap.put(from, to); + DexField old = fieldMap.put(from, to); + assert old == null || old == to; } void move(DexMethod from, DexMethod to) { - methodMap.put(from, to); + DexMethod old = methodMap.put(from, to); + assert old == null || old == to; originalMethodSignatures.put(to, from); } SyntheticFinalizationGraphLens build(GraphLens previous, DexItemFactory factory) { - assert verifySubMap(syntheticClassesMap, typeMap); if (typeMap.isEmpty() && fieldMap.isEmpty() && methodMap.isEmpty()) { return null; } return new SyntheticFinalizationGraphLens( previous, - syntheticClassesMap, - syntheticMethodsMap, typeMap, fieldMap, methodMap, originalMethodSignatures, factory); } - - private static <K, V> boolean verifySubMap(Map<K, V> sub, Map<K, V> sup) { - for (Entry<K, V> entry : sub.entrySet()) { - assert sup.get(entry.getKey()) == entry.getValue(); - } - return true; - } } public static class EquivalenceGroup<T extends SyntheticDefinition<?, T, ?>> { @@ -231,26 +179,38 @@ assert !appView.appInfo().hasClassHierarchy(); assert !appView.appInfo().hasLiveness(); Result result = appView.getSyntheticItems().computeFinalSynthetics(appView); - appView.setAppInfo(new AppInfo(result.commit, appView.appInfo().getMainDexInfo())); - appView.pruneItems(result.prunedItems); + appView.setAppInfo(new AppInfo(result.commit, result.mainDexInfo)); if (result.lens != null) { + appView.setAppInfo( + appView + .appInfo() + .rebuildWithMainDexInfo( + appView.appInfo().getMainDexInfo().rewrittenWithLens(result.lens))); appView.setGraphLens(result.lens); } + appView.pruneItems(result.prunedItems); } public static void finalizeWithClassHierarchy(AppView<AppInfoWithClassHierarchy> appView) { assert !appView.appInfo().hasLiveness(); Result result = appView.getSyntheticItems().computeFinalSynthetics(appView); appView.setAppInfo(appView.appInfo().rebuildWithClassHierarchy(result.commit)); - appView.pruneItems(result.prunedItems); + appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(result.mainDexInfo)); if (result.lens != null) { appView.setGraphLens(result.lens); + appView.setAppInfo( + appView + .appInfo() + .rebuildWithMainDexInfo( + appView.appInfo().getMainDexInfo().rewrittenWithLens(result.lens))); } + appView.pruneItems(result.prunedItems); } public static void finalizeWithLiveness(AppView<AppInfoWithLiveness> appView) { Result result = appView.getSyntheticItems().computeFinalSynthetics(appView); appView.setAppInfo(appView.appInfo().rebuildWithLiveness(result.commit)); + appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(result.mainDexInfo)); appView.rewriteWithLens(result.lens); appView.pruneItems(result.prunedItems); } @@ -263,23 +223,20 @@ ImmutableMap.builder(); ImmutableMap.Builder<DexType, SyntheticProgramClassReference> finalClassesBuilder = ImmutableMap.builder(); - List<DexProgramClass> finalSyntheticProgramDefinitions = new ArrayList<>(); + Set<DexType> derivedMainDexTypes = Sets.newIdentityHashSet(); { Map<String, NumberGenerator> generators = new HashMap<>(); application = buildLensAndProgram( appView, - computeEquivalences(appView, committed.getNonLegacyMethods().values(), generators), - computeEquivalences(appView, committed.getNonLegacyClasses().values(), generators), + computeEquivalences( + appView, committed.getNonLegacyMethods().values(), generators, lensBuilder), + computeEquivalences( + appView, committed.getNonLegacyClasses().values(), generators, lensBuilder), lensBuilder, - (clazz, reference) -> { - finalSyntheticProgramDefinitions.add(clazz); - finalClassesBuilder.put(clazz.getType(), reference); - }, - (clazz, reference) -> { - finalSyntheticProgramDefinitions.add(clazz); - finalMethodsBuilder.put(clazz.getType(), reference); - }); + (clazz, reference) -> finalClassesBuilder.put(clazz.getType(), reference), + (clazz, reference) -> finalMethodsBuilder.put(clazz.getType(), reference), + derivedMainDexTypes); } ImmutableMap<DexType, SyntheticMethodReference> finalMethods = finalMethodsBuilder.build(); ImmutableMap<DexType, SyntheticProgramClassReference> finalClasses = @@ -294,6 +251,10 @@ } }); + // TODO(b/181858113): Remove once deprecated main-dex-list is removed. + MainDexInfo.Builder mainDexInfoBuilder = appView.appInfo().getMainDexInfo().builderFromCopy(); + derivedMainDexTypes.forEach(mainDexInfoBuilder::addList); + return new Result( new CommittedItems( SyntheticItems.INVALID_ID_AFTER_SYNTHETIC_FINALIZATION, @@ -302,17 +263,16 @@ committed.getLegacyTypes(), finalMethods, finalClasses), ImmutableList.of()), lensBuilder.build(appView.graphLens(), appView.dexItemFactory()), - PrunedItems.builder() - .setPrunedApp(application) - .addRemovedClasses(prunedSynthetics) - .build()); + PrunedItems.builder().setPrunedApp(application).addRemovedClasses(prunedSynthetics).build(), + mainDexInfoBuilder.build()); } private <R extends SyntheticReference<R, D, ?>, D extends SyntheticDefinition<R, D, ?>> Map<DexType, EquivalenceGroup<D>> computeEquivalences( AppView<?> appView, ImmutableCollection<R> references, - Map<String, NumberGenerator> generators) { + Map<String, NumberGenerator> generators, + Builder lensBuilder) { boolean intermediate = appView.options().intermediate; Map<DexType, D> definitions = lookupDefinitions(appView, references); ClassToFeatureSplitMap classToFeatureSplitMap = @@ -328,7 +288,12 @@ classToFeatureSplitMap, synthetics); return computeActualEquivalences( - potentialEquivalences, generators, appView, intermediate, classToFeatureSplitMap); + potentialEquivalences, + generators, + appView, + intermediate, + classToFeatureSplitMap, + lensBuilder); } private boolean isNotSyntheticType(DexType type) { @@ -339,7 +304,8 @@ // Check that a context is never itself synthetic class. committed.forEachNonLegacyItem( item -> { - assert isNotSyntheticType(item.getContext().getSynthesizingContextType()); + assert isNotSyntheticType(item.getContext().getSynthesizingContextType()) + || item.getKind().allowSyntheticContext(); }); return true; } @@ -350,75 +316,18 @@ Map<DexType, EquivalenceGroup<SyntheticProgramClassDefinition>> syntheticClassGroups, Builder lensBuilder, BiConsumer<DexProgramClass, SyntheticProgramClassReference> addFinalSyntheticClass, - BiConsumer<DexProgramClass, SyntheticMethodReference> addFinalSyntheticMethod) { + BiConsumer<DexProgramClass, SyntheticMethodReference> addFinalSyntheticMethod, + Set<DexType> derivedMainDexSynthetics) { DexApplication application = appView.appInfo().app(); - DexItemFactory factory = appView.dexItemFactory(); + MainDexInfo mainDexInfo = appView.appInfo().getMainDexInfo(); List<DexProgramClass> newProgramClasses = new ArrayList<>(); Set<DexType> pruned = Sets.newIdentityHashSet(); - syntheticMethodGroups.forEach( - (syntheticType, syntheticGroup) -> { - SyntheticMethodDefinition representative = syntheticGroup.getRepresentative(); - SynthesizingContext context = representative.getContext(); - context.registerPrefixRewriting(syntheticType, appView); - DexProgramClass externalSyntheticClass = - createExternalMethodClass(syntheticType, representative, factory); - newProgramClasses.add(externalSyntheticClass); - addSyntheticMarker(representative.getKind(), externalSyntheticClass, context, appView); - assert externalSyntheticClass.getMethodCollection().size() == 1; - DexEncodedMethod externalSyntheticMethod = - externalSyntheticClass.methods().iterator().next(); - for (SyntheticMethodDefinition member : syntheticGroup.getMembers()) { - DexMethod memberReference = member.getMethod().getReference(); - pruned.add(member.getHolder().getType()); - if (memberReference != externalSyntheticMethod.method) { - lensBuilder.moveSyntheticMethod(memberReference, externalSyntheticMethod.method); - } - } - }); - - List<DexProgramClass> deduplicatedClasses = new ArrayList<>(); - syntheticClassGroups.forEach( - (syntheticType, syntheticGroup) -> { - SyntheticProgramClassDefinition representative = syntheticGroup.getRepresentative(); - SynthesizingContext context = representative.getContext(); - context.registerPrefixRewriting(syntheticType, appView); - DexProgramClass externalSyntheticClass = representative.getHolder(); - newProgramClasses.add(externalSyntheticClass); - addSyntheticMarker(representative.getKind(), externalSyntheticClass, context, appView); - for (SyntheticProgramClassDefinition member : syntheticGroup.getMembers()) { - DexProgramClass memberClass = member.getHolder(); - DexType memberType = memberClass.getType(); - pruned.add(memberType); - if (memberType != syntheticType) { - lensBuilder.moveSyntheticClass(memberType, syntheticType); - } - // The aliasing of the non-representative members needs to be recorded manually. - if (member != representative) { - deduplicatedClasses.add(memberClass); - } - } - }); - - for (DexProgramClass clazz : application.classes()) { - if (!pruned.contains(clazz.type)) { - newProgramClasses.add(clazz); - } - } - application = application.builder().replaceProgramClasses(newProgramClasses).build(); - - // We can only assert that the method container classes are in here as the classes need - // to be rewritten by the tree-fixer. - for (DexType key : syntheticMethodGroups.keySet()) { - assert application.definitionFor(key) != null; - } - - DexApplication.Builder<?> builder = application.builder(); TreeFixerBase treeFixer = new TreeFixerBase(appView) { @Override public DexType mapClassType(DexType type) { - return lensBuilder.syntheticClassesMap.getOrDefault(type, type); + return lensBuilder.typeMap.getOrDefault(type, type); } @Override @@ -436,6 +345,59 @@ lensBuilder.move(from, to); } }; + + List<DexProgramClass> deduplicatedClasses = new ArrayList<>(); + syntheticMethodGroups.forEach( + (syntheticType, syntheticGroup) -> { + SyntheticMethodDefinition representative = syntheticGroup.getRepresentative(); + SynthesizingContext context = representative.getContext(); + context.registerPrefixRewriting(syntheticType, appView); + DexProgramClass representativeClass = representative.getHolder(); + addSyntheticMarker(representative.getKind(), representativeClass, context, appView); + assert representativeClass.getMethodCollection().size() == 1; + for (SyntheticMethodDefinition member : syntheticGroup.getMembers()) { + if (member != representative) { + pruned.add(member.getHolder().getType()); + deduplicatedClasses.add(member.getHolder()); + } + if (member.getContext().isDerivedFromMainDexList(mainDexInfo)) { + derivedMainDexSynthetics.add(syntheticType); + } + } + }); + + syntheticClassGroups.forEach( + (syntheticType, syntheticGroup) -> { + SyntheticProgramClassDefinition representative = syntheticGroup.getRepresentative(); + SynthesizingContext context = representative.getContext(); + context.registerPrefixRewriting(syntheticType, appView); + addSyntheticMarker( + representative.getKind(), representative.getHolder(), context, appView); + for (SyntheticProgramClassDefinition member : syntheticGroup.getMembers()) { + DexProgramClass memberClass = member.getHolder(); + DexType memberType = memberClass.getType(); + if (member != representative) { + pruned.add(memberType); + deduplicatedClasses.add(memberClass); + } + if (member.getContext().isDerivedFromMainDexList(mainDexInfo)) { + derivedMainDexSynthetics.add(syntheticType); + } + } + }); + + for (DexProgramClass clazz : application.classes()) { + if (!pruned.contains(clazz.type)) { + newProgramClasses.add(clazz); + } + } + application = application.builder().replaceProgramClasses(newProgramClasses).build(); + + // Assert that the non-representatives have been removed from the app. + assert verifyNonRepresentativesRemovedFromApplication(application, syntheticClassGroups); + assert verifyNonRepresentativesRemovedFromApplication(application, syntheticMethodGroups); + + DexApplication.Builder<?> builder = application.builder(); treeFixer.fixupClasses(deduplicatedClasses); builder.replaceProgramClasses(treeFixer.fixupClasses(application.classes())); application = builder.build(); @@ -452,34 +414,21 @@ representative.getKind(), representative.getContext(), externalSyntheticClass.type)); - for (SyntheticProgramClassDefinition member : syntheticGroup.getMembers()) { - addMainDexAndSynthesizedFromForMember( - member, - externalSyntheticClass, - appView.appInfo().getMainDexInfo(), - appForLookup::programDefinitionFor); - } }); syntheticMethodGroups.forEach( (syntheticType, syntheticGroup) -> { DexProgramClass externalSyntheticClass = appForLookup.programDefinitionFor(syntheticType); SyntheticMethodDefinition representative = syntheticGroup.getRepresentative(); + assert externalSyntheticClass.getMethodCollection().size() == 1; + assert externalSyntheticClass.getMethodCollection().hasDirectMethods(); + DexEncodedMethod syntheticMethodDefinition = + externalSyntheticClass.getMethodCollection().getDirectMethod(alwaysTrue()); addFinalSyntheticMethod.accept( externalSyntheticClass, new SyntheticMethodReference( representative.getKind(), representative.getContext(), - representative - .getMethod() - .getReference() - .withHolder(externalSyntheticClass.type, factory))); - for (SyntheticMethodDefinition member : syntheticGroup.getMembers()) { - addMainDexAndSynthesizedFromForMember( - member, - externalSyntheticClass, - appView.appInfo().getMainDexInfo(), - appForLookup::programDefinitionFor); - } + syntheticMethodDefinition.getReference())); }); for (DexType key : syntheticMethodGroups.keySet()) { @@ -493,6 +442,18 @@ return application; } + private static <T extends SyntheticDefinition<?, T, ?>> + boolean verifyNonRepresentativesRemovedFromApplication( + DexApplication application, Map<DexType, EquivalenceGroup<T>> syntheticGroups) { + for (EquivalenceGroup<?> syntheticGroup : syntheticGroups.values()) { + for (SyntheticDefinition<?, ?, ?> member : syntheticGroup.getMembers()) { + assert member == syntheticGroup.getRepresentative() + || application.definitionFor(member.getHolder().getType()) == null; + } + } + return true; + } + private static void addSyntheticMarker( SyntheticKind kind, DexProgramClass externalSyntheticClass, @@ -504,38 +465,6 @@ } } - private static DexProgramClass createExternalMethodClass( - DexType syntheticType, SyntheticMethodDefinition representative, DexItemFactory factory) { - SyntheticProgramClassBuilder builder = - new SyntheticProgramClassBuilder(syntheticType, representative.getContext(), factory); - // TODO(b/158159959): Support grouping multiple methods per synthetic class. - builder.addMethod( - methodBuilder -> { - DexEncodedMethod definition = representative.getMethod().getDefinition(); - methodBuilder - .setName(SyntheticNaming.INTERNAL_SYNTHETIC_METHOD_PREFIX) - .setAccessFlags(definition.accessFlags) - .setProto(definition.getProto()) - .setClassFileVersion( - definition.hasClassFileVersion() ? definition.getClassFileVersion() : null) - .setCode(m -> definition.getCode()); - }); - return builder.build(); - } - - private static void addMainDexAndSynthesizedFromForMember( - SyntheticDefinition<?, ?, ?> member, - DexProgramClass externalSyntheticClass, - MainDexInfo mainDexInfo, - Function<DexType, DexProgramClass> definitions) { - member.getContext().addIfDerivedFromMainDexClass(externalSyntheticClass, mainDexInfo); - // TODO(b/168584485): Remove this once class-mapping support is removed. - DexProgramClass from = definitions.apply(member.getContext().getSynthesizingContextType()); - if (from != null) { - externalSyntheticClass.addSynthesizedFrom(from); - } - } - private static boolean shouldAnnotateSynthetics(InternalOptions options) { // Only intermediate builds have annotated synthetics to allow later sharing. // This is currently also disabled on CF to CF desugaring to avoid missing class references to @@ -550,7 +479,8 @@ Map<String, NumberGenerator> generators, AppView<?> appView, boolean intermediate, - ClassToFeatureSplitMap classToFeatureSplitMap) { + ClassToFeatureSplitMap classToFeatureSplitMap, + Builder lensBuilder) { Map<String, List<EquivalenceGroup<T>>> groupsPerPrefix = new HashMap<>(); potentialEquivalences.forEach( members -> { @@ -599,6 +529,9 @@ DexType representativeType = createExternalType(kind, externalSyntheticTypePrefix, generators, appView); equivalences.put(representativeType, group); + for (T member : group.getMembers()) { + lensBuilder.move(member.getHolder().getType(), representativeType); + } } }); return equivalences;
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java index 9922011..65c53d5 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java +++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -19,6 +19,7 @@ import com.android.tools.r8.graph.PrunedItems; import com.android.tools.r8.synthesis.SyntheticFinalization.Result; import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind; +import com.android.tools.r8.utils.ListUtils; import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.Collection; @@ -47,7 +48,7 @@ * * <p>TODO(b/158159959): Remove legacy support. */ - private final Map<DexType, DexProgramClass> legacyClasses = new ConcurrentHashMap<>(); + private final Map<DexType, LegacySyntheticDefinition> legacyClasses = new ConcurrentHashMap<>(); /** Thread safe collection of synthetic items not yet committed to the application. */ private final ConcurrentHashMap<DexType, SyntheticDefinition<?, ?, ?>> nonLegacyDefinitions = @@ -75,7 +76,9 @@ allPending.add(item.asProgramDefinition().getHolder()); } } - allPending.addAll(legacyClasses.values()); + for (LegacySyntheticDefinition legacy : legacyClasses.values()) { + allPending.add(legacy.getDefinition()); + } return Collections.unmodifiableList(allPending); } } @@ -146,17 +149,24 @@ @Override public DexClass definitionFor(DexType type, Function<DexType, DexClass> baseDefinitionFor) { - DexClass clazz = pending.legacyClasses.get(type); - if (clazz == null) { + DexClass clazz = null; + SyntheticKind kind = null; + LegacySyntheticDefinition legacyItem = pending.legacyClasses.get(type); + if (legacyItem != null) { + clazz = legacyItem.getDefinition(); + } else { SyntheticDefinition<?, ?, ?> item = pending.nonLegacyDefinitions.get(type); if (item != null) { clazz = item.getHolder(); + kind = item.getKind(); assert clazz.isProgramClass() == item.isProgramDefinition(); assert clazz.isClasspathClass() == item.isClasspathDefinition(); } } if (clazz != null) { + assert legacyItem != null || kind != null; assert baseDefinitionFor.apply(type) == null + || (kind != null && kind.mayOverridesNonProgramType) : "Pending synthetic definition also present in the active program: " + type; return clazz; } @@ -212,6 +222,22 @@ return isCommittedSynthetic(type) || isPendingSynthetic(type); } + public SyntheticKind getNonLegacySyntheticKind(DexProgramClass clazz) { + assert isNonLegacySynthetic(clazz); + SyntheticReference<?, ?, ?> reference = committed.getNonLegacyItem(clazz.getType()); + if (reference == null) { + SyntheticDefinition<?, ?, ?> definition = pending.nonLegacyDefinitions.get(clazz.getType()); + if (definition != null) { + reference = definition.toReference(); + } + } + if (reference != null) { + return reference.getKind(); + } + assert false; + return null; + } + public boolean isSyntheticClass(DexType type) { return isLegacySyntheticClass(type) || isNonLegacySynthetic(type); } @@ -229,6 +255,14 @@ if (definition != null) { return Collections.singletonList(definition.getContext().getSynthesizingContextType()); } + LegacySyntheticReference legacyReference = committed.getLegacyTypes().get(type); + if (legacyReference != null) { + return legacyReference.getContexts(); + } + LegacySyntheticDefinition legacyDefinition = pending.legacyClasses.get(type); + if (legacyDefinition != null) { + return legacyDefinition.getContexts(); + } return Collections.emptyList(); } @@ -266,7 +300,7 @@ } public Collection<DexProgramClass> getLegacyPendingClasses() { - return Collections.unmodifiableCollection(pending.legacyClasses.values()); + return ListUtils.map(pending.legacyClasses.values(), LegacySyntheticDefinition::getDefinition); } private SynthesizingContext getSynthesizingContext(ProgramDefinition context) { @@ -285,12 +319,26 @@ // Addition and creation of synthetic items. + public void addLegacySyntheticClassForLibraryDesugaring(DexProgramClass clazz) { + internalAddLegacySyntheticClass(clazz); + // No context information is added for library context. + // This is intended only to support desugared-library compilation. + } + // TODO(b/158159959): Remove the usage of this direct class addition. - public void addLegacySyntheticClass(DexProgramClass clazz) { + public void addLegacySyntheticClass(DexProgramClass clazz, ProgramDefinition context) { + LegacySyntheticDefinition legacyItem = internalAddLegacySyntheticClass(clazz); + legacyItem.addContext(context); + } + + private LegacySyntheticDefinition internalAddLegacySyntheticClass(DexProgramClass clazz) { assert !isCommittedSynthetic(clazz.type); assert !pending.nonLegacyDefinitions.containsKey(clazz.type); - DexProgramClass previous = pending.legacyClasses.put(clazz.type, clazz); - assert previous == null || previous == clazz; + LegacySyntheticDefinition legacyItem = + pending.legacyClasses.computeIfAbsent( + clazz.getType(), type -> new LegacySyntheticDefinition(clazz)); + assert legacyItem.getDefinition() == clazz; + return legacyItem; } public DexProgramClass createClass( @@ -342,6 +390,23 @@ return clazz; } + public DexProgramClass createFixedClassFromType( + SyntheticKind kind, + DexType contextType, + DexItemFactory factory, + Consumer<SyntheticProgramClassBuilder> fn) { + // Obtain the outer synthesizing context in the case the context itself is synthetic. + // This is to ensure a flat input-type -> synthetic-item mapping. + SynthesizingContext outerContext = SynthesizingContext.fromType(contextType); + DexType type = SyntheticNaming.createFixedType(kind, outerContext, factory); + SyntheticProgramClassBuilder classBuilder = + new SyntheticProgramClassBuilder(type, outerContext, factory); + fn.accept(classBuilder); + DexProgramClass clazz = classBuilder.build(); + addPendingDefinition(new SyntheticProgramClassDefinition(kind, outerContext, clazz)); + return clazz; + } + public DexClasspathClass createFixedClasspathClass( SyntheticKind kind, DexClasspathClass context, DexItemFactory factory) { // Obtain the outer synthesizing context in the case the context itself is synthetic. @@ -418,7 +483,7 @@ CommittedSyntheticsCollection.Builder builder = committed.builder(); // Legacy synthetics must already have been committed to the app. assert verifyClassesAreInApp(application, pending.legacyClasses.values()); - builder.addLegacyClasses(pending.legacyClasses.values()); + builder.addLegacyClasses(pending.legacyClasses); // Compute the synthetic additions and add them to the application. ImmutableList<DexType> committedProgramTypes; DexApplication amendedApplication; @@ -432,7 +497,12 @@ if (!removedClasses.contains(definition.getHolder().getType())) { if (definition.isProgramDefinition()) { committedProgramTypesBuilder.add(definition.getHolder().getType()); - appBuilder.addProgramClass(definition.asProgramDefinition().getHolder()); + if (definition.getKind().mayOverridesNonProgramType) { + appBuilder.addProgramClassPotentiallyOverridingNonProgramClass( + definition.asProgramDefinition().getHolder()); + } else { + appBuilder.addProgramClass(definition.asProgramDefinition().getHolder()); + } } else if (appBuilder.isDirect()) { assert definition.isClasspathDefinition(); appBuilder.asDirect().addClasspathClass(definition.asClasspathDefinition().getHolder()); @@ -451,8 +521,9 @@ } private static boolean verifyClassesAreInApp( - DexApplication app, Collection<DexProgramClass> classes) { - for (DexProgramClass clazz : classes) { + DexApplication app, Collection<LegacySyntheticDefinition> classes) { + for (LegacySyntheticDefinition item : classes) { + DexProgramClass clazz = item.getDefinition(); assert app.programDefinitionFor(clazz.type) != null : "Missing synthetic: " + clazz.type; } return true;
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java index dfc240b..9c19e81 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java +++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
@@ -20,7 +20,7 @@ */ class SyntheticMethodReference extends SyntheticReference<SyntheticMethodReference, SyntheticMethodDefinition, DexProgramClass> - implements SyntheticProgramReference { + implements SyntheticProgramReference, Rewritable<SyntheticMethodReference> { final DexMethod method; SyntheticMethodReference(SyntheticKind kind, SynthesizingContext context, DexMethod method) { @@ -29,7 +29,7 @@ } @Override - DexType getHolder() { + public DexType getHolder() { return method.holder; }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java index 1b80f41..7148738 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java +++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -23,9 +23,13 @@ */ public enum SyntheticKind { // Class synthetics. + RECORD_TAG("", false, true, true), COMPANION_CLASS("CompanionClass", false), LAMBDA("Lambda", false), INIT_TYPE_ARGUMENT("-IA", false, true), + HORIZONTAL_INIT_TYPE_ARGUMENT_1(SYNTHETIC_CLASS_SEPARATOR + "IA$1", false, true), + HORIZONTAL_INIT_TYPE_ARGUMENT_2(SYNTHETIC_CLASS_SEPARATOR + "IA$2", false, true), + HORIZONTAL_INIT_TYPE_ARGUMENT_3(SYNTHETIC_CLASS_SEPARATOR + "IA$3", false, true), // Method synthetics. BACKPORT("Backport", true), STATIC_INTERFACE_CALL("StaticInterfaceCall", true), @@ -41,6 +45,7 @@ public final String descriptor; public final boolean isSingleSyntheticMethod; public final boolean isFixedSuffixSynthetic; + public final boolean mayOverridesNonProgramType; SyntheticKind(String descriptor, boolean isSingleSyntheticMethod) { this(descriptor, isSingleSyntheticMethod, false); @@ -48,9 +53,22 @@ SyntheticKind( String descriptor, boolean isSingleSyntheticMethod, boolean isFixedSuffixSynthetic) { + this(descriptor, isSingleSyntheticMethod, isFixedSuffixSynthetic, false); + } + + SyntheticKind( + String descriptor, + boolean isSingleSyntheticMethod, + boolean isFixedSuffixSynthetic, + boolean mayOverridesNonProgramType) { this.descriptor = descriptor; this.isSingleSyntheticMethod = isSingleSyntheticMethod; this.isFixedSuffixSynthetic = isFixedSuffixSynthetic; + this.mayOverridesNonProgramType = mayOverridesNonProgramType; + } + + public boolean allowSyntheticContext() { + return this == RECORD_TAG; } public static SyntheticKind fromDescriptor(String descriptor) {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassReference.java index 5a17b04..d5973f6 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassReference.java +++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassReference.java
@@ -19,7 +19,7 @@ class SyntheticProgramClassReference extends SyntheticClassReference< SyntheticProgramClassReference, SyntheticProgramClassDefinition, DexProgramClass> - implements SyntheticProgramReference { + implements SyntheticProgramReference, Rewritable<SyntheticProgramClassReference> { SyntheticProgramClassReference(SyntheticKind kind, SynthesizingContext context, DexType type) { super(kind, context, type); @@ -42,9 +42,6 @@ // If the reference has been non-trivially rewritten the compiler has changed it and it can no // longer be considered a synthetic. The context may or may not have changed. if (type != rewritten && !lens.isSimpleRenaming(type, rewritten)) { - // If the referenced item is rewritten, it should be moved to another holder as the - // synthetic holder is no longer part of the synthetic collection. - assert SyntheticNaming.verifyNotInternalSynthetic(rewritten); return null; } if (rewrittenContext == getContext() && rewritten == type) {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java index ddb28f7..9824ebc 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java +++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java
@@ -44,7 +44,7 @@ abstract DexReference getReference(); - final R rewrite(NonIdentityGraphLens lens) { + public final R rewrite(NonIdentityGraphLens lens) { SynthesizingContext rewrittenContext = getContext().rewrite(lens); return internalRewrite(rewrittenContext, lens); }
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java index 1534b4a..d1f6e4c 100644 --- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java +++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -89,6 +89,7 @@ private static final String dumpBuildPropertiesFileName = "build.properties"; private static final String dumpDesugaredLibraryFileName = "desugared-library.json"; private static final String dumpMainDexListResourceFileName = "main-dex-list.txt"; + private static final String dumpMainDexRulesResourceFileName = "main-dex-rules.txt"; private static final String dumpProgramFileName = "program.jar"; private static final String dumpClasspathFileName = "classpath.jar"; private static final String dumpLibraryFileName = "library.jar"; @@ -512,6 +513,13 @@ String join = StringUtils.join(mainDexList, "\n"); writeToZipStream(out, dumpMainDexListResourceFileName, join.getBytes(), ZipEntry.DEFLATED); } + if (options.hasMainDexKeepRules()) { + writeToZipStream( + out, + dumpMainDexRulesResourceFileName, + StringUtils.joinLines(options.getMainDexKeepRules()).getBytes(), + ZipEntry.DEFLATED); + } nextDexIndex = dumpProgramResources( dumpProgramFileName,
diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java index b7839ea..5a4ee11 100644 --- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java +++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -13,6 +13,15 @@ public class ArrayUtils { + public static boolean containsInt(int[] array, int value) { + for (int element : array) { + if (element == value) { + return true; + } + } + return false; + } + /** * Copies the input array and then applies specified sparse changes. * @@ -71,6 +80,13 @@ clazz.cast(Array.newInstance(clazz.getComponentType(), filtered.size()))); } + public static boolean isSorted(int[] array) { + for (int i = 0; i < array.length - 1; i++) { + assert array[i] < array[i + 1]; + } + return true; + } + /** * Rewrites the input array based on the given function. *
diff --git a/src/main/java/com/android/tools/r8/utils/BiPredicateUtils.java b/src/main/java/com/android/tools/r8/utils/BiPredicateUtils.java index f470d35..5245876 100644 --- a/src/main/java/com/android/tools/r8/utils/BiPredicateUtils.java +++ b/src/main/java/com/android/tools/r8/utils/BiPredicateUtils.java
@@ -15,4 +15,16 @@ public static <S, T> BiPredicate<S, T> alwaysTrue() { return (s, t) -> true; } + + @SafeVarargs + public static <S, T> BiPredicate<S, T> or(BiPredicate<S, T>... predicates) { + return (s, t) -> { + for (BiPredicate<S, T> predicate : predicates) { + if (predicate.test(s, t)) { + return true; + } + } + return false; + }; + } }
diff --git a/src/main/java/com/android/tools/r8/utils/ClassMap.java b/src/main/java/com/android/tools/r8/utils/ClassMap.java index 3c7b99b..c885d9e 100644 --- a/src/main/java/com/android/tools/r8/utils/ClassMap.java +++ b/src/main/java/com/android/tools/r8/utils/ClassMap.java
@@ -9,6 +9,7 @@ import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexType; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import java.util.ArrayList; import java.util.Iterator; @@ -115,6 +116,21 @@ } /** + * Clears the type so if a class with the given type was present, it cannot be found anymore. This + * has to be run at a join point, concurrent accesses may be confused. + */ + public void clearType(DexType type) { + if (classes.containsKey(type)) { + classes.remove(type); + } + ClassProvider<T> provider = classProvider.get(); + if (provider == null) { + return; + } + classProvider.set(provider.without(ImmutableSet.of(type))); + } + + /** * Returns all classes from the collection. The collection must be force-loaded. */ public List<T> getAllClasses() {
diff --git a/src/main/java/com/android/tools/r8/utils/ClassProvider.java b/src/main/java/com/android/tools/r8/utils/ClassProvider.java index c048529..af272b8 100644 --- a/src/main/java/com/android/tools/r8/utils/ClassProvider.java +++ b/src/main/java/com/android/tools/r8/utils/ClassProvider.java
@@ -13,6 +13,7 @@ import com.android.tools.r8.graph.JarApplicationReader; import com.android.tools.r8.graph.JarClassFileReader; import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; import java.util.ArrayList; @@ -70,6 +71,10 @@ return new PreloadedClassProvider<>(classKind, builder.build()); } + public FilteringClassProvider<T> without(Set<DexType> filteredTypes) { + return new FilteringClassProvider(classKind, this, filteredTypes); + } + /** Create class provider for preloaded classes. */ public static <T extends DexClass> ClassProvider<T> combine( ClassKind<T> classKind, List<ClassProvider<T>> providers) { @@ -145,6 +150,47 @@ } } + /** Class provider which ignores a list of filtered classes */ + private static class FilteringClassProvider<T extends DexClass> extends ClassProvider<T> { + private final ClassProvider<T> provider; + private final Set<DexType> filteredOut; + + FilteringClassProvider( + ClassKind<T> classKind, ClassProvider<T> provider, Set<DexType> filteredOut) { + super(classKind); + assert !(provider instanceof FilteringClassProvider) : "Nested Filtering class providers"; + this.provider = provider; + this.filteredOut = filteredOut; + } + + @Override + public FilteringClassProvider<T> without(Set<DexType> filteredTypes) { + ImmutableSet<DexType> newSet = + ImmutableSet.<DexType>builder().addAll(filteredOut).addAll(filteredTypes).build(); + return new FilteringClassProvider(getClassKind(), provider, newSet); + } + + @Override + public void collectClass(DexType type, Consumer<T> classConsumer) { + if (filteredOut.contains(type)) { + return; + } + provider.collectClass(type, classConsumer); + } + + @Override + public Collection<DexType> collectTypes() { + Collection<DexType> dexTypes = provider.collectTypes(); + dexTypes.removeAll(filteredOut); + return dexTypes; + } + + @Override + public String toString() { + return provider + " without " + filteredOut; + } + } + private static class CombinedClassProvider<T extends DexClass> extends ClassProvider<T> { private final List<ClassProvider<T>> providers;
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java index a32e296..292f103 100644 --- a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java +++ b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
@@ -75,6 +75,7 @@ List<Path> library = new ArrayList<>(); List<Path> classpath = new ArrayList<>(); List<Path> config = new ArrayList<>(); + List<Path> mainDexRulesFiles = new ArrayList<>(); int minApi = 1; int threads = -1; for (int i = 0; i < args.length; i++) { @@ -147,6 +148,11 @@ threads = Integer.parseInt(operand); break; } + case "--main-dex-rules": + { + mainDexRulesFiles.add(Paths.get(operand)); + break; + } default: throw new IllegalArgumentException("Unimplemented option: " + option); } @@ -178,6 +184,7 @@ .addLibraryFiles(library) .addClasspathFiles(classpath) .addProguardConfigurationFiles(config) + .addMainDexRulesFiles(mainDexRulesFiles) .setOutput(outputPath, outputMode) .setMode(compilationMode); if (desugaredLibJson != null) {
diff --git a/src/main/java/com/android/tools/r8/utils/Int2ObjectMapUtils.java b/src/main/java/com/android/tools/r8/utils/Int2ObjectMapUtils.java new file mode 100644 index 0000000..7da945b --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/Int2ObjectMapUtils.java
@@ -0,0 +1,21 @@ +// 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.utils; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; + +public class Int2ObjectMapUtils { + + public static <V> void forEach(Int2ObjectMap<V> map, IntObjConsumer<V> consumer) { + for (Int2ObjectMap.Entry<V> entry : map.int2ObjectEntrySet()) { + consumer.accept(entry.getIntKey(), entry.getValue()); + } + } + + public static <V> V getOrDefault(Int2ObjectMap<V> map, int key, V defaultValue) { + V value = map.get(key); + return value != null ? value : defaultValue; + } +}
diff --git a/src/main/java/com/android/tools/r8/utils/IntObjPredicate.java b/src/main/java/com/android/tools/r8/utils/IntObjPredicate.java new file mode 100644 index 0000000..a73a7be --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/IntObjPredicate.java
@@ -0,0 +1,10 @@ +// 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.utils; + +public interface IntObjPredicate<T> { + + boolean test(int i, T obj); +}
diff --git a/src/main/java/com/android/tools/r8/utils/IntObjToObjFunction.java b/src/main/java/com/android/tools/r8/utils/IntObjToObjFunction.java new file mode 100644 index 0000000..9c07bed --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/IntObjToObjFunction.java
@@ -0,0 +1,10 @@ +// 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.utils; + +public interface IntObjToObjFunction<S, T> { + + S apply(int i, T obj); +}
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 ea310aa..54bbbcc 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -490,9 +490,16 @@ return !canUseNestBasedAccess(); } - public boolean canUseRecords() { - // TODO(b/169645628): Replace by true when records are supported. - return testing.canUseRecords; + public boolean enableExperimentalRecordDesugaring() { + // TODO(b/169645628): Remove when records are supported. + return testing.enableExperimentalRecordDesugaring; + } + + public boolean shouldDesugarRecords() { + if (!enableExperimentalRecordDesugaring()) { + return false; + } + return desugarState.isOn() && !canUseRecords(); } public Set<String> extensiveLoggingFilter = getExtensiveLoggingFilter(); @@ -1032,7 +1039,7 @@ } // Currently the filter is simple string equality on the qualified name. String qualifiedName = method.qualifiedName(); - return methodsFilter.indexOf(qualifiedName) >= 0; + return methodsFilter.contains(qualifiedName); } public boolean methodMatchesLogArgumentsFilter(DexEncodedMethod method) { @@ -1042,7 +1049,7 @@ } // Currently the filter is simple string equality on the qualified name. String qualifiedName = method.qualifiedName(); - return logArgumentsFilter.indexOf(qualifiedName) >= 0; + return logArgumentsFilter.contains(qualifiedName); } public enum PackageObfuscationMode { @@ -1151,15 +1158,10 @@ public boolean enable = true; public boolean enableConstructorMerging = true; - // TODO(b/174809311): Update or remove the option and its tests after new lambdas synthetics. - public boolean enableJavaLambdaMerging = false; + public boolean enableJavaLambdaMerging = true; - public int syntheticArgumentCount = 3; public int maxGroupSize = 30; - // TODO(b/179019716): Add support for merging in presence of annotations. - public boolean skipNoClassesOrMembersWithAnnotationsPolicyForTesting = false; - public void disable() { enable = false; } @@ -1180,10 +1182,6 @@ return maxGroupSize; } - public int getSyntheticArgumentCount() { - return syntheticArgumentCount; - } - public boolean isConstructorMergingEnabled() { return enableConstructorMerging; } @@ -1295,7 +1293,7 @@ public boolean enableSwitchToIfRewriting = true; public boolean enableEnumUnboxingDebugLogs = false; public boolean forceRedundantConstNumberRemoval = false; - public boolean canUseRecords = false; + public boolean enableExperimentalRecordDesugaring = false; public boolean invertConditionals = false; public boolean placeExceptionalBlocksLast = false; public boolean dontCreateMarkerInD8 = false; @@ -1456,6 +1454,10 @@ return !isDesugaring(); } + public boolean canUseRecords() { + return !isDesugaring(); + } + public boolean canLeaveStaticInterfaceMethodInvokes() { return !isDesugaring() || hasMinApi(AndroidApiLevel.L); }
diff --git a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java index 93b7e4a..3ecdc4f 100644 --- a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java +++ b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
@@ -9,6 +9,7 @@ import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.InstructionIterator; import com.android.tools.r8.ir.code.InstructionListIterator; +import com.google.common.collect.Iterables; import java.util.Iterator; import java.util.ListIterator; import java.util.NoSuchElementException; @@ -16,6 +17,27 @@ public class IteratorUtils { + public static <T> Iterator<T> createCircularIterator(Iterable<T> iterable) { + assert !Iterables.isEmpty(iterable); + return new Iterator<T>() { + + private Iterator<T> iterator = iterable.iterator(); + + @Override + public boolean hasNext() { + return true; + } + + @Override + public T next() { + if (!iterator.hasNext()) { + iterator = iterable.iterator(); + } + return iterator.next(); + } + }; + } + public static <T> int countRemaining(Iterator<T> iterator) { IntBox counter = new IntBox(); iterator.forEachRemaining(ignore -> counter.increment());
diff --git a/src/main/java/com/android/tools/r8/utils/JoiningStringConsumer.java b/src/main/java/com/android/tools/r8/utils/JoiningStringConsumer.java new file mode 100644 index 0000000..48a1e90 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/JoiningStringConsumer.java
@@ -0,0 +1,44 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.utils; + +import com.android.tools.r8.DiagnosticsHandler; +import com.android.tools.r8.StringConsumer; +import com.android.tools.r8.StringConsumer.ForwardingConsumer; + +/* Joining String Consumer to join strings that it accepts. */ +public class JoiningStringConsumer extends ForwardingConsumer { + + private final String separator; + private final StringConsumer consumer; + private final StringBuilder builder = new StringBuilder(); + + /** + * @param consumer Consumer to forward to the joined input to. If null, nothing will be forwarded. + */ + public JoiningStringConsumer(StringConsumer consumer, String separator) { + super(consumer); + this.consumer = consumer; + this.separator = separator; + } + + @Override + public void accept(String string, DiagnosticsHandler handler) { + if (builder.length() > 0) { + builder.append(separator); + } + builder.append(string); + } + + @Override + public void finished(DiagnosticsHandler handler) { + super.accept(builder.toString(), handler); + super.finished(handler); + } + + public StringConsumer getConsumer() { + return consumer; + } +}
diff --git a/src/main/java/com/android/tools/r8/utils/SortingStringConsumer.java b/src/main/java/com/android/tools/r8/utils/SortingStringConsumer.java new file mode 100644 index 0000000..8c65b00 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/SortingStringConsumer.java
@@ -0,0 +1,38 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.utils; + +import com.android.tools.r8.DiagnosticsHandler; +import com.android.tools.r8.StringConsumer; +import com.android.tools.r8.StringConsumer.ForwardingConsumer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** Sorting consumer that accepts all input and then sorts it when calling finished */ +public class SortingStringConsumer extends ForwardingConsumer { + + private final List<String> accepted = new ArrayList<>(); + + /** + * @param consumer Consumer to forward to the sorted consumed input to. If null, nothing will be + * forwarded. + */ + public SortingStringConsumer(StringConsumer consumer) { + super(consumer); + } + + @Override + public void accept(String string, DiagnosticsHandler handler) { + this.accepted.add(string); + } + + @Override + public void finished(DiagnosticsHandler handler) { + Collections.sort(accepted); + accepted.forEach(string -> super.accept(string, handler)); + super.finished(handler); + } +}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneHashMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneHashMap.java index 8f29df9..b75d224 100644 --- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneHashMap.java +++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneHashMap.java
@@ -130,10 +130,11 @@ } @Override - public void put(K key, V value) { - remove(key); + public V put(K key, V value) { + V old = remove(key); backing.put(key, value); inverse.computeIfAbsent(value, ignore -> new LinkedHashSet<>()).add(key); + return old; } @Override
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 830138a..e23a565 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
@@ -77,6 +77,11 @@ } @Override + public K getKey(V value) { + return backing.inverse().get(value); + } + + @Override public BiMap<K, V> getForwardMap() { return backing; } @@ -88,12 +93,12 @@ @Override public K getRepresentativeKey(V value) { - return backing.inverse().get(value); + return getKey(value); } @Override public V getRepresentativeValue(K key) { - return backing.get(key); + return get(key); } @Override
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneMap.java index e5085c2..569c52e 100644 --- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneMap.java +++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneMap.java
@@ -15,6 +15,8 @@ public interface BidirectionalOneToOneMap<K, V> extends BidirectionalManyToOneRepresentativeMap<K, V> { + K getKey(V value); + @Override BiMap<K, V> getForwardMap();
diff --git a/src/main/java/com/android/tools/r8/utils/collections/EmptyBidirectionalOneToOneMap.java b/src/main/java/com/android/tools/r8/utils/collections/EmptyBidirectionalOneToOneMap.java index f91f7df..62fba9a 100644 --- a/src/main/java/com/android/tools/r8/utils/collections/EmptyBidirectionalOneToOneMap.java +++ b/src/main/java/com/android/tools/r8/utils/collections/EmptyBidirectionalOneToOneMap.java
@@ -57,6 +57,11 @@ } @Override + public K getKey(V value) { + return null; + } + + @Override public BiMap<K, V> getForwardMap() { return HashBiMap.create(); }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/MutableBidirectionalManyToOneMap.java b/src/main/java/com/android/tools/r8/utils/collections/MutableBidirectionalManyToOneMap.java index 7953e91..05bd5ea 100644 --- a/src/main/java/com/android/tools/r8/utils/collections/MutableBidirectionalManyToOneMap.java +++ b/src/main/java/com/android/tools/r8/utils/collections/MutableBidirectionalManyToOneMap.java
@@ -11,7 +11,7 @@ void clear(); - void put(K key, V value); + V put(K key, V value); void put(Iterable<K> key, V value);
diff --git a/src/main/java/com/android/tools/r8/utils/collections/MutableBidirectionalOneToOneMap.java b/src/main/java/com/android/tools/r8/utils/collections/MutableBidirectionalOneToOneMap.java index 5e5f469..87a29fa 100644 --- a/src/main/java/com/android/tools/r8/utils/collections/MutableBidirectionalOneToOneMap.java +++ b/src/main/java/com/android/tools/r8/utils/collections/MutableBidirectionalOneToOneMap.java
@@ -10,4 +10,6 @@ V put(K key, V value); void putAll(BidirectionalManyToManyMap<K, V> map); + + V remove(Object key); }
diff --git a/src/test/examples/classmerging/ProguardFieldMapTest.java b/src/test/examples/classmerging/ProguardFieldMapTest.java index e512173..98ca7b2 100644 --- a/src/test/examples/classmerging/ProguardFieldMapTest.java +++ b/src/test/examples/classmerging/ProguardFieldMapTest.java
@@ -13,7 +13,7 @@ public static class A { - public String f = "A.f"; + public String f = System.currentTimeMillis() > 0 ? "A.f" : null; } @NeverClassInline
diff --git a/src/test/examplesAndroidO/lambdadesugaring/LambdaDesugaring.java b/src/test/examplesAndroidO/lambdadesugaring/LambdaDesugaring.java index 627d255..25c0af0 100644 --- a/src/test/examplesAndroidO/lambdadesugaring/LambdaDesugaring.java +++ b/src/test/examplesAndroidO/lambdadesugaring/LambdaDesugaring.java
@@ -471,15 +471,6 @@ static final InstanceAndClassChecks INSTANCE = new InstanceAndClassChecks(); static void test() { - assertSameInstance( - InstanceAndClassChecks::staticProvider, - InstanceAndClassChecks::staticProvider, - "Instances must be same"); - assertSameInstance( - InstanceAndClassChecks::staticProvider, - statelessLambda(), - "Instances must be same"); - assertDifferentInstance( INSTANCE::instanceProvider, INSTANCE::instanceProvider,
diff --git a/src/test/examplesJava9/backport/IntegerBackportJava9Main.java b/src/test/examplesJava9/backport/IntegerBackportJava9Main.java new file mode 100644 index 0000000..30e9086 --- /dev/null +++ b/src/test/examplesJava9/backport/IntegerBackportJava9Main.java
@@ -0,0 +1,97 @@ +// Copyright (c) 2021, 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 backport; + +import java.math.BigInteger; + +public final class IntegerBackportJava9Main { + private static final int[] interestingValues = { + Integer.MIN_VALUE, + Integer.MAX_VALUE, + Short.MIN_VALUE, + Short.MAX_VALUE, + Byte.MIN_VALUE, + Byte.MAX_VALUE, + 0, + -1, + 1, + -42, + 42 + }; + + public static void main(String[] args) { + testParseIntegerSubsequenceWithRadix(args.length == 0 || !args[0].startsWith("4.")); + } + + private static void testParseIntegerSubsequenceWithRadix(boolean supportsPlusPrefix) { + for (int value : interestingValues) { + for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) { + for (String prefix : new String[] {"", "x", "xxx"}) { + for (String postfix : new String[] {"", "x", "xxx"}) { + String valueString = prefix + Long.toString(value, radix) + postfix; + int start = prefix.length(); + int end = valueString.length() - postfix.length(); + assertEquals(valueString, value, Integer.parseInt(valueString, start, end, radix)); + if (value > 0 && supportsPlusPrefix) { + valueString = prefix + '+' + Long.toString(value, radix) + postfix; + end++; + assertEquals(valueString, value, Integer.parseInt(valueString, start, end, radix)); + } + } + } + } + } + + try { + throw new AssertionError(Long.parseUnsignedLong("0", 0, 1, Character.MIN_RADIX - 1)); + } catch (IllegalArgumentException expected) { + } + try { + throw new AssertionError(Long.parseUnsignedLong("0", 0, 1, Character.MAX_RADIX + 1)); + } catch (IllegalArgumentException expected) { + } + + try { + throw new AssertionError(Long.parseUnsignedLong("", 0, 0, 16)); + } catch (NumberFormatException expected) { + } + try { + throw new AssertionError(Long.parseUnsignedLong("-", 0, 1, 16)); + } catch (NumberFormatException expected) { + } + try { + throw new AssertionError(Long.parseUnsignedLong("+", 0, 1, 16)); + } catch (NumberFormatException expected) { + } + + BigInteger overflow = new BigInteger("18446744073709551616"); + for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) { + for (String prefix : new String[] {"", "x", "xxx"}) { + for (String postfix : new String[] {"", "x", "xxx"}) { + String overflowString = prefix + overflow.toString(radix) + postfix; + int start = prefix.length(); + int end = overflowString.length() - postfix.length(); + try { + throw new AssertionError(Long.parseLong(overflowString, start, end, radix)); + } catch (NumberFormatException expected) { + } + String underflowString = prefix + '-' + overflow.toString(radix) + postfix; + start = prefix.length(); + end = underflowString.length() - postfix.length(); + try { + throw new AssertionError(Long.parseLong(underflowString, start, end, radix)); + } catch (NumberFormatException expected) { + } + } + } + } + } + + private static void assertEquals(String m, int expected, int actual) { + if (expected != actual) { + throw new AssertionError(m + " Expected <" + expected + "> but was <" + actual + '>'); + } + } +}
diff --git a/src/test/examplesJava9/backport/LongBackportJava9Main.java b/src/test/examplesJava9/backport/LongBackportJava9Main.java new file mode 100644 index 0000000..1634a43 --- /dev/null +++ b/src/test/examplesJava9/backport/LongBackportJava9Main.java
@@ -0,0 +1,154 @@ +// Copyright (c) 2021, 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 backport; + +import java.math.BigInteger; + +public final class LongBackportJava9Main { + private static final long[] interestingValues = { + Long.MIN_VALUE, + Long.MAX_VALUE, + Integer.MIN_VALUE, + Integer.MAX_VALUE, + Short.MIN_VALUE, + Short.MAX_VALUE, + Byte.MIN_VALUE, + Byte.MAX_VALUE, + 0L, + -1L, + 1L, + -42L, + 42L + }; + + public static void main(String[] args) { + testParseLongSubsequenceWithRadix(args.length == 0 || !args[0].startsWith("4.")); + testParseUnsignedLongSubsequenceWithRadix(); + } + + private static void testParseLongSubsequenceWithRadix(boolean supportsPlusPrefix) { + for (long value : interestingValues) { + for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) { + for (String prefix : new String[] {"", "x", "xxx"}) { + for (String postfix : new String[] {"", "x", "xxx"}) { + String valueString = prefix + Long.toString(value, radix) + postfix; + int start = prefix.length(); + int end = valueString.length() - postfix.length(); + assertEquals(valueString, value, Long.parseLong(valueString, start, end, radix)); + if (value > 0 && supportsPlusPrefix) { + valueString = prefix + "+" + Long.toString(value, radix) + postfix; + end++; + assertEquals(valueString, value, Long.parseLong(valueString, start, end, radix)); + } + } + } + } + } + + try { + throw new AssertionError(Long.parseUnsignedLong("0", 0, 1, Character.MIN_RADIX - 1)); + } catch (IllegalArgumentException expected) { + } + try { + throw new AssertionError(Long.parseUnsignedLong("0", 0, 1, Character.MAX_RADIX + 1)); + } catch (IllegalArgumentException expected) { + } + + try { + throw new AssertionError(Long.parseUnsignedLong("", 0, 0, 16)); + } catch (NumberFormatException expected) { + } + try { + throw new AssertionError(Long.parseUnsignedLong("-", 0, 1, 16)); + } catch (NumberFormatException expected) { + } + try { + throw new AssertionError(Long.parseUnsignedLong("+", 0, 1, 16)); + } catch (NumberFormatException expected) { + } + + BigInteger overflow = new BigInteger("18446744073709551616"); + for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) { + for (String prefix : new String[] {"", "x", "xxx"}) { + for (String postfix : new String[] {"", "x", "xxx"}) { + String overflowString = prefix + overflow.toString(radix) + postfix; + int start = prefix.length(); + int end = overflowString.length() - postfix.length(); + try { + throw new AssertionError(Long.parseLong(overflowString, start, end, radix)); + } catch (NumberFormatException expected) { + } + } + } + } + } + + private static void testParseUnsignedLongSubsequenceWithRadix() { + for (long value : interestingValues) { + for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) { + for (String prefix : new String[] {"", "x", "xxx"}) { + for (String postfix : new String[] {"", "x", "xxx"}) { + String valueString = prefix + unsignedLongToBigInteger(value).toString(radix) + postfix; + int start = prefix.length(); + int end = valueString.length() - postfix.length(); + assertEquals( + valueString, value, Long.parseUnsignedLong(valueString, start, end, radix)); + valueString = prefix + "+" + unsignedLongToBigInteger(value).toString(radix) + postfix; + end++; + assertEquals( + valueString, value, Long.parseUnsignedLong(valueString, start, end, radix)); + } + } + } + } + + try { + throw new AssertionError(Long.parseUnsignedLong("0", 0, 1, Character.MIN_RADIX - 1)); + } catch (IllegalArgumentException expected) { + } + try { + throw new AssertionError(Long.parseUnsignedLong("0", 0, 1, Character.MAX_RADIX + 1)); + } catch (IllegalArgumentException expected) { + } + + try { + throw new AssertionError(Long.parseUnsignedLong("", 0, 0, 16)); + } catch (NumberFormatException expected) { + } + try { + throw new AssertionError(Long.parseUnsignedLong("+", 0, 1, 16)); + } catch (NumberFormatException expected) { + } + + BigInteger overflow = new BigInteger("18446744073709551616"); + for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) { + for (String prefix : new String[] {"", "x", "xxx", "+", "x+", "xxx+"}) { + for (String postfix : new String[] {"", "x", "xxx"}) { + String overflowString = prefix + overflow.toString(radix) + postfix; + int start = prefix.length(); + int end = overflowString.length() - postfix.length(); + try { + throw new AssertionError(Long.parseUnsignedLong(overflowString, start, end, radix)); + } catch (NumberFormatException expected) { + } + } + } + } + } + + private static BigInteger unsignedLongToBigInteger(long value) { + BigInteger bigInt = BigInteger.valueOf(value & 0x7fffffffffffffffL); + if (value < 0) { + bigInt = bigInt.setBit(Long.SIZE - 1); + } + return bigInt; + } + + private static void assertEquals(String m, long expected, long actual) { + if (expected != actual) { + throw new AssertionError(m + " Expected <" + expected + "> but was <" + actual + '>'); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java index 43bbd99..c5ce290 100644 --- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java +++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -362,11 +362,7 @@ @Override protected void testIntermediateWithMainDexList( - String packageName, - Path input, - int expectedMainDexListSize, - List<String> mainDexClasses, - List<String> mainDexOverApproximation) + String packageName, Path input, int expectedMainDexListSize, List<String> mainDexClasses) throws Throwable { // Skip those tests. Assume.assumeTrue(false);
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java index 4913bc5..67eeeb0 100644 --- a/src/test/java/com/android/tools/r8/D8TestBuilder.java +++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -99,4 +99,11 @@ } return self(); } + + public D8TestBuilder addMainDexKeepClassAndMemberRules(Class<?>... classes) { + for (Class<?> clazz : classes) { + addMainDexRules("-keep class " + clazz.getTypeName() + " { *; }"); + } + return self(); + } }
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java index e169df0..488480c 100644 --- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java +++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.OffOrAuto; +import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.FoundClassSubject; @@ -107,14 +108,14 @@ .withOptionConsumer(opts -> opts.enableClassInlining = false) .withBuilderTransformation( b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown())) - .withDexCheck(inspector -> checkLambdaCount(inspector, 102, "lambdadesugaring")) + .withDexCheck(inspector -> checkLambdaCount(inspector, 18, "lambdadesugaring")) .run(); test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring") .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K)) .withBuilderTransformation( b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown())) - .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaring")) + .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaring")) .run(); } @@ -146,14 +147,14 @@ .withOptionConsumer(opts -> opts.enableClassInlining = false) .withBuilderTransformation( b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown())) - .withDexCheck(inspector -> checkLambdaCount(inspector, 102, "lambdadesugaring")) + .withDexCheck(inspector -> checkLambdaCount(inspector, 18, "lambdadesugaring")) .run(); test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring") .withMinApiLevel(AndroidApiLevel.N) .withBuilderTransformation( b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown())) - .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaring")) + .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaring")) .run(); } @@ -167,7 +168,7 @@ .withBuilderTransformation(ToolHelper::allowTestProguardOptions) .withBuilderTransformation( b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown())) - .withDexCheck(inspector -> checkLambdaCount(inspector, 37, "lambdadesugaringnplus")) + .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaringnplus")) .run(); test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods") @@ -176,7 +177,7 @@ .withBuilderTransformation(ToolHelper::allowTestProguardOptions) .withBuilderTransformation( b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown())) - .withDexCheck(inspector -> checkLambdaCount(inspector, 6, "lambdadesugaringnplus")) + .withDexCheck(inspector -> checkLambdaCount(inspector, 4, "lambdadesugaringnplus")) .run(); } @@ -190,7 +191,7 @@ .withBuilderTransformation(ToolHelper::allowTestProguardOptions) .withBuilderTransformation( b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown())) - .withDexCheck(inspector -> checkLambdaCount(inspector, 36, "lambdadesugaringnplus")) + .withDexCheck(inspector -> checkLambdaCount(inspector, 6, "lambdadesugaringnplus")) .run(); test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods") @@ -199,19 +200,19 @@ .withBuilderTransformation(ToolHelper::allowTestProguardOptions) .withBuilderTransformation( b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown())) - .withDexCheck(inspector -> checkLambdaCount(inspector, 2, "lambdadesugaringnplus")) + .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaringnplus")) .run(); } private void checkLambdaCount(CodeInspector inspector, int maxExpectedCount, String prefix) { - int count = 0; + List<String> found = new ArrayList<>(); for (FoundClassSubject clazz : inspector.allClasses()) { if (clazz.isSynthesizedJavaLambdaClass() && clazz.getOriginalName().startsWith(prefix)) { - count++; + found.add(clazz.getOriginalName()); } } - assertEquals(maxExpectedCount, count); + assertEquals(StringUtils.lines(found), maxExpectedCount, found.size()); } private void checkTestMultipleInterfacesCheckCastCount(
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java index 52480be..bd9bd27 100644 --- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java +++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -10,13 +10,13 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.junit.Assume.assumeFalse; import com.android.tools.r8.ToolHelper.DexVm; import com.android.tools.r8.ToolHelper.DexVm.Version; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.AndroidApp; -import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.ListUtils; import com.android.tools.r8.utils.OffOrAuto; @@ -115,21 +115,19 @@ return self(); } - C withMainDexClass(String... classes) { - return withBuilderTransformation(builder -> builder.addMainDexClasses(classes)); - } - C withMainDexKeepClassRules(List<String> classes) { return withBuilderTransformation( builder -> { if (builder instanceof D8Command.Builder) { ((D8Command.Builder) builder) .addMainDexRules( - ListUtils.map(classes, c -> "-keep class " + c), Origin.unknown()); + ListUtils.map(classes, c -> "-keep class " + c + " { *; }"), + Origin.unknown()); } else if (builder instanceof R8Command.Builder) { ((R8Command.Builder) builder) .addMainDexRules( - ListUtils.map(classes, c -> "-keep class " + c), Origin.unknown()); + ListUtils.map(classes, c -> "-keep class " + c + " { *; }"), + Origin.unknown()); } else { fail("Unexpected builder type: " + builder.getClass()); } @@ -486,22 +484,14 @@ public void testLambdaDesugaringWithMainDexList1() throws Throwable { // Minimal case: there are synthesized classes but not form the main dex class. testIntermediateWithMainDexList( - "lambdadesugaring", - 1, - ImmutableList.of("lambdadesugaring.LambdaDesugaring$I"), - ImmutableList.of()); + "lambdadesugaring", 1, ImmutableList.of("lambdadesugaring.LambdaDesugaring$I")); } @Test public void testLambdaDesugaringWithMainDexList2() throws Throwable { // Main dex class has many lambdas. testIntermediateWithMainDexList( - "lambdadesugaring", - // TODO(b/180074885): Over approximation not present in R8. - this instanceof R8RunExamplesAndroidOTest ? 51 : 52, - ImmutableList.of("lambdadesugaring.LambdaDesugaring$Refs$B"), - // TODO(b/180074885): Over approximation due to invoke-dynamic reference adds as dependency. - ImmutableList.of("lambdadesugaring.LambdaDesugaring$Refs$D")); + "lambdadesugaring", 98, ImmutableList.of("lambdadesugaring.LambdaDesugaring$Refs$B")); } @Test @@ -511,11 +501,7 @@ "interfacemethods", Paths.get(ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR, "interfacemethods" + JAR_EXTENSION), 2, - ImmutableList.of("interfacemethods.I1"), - // TODO(b/180074885): Over approximation due to including I1-CC by being derived from I1, - // but after desugaring I1 does not reference I1$-CC (the static method is moved), so it - // is incorrect to include I1-CC in the main dex. - ImmutableList.of("interfacemethods.I1$-CC")); + ImmutableList.of("interfacemethods.I2", "interfacemethods.I2$-CC")); } @@ -526,11 +512,7 @@ "interfacemethods", Paths.get(ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR, "interfacemethods" + JAR_EXTENSION), 2, - ImmutableList.of("interfacemethods.I2"), - // TODO(b/180074885): Over approximation due to including I2$-CC by being derived from I2, - // but after desugaring I2 does not reference I2$-CC (the default method is moved), so it - // is incorrect to include I2$-CC in the main dex. - ImmutableList.of("interfacemethods.I2$-CC")); + ImmutableList.of("interfacemethods.I2", "interfacemethods.I2$-CC")); } @Test @@ -544,26 +526,21 @@ } private void testIntermediateWithMainDexList( - String packageName, - int expectedMainDexListSize, - List<String> mainDexClasses, - List<String> mainDexOverApproximation) + String packageName, int expectedMainDexListSize, List<String> mainDexClasses) throws Throwable { testIntermediateWithMainDexList( packageName, Paths.get(EXAMPLE_DIR, packageName + JAR_EXTENSION), expectedMainDexListSize, - mainDexClasses, - mainDexOverApproximation); + mainDexClasses); } protected void testIntermediateWithMainDexList( - String packageName, - Path input, - int expectedMainDexListSize, - List<String> mainDexClasses, - List<String> mainDexOverApproximation) + String packageName, Path input, int expectedMainDexListSize, List<String> mainDexClasses) throws Throwable { + // R8 does not support merging intermediate builds via DEX. + assumeFalse(this instanceof R8RunExamplesAndroidOTest); + AndroidApiLevel minApi = AndroidApiLevel.K; // Full build, will be used as reference. @@ -604,13 +581,8 @@ // Check. Assert.assertEquals(expectedMainDexListSize, fullMainClasses.size()); - SetView<String> adjustedFull = - Sets.difference( - fullMainClasses, - new HashSet<>( - ListUtils.map(mainDexOverApproximation, DescriptorUtils::javaTypeToDescriptor))); - assertEqualSets(adjustedFull, indexedIntermediateMainClasses); - assertEqualSets(adjustedFull, filePerInputClassIntermediateMainClasses); + assertEqualSets(fullMainClasses, indexedIntermediateMainClasses); + assertEqualSets(fullMainClasses, filePerInputClassIntermediateMainClasses); } <T> void assertEqualSets(Set<T> expected, Set<T> actual) {
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java index 26477d3..e9cd5ff 100644 --- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java +++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -285,13 +285,6 @@ return setMinApi(minApi); } - public T setMinApiThreshold(TestRuntime runtime) { - if (runtime.isDex()) { - setMinApiThreshold(runtime.asDex().getMinApiLevel()); - } - return self(); - } - public T setMinApi(AndroidApiLevel minApiLevel) { return setMinApi(minApiLevel.getLevel()); }
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/InvalidLongStackValueMaxHeightTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/InvalidLongStackValueMaxHeightTest.java new file mode 100644 index 0000000..ae574e9 --- /dev/null +++ b/src/test/java/com/android/tools/r8/cf/stackmap/InvalidLongStackValueMaxHeightTest.java
@@ -0,0 +1,91 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.cf.stackmap; + +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assume.assumeTrue; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class InvalidLongStackValueMaxHeightTest extends TestBase { + + private final String[] EXPECTED = new String[] {"52"}; + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(); + } + + public InvalidLongStackValueMaxHeightTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void smokeTest() throws Exception { + testForRuntime(parameters) + .addProgramClasses(Main.class, Tester.class) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines(EXPECTED); + } + + @Test(expected = CompilationFailedException.class) + public void testD8Cf() throws Exception { + assumeTrue(parameters.isCfRuntime()); + testForD8(parameters.getBackend()) + .addProgramClasses(Tester.class) + .addProgramClassFileData(getMainWithChangedMaxStackHeight()) + .setMinApi(parameters.getApiLevel()) + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertWarningThatMatches( + diagnosticMessage(containsString("The max stack height of 2 is violated"))); + }); + } + + @Test() + public void testD8Dex() throws Exception { + assumeTrue(parameters.isDexRuntime()); + testForD8(parameters.getBackend()) + .addProgramClasses(Tester.class) + .addProgramClassFileData(getMainWithChangedMaxStackHeight()) + .setMinApi(parameters.getApiLevel()) + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertWarningThatMatches( + diagnosticMessage(containsString("The max stack height of 2 is violated"))); + }) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines(EXPECTED); + } + + public byte[] getMainWithChangedMaxStackHeight() throws Exception { + return transformer(Main.class).setMaxStackHeight(MethodPredicate.onName("main"), 2).transform(); + } + + public static class Tester { + + public static void test(long x, int y) { + System.out.println(x + y); + } + } + + public static class Main { + + public static void main(String[] args) { + Tester.test(10L, 42); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/InvalidStackHeightTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/InvalidStackHeightTest.java new file mode 100644 index 0000000..71b6637 --- /dev/null +++ b/src/test/java/com/android/tools/r8/cf/stackmap/InvalidStackHeightTest.java
@@ -0,0 +1,106 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.cf.stackmap; + +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assume.assumeTrue; + +import com.android.tools.r8.CompilationFailedException; +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 com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class InvalidStackHeightTest extends TestBase { + + private final String[] EXPECTED = new String[] {"42"}; + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(); + } + + public InvalidStackHeightTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void smokeTest() throws Exception { + testForRuntime(parameters) + .addProgramClasses(Main.class) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines(EXPECTED); + } + + @Test(expected = CompilationFailedException.class) + public void testD8Cf() throws Exception { + assumeTrue(parameters.isCfRuntime()); + testForD8(parameters.getBackend()) + .addProgramClassFileData(getMainWithChangedMaxStackHeight()) + .setMinApi(parameters.getApiLevel()) + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertWarningMessageThatMatches( + containsString("The max stack height of 1 is violated")); + }); + } + + @Test + public void testD8Dex() throws Exception { + assumeTrue(parameters.isDexRuntime()); + testForD8(parameters.getBackend()) + .addProgramClassFileData(getMainWithChangedMaxStackHeight()) + .setMinApi(parameters.getApiLevel()) + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertWarningMessageThatMatches( + containsString("The max stack height of 1 is violated")); + }) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines(EXPECTED); + } + + @Test() + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClassFileData(getMainWithChangedMaxStackHeight()) + .enableInliningAnnotations() + .addKeepMainRule(Main.class) + .setMinApi(parameters.getApiLevel()) + .allowDiagnosticWarningMessages() + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertWarningsMatch( + diagnosticMessage(containsString("The max stack height of 1 is violated"))); + }) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines(EXPECTED); + } + + public byte[] getMainWithChangedMaxStackHeight() throws Exception { + return transformer(Main.class).setMaxStackHeight(MethodPredicate.onName("main"), 1).transform(); + } + + public static class Main { + + @NeverInline + private void test(int x, int y) { + System.out.println(x + y); + } + + public static void main(String[] args) { + new Main().test(args.length, 42); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/LongStackValuesInFramesTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/LongStackValuesInFramesTest.java new file mode 100644 index 0000000..742472b --- /dev/null +++ b/src/test/java/com/android/tools/r8/cf/stackmap/LongStackValuesInFramesTest.java
@@ -0,0 +1,151 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.cf.stackmap; + +import static com.android.tools.r8.cf.stackmap.LongStackValuesInFramesTest.LongStackValuesInFramesTest$MainDump.dump; +import static org.junit.Assume.assumeTrue; + +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.Parameters; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +@RunWith(Parameterized.class) +public class LongStackValuesInFramesTest extends TestBase { + + private final String[] EXPECTED = new String[] {"52"}; + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(); + } + + public LongStackValuesInFramesTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testJvm() throws Exception { + assumeTrue(parameters.isCfRuntime()); + testForJvm() + .addProgramClasses(Tester.class) + .addProgramClassFileData(dump()) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines(EXPECTED); + } + + @Test + public void testD8() throws Exception { + testForD8(parameters.getBackend()) + .addProgramClasses(Tester.class) + .addProgramClassFileData(dump()) + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines(EXPECTED); + } + + public static class Tester { + + @NeverInline + public static void test(long x, int y) { + System.out.println(x + y); + } + } + + public static class Main { + + // This code will be rewritten to: + // ldc_w 10 + // bipush 10 + // if (args.length == 0) { + // invoke Tester.test(JI)V + // } + // pop + // pop2 + public static void main(String[] args) { + long x = 10L; + int y = 42; + if (args.length == 0) { + Tester.test(x, y); + } + } + } + + public static class LongStackValuesInFramesTest$MainDump implements Opcodes { + + public static byte[] dump() { + + ClassWriter classWriter = new ClassWriter(0); + MethodVisitor methodVisitor; + + classWriter.visit( + V1_8, + ACC_PUBLIC | ACC_SUPER, + "com/android/tools/r8/cf/stackmap/LongStackValuesInFramesTest$Main", + null, + "java/lang/Object", + null); + classWriter.visitSource("LongStackValuesInFramesTest.java", null); + classWriter.visitInnerClass( + "com/android/tools/r8/cf/stackmap/LongStackValuesInFramesTest$Main", + "com/android/tools/r8/cf/stackmap/LongStackValuesInFramesTest", + "Main", + ACC_PUBLIC | ACC_STATIC); + classWriter.visitInnerClass( + "com/android/tools/r8/cf/stackmap/LongStackValuesInFramesTest$Tester", + "com/android/tools/r8/cf/stackmap/LongStackValuesInFramesTest", + "Tester", + ACC_PUBLIC | ACC_STATIC); + + { + methodVisitor = + classWriter.visitMethod( + ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null); + methodVisitor.visitCode(); + methodVisitor.visitLdcInsn(10L); + methodVisitor.visitIntInsn(BIPUSH, 42); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitInsn(ARRAYLENGTH); + Label label1 = new Label(); + methodVisitor.visitJumpInsn(IFNE, label1); + methodVisitor.visitMethodInsn( + INVOKESTATIC, + "com/android/tools/r8/cf/stackmap/LongStackValuesInFramesTest$Tester", + "test", + "(JI)V", + false); + Label label2 = new Label(); + methodVisitor.visitJumpInsn(Opcodes.GOTO, label2); + methodVisitor.visitLabel(label1); + methodVisitor.visitFrame( + Opcodes.F_FULL, + 1, + new Object[] {"[Ljava/lang/String;"}, + 2, + new Object[] {Opcodes.LONG, Opcodes.INTEGER}); + methodVisitor.visitInsn(Opcodes.POP); + methodVisitor.visitInsn(Opcodes.POP2); + methodVisitor.visitLabel(label2); + methodVisitor.visitFrame(Opcodes.F_FULL, 1, new Object[] {"[Ljava/lang/String;"}, 0, null); + methodVisitor.visitInsn(RETURN); + methodVisitor.visitMaxs(4, 3); + methodVisitor.visitEnd(); + } + classWriter.visitEnd(); + + return classWriter.toByteArray(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest.java new file mode 100644 index 0000000..113a40f --- /dev/null +++ b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest.java
@@ -0,0 +1,191 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.cf.stackmap; + +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage; +import static com.android.tools.r8.cf.stackmap.UninitializedGetFieldTest.MainDump.dump; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assume.assumeTrue; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +@RunWith(Parameterized.class) +public class UninitializedGetFieldTest extends TestBase { + + private final String[] EXPECTED = new String[] {"Main::foo"}; + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(); + } + + public UninitializedGetFieldTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testJvm() throws Exception { + assumeTrue(parameters.isCfRuntime()); + testForJvm() + .addProgramClassFileData(dump()) + .run(parameters.getRuntime(), Main.class) + .assertFailureWithErrorThatThrows(VerifyError.class); + } + + @Test(expected = CompilationFailedException.class) + public void testD8Cf() throws Exception { + assumeTrue(parameters.isCfRuntime()); + testForD8(parameters.getBackend()) + .addProgramClassFileData(dump()) + .setMinApi(parameters.getApiLevel()) + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertWarningThatMatches( + diagnosticMessage(containsString("The expected type uninitialized"))); + }); + } + + @Test + public void testD8Dex() throws Exception { + assumeTrue(parameters.isDexRuntime()); + testForD8(parameters.getBackend()) + .addProgramClassFileData(dump()) + .setMinApi(parameters.getApiLevel()) + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertWarningThatMatches( + diagnosticMessage(containsString("The expected type uninitialized"))); + }) + .run(parameters.getRuntime(), Main.class) + .assertFailureWithErrorThatThrows(VerifyError.class); + } + + public static class Main { + + private Object object; + + private Main() { + this.object = new Object(); + foo(this.object); + } + + private void foo(Object object) { + System.out.println("Main::foo"); + } + + public static void main(String[] args) { + new Main(); + } + } + + // The dump is generated from the above code. The change made is to Main::<init> where we + // now putfield and getfield before initializing. + static class MainDump implements Opcodes { + + static byte[] dump() throws Exception { + + ClassWriter classWriter = new ClassWriter(0); + FieldVisitor fieldVisitor; + MethodVisitor methodVisitor; + + classWriter.visit( + V1_8, + ACC_PUBLIC | ACC_SUPER, + "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest$Main", + null, + "java/lang/Object", + null); + + classWriter.visitInnerClass( + "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest$Main", + "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest", + "Main", + ACC_PUBLIC | ACC_STATIC); + + { + fieldVisitor = + classWriter.visitField(ACC_PRIVATE, "object", "Ljava/lang/Object;", null, null); + fieldVisitor.visitEnd(); + } + { + methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "<init>", "()V", null, null); + methodVisitor.visitCode(); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitTypeInsn(NEW, "java/lang/Object"); + methodVisitor.visitInsn(DUP); + methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); + methodVisitor.visitFieldInsn( + PUTFIELD, + "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest$Main", + "object", + "Ljava/lang/Object;"); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitFieldInsn( + GETFIELD, + "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest$Main", + "object", + "Ljava/lang/Object;"); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitMethodInsn( + INVOKESPECIAL, + "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest$Main", + "foo", + "(Ljava/lang/Object;)V", + false); + methodVisitor.visitInsn(RETURN); + methodVisitor.visitMaxs(3, 1); + methodVisitor.visitEnd(); + } + { + methodVisitor = + classWriter.visitMethod(ACC_PRIVATE, "foo", "(Ljava/lang/Object;)V", null, null); + methodVisitor.visitCode(); + methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); + methodVisitor.visitLdcInsn("Main::foo"); + methodVisitor.visitMethodInsn( + INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); + methodVisitor.visitInsn(RETURN); + methodVisitor.visitMaxs(2, 2); + methodVisitor.visitEnd(); + } + { + methodVisitor = + classWriter.visitMethod( + ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null); + methodVisitor.visitCode(); + methodVisitor.visitTypeInsn( + NEW, "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest$Main"); + methodVisitor.visitInsn(DUP); + methodVisitor.visitMethodInsn( + INVOKESPECIAL, + "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest$Main", + "<init>", + "()V", + false); + methodVisitor.visitInsn(POP); + methodVisitor.visitInsn(RETURN); + methodVisitor.visitMaxs(2, 1); + methodVisitor.visitEnd(); + } + classWriter.visitEnd(); + + return classWriter.toByteArray(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest.java new file mode 100644 index 0000000..30b2a78 --- /dev/null +++ b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest.java
@@ -0,0 +1,133 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.cf.stackmap; + +import static com.android.tools.r8.cf.stackmap.UninitializedInstanceOfTest.MainDump.dump; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assume.assumeTrue; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.TestRunResult; +import com.android.tools.r8.ToolHelper.DexVm.Version; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +@RunWith(Parameterized.class) +public class UninitializedInstanceOfTest extends TestBase { + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(); + } + + @Test + public void testJvm() throws Exception { + assumeTrue(parameters.isCfRuntime()); + testForJvm() + .addProgramClassFileData(dump()) + .run(parameters.getRuntime(), Main.class) + .assertFailureWithErrorThatThrows(VerifyError.class); + } + + @Test(expected = CompilationFailedException.class) + public void testD8Cf() throws Exception { + assumeTrue(parameters.isCfRuntime()); + testForD8(parameters.getBackend()) + .addProgramClassFileData(dump()) + .setMinApi(parameters.getApiLevel()) + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertWarningMessageThatMatches( + containsString("The expected type uninitialized new is not assignable")); + diagnostics.assertErrorMessageThatMatches( + containsString("Could not validate stack map frames")); + }); + } + + @Test() + public void testD8Dex() throws Exception { + assumeTrue(parameters.isDexRuntime()); + boolean expectFailure = parameters.getDexRuntimeVersion().isAtLeast(Version.V7_0_0); + testForD8(parameters.getBackend()) + .addProgramClassFileData(dump()) + .setMinApi(parameters.getApiLevel()) + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertWarningMessageThatMatches( + containsString("The expected type uninitialized new is not assignable")); + }) + .run(parameters.getRuntime(), Main.class) + .applyIf( + expectFailure, + result -> result.assertFailureWithErrorThatThrows(VerifyError.class), + TestRunResult::assertSuccessWithOutputLines); + } + + public UninitializedInstanceOfTest(TestParameters parameters) { + this.parameters = parameters; + } + + public static class Main { + + // The dump is generated from the following code, where we just swap the instanceof with the + // initializer call. + public static void main(String[] args) { + boolean m = (new Object() instanceof Main); + } + } + + static class MainDump implements Opcodes { + + static byte[] dump() throws Exception { + + ClassWriter classWriter = new ClassWriter(0); + MethodVisitor methodVisitor; + + classWriter.visit( + V1_8, + ACC_PUBLIC | ACC_SUPER, + "com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest$Main", + null, + "java/lang/Object", + null); + + classWriter.visitInnerClass( + "com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest$Main", + "com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest", + "Main", + ACC_PUBLIC | ACC_STATIC); + + { + methodVisitor = + classWriter.visitMethod( + ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null); + methodVisitor.visitCode(); + methodVisitor.visitTypeInsn(NEW, "java/lang/Object"); + methodVisitor.visitInsn(DUP); + // INSTANCEOF and INVOKESPECIAL is swapped. + methodVisitor.visitTypeInsn( + INSTANCEOF, "com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest$Main"); + methodVisitor.visitVarInsn(ISTORE, 1); + methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); + methodVisitor.visitInsn(RETURN); + methodVisitor.visitMaxs(2, 2); + methodVisitor.visitEnd(); + } + classWriter.visitEnd(); + + return classWriter.toByteArray(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest.java new file mode 100644 index 0000000..86429af --- /dev/null +++ b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest.java
@@ -0,0 +1,127 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.cf.stackmap; + +import static com.android.tools.r8.cf.stackmap.UninitializedNewCheckCastTest.MainDump.dump; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assume.assumeTrue; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +@RunWith(Parameterized.class) +public class UninitializedNewCheckCastTest extends TestBase { + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(); + } + + @Test + public void testJvm() throws Exception { + assumeTrue(parameters.isCfRuntime()); + testForJvm() + .addProgramClassFileData(dump()) + .run(parameters.getRuntime(), Main.class) + .assertFailureWithErrorThatThrows(VerifyError.class); + } + + @Test(expected = CompilationFailedException.class) + public void testD8Cf() throws Exception { + assumeTrue(parameters.isCfRuntime()); + testForD8(parameters.getBackend()) + .addProgramClassFileData(dump()) + .setMinApi(parameters.getApiLevel()) + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertWarningMessageThatMatches( + containsString("The expected type uninitialized new is not assignable")); + diagnostics.assertErrorMessageThatMatches( + containsString("Could not validate stack map frames")); + }); + } + + @Test + public void testD8Dex() throws Exception { + assumeTrue(parameters.isDexRuntime()); + testForD8(parameters.getBackend()) + .addProgramClassFileData(dump()) + .setMinApi(parameters.getApiLevel()) + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertWarningMessageThatMatches( + containsString("The expected type uninitialized new is not assignable")); + }) + .run(parameters.getRuntime(), Main.class) + .assertFailureWithErrorThatThrows(VerifyError.class); + } + + public UninitializedNewCheckCastTest(TestParameters parameters) { + this.parameters = parameters; + } + + public static class Main { + + // The dump is generated from the following code, where we just swap the CheckCast with the + // initializer call. + public static void main(String[] args) { + Main m = (Main) new Object(); + } + } + + static class MainDump implements Opcodes { + + static byte[] dump() { + + ClassWriter classWriter = new ClassWriter(0); + MethodVisitor methodVisitor; + + classWriter.visit( + V1_8, + ACC_PUBLIC | ACC_SUPER, + "com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest$Main", + null, + "java/lang/Object", + null); + + classWriter.visitInnerClass( + "com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest$Main", + "com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest", + "Main", + ACC_PUBLIC | ACC_STATIC); + + { + methodVisitor = + classWriter.visitMethod( + ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null); + methodVisitor.visitCode(); + methodVisitor.visitTypeInsn(NEW, "java/lang/Object"); + methodVisitor.visitInsn(DUP); + // CHECK-CAST has swapped position with INVOKESPECIAL. + methodVisitor.visitTypeInsn( + CHECKCAST, "com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest$Main"); + methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); + methodVisitor.visitVarInsn(ASTORE, 1); + methodVisitor.visitInsn(RETURN); + methodVisitor.visitMaxs(2, 2); + methodVisitor.visitEnd(); + } + classWriter.visitEnd(); + + return classWriter.toByteArray(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest.java new file mode 100644 index 0000000..9a52445 --- /dev/null +++ b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest.java
@@ -0,0 +1,198 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.cf.stackmap; + +import static com.android.tools.r8.cf.stackmap.UninitializedPutFieldSelfTest.MainDump.dump; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assume.assumeTrue; + +import com.android.tools.r8.CompilationFailedException; +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 org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +@RunWith(Parameterized.class) +public class UninitializedPutFieldSelfTest extends TestBase { + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(); + } + + @Test + public void testJvm() throws Exception { + assumeTrue(parameters.isCfRuntime()); + testForJvm() + .addProgramClassFileData(dump()) + .run(parameters.getRuntime(), Main.class) + .assertFailureWithErrorThatThrows(VerifyError.class); + } + + @Test(expected = CompilationFailedException.class) + public void testD8Cf() throws Exception { + assumeTrue(parameters.isCfRuntime()); + testForD8(parameters.getBackend()) + .addProgramClassFileData(dump()) + .setMinApi(parameters.getApiLevel()) + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertWarningMessageThatMatches( + containsString("The expected type uninitialized this is not assignable")); + diagnostics.assertErrorMessageThatMatches( + containsString("Could not validate stack map frames")); + }); + } + + @Test + public void testD8Dex() throws Exception { + assumeTrue(parameters.isDexRuntime()); + boolean willFailVerification = + parameters.getDexRuntimeVersion().isOlderThan(Version.V5_1_1) + || parameters.getDexRuntimeVersion().isNewerThan(Version.V6_0_1); + testForD8(parameters.getBackend()) + .addProgramClassFileData(dump()) + .setMinApi(parameters.getApiLevel()) + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertWarningMessageThatMatches( + containsString("The expected type uninitialized this is not assignable")); + }) + .run(parameters.getRuntime(), Main.class) + .assertFailureWithErrorThatThrowsIf(willFailVerification, VerifyError.class) + .assertSuccessWithOutputLinesIf(!willFailVerification, "Main::foo"); + } + + public UninitializedPutFieldSelfTest(TestParameters parameters) { + this.parameters = parameters; + } + + public static class Main { + + private Main main; + + private Main() { + this.main = this; + this.main.foo(); + } + + private void foo() { + System.out.println("Main::foo"); + } + + public static void main(String[] args) { + new Main(); + } + } + + // The dump is generated from the above code. The change that is made is to Main::<init> where we + // now putfield before initializing. That will try and assign an uninstantiated type to a field + // which is not allowed. + public static class MainDump implements Opcodes { + + public static byte[] dump() { + + ClassWriter classWriter = new ClassWriter(0); + FieldVisitor fieldVisitor; + MethodVisitor methodVisitor; + + classWriter.visit( + V1_8, + ACC_PUBLIC | ACC_SUPER, + "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main", + null, + "java/lang/Object", + null); + + classWriter.visitInnerClass( + "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main", + "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest", + "Main", + ACC_PUBLIC | ACC_STATIC); + + { + fieldVisitor = + classWriter.visitField( + ACC_PRIVATE, + "main", + "Lcom/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main;", + null, + null); + fieldVisitor.visitEnd(); + } + { + methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "<init>", "()V", null, null); + methodVisitor.visitCode(); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitFieldInsn( + PUTFIELD, + "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main", + "main", + "Lcom/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main;"); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitFieldInsn( + GETFIELD, + "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main", + "main", + "Lcom/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main;"); + methodVisitor.visitMethodInsn( + INVOKESPECIAL, + "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main", + "foo", + "()V", + false); + methodVisitor.visitInsn(RETURN); + methodVisitor.visitMaxs(2, 1); + methodVisitor.visitEnd(); + } + { + methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "foo", "()V", null, null); + methodVisitor.visitCode(); + methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); + methodVisitor.visitLdcInsn("Main::foo"); + methodVisitor.visitMethodInsn( + INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); + methodVisitor.visitInsn(RETURN); + methodVisitor.visitMaxs(2, 1); + methodVisitor.visitEnd(); + } + { + methodVisitor = + classWriter.visitMethod( + ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null); + methodVisitor.visitCode(); + methodVisitor.visitTypeInsn( + NEW, "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main"); + methodVisitor.visitInsn(DUP); + methodVisitor.visitMethodInsn( + INVOKESPECIAL, + "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main", + "<init>", + "()V", + false); + methodVisitor.visitInsn(POP); + methodVisitor.visitInsn(RETURN); + methodVisitor.visitMaxs(2, 1); + methodVisitor.visitEnd(); + } + classWriter.visitEnd(); + + return classWriter.toByteArray(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest.java new file mode 100644 index 0000000..e943ebc --- /dev/null +++ b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest.java
@@ -0,0 +1,192 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.cf.stackmap; + +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage; +import static com.android.tools.r8.cf.stackmap.UninitializedPutFieldTest.MainDump.dump; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assume.assumeTrue; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +@RunWith(Parameterized.class) +public class UninitializedPutFieldTest extends TestBase { + + private final String[] EXPECTED = new String[] {"Main::foo"}; + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(); + } + + @Test + public void testJvm() throws Exception { + assumeTrue(parameters.isCfRuntime()); + testForJvm() + .addProgramClassFileData(dump()) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines(EXPECTED); + } + + @Test() + public void testD8Cf() throws Exception { + assumeTrue(parameters.isCfRuntime()); + testForD8(parameters.getBackend()) + .addProgramClassFileData(dump()) + .setMinApi(parameters.getApiLevel()) + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertNoWarningsMatch( + diagnosticMessage(containsString("The expected type uninitialized"))); + }) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines(EXPECTED); + } + + @Test + public void testD8Dex() throws Exception { + assumeTrue(parameters.isDexRuntime()); + testForD8(parameters.getBackend()) + .addProgramClassFileData(dump()) + .setMinApi(parameters.getApiLevel()) + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertNoWarningsMatch( + diagnosticMessage(containsString("The expected type uninitialized"))); + }) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines(EXPECTED); + } + + public UninitializedPutFieldTest(TestParameters parameters) { + this.parameters = parameters; + } + + public static class Main { + + private Object object; + + private Main() { + this.object = new Object(); + foo(this.object); + } + + private void foo(Object object) { + System.out.println("Main::foo"); + } + + public static void main(String[] args) { + new Main(); + } + } + + // The dump is generated from the above code. The change made is to Main::<init> where we + // now putfield before initializing this. + static class MainDump implements Opcodes { + + static byte[] dump() throws Exception { + + ClassWriter classWriter = new ClassWriter(0); + FieldVisitor fieldVisitor; + MethodVisitor methodVisitor; + + classWriter.visit( + V1_8, + ACC_PUBLIC | ACC_SUPER, + "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest$Main", + null, + "java/lang/Object", + null); + + classWriter.visitInnerClass( + "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest$Main", + "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest", + "Main", + ACC_PUBLIC | ACC_STATIC); + + { + fieldVisitor = + classWriter.visitField(ACC_PRIVATE, "object", "Ljava/lang/Object;", null, null); + fieldVisitor.visitEnd(); + } + { + methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "<init>", "()V", null, null); + methodVisitor.visitCode(); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitTypeInsn(NEW, "java/lang/Object"); + methodVisitor.visitInsn(DUP); + methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); + methodVisitor.visitFieldInsn( + PUTFIELD, + "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest$Main", + "object", + "Ljava/lang/Object;"); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitFieldInsn( + GETFIELD, + "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest$Main", + "object", + "Ljava/lang/Object;"); + methodVisitor.visitMethodInsn( + INVOKESPECIAL, + "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest$Main", + "foo", + "(Ljava/lang/Object;)V", + false); + methodVisitor.visitInsn(RETURN); + methodVisitor.visitMaxs(3, 1); + methodVisitor.visitEnd(); + } + { + methodVisitor = + classWriter.visitMethod(ACC_PRIVATE, "foo", "(Ljava/lang/Object;)V", null, null); + methodVisitor.visitCode(); + methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); + methodVisitor.visitLdcInsn("Main::foo"); + methodVisitor.visitMethodInsn( + INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); + methodVisitor.visitInsn(RETURN); + methodVisitor.visitMaxs(2, 2); + methodVisitor.visitEnd(); + } + { + methodVisitor = + classWriter.visitMethod( + ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null); + methodVisitor.visitCode(); + methodVisitor.visitTypeInsn( + NEW, "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest$Main"); + methodVisitor.visitInsn(DUP); + methodVisitor.visitMethodInsn( + INVOKESPECIAL, + "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest$Main", + "<init>", + "()V", + false); + methodVisitor.visitInsn(POP); + methodVisitor.visitInsn(RETURN); + methodVisitor.visitMaxs(2, 1); + methodVisitor.visitEnd(); + } + classWriter.visitEnd(); + + return classWriter.toByteArray(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java index 4161d87..fcc79dc 100644 --- a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java +++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
@@ -8,6 +8,7 @@ import com.android.tools.r8.ToolHelper; import com.android.tools.r8.ToolHelper.ProcessResult; import com.android.tools.r8.cf.CfCodePrinter; +import com.android.tools.r8.graph.CfCode; import com.android.tools.r8.graph.ClassKind; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexItemFactory; @@ -18,6 +19,7 @@ import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.FileUtils; import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.Reporter; import com.android.tools.r8.utils.StringUtils; import com.google.common.collect.ImmutableList; import java.io.File; @@ -66,6 +68,10 @@ return Paths.get(ToolHelper.SOURCE_DIR, getGeneratedType().getInternalName() + ".java"); } + protected CfCode getCode(String holderName, String methodName, CfCode code) { + return code; + } + private String getGeneratedClassName() { return getGeneratedType().getName(); } @@ -94,7 +100,7 @@ } private void readMethodTemplatesInto(CfCodePrinter codePrinter) throws IOException { - InternalOptions options = new InternalOptions(); + InternalOptions options = new InternalOptions(factory, new Reporter()); options.testing.readInputStackMaps = true; JarClassFileReader<DexProgramClass> reader = new JarClassFileReader<>( @@ -104,9 +110,13 @@ if (method.isInitializer()) { continue; } - String methodName = - method.getHolderType().getName() + "_" + method.method.name.toString(); - codePrinter.visitMethod(methodName, method.getCode().asCfCode()); + String holderName = method.getHolderType().getName(); + String methodName = method.method.name.toString(); + String generatedMethodName = holderName + "_" + methodName; + CfCode code = getCode(holderName, methodName, method.getCode().asCfCode()); + if (code != null) { + codePrinter.visitMethod(generatedMethodName, code); + } } }, ClassKind.PROGRAM);
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingTestBase.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingTestBase.java index d84c164..5b2dbe7 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingTestBase.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingTestBase.java
@@ -6,7 +6,7 @@ import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; -import com.android.tools.r8.horizontalclassmerging.SyntheticArgumentClass; +import com.android.tools.r8.synthesis.SyntheticItemsTestUtils; import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; @@ -35,10 +35,8 @@ return codeInspector.allClasses().stream() .filter( clazz -> - clazz.isSynthetic() - && clazz - .getOriginalName() - .endsWith(SyntheticArgumentClass.SYNTHETIC_CLASS_SUFFIX)) + SyntheticItemsTestUtils.isHorizontalInitializerTypeArgument( + clazz.getOriginalReference())) .findFirst() .get(); }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/JavaLambdaMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/JavaLambdaMergingTest.java index 63c1f18..f7aacb5 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/JavaLambdaMergingTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/JavaLambdaMergingTest.java
@@ -5,7 +5,6 @@ package com.android.tools.r8.classmerging.horizontal; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import com.android.tools.r8.TestParameters; @@ -15,7 +14,6 @@ import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector; import java.util.Set; import java.util.stream.Collectors; -import org.junit.Ignore; import org.junit.Test; public class JavaLambdaMergingTest extends HorizontalClassMergingTestBase { @@ -25,17 +23,13 @@ } @Test - @Ignore("b/174809311): Test does not work with hygienic lambdas. Rewrite or remove") public void test() throws Exception { testForR8(parameters.getBackend()) .addInnerClasses(getClass()) .addKeepMainRule(Main.class) .addOptionsModification( - options -> { - options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging); - assertFalse(options.horizontalClassMergerOptions().isJavaLambdaMergingEnabled()); - options.horizontalClassMergerOptions().enableJavaLambdaMerging(); - }) + options -> + options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging)) .addHorizontallyMergedClassesInspectorIf( enableHorizontalClassMerging && parameters.isDexRuntime(), inspector -> { @@ -59,7 +53,7 @@ } private static boolean isLambda(DexType type) { - return SyntheticItemsTestUtils.isExternalLambda( + return SyntheticItemsTestUtils.isInternalLambda( Reference.classFromDescriptor(type.toDescriptorString())); }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStaticizerTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStaticizerTest.java new file mode 100644 index 0000000..e7eda20 --- /dev/null +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStaticizerTest.java
@@ -0,0 +1,73 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.classmerging.horizontal; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.TestParameters; +import org.junit.Test; + +public class MergedVirtualMethodStaticizerTest extends HorizontalClassMergingTestBase { + public MergedVirtualMethodStaticizerTest( + TestParameters parameters, boolean enableHorizontalClassMerging) { + super(parameters, enableHorizontalClassMerging); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(Program.class) + .addKeepClassAndMembersRules(Program.Main.class) + .addOptionsModification( + options -> + options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging)) + .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() + .setMinApi(parameters.getApiLevel()) + .addHorizontallyMergedClassesInspectorIf( + enableHorizontalClassMerging, + inspector -> inspector.assertMergedInto(Program.B.class, Program.A.class)) + .run(parameters.getRuntime(), Program.Main.class) + .assertSuccessWithOutputLines("A::foo", "Staticized::foo", "B::foo"); + } + + public static class Program { + + @NeverClassInline + public static class Staticized { + public static final Staticized staticized = new Staticized(); + + @NeverInline + public void foo() { + System.out.println("Staticized::foo"); + } + } + + @NeverClassInline + public static class A { + + public void foo() { + System.out.println("A::foo"); + Staticized.staticized.foo(); + } + } + + @NeverClassInline + public static class B { + + public void foo() { + System.out.println("B::foo"); + } + } + + public static class Main { + + public static void main(String[] args) { + new A().foo(); + new B().foo(); + } + } + } +}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java index 74d0ad6..441934e 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java
@@ -5,6 +5,7 @@ package com.android.tools.r8.classmerging.horizontal; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static com.android.tools.r8.utils.codeinspector.Matchers.notIf; import static org.hamcrest.MatcherAssert.assertThat; import com.android.tools.r8.NeverClassInline; @@ -33,6 +34,9 @@ .addOptionsModification( options -> options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging)) + .addHorizontallyMergedClassesInspectorIf( + enableHorizontalClassMerging, + inspector -> inspector.assertIsCompleteMergeGroup(A.class, C.class)) .enableNeverClassInliningAnnotations() .enableInliningAnnotations() .setMinApi(parameters.getApiLevel()) @@ -45,7 +49,8 @@ assertThat(codeInspector.clazz(MethodAnnotation.class), isPresent()); assertThat(codeInspector.clazz(A.class), isPresent()); assertThat(codeInspector.clazz(B.class), isPresent()); - assertThat(codeInspector.clazz(C.class), isPresent()); + assertThat( + codeInspector.clazz(C.class), notIf(isPresent(), enableHorizontalClassMerging)); }); } @@ -57,6 +62,7 @@ @Target({ElementType.METHOD}) public @interface MethodAnnotation {} + @TypeAnnotation @NeverClassInline public static class A { public A() {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceCollisionTest.java index 6f3c8f8..dbc83c8 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceCollisionTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceCollisionTest.java
@@ -99,6 +99,7 @@ } } + @NeverClassInline public static class C extends Parent implements I { @Override @NeverInline
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceFixedCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceFixedCollisionTest.java index 627838f..169a59b 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceFixedCollisionTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceFixedCollisionTest.java
@@ -99,6 +99,7 @@ } } + @NeverClassInline public static class C extends Parent implements I { @Override @NeverInline
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java index 635ec43..11164b1 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java
@@ -7,6 +7,7 @@ import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static org.hamcrest.MatcherAssert.assertThat; +import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; import com.android.tools.r8.NoVerticalClassMerging; import com.android.tools.r8.TestParameters; @@ -15,6 +16,7 @@ import org.junit.Test; public class OverrideAbstractMethodWithDefaultTest extends HorizontalClassMergingTestBase { + public OverrideAbstractMethodWithDefaultTest( TestParameters parameters, boolean enableHorizontalClassMerging) { super(parameters, enableHorizontalClassMerging); @@ -29,6 +31,7 @@ options -> options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging)) .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() .enableNoVerticalClassMergingAnnotations() .setMinApi(parameters.getApiLevel()) .addHorizontallyMergedClassesInspectorIf( @@ -73,8 +76,10 @@ } } + @NeverClassInline static class C1 extends B1 implements J {} + @NeverClassInline static class C2 extends B2 {} static class Main {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java index 0e3a098..15ca564 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java
@@ -7,6 +7,7 @@ import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static org.hamcrest.MatcherAssert.assertThat; +import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; import com.android.tools.r8.NoVerticalClassMerging; import com.android.tools.r8.TestParameters; @@ -29,6 +30,7 @@ options -> options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging)) .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() .enableNoVerticalClassMergingAnnotations() .setMinApi(parameters.getApiLevel()) .addHorizontallyMergedClassesInspectorIf( @@ -52,8 +54,10 @@ } } + @NeverClassInline public static class A implements I {} + @NeverClassInline public static class B implements I { @NeverInline @Override @@ -69,6 +73,7 @@ } } + @NeverClassInline public static class C extends A implements J {} public static class Main {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java index fa2dcb2..2aea3d0 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
@@ -7,6 +7,7 @@ import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static org.hamcrest.MatcherAssert.assertThat; +import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; import com.android.tools.r8.NoUnusedInterfaceRemoval; import com.android.tools.r8.NoVerticalClassMerging; @@ -30,6 +31,7 @@ options -> options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging)) .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() .enableNoUnusedInterfaceRemovalAnnotations() .enableNoVerticalClassMergingAnnotations() .setMinApi(parameters.getApiLevel()) @@ -58,8 +60,10 @@ public static class Parent implements I {} + @NeverClassInline public static class A extends Parent {} + @NeverClassInline public static class B extends Parent { @NeverInline @Override @@ -68,6 +72,7 @@ } } + @NeverClassInline @NoUnusedInterfaceRemoval @NoVerticalClassMerging interface J extends I { @@ -76,6 +81,7 @@ } } + @NeverClassInline public static class C extends A implements J {} public static class Main {
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java index 646dbf9..784e875 100644 --- a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java +++ b/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
@@ -102,7 +102,10 @@ .apply(this::configureProgram) .setIncludeClassesChecksum(true) .compile() - .run(parameters.getRuntime(), testClassName) + .run( + parameters.getRuntime(), + testClassName, + parameters.getRuntime().asDex().getVm().getVersion().toString()) .assertSuccess() .inspect(this::assertDesugaring); }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/IntegerBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/IntegerBackportJava9Test.java new file mode 100644 index 0000000..a2e1120 --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/backports/IntegerBackportJava9Test.java
@@ -0,0 +1,37 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.desugar.backports; + +import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION; + +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestRuntime.CfVm; +import com.android.tools.r8.ToolHelper; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public final class IntegerBackportJava9Test extends AbstractBackportTest { + @Parameters(name = "{0}") + public static Iterable<?> data() { + return getTestParameters() + .withCfRuntimesStartingFromIncluding(CfVm.JDK9) + .withDexRuntimes() + .withAllApiLevels() + .build(); + } + + private static final Path TEST_JAR = + Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR).resolve("backport" + JAR_EXTENSION); + + public IntegerBackportJava9Test(TestParameters parameters) { + super(parameters, Short.class, TEST_JAR, "backport.IntegerBackportJava9Main"); + // Note: None of the methods in this test exist in the latest android.jar. If/when they ship in + // an actual API level, migrate these tests to IntegerBackportTest. + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/LongBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/LongBackportJava9Test.java new file mode 100644 index 0000000..4bd90c8 --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/backports/LongBackportJava9Test.java
@@ -0,0 +1,37 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.desugar.backports; + +import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION; + +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestRuntime.CfVm; +import com.android.tools.r8.ToolHelper; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public final class LongBackportJava9Test extends AbstractBackportTest { + @Parameters(name = "{0}") + public static Iterable<?> data() { + return getTestParameters() + .withCfRuntimesStartingFromIncluding(CfVm.JDK9) + .withDexRuntimes() + .withAllApiLevels() + .build(); + } + + private static final Path TEST_JAR = + Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR).resolve("backport" + JAR_EXTENSION); + + public LongBackportJava9Test(TestParameters parameters) { + super(parameters, Short.class, TEST_JAR, "backport.LongBackportJava9Main"); + // Note: None of the methods in this test exist in the latest android.jar. If/when they ship in + // an actual API level, migrate these tests to LongBackportTest. + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaEqualityTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaEqualityTest.java index 90c8009..bf4f43f 100644 --- a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaEqualityTest.java +++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaEqualityTest.java
@@ -67,8 +67,8 @@ "false", "Empty lambda", "true", // R8 will eliminate the call to the impl method thus making lambdas equal. - "true", - "true"); + "false", + "false"); private final TestParameters parameters;
diff --git a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java index c68ec40..2e97d1e 100644 --- a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java +++ b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
@@ -57,7 +57,7 @@ .addKeepMainRule(MAIN_TYPE) .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp)) .addOptionsModification(TestingOptions::allowExperimentClassFileVersion) - .addOptionsModification(opt -> opt.testing.canUseRecords = true) + .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true) .compile() .writeToZip(); RecordTestUtils.assertRecordsAreRecords(output);
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java index 811d24a..5fce5e2 100644 --- a/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java +++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
@@ -57,7 +57,7 @@ .addKeepMainRule(MAIN_TYPE) .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp)) .addOptionsModification(TestingOptions::allowExperimentClassFileVersion) - .addOptionsModification(opt -> opt.testing.canUseRecords = true) + .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true) .compile() .writeToZip(); RecordTestUtils.assertRecordsAreRecords(output);
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java index fc7a247..071af85 100644 --- a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java +++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
@@ -70,7 +70,7 @@ .addKeepMainRule(MAIN_TYPE) .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp)) .addOptionsModification(TestingOptions::allowExperimentClassFileVersion) - .addOptionsModification(opt -> opt.testing.canUseRecords = true) + .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true) .compile() .writeToZip(); RecordTestUtils.assertRecordsAreRecords(output);
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java index eca975d..15684fe 100644 --- a/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java +++ b/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
@@ -67,7 +67,7 @@ .addKeepMainRule(MAIN_TYPE) .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp)) .addOptionsModification(TestingOptions::allowExperimentClassFileVersion) - .addOptionsModification(opt -> opt.testing.canUseRecords = true) + .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true) .compile() .writeToZip(); RecordTestUtils.assertRecordsAreRecords(output);
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java b/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java index 3a0a306..7589764 100644 --- a/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java +++ b/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java
@@ -99,7 +99,8 @@ } public static void assertRecordsAreRecords(Path output) throws IOException { - CodeInspector inspector = new CodeInspector(output, opt -> opt.testing.canUseRecords = true); + CodeInspector inspector = + new CodeInspector(output, opt -> opt.testing.enableExperimentalRecordDesugaring = true); for (FoundClassSubject clazz : inspector.allClasses()) { if (clazz.getDexProgramClass().superType.toString().equals("java.lang.Record")) { assertTrue(clazz.getDexProgramClass().isRecord());
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java index e6d1444..610b061 100644 --- a/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java +++ b/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
@@ -61,7 +61,7 @@ .addKeepMainRule(MAIN_TYPE) .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp)) .addOptionsModification(TestingOptions::allowExperimentClassFileVersion) - .addOptionsModification(opt -> opt.testing.canUseRecords = true) + .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true) .compile() .writeToZip(); RecordTestUtils.assertRecordsAreRecords(output);
diff --git a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java index ff540d1..d5d2b1a 100644 --- a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java +++ b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
@@ -13,6 +13,7 @@ import com.android.tools.r8.utils.StringUtils; import java.nio.file.Path; import java.util.List; +import org.junit.Assume; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -36,20 +37,37 @@ public static List<Object[]> data() { // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15). return buildParameters( - getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk15()).build()); + getTestParameters() + .withCustomRuntime(CfRuntime.getCheckedInJdk15()) + .withDexRuntimes() + .withAllApiLevels() + .build()); } @Test - public void testJvm() throws Exception { - testForJvm() + public void testD8AndJvm() throws Exception { + if (parameters.isCfRuntime()) { + testForJvm() + .addProgramClassFileData(PROGRAM_DATA) + .enablePreview() + .run(parameters.getRuntime(), MAIN_TYPE) + .assertSuccessWithOutput(EXPECTED_RESULT); + // TODO(b/179146128): Add a test for D8 cf to cf. + return; + } + testForD8() .addProgramClassFileData(PROGRAM_DATA) - .enablePreview() + .setMinApi(parameters.getApiLevel()) + .addOptionsModification(TestingOptions::allowExperimentClassFileVersion) + .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true) + .compile() .run(parameters.getRuntime(), MAIN_TYPE) .assertSuccessWithOutput(EXPECTED_RESULT); } @Test public void testR8Cf() throws Exception { + Assume.assumeTrue(parameters.isCfRuntime()); Path output = testForR8(parameters.getBackend()) .addProgramClassFileData(PROGRAM_DATA) @@ -58,7 +76,7 @@ .addKeepMainRule(MAIN_TYPE) .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp)) .addOptionsModification(TestingOptions::allowExperimentClassFileVersion) - .addOptionsModification(opt -> opt.testing.canUseRecords = true) + .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true) .compile() .writeToZip(); RecordTestUtils.assertRecordsAreRecords(output);
diff --git a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java index 81ae312..1584f41 100644 --- a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java +++ b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
@@ -122,11 +122,11 @@ DexEncodedMethod.EMPTY_ARRAY, new DexEncodedMethod[] {makeMethod(type, stringCount, startOffset)}, false, - DexProgramClass::invalidChecksumRequest, - synthesizedFrom); + DexProgramClass::invalidChecksumRequest); return programClass; } + // TODO(b/181636450): Reconsider this test as it no longer reflects the compiler synthetics. @Test public void manyFilesWithSharedSynthesizedClass() throws ExecutionException, IOException { InternalOptions options = new InternalOptions(dexItemFactory, new Reporter()); @@ -154,12 +154,15 @@ builder.addSynthesizedClass(sharedSynthesizedClass); classes.forEach(builder::addProgramClass); DexApplication application = builder.build(); + AppView<AppInfo> appView = AppView.createForD8(AppInfo.createInitialAppInfo(application)); + classes.forEach( + c -> appView.getSyntheticItems().addLegacySyntheticClass(sharedSynthesizedClass, c)); CollectInfoConsumer consumer = new CollectInfoConsumer(); options.programConsumer = consumer; ApplicationWriter writer = new ApplicationWriter( - AppView.createForD8(AppInfo.createInitialAppInfo(application)), + appView, null, GraphLens.getIdentityLens(), InitClassLens.getDefault(),
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java index 278d99c..96220f8 100644 --- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java +++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
@@ -18,8 +18,8 @@ import com.android.tools.r8.R8TestCompileResult; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ThrowableConsumer; import com.android.tools.r8.ToolHelper.ProcessResult; -import com.android.tools.r8.utils.ConsumerUtils; import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.ThrowingConsumer; import com.android.tools.r8.utils.codeinspector.ClassSubject; @@ -75,7 +75,7 @@ @Test public void testOnR8Splitter() throws IOException, CompilationFailedException { assumeTrue(parameters.isDexRuntime()); - Consumer<R8FullTestBuilder> configurator = + ThrowableConsumer<R8FullTestBuilder> configurator = r8FullTestBuilder -> r8FullTestBuilder.enableNoVerticalClassMergingAnnotations().noMinification(); ProcessResult processResult = @@ -84,7 +84,7 @@ ImmutableSet.of(BaseSuperClass.class), ImmutableSet.of(FeatureClass.class), FeatureClass.class, - ConsumerUtils.emptyThrowingConsumer(), + ThrowableConsumer.empty(), configurator); assertEquals(processResult.exitCode, 0);
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java index e6d0ee9..938a8f6 100644 --- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java +++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
@@ -18,8 +18,8 @@ import com.android.tools.r8.R8TestCompileResult; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ThrowableConsumer; import com.android.tools.r8.ToolHelper.ProcessResult; -import com.android.tools.r8.utils.ConsumerUtils; import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.ThrowingConsumer; import com.android.tools.r8.utils.codeinspector.ClassSubject; @@ -77,7 +77,7 @@ ImmutableSet.of(BaseSuperClass.class), ImmutableSet.of(FeatureClass.class, FeatureEnum.class), FeatureClass.class, - ConsumerUtils.emptyThrowingConsumer(), + ThrowableConsumer.empty(), R8TestBuilder::enableInliningAnnotations); assertEquals(processResult.exitCode, 0); assertEquals(processResult.stdout, EXPECTED);
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java index 780696e..aa298a7 100644 --- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java +++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
@@ -18,8 +18,8 @@ import com.android.tools.r8.R8TestCompileResult; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ThrowableConsumer; import com.android.tools.r8.ToolHelper.ProcessResult; -import com.android.tools.r8.utils.ConsumerUtils; import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.ThrowingConsumer; import com.android.tools.r8.utils.codeinspector.ClassSubject; @@ -80,7 +80,7 @@ @Test public void testOnR8Splitter() throws IOException, CompilationFailedException { assumeTrue(parameters.isDexRuntime()); - Consumer<R8FullTestBuilder> configurator = + ThrowableConsumer<R8FullTestBuilder> configurator = r8FullTestBuilder -> r8FullTestBuilder.enableNoVerticalClassMergingAnnotations().noMinification(); ProcessResult processResult = @@ -89,7 +89,7 @@ ImmutableSet.of(BaseClass.class, BaseWithStatic.class), ImmutableSet.of(FeatureClass.class, AFeatureWithStatic.class), FeatureClass.class, - ConsumerUtils.emptyThrowingConsumer(), + ThrowableConsumer.empty(), configurator); assertEquals(processResult.exitCode, 0);
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java b/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java index 46ac75d..496c3d3 100644 --- a/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java +++ b/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java
@@ -15,12 +15,11 @@ import com.android.tools.r8.R8TestCompileResult; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ThrowableConsumer; import com.android.tools.r8.ToolHelper.ProcessResult; import com.android.tools.r8.utils.StringUtils; -import com.android.tools.r8.utils.ThrowingConsumer; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.google.common.collect.ImmutableSet; -import java.util.function.Consumer; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -49,10 +48,10 @@ @Test public void testInlining() throws Exception { assumeTrue(parameters.isDexRuntime()); - Consumer<R8FullTestBuilder> configurator = + ThrowableConsumer<R8FullTestBuilder> configurator = r8FullTestBuilder -> r8FullTestBuilder.enableNoVerticalClassMergingAnnotations().noMinification(); - ThrowingConsumer<R8TestCompileResult, Exception> ensureInlined = + ThrowableConsumer<R8TestCompileResult> ensureInlined = r8TestCompileResult -> { // Ensure that isEarly from BaseUtilClass is inlined into the feature ClassSubject clazz = r8TestCompileResult.inspector().clazz(BaseUtilClass.class);
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java index dbb1e76..05d6d3d 100644 --- a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java +++ b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
@@ -13,6 +13,7 @@ import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestRuntime; +import com.android.tools.r8.ThrowableConsumer; import com.android.tools.r8.ToolHelper; import com.android.tools.r8.ToolHelper.ArtCommandBuilder; import com.android.tools.r8.ToolHelper.ProcessResult; @@ -119,14 +120,14 @@ return builder.build(); } - <E extends Throwable> ProcessResult testR8Splitter( + public ProcessResult testR8Splitter( TestParameters parameters, Set<Class<?>> baseClasses, Set<Class<?>> featureClasses, Class<?> toRun, - ThrowingConsumer<R8TestCompileResult, E> compileResultConsumer, - Consumer<R8FullTestBuilder> r8TestConfigurator) - throws IOException, CompilationFailedException, E { + ThrowableConsumer<R8TestCompileResult> compileResultConsumer, + ThrowableConsumer<R8FullTestBuilder> r8TestConfigurator) + throws IOException, CompilationFailedException { R8FullTestBuilder r8FullTestBuilder = testForR8(parameters.getBackend()); if (parameters.isCfRuntime()) { // Compiling to jar we need to support the same way of loading code at runtime as @@ -136,19 +137,19 @@ .addKeepClassAndMembersRules(PathClassLoader.class); } - r8FullTestBuilder - .addProgramClasses(SplitRunner.class, RunInterface.class) - .addProgramClasses(baseClasses) - .addFeatureSplit(featureClasses.toArray(new Class[0])) - .addInliningAnnotations() - .setMinApi(parameters.getApiLevel()) - .addKeepMainRule(SplitRunner.class) - .addKeepClassRules(toRun); + R8TestCompileResult r8TestCompileResult = + r8FullTestBuilder + .addProgramClasses(SplitRunner.class, RunInterface.class) + .addProgramClasses(baseClasses) + .addFeatureSplit(featureClasses.toArray(new Class[0])) + .addInliningAnnotations() + .setMinApi(parameters.getApiLevel()) + .addKeepMainRule(SplitRunner.class) + .addKeepClassRules(toRun) + .apply(r8TestConfigurator) + .compile() + .apply(compileResultConsumer); - r8TestConfigurator.accept(r8FullTestBuilder); - - R8TestCompileResult r8TestCompileResult = r8FullTestBuilder.compile(); - compileResultConsumer.accept(r8TestCompileResult); Path baseOutput = r8TestCompileResult.writeToZip(); return runFeatureOnArt( toRun, baseOutput, r8TestCompileResult.getFeature(0), parameters.getRuntime());
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java b/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java index 20c2b51..61b0fd7 100644 --- a/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java +++ b/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java
@@ -14,15 +14,12 @@ import com.android.tools.r8.R8TestCompileResult; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; -import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.ThrowableConsumer; import com.android.tools.r8.ToolHelper.ProcessResult; import com.android.tools.r8.references.Reference; -import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.StringUtils; -import com.android.tools.r8.utils.ThrowingConsumer; import com.android.tools.r8.utils.codeinspector.FoundClassSubject; import com.google.common.collect.ImmutableSet; -import java.util.function.Consumer; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -51,31 +48,25 @@ @Test public void testDistribution() throws Exception { assumeTrue(parameters.isDexRuntime()); - Consumer<R8FullTestBuilder> configurator = - r8FullTestBuilder -> - r8FullTestBuilder - .noMinification() - .enableInliningAnnotations() - .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.O)); - ThrowingConsumer<R8TestCompileResult, Exception> ensureLambdaNotInBase = - r8TestCompileResult -> { - r8TestCompileResult.inspect( - base -> - assertFalse(base.allClasses().stream().anyMatch(FoundClassSubject::isSynthetic)), - feature -> - assertTrue( - feature.allClasses().stream().anyMatch(FoundClassSubject::isSynthetic))); - }; + ThrowableConsumer<R8TestCompileResult> ensureLambdaNotInBase = + r8TestCompileResult -> + r8TestCompileResult.inspect( + base -> + assertFalse( + base.allClasses().stream().anyMatch(FoundClassSubject::isSynthetic)), + feature -> + assertTrue( + feature.allClasses().stream().anyMatch(FoundClassSubject::isSynthetic))); ProcessResult processResult = testR8Splitter( parameters, - ImmutableSet.of(BaseSuperClass.class), - ImmutableSet.of(FeatureClass.class, MyFunction.class), + ImmutableSet.of(BaseSuperClass.class, MyFunction.class), + ImmutableSet.of(FeatureClass.class), FeatureClass.class, ensureLambdaNotInBase, - configurator); - assertEquals(processResult.exitCode, 0); - assertEquals(processResult.stdout, StringUtils.lines("42foobar")); + this::configure); + assertEquals(0, processResult.exitCode); + assertEquals(StringUtils.lines("42foobar"), processResult.stdout); } @Test @@ -87,13 +78,7 @@ .addFeatureSplit(FeatureClass.class) .addFeatureSplit(Feature2Class.class) .addKeepFeatureMainRules(BaseSuperClass.class, FeatureClass.class, Feature2Class.class) - .addKeepMethodRules( - Reference.methodFromMethod( - BaseSuperClass.class.getDeclaredMethod( - "keptApplyLambda", MyFunction.class, String.class))) - .noMinification() - .enableInliningAnnotations() - .setMinApi(parameters.getApiLevel()) + .apply(this::configure) .compile(); compileResult @@ -105,6 +90,17 @@ .assertSuccessWithOutputLines("43barfoo"); } + private void configure(R8FullTestBuilder testBuilder) throws NoSuchMethodException { + testBuilder + .addKeepMethodRules( + Reference.methodFromMethod( + BaseSuperClass.class.getDeclaredMethod( + "keptApplyLambda", MyFunction.class, String.class))) + .enableInliningAnnotations() + .noMinification() + .setMinApi(parameters.getApiLevel()); + } + public abstract static class BaseSuperClass implements RunInterface { @Override public void run() {
diff --git a/src/test/java/com/android/tools/r8/dump/DumpMainDexInputsTest.java b/src/test/java/com/android/tools/r8/dump/DumpMainDexInputsTest.java index ede7822..7c8eafe 100644 --- a/src/test/java/com/android/tools/r8/dump/DumpMainDexInputsTest.java +++ b/src/test/java/com/android/tools/r8/dump/DumpMainDexInputsTest.java
@@ -5,6 +5,7 @@ import static java.util.stream.Collectors.toList; import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -20,7 +21,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.stream.Stream; import org.junit.Assume; @@ -32,6 +32,16 @@ public class DumpMainDexInputsTest extends TestBase { private final TestParameters parameters; + private final String mainDexRulesForMainDexFile1AndMainDexFile2 = + StringUtils.lines( + "-keep class " + MainDexFile1.class.getTypeName() + " {", + " *;", + "}", + "-keep class " + MainDexFile2.class.getTypeName() + " {", + " *;", + "}"); + private final String mainDexRulesForMainDexFile3 = + "-keep class " + MainDexFile3.class.getTypeName(); @Parameterized.Parameters(name = "{0}") public static TestParametersCollection data() { @@ -42,14 +52,33 @@ this.parameters = parameters; } - private Path newMainDexListPath() throws IOException { - Path mainDexPath = temp.newFile("main-dex-list.txt").toPath(); + private Path mainDexListForMainDexFile1AndMainDexFile2() throws IOException { + Path mainDexPath = temp.newFile("mylist1.txt").toPath(); String mainDexList = StringUtils.lines(toMainDexFormat(MainDexFile1.class), toMainDexFormat(MainDexFile2.class)); Files.write(mainDexPath, mainDexList.getBytes()); return mainDexPath; } + private Path mainDexListForMainDexFile3() throws IOException { + Path mainDexPath = temp.newFile("mylist2.txt").toPath(); + String mainDexList = StringUtils.lines(toMainDexFormat(MainDexFile3.class)); + Files.write(mainDexPath, mainDexList.getBytes()); + return mainDexPath; + } + + private Path newMainDexRulesPath1() throws IOException { + Path mainDexPath = temp.newFile("myrules1.txt").toPath(); + Files.write(mainDexPath, mainDexRulesForMainDexFile1AndMainDexFile2.getBytes()); + return mainDexPath; + } + + private Path newMainDexRulesPath2() throws IOException { + Path mainDexPath = temp.newFile("myrules2.txt").toPath(); + Files.write(mainDexPath, mainDexRulesForMainDexFile3.getBytes()); + return mainDexPath; + } + private static String toMainDexFormat(Class<?> clazz) { return clazz.getName().replace(".", "/") + FileUtils.CLASS_EXTENSION; } @@ -63,7 +92,8 @@ testForD8() .setMinApi(parameters.getApiLevel()) .addInnerClasses(DumpMainDexInputsTest.class) - .addMainDexListFiles(Collections.singleton(newMainDexListPath())) + .addMainDexListFiles( + mainDexListForMainDexFile1AndMainDexFile2(), mainDexListForMainDexFile3()) .addMainDexListClasses(MainDexClass1.class, MainDexClass2.class, TestClass.class) .addLibraryFiles(ToolHelper.getJava8RuntimeJar()) .addOptionsModification(options -> options.dumpInputToDirectory = dumpDir.toString()) @@ -74,24 +104,64 @@ "Dumping main dex list resources may have side effects due to I/O on Paths.")) .run(parameters.getRuntime(), TestClass.class) .assertSuccessWithOutputLines("Hello, world"); - verifyDumpDir(dumpDir); + verifyDumpDir(dumpDir, true); } - private void verifyDumpDir(Path dumpDir) throws IOException { + @Test + public void testD8MainDexRules() throws Exception { + Assume.assumeTrue( + "pre-native-multidex only", + parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.K)); + Path dumpDir = temp.newFolder().toPath(); + testForD8() + .setMinApi(parameters.getApiLevel()) + .addInnerClasses(DumpMainDexInputsTest.class) + .addMainDexRulesFiles(newMainDexRulesPath1(), newMainDexRulesPath2()) + .addMainDexListClasses(MainDexClass1.class, MainDexClass2.class, TestClass.class) + .addOptionsModification(options -> options.dumpInputToDirectory = dumpDir.toString()) + .compile() + .assertAllInfoMessagesMatch(containsString("Dumped compilation inputs to:")) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("Hello, world"); + verifyDumpDir(dumpDir, false); + } + + @Test + public void testR8MainDexRules() throws Exception { + Assume.assumeTrue( + "pre-native-multidex only", + parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.K)); + Path dumpDir = temp.newFolder().toPath(); + testForR8(parameters.getBackend()) + .setMinApi(parameters.getApiLevel()) + .addInnerClasses(DumpMainDexInputsTest.class) + .addMainDexRuleFiles(newMainDexRulesPath1(), newMainDexRulesPath2()) + .addMainDexListClasses(MainDexClass1.class, MainDexClass2.class, TestClass.class) + .addOptionsModification(options -> options.dumpInputToDirectory = dumpDir.toString()) + .addKeepAllClassesRule() + .allowDiagnosticMessages() + .compile() + .assertAllInfoMessagesMatch(containsString("Dumped compilation inputs to:")) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("Hello, world"); + verifyDumpDir(dumpDir, false); + } + + private void verifyDumpDir(Path dumpDir, boolean checkMainDexList) throws IOException { assertTrue(Files.isDirectory(dumpDir)); List<Path> paths = Files.walk(dumpDir, 1).collect(toList()); boolean hasVerified = false; for (Path path : paths) { if (!path.equals(dumpDir)) { // The non-external run here results in assert code calling application read. - verifyDump(path); + verifyDump(path, checkMainDexList); hasVerified = true; } } assertTrue(hasVerified); } - private void verifyDump(Path dumpFile) throws IOException { + private void verifyDump(Path dumpFile, boolean checkMainDexList) throws IOException { assertTrue(Files.exists(dumpFile)); Path unzipped = temp.newFolder().toPath(); ZipUtils.unzip(dumpFile.toString(), unzipped.toFile()); @@ -99,20 +169,27 @@ assertTrue(Files.exists(unzipped.resolve("program.jar"))); assertTrue(Files.exists(unzipped.resolve("library.jar"))); assertTrue(Files.exists(unzipped.resolve("main-dex-list.txt"))); - String mainDex = new String(Files.readAllBytes(unzipped.resolve("main-dex-list.txt"))); - List<String> mainDexLines = - Arrays.stream(mainDex.split("\n")).filter(s -> !s.isEmpty()).collect(toList()); - assertEquals(5, mainDexLines.size()); - List<String> expected = - Stream.of( - MainDexFile1.class, - MainDexFile2.class, - MainDexClass1.class, - MainDexClass2.class, - TestClass.class) - .map(DumpMainDexInputsTest::toMainDexFormat) - .collect(toList()); - assertEquals(expected, mainDexLines); + if (checkMainDexList) { + String mainDex = new String(Files.readAllBytes(unzipped.resolve("main-dex-list.txt"))); + List<String> mainDexLines = + Arrays.stream(mainDex.split("\n")).filter(s -> !s.isEmpty()).collect(toList()); + assertEquals(6, mainDexLines.size()); + List<String> expected = + Stream.of( + MainDexFile1.class, + MainDexFile2.class, + MainDexFile3.class, + MainDexClass1.class, + MainDexClass2.class, + TestClass.class) + .map(DumpMainDexInputsTest::toMainDexFormat) + .collect(toList()); + assertEquals(expected, mainDexLines); + } else { + String mainDexRules = new String(Files.readAllBytes(unzipped.resolve("main-dex-rules.txt"))); + assertThat(mainDexRules, containsString(mainDexRulesForMainDexFile1AndMainDexFile2)); + assertThat(mainDexRules, containsString(mainDexRulesForMainDexFile3)); + } } static class TestClass { @@ -129,6 +206,8 @@ static class MainDexFile2 {} + static class MainDexFile3 {} + static class NonMainDex1 {} static class NonMainDex2 {}
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV9TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV9TreeShakeJarVerificationTest.java index 3a09469..a9e28c9 100644 --- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV9TreeShakeJarVerificationTest.java +++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV9TreeShakeJarVerificationTest.java
@@ -5,7 +5,6 @@ import com.android.tools.r8.CompilationMode; import com.android.tools.r8.StringConsumer.FileConsumer; -import com.android.tools.r8.utils.AndroidApp; import java.io.File; import java.nio.file.Path; import org.junit.Test; @@ -15,12 +14,11 @@ @Test public void buildAndTreeShakeFromDeployJar() throws Exception { Path proguardMapPath = File.createTempFile("mapping", ".txt", temp.getRoot()).toPath(); - AndroidApp app = - buildAndTreeShakeFromDeployJar( - CompilationMode.RELEASE, - GMSCORE_V9_DIR, - true, - GMSCORE_V9_MAX_SIZE, - options -> options.proguardMapConsumer = new FileConsumer(proguardMapPath)); + buildAndTreeShakeFromDeployJar( + CompilationMode.RELEASE, + GMSCORE_V9_DIR, + true, + GMSCORE_V9_MAX_SIZE, + options -> options.proguardMapConsumer = new FileConsumer(proguardMapPath)); } }
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCanBePostponedTest.java b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCanBePostponedTest.java index 9461c6b..1950e53 100644 --- a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCanBePostponedTest.java +++ b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCanBePostponedTest.java
@@ -4,6 +4,7 @@ package com.android.tools.r8.ir.analysis.sideeffect; +import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; @@ -48,7 +49,7 @@ private void inspect(CodeInspector inspector) { ClassSubject classSubject = inspector.clazz(A.class); - assertThat(classSubject, isPresent()); + assertThat(classSubject, isAbsent()); // A.inlineable() should be inlined because we should be able to determine that A.<clinit>() can // safely be postponed.
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java index d78a1c7..455c5c2 100644 --- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java +++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -9,7 +9,11 @@ import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.TestRuntime.CfVm; +import com.android.tools.r8.cf.code.CfInstruction; +import com.android.tools.r8.cf.code.CfInvoke; import com.android.tools.r8.cfmethodgeneration.MethodGenerationBase; +import com.android.tools.r8.graph.CfCode; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.utils.FileUtils; import com.google.common.collect.ImmutableList; @@ -17,6 +21,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.stream.Collectors; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -73,6 +78,44 @@ return 2020; } + private static CfInstruction rewriteToJava9API( + DexItemFactory itemFactory, CfInstruction instruction) { + // Rewrite static invoke of javaUtilLongParseUnsignedLongStub to j.l.Long.parseUnsignedLong. + if (instruction.isInvoke() + && instruction + .asInvoke() + .getMethod() + .getName() + .toString() + .equals("javaLangLongParseUnsignedLongStub")) { + CfInvoke invoke = instruction.asInvoke(); + return new CfInvoke( + invoke.getOpcode(), + itemFactory.createMethod( + itemFactory.createType("Ljava/lang/Long;"), + invoke.getMethod().getProto(), + itemFactory.createString("parseUnsignedLong")), + invoke.isInterface()); + } else { + return instruction; + } + } + + @Override + protected CfCode getCode(String holderName, String methodName, CfCode code) { + if (methodName.endsWith("Stub")) { + // Don't include stubs targeted only for rewriting in the generated code. + return null; + } + if (holderName.equals("LongMethods") && methodName.equals("parseUnsignedLongWithRadix")) { + code.setInstructions( + code.getInstructions().stream() + .map(instruction -> rewriteToJava9API(factory, instruction)) + .collect(Collectors.toList())); + } + return code; + } + @Test public void testBackportsGenerated() throws Exception { ArrayList<Class<?>> sorted = new ArrayList<>(getMethodTemplateClasses());
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/IntegerMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/IntegerMethods.java index 0560b97..a3bd157 100644 --- a/src/test/java/com/android/tools/r8/ir/desugar/backports/IntegerMethods.java +++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/IntegerMethods.java
@@ -57,4 +57,9 @@ long asLong = i & 0xffffffffL; return Long.toString(asLong, radix); } + + public static int parseIntSubsequenceWithRadix( + CharSequence s, int beginIndex, int endIndex, int radix) throws NumberFormatException { + return Integer.parseInt(s.subSequence(beginIndex, endIndex).toString(), radix); + } }
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/LongMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/LongMethods.java index 643d295..65a60ef 100644 --- a/src/test/java/com/android/tools/r8/ir/desugar/backports/LongMethods.java +++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/LongMethods.java
@@ -10,6 +10,11 @@ return (int) (l ^ (l >>> 32)); } + public static long parseLongSubsequenceWithRadix( + CharSequence s, int beginIndex, int endIndex, int radix) { + return Long.parseLong(s.subSequence(beginIndex, endIndex).toString(), radix); + } + public static long divideUnsigned(long dividend, long divisor) { // This implementation is adapted from Guava's UnsignedLongs.java and Longs.java. @@ -92,10 +97,20 @@ return Long.parseUnsignedLong(s, 10); } + private static long javaLangLongParseUnsignedLongStub( + CharSequence s, int beginIndex, int endIndex, int radix) { + throw new RuntimeException("Stub invoked"); + } + public static long parseUnsignedLongWithRadix(String s, int radix) { + return javaLangLongParseUnsignedLongStub(s, 0, s.length(), radix); + } + + public static long parseUnsignedLongSubsequenceWithRadix( + CharSequence s, int beginIndex, int endIndex, int radix) { // This implementation is adapted from Guava's UnsignedLongs.java - int length = s.length(); + int length = endIndex - beginIndex; if (length == 0) { throw new NumberFormatException("empty string"); } @@ -106,23 +121,23 @@ long maxValueBeforeRadixMultiply = Long.divideUnsigned(-1L, radix); // If the string starts with '+' and contains at least two characters, skip the plus. - int start = s.charAt(0) == '+' && length > 1 ? 1 : 0; + int start = s.charAt(beginIndex) == '+' && length > 1 ? beginIndex + 1 : beginIndex; long value = 0; - for (int pos = start; pos < length; pos++) { + for (int pos = start; pos < endIndex; pos++) { int digit = Character.digit(s.charAt(pos), radix); if (digit == -1) { - throw new NumberFormatException(s); + throw new NumberFormatException(s.toString()); } - if (// high bit is already set - value < 0 + if ( // high bit is already set + value < 0 // or radix multiply will overflow || value > maxValueBeforeRadixMultiply // or digit add will overflow after radix multiply || (value == maxValueBeforeRadixMultiply && digit > (int) Long.remainderUnsigned(-1L, radix))) { // Explicit String.concat to work around https://issuetracker.google.com/issues/136596951. - throw new NumberFormatException("Too large for unsigned long: ".concat(s)); + throw new NumberFormatException("Too large for unsigned long: ".concat(s.toString())); } value = (value * radix) + digit; }
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 1740c8a..6001549 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
@@ -73,7 +73,7 @@ .addKeepMainRule(Main.class) .addOptionsModification( options -> { - options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE); + options.testing.validInliningReasons = ImmutableSet.of(Reason.ALWAYS); options.testing.inlineeIrModifier = this::modifyIr; }) .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderTest.java index 1d1f00e..b1e3cfc 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderTest.java
@@ -77,24 +77,12 @@ private void inspect(CodeInspector inspector) { ClassSubject clazz = inspector.clazz(TestClass.class); - if (parameters.isCfRuntime()) { - assertThat(inspector.clazz(PairBuilder.class), isPresent()); + assertThat(inspector.clazz(PairBuilder.class), not(isPresent())); - // const-string canonicalization is disabled in CF, which helps ClassInliner identify - // PairBuilder as candidate. - Set<String> expected = - ImmutableSet.of(StringBuilder.class.getTypeName(), PairBuilder.class.getTypeName()); - assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder1"))); - assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder2"))); - assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder3"))); - } else { - assertThat(inspector.clazz(PairBuilder.class), not(isPresent())); - - Set<String> expected = ImmutableSet.of(StringBuilder.class.getTypeName()); - assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder1"))); - assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder2"))); - assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder3"))); - } + Set<String> expected = ImmutableSet.of(StringBuilder.class.getTypeName()); + assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder1"))); + assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder2"))); + assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder3"))); } static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/LibraryOverrideClassInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/LibraryOverrideClassInliningTest.java index 4f020fd..24c1c8b 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/LibraryOverrideClassInliningTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/LibraryOverrideClassInliningTest.java
@@ -6,6 +6,7 @@ import static com.android.tools.r8.references.Reference.methodFromMethod; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import com.android.tools.r8.NeverInline; @@ -113,8 +114,9 @@ .holder .toString() .equals(SimpleLibraryOverride.class.getTypeName()))); - // Check the non-simple run is not inlined. - assertTrue( + // TODO(b/181942160): The non-simple run should ideally not inlined by the class inliner, since + // there is an instance of NonSimpleLibraryOverride that is not eligible for class inlining. + assertFalse( "Expected NonSimple.run invoke in:\n" + main.getMethod().codeToString(), main.streamInstructions() .anyMatch(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/fields/SwitchOnConstantClassIdAfterBranchPruningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/fields/SwitchOnConstantClassIdAfterBranchPruningTest.java new file mode 100644 index 0000000..9ddf887 --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/optimize/fields/SwitchOnConstantClassIdAfterBranchPruningTest.java
@@ -0,0 +1,103 @@ +// Copyright (c) 2021, 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.fields; + +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.assertTrue; + +import com.android.tools.r8.NeverClassInline; +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.FoundMethodSubject; +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.Parameters; + +@RunWith(Parameterized.class) +public class SwitchOnConstantClassIdAfterBranchPruningTest extends TestBase { + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public SwitchOnConstantClassIdAfterBranchPruningTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .addHorizontallyMergedClassesInspector( + inspector -> inspector.assertIsCompleteMergeGroup(A.class, B.class, C.class)) + .enableNeverClassInliningAnnotations() + .setMinApi(parameters.getApiLevel()) + .noMinification() + .compile() + .inspect( + inspector -> { + ClassSubject aClassSubject = inspector.clazz(A.class); + assertThat(aClassSubject, isPresent()); + assertEquals(0, aClassSubject.allInstanceFields().size()); + + MethodSubject mMethodSubject = + aClassSubject.uniqueMethodThatMatches(FoundMethodSubject::isVirtual); + assertThat(mMethodSubject, isPresent()); + assertTrue( + mMethodSubject.streamInstructions().noneMatch(x -> x.isIf() || x.isSwitch())); + }) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("A"); + } + + static class Main { + + public static void main(String[] args) { + new A().m(); + if (alwaysFalse()) { + new B().m(); + new C().m(); + } + } + + static boolean alwaysFalse() { + return false; + } + } + + @NeverClassInline + static class A { + + void m() { + System.out.println("A"); + } + } + + @NeverClassInline + static class B { + + void m() { + System.out.println("B"); + } + } + + @NeverClassInline + static class C { + + void m() { + System.out.println("C"); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/fields/SwitchOnNonConstantClassIdAfterBranchPruningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/fields/SwitchOnNonConstantClassIdAfterBranchPruningTest.java new file mode 100644 index 0000000..a2bfb2c --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/optimize/fields/SwitchOnNonConstantClassIdAfterBranchPruningTest.java
@@ -0,0 +1,105 @@ +// Copyright (c) 2021, 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.fields; + +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.assertTrue; + +import com.android.tools.r8.NeverClassInline; +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.FoundMethodSubject; +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.Parameters; + +@RunWith(Parameterized.class) +public class SwitchOnNonConstantClassIdAfterBranchPruningTest extends TestBase { + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public SwitchOnNonConstantClassIdAfterBranchPruningTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .addHorizontallyMergedClassesInspector( + inspector -> inspector.assertIsCompleteMergeGroup(A.class, B.class, C.class)) + .enableNeverClassInliningAnnotations() + .setMinApi(parameters.getApiLevel()) + .noMinification() + .compile() + .inspect( + inspector -> { + ClassSubject aClassSubject = inspector.clazz(A.class); + assertThat(aClassSubject, isPresent()); + assertEquals(1, aClassSubject.allInstanceFields().size()); + + MethodSubject mMethodSubject = + aClassSubject.uniqueMethodThatMatches(FoundMethodSubject::isVirtual); + assertThat(mMethodSubject, isPresent()); + assertTrue( + mMethodSubject + .streamInstructions() + .noneMatch(instruction -> instruction.isConstString("C"))); + }) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("A", "B"); + } + + static class Main { + + public static void main(String[] args) { + new A().m(); + new B().m(); + if (alwaysFalse()) { + new C().m(); + } + } + + static boolean alwaysFalse() { + return false; + } + } + + @NeverClassInline + static class A { + + void m() { + System.out.println("A"); + } + } + + @NeverClassInline + static class B { + + void m() { + System.out.println("B"); + } + } + + @NeverClassInline + static class C { + + void m() { + System.out.println("C"); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InterfaceMethodTest.java index f489112..209f9f9 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InterfaceMethodTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InterfaceMethodTest.java
@@ -7,6 +7,7 @@ import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static org.hamcrest.MatcherAssert.assertThat; +import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.NoVerticalClassMerging; @@ -48,6 +49,7 @@ .addInnerClasses(InterfaceMethodTest.class) .addKeepMainRule(TestClass.class) .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() .enableNoVerticalClassMergingAnnotations() .enableNoHorizontalClassMergingAnnotations() .run(TestClass.class) @@ -84,6 +86,7 @@ Uninstantiated m(); } + @NeverClassInline static class A implements I { @NeverInline @@ -97,6 +100,7 @@ // The purpose of this class is merely to avoid that the invoke-interface instruction in // TestClass.test() gets devirtualized to an invoke-virtual instruction. Otherwise the method // I.m() would not be present in the output. + @NeverClassInline @NoHorizontalClassMerging static class B implements I {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java index 7890640..3cbaed0 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java
@@ -7,6 +7,7 @@ import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static org.hamcrest.MatcherAssert.assertThat; +import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; import com.android.tools.r8.NoVerticalClassMerging; import com.android.tools.r8.TestBase; @@ -49,6 +50,7 @@ .addInnerClasses(NestedInterfaceMethodTest.class) .addKeepMainRule(TestClass.class) .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() .enableNoVerticalClassMergingAnnotations() .addOptionsModification( options -> { @@ -91,6 +93,7 @@ @NoVerticalClassMerging interface J extends I {} + @NeverClassInline static class A implements J { @Override @@ -100,11 +103,13 @@ } } + @NeverClassInline static class B extends A {} // The purpose of this class is merely to avoid that the invoke-interface instruction in // TestClass.test() gets devirtualized to an invoke-virtual instruction. Otherwise the method // I.m() would not be present in the output. + @NeverClassInline static class C extends A {} static class Uninstantiated {}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/SynchronizedMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/SynchronizedMethodTest.java index 88459a5..a04df37 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/SynchronizedMethodTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/SynchronizedMethodTest.java
@@ -4,11 +4,17 @@ package com.android.tools.r8.ir.optimize.uninstantiatedtypes; +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.assertSame; +import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; @@ -18,23 +24,41 @@ import java.util.List; import java.util.stream.Collectors; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +@RunWith(Parameterized.class) public class SynchronizedMethodTest extends TestBase { + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public SynchronizedMethodTest(TestParameters parameters) { + this.parameters = parameters; + } + @Test public void test() throws Exception { String expectedOutput = StringUtils.lines("In A.m()", "Got NullPointerException"); CodeInspector inspector = - testForR8(Backend.DEX) + testForR8(parameters.getBackend()) .addInnerClasses(SynchronizedMethodTest.class) .addKeepMainRule(TestClass.class) .enableInliningAnnotations() - .run(TestClass.class) + .enableNeverClassInliningAnnotations() + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), TestClass.class) .assertSuccessWithOutput(expectedOutput) .inspector(); ClassSubject clazz = inspector.clazz(A.class); MethodSubject method = clazz.uniqueMethodWithName("m"); + assertThat(method, isPresent()); // The invoke on the uninstantiated turns into a "throw null", and the synchronized method // already have a throw instruction in the catch all handler ensuring monitor exit is called. @@ -43,24 +67,30 @@ .streamInstructions() .filter(InstructionSubject::isThrow) .collect(Collectors.toList()); - assertEquals(2, throwInstructions.size()); + assertEquals(1 + BooleanUtils.intValue(parameters.isDexRuntime()), throwInstructions.size()); - // The inserted "throw null" should still be covered by the catch all to ensure monitor exit - // is called. - List<InstructionSubject> catchAllCoveredInstructions = new ArrayList<>(); - method.iterateTryCatches().forEachRemaining(tryCatchSubject -> { - if (tryCatchSubject.hasCatchAll()) { - catchAllCoveredInstructions.addAll( - throwInstructions - .stream() - .filter( - throwInstruction -> - tryCatchSubject.getRange().includes(throwInstruction.getOffset(method))) - .collect(Collectors.toList())); - } - }); - assertEquals(1, catchAllCoveredInstructions.size()); - assertSame(throwInstructions.get(0), catchAllCoveredInstructions.get(0)); + if (parameters.isDexRuntime()) { + // The inserted "throw null" should still be covered by the catch all to ensure monitor exit + // is called. + List<InstructionSubject> catchAllCoveredInstructions = new ArrayList<>(); + method + .iterateTryCatches() + .forEachRemaining( + tryCatchSubject -> { + if (tryCatchSubject.hasCatchAll()) { + catchAllCoveredInstructions.addAll( + throwInstructions.stream() + .filter( + throwInstruction -> + tryCatchSubject + .getRange() + .includes(throwInstruction.getOffset(method))) + .collect(Collectors.toList())); + } + }); + assertEquals(1, catchAllCoveredInstructions.size()); + assertSame(throwInstructions.get(0), catchAllCoveredInstructions.get(0)); + } } static class TestClass { @@ -79,6 +109,7 @@ } } + @NeverClassInline static class A { @NeverInline
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsWithMissingAnnotationsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsWithMissingAnnotationsTest.java index ef269dc..49b90e7 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsWithMissingAnnotationsTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsWithMissingAnnotationsTest.java
@@ -32,7 +32,7 @@ @Parameterized.Parameters(name = "{0}") public static TestParametersCollection data() { - return getTestParameters().withAllRuntimes().build(); + return getTestParameters().withAllRuntimesAndApiLevels().build(); } public UnusedAnnotatedArgumentsWithMissingAnnotationsTest(TestParameters parameters) { @@ -72,17 +72,20 @@ dumpAnnotation2(), dumpAnnotation3()) .addKeepMainRule("Test") - .addKeepRules("-keep @interface Annotation?") - .addKeepAttributes("RuntimeVisibleParameterAnnotations") + .addKeepRules( + "-keep @interface Annotation?", + "-neverclassinline class *", + "-nohorizontalclassmerging class Test$Inner?") + .addKeepRuntimeVisibleParameterAnnotations() .enableProguardTestOptions() - .addKeepRules("-neverclassinline class *") - .setMinApi(parameters.getRuntime()) + .setMinApi(parameters.getApiLevel()) .compile() - .inspect(inspector -> { - checkClass(inspector.clazz("Test$Inner1"), "Annotation1"); - checkClass(inspector.clazz("Test$Inner2"), "Annotation2"); - checkClass(inspector.clazz("Test$Inner2"), "Annotation2"); - }) + .inspect( + inspector -> { + checkClass(inspector.clazz("Test$Inner1"), "Annotation1"); + checkClass(inspector.clazz("Test$Inner2"), "Annotation2"); + checkClass(inspector.clazz("Test$Inner2"), "Annotation2"); + }) .run(parameters.getRuntime(), "Test") .assertSuccessWithOutput(expectedOutput); }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java index 52131b3..768fbd6 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java
@@ -8,6 +8,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; +import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; import com.android.tools.r8.NoVerticalClassMerging; import com.android.tools.r8.TestBase; @@ -40,6 +41,7 @@ .addInnerClasses(UnusedInterfaceWithDefaultMethodTest.class) .addKeepMainRule(TestClass.class) .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() .enableNoVerticalClassMergingAnnotations() .setMinApi(parameters.getApiLevel()) .compile() @@ -94,5 +96,6 @@ } } + @NeverClassInline static class A implements J {} }
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java index 16413e0..f6b24da 100644 --- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -6,6 +6,7 @@ import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static com.android.tools.r8.utils.codeinspector.Matchers.notIf; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -20,12 +21,11 @@ import com.android.tools.r8.graph.DexType; import com.android.tools.r8.naming.MemberNaming.MethodSignature; import com.android.tools.r8.utils.codeinspector.ClassSubject; -import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.InstructionSubject; import com.android.tools.r8.utils.codeinspector.MethodSubject; import com.google.common.collect.Lists; -import com.google.common.collect.Sets; import com.google.common.collect.Streams; +import java.util.Collections; import java.util.List; import java.util.Set; import java.util.function.Predicate; @@ -63,15 +63,6 @@ && clazz.getInterfaces().size() == 1; } - private static Predicate<DexType> createLambdaCheck(CodeInspector inspector) { - Set<DexType> lambdaClasses = - inspector.allClasses().stream() - .filter(clazz -> isLambda(clazz.getDexProgramClass())) - .map(clazz -> clazz.getDexProgramClass().type) - .collect(Collectors.toSet()); - return lambdaClasses::contains; - } - @Test public void testJStyleLambdas() throws Exception { String mainClassName = "class_inliner_lambda_j_style.MainKt"; @@ -116,10 +107,10 @@ .addKeepRules("-neverinline class * { void test*State*(...); }")) .inspect( inspector -> { - // TODO(b/173337498): MainKt$testStateless$1 should be class inlined. + // TODO(b/173337498): MainKt$testStateless$1 should always be class inlined. assertThat( inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateless$1"), - isPresent()); + notIf(isPresent(), testParameters.isDexRuntime())); // TODO(b/173337498): MainKt$testStateful$1 should be class inlined. assertThat( @@ -209,10 +200,8 @@ .inspect( inspector -> { ClassSubject clazz = inspector.clazz(mainClassName); - - // TODO(b/141719453): Data class should maybe be class inlined. assertEquals( - Sets.newHashSet("class_inliner_data_class.Alpha"), + Collections.emptySet(), collectAccessedTypes( type -> !type.toSourceString().startsWith("java."), clazz,
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java index d3d99ee..2dd90c1 100644 --- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java +++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
@@ -120,7 +120,7 @@ testForD8() .setMinApi(AndroidApiLevel.K) .addProgramClasses(ImmutableList.of(TestClass.class, MyConsumer.class)) - .addMainDexListClasses(TestClass.class) + .addMainDexKeepClassAndMemberRules(TestClass.class) .setMainDexListConsumer(consumer) .compile(); assertTrue(consumer.called); @@ -141,7 +141,7 @@ testForD8() .setMinApi(AndroidApiLevel.K) .addProgramFiles(dexOutput) - .addMainDexKeepClassRules(TestClass.class) + .addMainDexKeepClassAndMemberRules(TestClass.class) .setMainDexListConsumer(consumer) .compile(); assertTrue(consumer.called);
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java index 5e88d7d..8252f4d 100644 --- a/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java +++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
@@ -8,11 +8,14 @@ import com.android.tools.r8.D8TestCompileResult; import com.android.tools.r8.OutputMode; +import com.android.tools.r8.R8TestCompileResult; import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestCompileResult; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.synthesis.SyntheticItemsTestUtils; import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.codeinspector.CodeInspector; import java.nio.file.Files; @@ -26,7 +29,7 @@ static final AndroidApiLevel nativeMultiDexLevel = AndroidApiLevel.L; - static final String EXPECTED = StringUtils.lines("AB"); + static final String EXPECTED = StringUtils.lines("A"); private final TestParameters parameters; @@ -53,8 +56,8 @@ D8TestCompileResult compileResult = testForD8() .addInnerClasses(MainDexWithSynthesizedClassesTest.class) - .addMainDexListClasses(TestClass.class, A.class) - .setMinApiThreshold(parameters.getApiLevel()) + .addMainDexKeepClassAndMemberRules(TestClass.class) + .setMinApi(parameters.getApiLevel()) .compile(); checkCompilationResult(compileResult); } @@ -66,19 +69,67 @@ D8TestCompileResult intermediateResult = testForD8() .addInnerClasses(MainDexWithSynthesizedClassesTest.class) - .setMinApiThreshold(parameters.getApiLevel()) + .setMinApi(parameters.getApiLevel()) .setIntermediate(true) .compile(); D8TestCompileResult compileResult = testForD8() .addProgramFiles(intermediateResult.writeToZip()) - .addMainDexKeepClassRules(TestClass.class, A.class) - .setMinApiThreshold(parameters.getApiLevel()) + .addMainDexKeepClassAndMemberRules(TestClass.class) + .setMinApi(parameters.getApiLevel()) .compile(); checkCompilationResult(compileResult); } + /** + * This test checks for maintained support of including synthetics from main-dex-list entries in + * the main-dex file. This test simulates that the tracing done at the class-file level has + * determined that TestClass and A are both traced. Thus the synthetic lambda from A will be + * included in the main-dex file. + * + * <p>TODO(b/181858113): Remove once deprecated main-dex-list is removed. + */ + @Test + public void testDeprecatedSyntheticsFromMainDexListD8() throws Exception { + assumeTrue(parameters.isDexRuntime()); + D8TestCompileResult compileResult = + testForD8() + .addInnerClasses(MainDexWithSynthesizedClassesTest.class) + .addMainDexListClasses(TestClass.class, A.class) + .setMinApi(parameters.getApiLevel()) + .compile(); + checkCompilationResult(compileResult); + } + + /** + * This test checks for maintained support of including synthetics from main-dex-list entries in + * the main-dex file. This test simulates that the tracing done at the class-file level has + * determined that TestClass and A are both traced. Thus the synthetic lambda from A will be + * included in the main-dex file. + * + * <p>TODO(b/181858113): Remove once deprecated main-dex-list is removed. + */ + @Test + public void testDeprecatedSyntheticsFromMainDexListR8() throws Exception { + assumeTrue(parameters.isDexRuntime()); + R8TestCompileResult compileResult = + testForR8(parameters.getBackend()) + .addInnerClasses(MainDexWithSynthesizedClassesTest.class) + .setMinApi(parameters.getApiLevel()) + .addOptionsModification(o -> o.minimalMainDex = true) + .addMainDexListClasses(TestClass.class, A.class) + .noMinification() + .noTreeShaking() + .compile(); + checkCompilationResult(compileResult, compileResult.app); + } + private void checkCompilationResult(D8TestCompileResult compileResult) throws Exception { + checkCompilationResult(compileResult, compileResult.app); + } + + private void checkCompilationResult(TestCompileResult compileResult, AndroidApp app) + throws Exception { if (parameters.getRuntime().asDex().getMinApiLevel().getLevel() < nativeMultiDexLevel.getLevel()) { compileResult.runDex2Oat(parameters.getRuntime()).assertNoVerificationErrors(); @@ -86,7 +137,7 @@ compileResult.run(parameters.getRuntime(), TestClass.class).assertSuccessWithOutput(EXPECTED); } Path out = temp.newFolder().toPath(); - compileResult.apply(b -> b.app.writeToDirectory(out, OutputMode.DexIndexed)); + app.writeToDirectory(out, OutputMode.DexIndexed); Path classes = out.resolve("classes.dex"); Path classes2 = out.resolve("classes2.dex"); assertTrue(Files.exists(classes)); @@ -119,7 +170,7 @@ static class A { Getter foo() { - return () -> "A" + new B().foo().get(); + return () -> "A"; } }
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java index fb89853..3727423 100644 --- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java +++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
@@ -338,6 +338,7 @@ static class B extends A { + @Override public void method() { System.out.println("In B.method()"); super.method();
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java index 6a01e83..85c8459 100644 --- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java +++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
@@ -4,6 +4,7 @@ package com.android.tools.r8.naming; +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; @@ -11,50 +12,51 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import com.android.tools.r8.CompilationFailedException; import com.android.tools.r8.DataDirectoryResource; import com.android.tools.r8.DataEntryResource; import com.android.tools.r8.DataResourceConsumer; import com.android.tools.r8.DataResourceProvider.Visitor; -import com.android.tools.r8.R8Command; -import com.android.tools.r8.StringConsumer; +import com.android.tools.r8.R8FullTestBuilder; +import com.android.tools.r8.R8TestBuilder; +import com.android.tools.r8.R8TestCompileResult; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ThrowableConsumer; import com.android.tools.r8.ToolHelper; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase; -import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.ArchiveResourceProvider; import com.android.tools.r8.utils.DataResourceConsumerForTesting; import com.android.tools.r8.utils.FileUtils; -import com.android.tools.r8.utils.KeepingDiagnosticHandler; import com.android.tools.r8.utils.StringUtils; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.io.File; -import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; import java.util.stream.Collectors; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; @RunWith(Parameterized.class) public class AdaptResourceFileNamesTest extends ProguardCompatibilityTestBase { - private Backend backend; + private final TestParameters parameters; - @Parameterized.Parameters(name = "Backend: {0}") - public static Backend[] data() { - return ToolHelper.getBackends(); + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); } - public AdaptResourceFileNamesTest(Backend backend) { - this.backend = backend; + public AdaptResourceFileNamesTest(TestParameters parameters) { + this.parameters = parameters; } private static final Path CF_DIR = @@ -63,15 +65,6 @@ Paths.get(ToolHelper.EXAMPLES_BUILD_DIR) .resolve("adaptresourcefilenames" + FileUtils.JAR_EXTENSION); - private KeepingDiagnosticHandler diagnosticsHandler; - private ClassNameMapper mapper = null; - - @Before - public void reset() { - diagnosticsHandler = new KeepingDiagnosticHandler(); - mapper = null; - } - private static String getProguardConfig( boolean enableAdaptResourceFileNames, String adaptResourceFileNamesPathFilter) { String adaptResourceFilenamesRule; @@ -86,7 +79,6 @@ return String.join( System.lineSeparator(), adaptResourceFilenamesRule, - "-keeppackagenames adaptresourcefilenames**", "-keep class adaptresourcefilenames.TestClass {", " public static void main(...);", "}"); @@ -114,30 +106,38 @@ } @Test - public void testEnabled() throws Exception { + public void testEnabled() throws Throwable { DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting(); compileWithR8( getProguardConfigWithNeverInline(true, null), dataResourceConsumer, - ToolHelper.consumeString(this::checkR8Renamings)); - // Check that the generated resources have the expected names. - for (DataEntryResource dataResource : getOriginalDataResources()) { - assertNotNull( - "Resource not renamed as expected: " + dataResource.getName(), - dataResourceConsumer.get(getExpectedRenamingFor(dataResource.getName(), mapper))); - } + R8TestBuilder::enableProguardTestOptions, + mapper -> { + checkR8Renamings(mapper); + // Check that the generated resources have the expected names. + for (DataEntryResource dataResource : getOriginalDataResources()) { + ImmutableList<String> object = + dataResourceConsumer.get(getExpectedRenamingFor(dataResource.getName(), mapper)); + if (object == null) { + object = + dataResourceConsumer.get(getExpectedRenamingFor(dataResource.getName(), mapper)); + } + assertNotNull("Resource not renamed as expected: " + dataResource.getName(), object); + } + }); } @Test - public void testEnabledWithFilter() throws Exception { + public void testEnabledWithFilter() throws Throwable { DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting(); compileWithR8( getProguardConfigWithNeverInline(true, "**.md"), dataResourceConsumer, - ToolHelper.consumeString(this::checkR8Renamings)); + R8TestBuilder::enableProguardTestOptions, + this::checkR8Renamings); // Check that the generated resources have the expected names. Map<String, String> expectedRenamings = - ImmutableMap.of("adaptresourcefilenames/B.md", "adaptresourcefilenames/b.md"); + ImmutableMap.of("adaptresourcefilenames/B.md", "a/b.md"); for (DataEntryResource dataResource : getOriginalDataResources()) { assertNotNull( "Resource not renamed as expected: " + dataResource.getName(), @@ -147,9 +147,12 @@ } @Test - public void testDisabled() throws Exception { + public void testDisabled() throws Throwable { DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting(); - compileWithR8(getProguardConfigWithNeverInline(false, null), dataResourceConsumer); + compileWithR8( + getProguardConfigWithNeverInline(false, null), + dataResourceConsumer, + R8TestBuilder::enableProguardTestOptions); // Check that none of the resources were renamed. for (DataEntryResource dataResource : getOriginalDataResources()) { assertNotNull( @@ -159,22 +162,25 @@ } @Test - public void testCollisionBehavior() throws Exception { + public void testCollisionBehavior() throws Throwable { DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting(); - compileWithR8( - getProguardConfigWithNeverInline(true, null), - dataResourceConsumer, - ToolHelper.consumeString(this::checkR8Renamings), - ImmutableList.<DataEntryResource>builder() - .addAll(getOriginalDataResources()) - .add( - DataEntryResource.fromBytes( - new byte[0], "adaptresourcefilenames/b.txt", Origin.unknown())) - .build()); - assertEquals(1, diagnosticsHandler.warnings.size()); - assertThat( - diagnosticsHandler.warnings.get(0).getDiagnosticMessage(), - containsString("Resource 'adaptresourcefilenames/b.txt' already exists.")); + R8TestCompileResult compileResult = + compileWithR8( + getProguardConfigWithNeverInline(true, null), + dataResourceConsumer, + builder -> { + builder.enableProguardTestOptions(); + builder.allowDiagnosticWarningMessages(); + }, + this::checkR8Renamings, + ImmutableList.<DataEntryResource>builder() + .addAll(getOriginalDataResources()) + .add(DataEntryResource.fromBytes(new byte[0], "a/b.txt", Origin.unknown())) + .build()); + compileResult.inspectDiagnosticMessages( + diagnosticMessages -> + diagnosticMessages.assertWarningsMatch( + diagnosticMessage(containsString("Resource 'a/b.txt' already exists.")))); assertEquals(getOriginalDataResources().size(), dataResourceConsumer.size()); } @@ -243,64 +249,59 @@ .count()); } - private AndroidApp compileWithR8(String proguardConfig, DataResourceConsumer dataResourceConsumer) - throws CompilationFailedException, IOException { - return compileWithR8(proguardConfig, dataResourceConsumer, null); - } - - private AndroidApp compileWithR8( + private void compileWithR8( String proguardConfig, DataResourceConsumer dataResourceConsumer, - StringConsumer proguardMapConsumer) - throws CompilationFailedException, IOException { - return compileWithR8( - proguardConfig, dataResourceConsumer, proguardMapConsumer, getOriginalDataResources()); + ThrowableConsumer<R8FullTestBuilder> builderConsumer) + throws Throwable { + compileWithR8(proguardConfig, dataResourceConsumer, builderConsumer, null); } - private AndroidApp compileWithR8( + private void compileWithR8( String proguardConfig, DataResourceConsumer dataResourceConsumer, - StringConsumer proguardMapConsumer, + ThrowableConsumer<R8FullTestBuilder> builderConsumer, + Consumer<ClassNameMapper> proguardMapConsumer) + throws Throwable { + compileWithR8( + proguardConfig, + dataResourceConsumer, + builderConsumer, + proguardMapConsumer, + getOriginalDataResources()); + } + + private R8TestCompileResult compileWithR8( + String proguardConfig, + DataResourceConsumer dataResourceConsumer, + ThrowableConsumer<R8FullTestBuilder> builderConsumer, + Consumer<ClassNameMapper> proguardMapConsumer, List<DataEntryResource> dataResources) - throws CompilationFailedException, IOException { - R8Command command = - ToolHelper.allowTestProguardOptions( - ToolHelper.prepareR8CommandBuilder( - getAndroidApp(dataResources), emptyConsumer(backend), diagnosticsHandler) - .addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown())) - .addLibraryFiles(runtimeJar(backend)) - .build(); - return ToolHelper.runR8( - command, - options -> { - options.dataResourceConsumer = dataResourceConsumer; - options.proguardMapConsumer = proguardMapConsumer; - }); - } - - private void checkR8Renamings(String proguardMap) { - try { - // Check that the renamings are as expected. These exact renamings are not important as - // such, but the test expectations rely on them. - mapper = ClassNameMapper.mapperFromString(proguardMap); - assertEquals( - "adaptresourcefilenames.TestClass", - mapper.deobfuscateClassName("adaptresourcefilenames.TestClass")); - assertEquals( - "adaptresourcefilenames.B", mapper.deobfuscateClassName("adaptresourcefilenames.b")); - assertEquals( - "adaptresourcefilenames.B$Inner", - mapper.deobfuscateClassName("adaptresourcefilenames.a")); - } catch (IOException e) { - throw new RuntimeException(e); + throws Throwable { + R8TestCompileResult compile = + testForR8(parameters.getBackend()) + .addProgramFiles(ToolHelper.getClassFilesForTestDirectory(CF_DIR)) + .addDataResources(dataResources) + .addKeepRules(proguardConfig) + .apply(builderConsumer) + .setMinApi(parameters.getApiLevel()) + .addOptionsModification(options -> options.dataResourceConsumer = dataResourceConsumer) + .compile(); + if (proguardMapConsumer != null) { + compile.inspectProguardMap( + map -> proguardMapConsumer.accept(ClassNameMapper.mapperFromString(map))); } + return compile; } - private AndroidApp getAndroidApp(List<DataEntryResource> dataResources) throws IOException { - AndroidApp.Builder builder = AndroidApp.builder(); - builder.addProgramFiles(ToolHelper.getClassFilesForTestDirectory(CF_DIR)); - dataResources.forEach(builder::addDataResource); - return builder.build(); + private void checkR8Renamings(ClassNameMapper mapper) { + // Check that the renamings are as expected. These exact renamings are not important as + // such, but the test expectations rely on them. + assertEquals( + "adaptresourcefilenames.TestClass", + mapper.deobfuscateClassName("adaptresourcefilenames.TestClass")); + assertEquals("adaptresourcefilenames.B", mapper.deobfuscateClassName("a.b")); + assertEquals("adaptresourcefilenames.B$Inner", mapper.deobfuscateClassName("a.a")); } private static List<DataEntryResource> getOriginalDataResources() {
diff --git a/src/test/java/com/android/tools/r8/naming/b139991218/TestRunner.java b/src/test/java/com/android/tools/r8/naming/b139991218/TestRunner.java index d9b9a3c..218e037 100644 --- a/src/test/java/com/android/tools/r8/naming/b139991218/TestRunner.java +++ b/src/test/java/com/android/tools/r8/naming/b139991218/TestRunner.java
@@ -69,11 +69,6 @@ options -> { options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE); options.enableClassInlining = false; - - // TODO(b/179019716): Add support for merging in presence of annotations. - options.horizontalClassMergerOptions() - .skipNoClassesOrMembersWithAnnotationsPolicyForTesting = - true; }) .addHorizontallyMergedClassesInspector( inspector ->
diff --git a/src/test/java/com/android/tools/r8/regress/Regress181837660.java b/src/test/java/com/android/tools/r8/regress/Regress181837660.java new file mode 100644 index 0000000..7fd9e77 --- /dev/null +++ b/src/test/java/com/android/tools/r8/regress/Regress181837660.java
@@ -0,0 +1,105 @@ +// Copyright (c) 2021, 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.regress; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.R8FullTestBuilder; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper.ProcessResult; +import com.android.tools.r8.dexsplitter.SplitterTestBase; +import com.android.tools.r8.utils.StringUtils; +import com.google.common.collect.ImmutableSet; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +/** + * We need to ensure that we distribute the synthetic items in the features where they where + * generated. + */ +@RunWith(Parameterized.class) +public class Regress181837660 extends SplitterTestBase { + + public static final String EXPECTED = StringUtils.lines("42"); + + @Parameters(name = "{0}") + public static TestParametersCollection params() { + return getTestParameters().withDexRuntimes().withAllApiLevels().build(); + } + + private final TestParameters parameters; + + public Regress181837660(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testDistribution() throws Exception { + ProcessResult processResult = + testR8Splitter( + parameters, + ImmutableSet.of(BaseClass.class), + ImmutableSet.of(FeatureClass.class), + FeatureClass.class, + b -> {}, + this::configure); + + assertEquals(1, processResult.exitCode); + // We can't actually read the field since it is in the feature. + assertTrue(processResult.stderr.contains("NoClassDefFoundError")); + } + + @Test + public void testRegress181571571() throws Exception { + ProcessResult processResult = + testR8Splitter( + parameters, + ImmutableSet.of(BaseClass.class), + ImmutableSet.of(FeatureClass.class), + FeatureClass.class, + b -> {}, + this::configureNoInlineAnnotations); + // TODO(b/181571571): This should not succeed as illustrated by non inlining case + assertEquals(0, processResult.exitCode); + // We can't actually read the field since it is in the feature. + assertFalse(processResult.stderr.contains("NoClassDefFoundError")); + } + + private void configure(R8FullTestBuilder testBuilder) throws NoSuchMethodException { + testBuilder.enableInliningAnnotations().noMinification().setMinApi(parameters.getApiLevel()); + } + + private void configureNoInlineAnnotations(R8FullTestBuilder testBuilder) + throws NoSuchMethodException { + testBuilder.noMinification().setMinApi(parameters.getApiLevel()); + } + + public static class BaseClass { + @NeverInline + public static String getFromFeature() { + return FeatureClass.featureString; + } + } + + public static class FeatureClass implements RunInterface { + + public static String featureString = "22"; + + public static String getAString() { + return BaseClass.getFromFeature(); + } + + @Override + public void run() { + System.out.println(getAString()); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdaReceiverTest.java b/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdaReceiverTest.java index 16d9503..0a1f760 100644 --- a/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdaReceiverTest.java +++ b/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdaReceiverTest.java
@@ -7,7 +7,8 @@ import static org.junit.Assume.assumeTrue; import com.android.tools.r8.TestBase; -import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -16,33 +17,34 @@ @RunWith(Parameterized.class) public class InstantiatedLambdaReceiverTest extends TestBase { - private Backend backend; - private static final String expectedOutput = "In C.m()"; + private final TestParameters parameters; - @Parameters(name = "Backend: {0}") - public static Backend[] data() { - return ToolHelper.getBackends(); + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withDexRuntimes().withAllApiLevels().build(); } - public InstantiatedLambdaReceiverTest(Backend backend) { - this.backend = backend; + public InstantiatedLambdaReceiverTest(TestParameters parameters) { + this.parameters = parameters; } @Test public void jvmTest() throws Exception { - assumeTrue( - "JVM test independent of Art version - only run when testing on latest", - ToolHelper.getDexVm().getVersion().isLatest()); - testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput); + assumeTrue(parameters.isCfRuntime()); + testForJvm() + .addTestClasspath() + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(expectedOutput); } @Test - public void dexTest() throws Exception { - testForR8(backend) + public void r8Test() throws Exception { + testForR8(parameters.getBackend()) .addInnerClasses(InstantiatedLambdaReceiverTest.class) .addKeepMainRule(TestClass.class) - .run(TestClass.class) + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), TestClass.class) .assertSuccessWithOutput(expectedOutput); }
diff --git a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java index 6f6ad5a..f98e5ea 100644 --- a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java +++ b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
@@ -44,25 +44,21 @@ public class NonVirtualOverrideTest extends TestBase { private final TestParameters parameters; - private final boolean enableClassInlining; private final boolean enableVerticalClassMerging; static class Dimensions { private final Backend backend; - private final boolean enableClassInlining; private final boolean enableVerticalClassMerging; - public Dimensions( - Backend backend, boolean enableClassInlining, boolean enableVerticalClassMerging) { + public Dimensions(Backend backend, boolean enableVerticalClassMerging) { this.backend = backend; - this.enableClassInlining = enableClassInlining; this.enableVerticalClassMerging = enableVerticalClassMerging; } @Override public int hashCode() { - return Objects.hash(backend, enableClassInlining, enableVerticalClassMerging); + return Objects.hash(backend, enableVerticalClassMerging); } @Override @@ -72,23 +68,19 @@ } Dimensions other = (Dimensions) o; return this.backend == other.backend - && this.enableClassInlining == other.enableClassInlining && this.enableVerticalClassMerging == other.enableVerticalClassMerging; } } - @Parameterized.Parameters(name = "Backend: {0}, class inlining: {1}, vertical class merging: {2}") + @Parameterized.Parameters(name = "{0}, vertical class merging: {1}") public static Collection<Object[]> data() { return buildParameters( getTestParameters().withAllRuntimes().build(), - BooleanUtils.values(), BooleanUtils.values()); } - public NonVirtualOverrideTest( - TestParameters parameters, boolean enableClassInlining, boolean enableVerticalClassMerging) { + public NonVirtualOverrideTest(TestParameters parameters, boolean enableVerticalClassMerging) { this.parameters = parameters; - this.enableClassInlining = enableClassInlining; this.enableVerticalClassMerging = enableVerticalClassMerging; } @@ -162,7 +154,6 @@ .addKeepMainRule(NonVirtualOverrideTestClass.class) .addOptionsModification( options -> { - options.enableClassInlining = dimensions.enableClassInlining; options.enableVerticalClassMerging = dimensions.enableVerticalClassMerging; options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE); }) @@ -173,18 +164,16 @@ @Test public void test() throws Exception { // Run the program on Art after is has been compiled with R8. - String referenceResult = - expectedResults.apply(!enableClassInlining && isDexVmBetween5_1_1and7_0_0(parameters)); + String referenceResult = expectedResults.apply(isDexVmBetween5_1_1and7_0_0(parameters)); R8TestCompileResult compiled = compilationResults.apply( - new Dimensions( - parameters.getBackend(), enableClassInlining, enableVerticalClassMerging)); + new Dimensions(parameters.getBackend(), enableVerticalClassMerging)); compiled .run(parameters.getRuntime(), NonVirtualOverrideTestClass.class) .assertSuccessWithOutput(referenceResult); // Check that B is present and that it doesn't contain the unused private method m2. - if (!enableClassInlining && !enableVerticalClassMerging) { + if (!enableVerticalClassMerging) { CodeInspector inspector = compiled.inspector(); ClassSubject classSubject = inspector.clazz(B.class.getName()); assertThat(classSubject, isPresentAndRenamed());
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java index ace926f..9246164 100644 --- a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java +++ b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java
@@ -4,6 +4,7 @@ package com.android.tools.r8.shaking.annotations; +import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.NoVerticalClassMerging; @@ -55,6 +56,7 @@ .addKeepMainRule(TestClass.class) .addKeepRuntimeVisibleAnnotations() .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() .enableNoHorizontalClassMergingAnnotations() .enableNoVerticalClassMergingAnnotations() .noMinification() @@ -94,6 +96,7 @@ void targetedMethod(); } + @NeverClassInline @NoHorizontalClassMerging static class InterfaceImpl implements Interface { @@ -104,6 +107,7 @@ } } + @NeverClassInline @NoHorizontalClassMerging static class OtherInterfaceImpl implements Interface {
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoMethods.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoMethods.java index 5bb8e48..035ddf7 100644 --- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoMethods.java +++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoMethods.java
@@ -5,6 +5,7 @@ import static com.android.tools.r8.references.Reference.methodFromMethod; +import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; import com.android.tools.r8.NoVerticalClassMerging; import com.android.tools.r8.TestBase; @@ -31,6 +32,7 @@ } } + @NeverClassInline @NoVerticalClassMerging public static class B extends A { // Intermediate to A. @@ -72,6 +74,7 @@ GraphInspector inspector = testForR8(parameters.getBackend()) .enableGraphInspector() + .enableNeverClassInliningAnnotations() .enableNoVerticalClassMergingAnnotations() .enableInliningAnnotations() .addKeepMainRule(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java index 6f3a6cc..46924fd 100644 --- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java +++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -90,6 +90,15 @@ return SyntheticNaming.isSynthetic(reference, null, SyntheticKind.INIT_TYPE_ARGUMENT); } + public static boolean isHorizontalInitializerTypeArgument(ClassReference reference) { + return SyntheticNaming.isSynthetic( + reference, null, SyntheticKind.HORIZONTAL_INIT_TYPE_ARGUMENT_1) + || SyntheticNaming.isSynthetic( + reference, null, SyntheticKind.HORIZONTAL_INIT_TYPE_ARGUMENT_2) + || SyntheticNaming.isSynthetic( + reference, null, SyntheticKind.HORIZONTAL_INIT_TYPE_ARGUMENT_3); + } + public static Matcher<String> containsInternalSyntheticReference() { return containsString(SyntheticNaming.getPhaseSeparator(Phase.INTERNAL)); }
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java index f828cfc..0169604 100644 --- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java +++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -517,6 +517,16 @@ static MethodPredicate onName(String name) { return (access, otherName, descriptor, signature, exceptions) -> name.equals(otherName); } + + static boolean testContext(MethodPredicate predicate, MethodContext context) { + MethodReference reference = context.getReference(); + return predicate.test( + context.accessFlags, + reference.getMethodName(), + reference.getMethodDescriptor(), + null, + null); + } } @FunctionalInterface @@ -970,4 +980,16 @@ } }); } + + public ClassFileTransformer setMaxStackHeight(MethodPredicate predicate, int newMaxStack) { + return addMethodTransformer( + new MethodTransformer() { + @Override + public void visitMaxs(int maxStack, int maxLocals) { + super.visitMaxs( + MethodPredicate.testContext(predicate, getContext()) ? newMaxStack : maxStack, + maxLocals); + } + }); + } }
diff --git a/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java b/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java index c35bbf2..f681673 100644 --- a/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java +++ b/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java
@@ -42,6 +42,9 @@ private Path getOutputPath(GenerateMainDexListCommand command) { StringConsumer consumer = command.getMainDexListConsumer(); + if (consumer instanceof JoiningStringConsumer) { + consumer = ((JoiningStringConsumer) consumer).getConsumer(); + } if (consumer instanceof StringConsumer.FileConsumer) { return ((StringConsumer.FileConsumer) consumer).getOutputPath(); }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java index 959a2c9..e8818b2 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -63,6 +63,10 @@ boolean isConstString(String value, JumboStringMode jumboStringMode); + default boolean isConstString(String value) { + return isConstString(value, JumboStringMode.ALLOW); + } + boolean isJumboString(); long getConstNumber();
diff --git a/third_party/internal-apps/youtube_15_33.tar.gz.sha1 b/third_party/internal-apps/youtube_15_33.tar.gz.sha1 new file mode 100644 index 0000000..ad38c2b --- /dev/null +++ b/third_party/internal-apps/youtube_15_33.tar.gz.sha1
@@ -0,0 +1 @@ +a992d1cdf6b957a6e4b06361ea1b8edbaecc3088 \ No newline at end of file
diff --git a/tools/adb.py b/tools/adb.py index c6cece7..07ca034 100644 --- a/tools/adb.py +++ b/tools/adb.py
@@ -20,6 +20,8 @@ ['adb', '-s', emulator_id, 'uninstall', app_id], stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = process.communicate() + stdout = stdout.decode('UTF-8') + stderr = stderr.decode('UTF-8') if stdout.strip() == 'Success': # Successfully uninstalled @@ -40,7 +42,7 @@ def wait_for_emulator(emulator_id): - stdout = subprocess.check_output(['adb', 'devices']) + stdout = subprocess.check_output(['adb', 'devices']).decode('UTF-8') if '{}\tdevice'.format(emulator_id) in stdout: return True @@ -51,7 +53,7 @@ while True: time.sleep(10) time_waited += 10 - stdout = subprocess.check_output(['adb', 'devices']) + stdout = subprocess.check_output(['adb', 'devices']).decode('UTF-8') if '{}\tdevice'.format(emulator_id) not in stdout: print('... still waiting for connection') if time_waited >= 5 * 60:
diff --git a/tools/compiledump.py b/tools/compiledump.py index b5f49d1..3828739 100755 --- a/tools/compiledump.py +++ b/tools/compiledump.py
@@ -147,10 +147,13 @@ if self.if_exists('proguard_input.config'): print("Unimplemented: proguard_input configuration.") - def main_dex_resource(self): + def main_dex_list_resource(self): if self.if_exists('main-dex-list.txt'): print("Unimplemented: main-dex-list.") + def main_dex_rules_resource(self): + return self.if_exists('main-dex-rules.txt') + def build_properties_file(self): return self.if_exists('build.properties') @@ -318,6 +321,8 @@ if hasattr(args, 'config_file_consumer') and args.config_file_consumer: args.config_file_consumer(dump.config_file()) cmd.extend(['--pg-conf', dump.config_file()]) + if dump.main_dex_rules_resource(): + cmd.extend(['--main-dex-rules', dump.main_dex_rules_resource()]) if compiler != 'd8': cmd.extend(['--pg-map-output', '%s.map' % out]) if min_api: @@ -332,10 +337,9 @@ print(subprocess.check_output(cmd, stderr=subprocess.STDOUT)) return 0 except subprocess.CalledProcessError as e: - print(e.output) if not args.nolib and version != 'source': stacktrace = os.path.join(temp, 'stacktrace') - open(stacktrace, 'w+').write(e.output) + open(stacktrace, 'w+').write(e.output.decode('UTF-8')) local_map = utils.R8LIB_MAP if version == 'master' else None hash_or_version = None if version == 'master' else version print("=" * 80) @@ -343,6 +347,8 @@ print("=" * 80) retrace.run( local_map, hash_or_version, stacktrace, is_hash(version), no_r8lib=False) + else: + print(e.output.decode('UTF-8')) return 1 def run(args, otherargs):
diff --git a/tools/run_on_app_dump.py b/tools/run_on_app_dump.py index c4de6e3..1644895 100755 --- a/tools/run_on_app_dump.py +++ b/tools/run_on_app_dump.py
@@ -8,6 +8,7 @@ import as_utils import compiledump import gradle +import hashlib import optparse import os import shutil @@ -50,6 +51,7 @@ 'folder': None, 'skip_recompilation': False, 'compiler_properties': [], + 'internal': False, } # This below does not work in python3 defaults.update(fields.items()) @@ -340,8 +342,6 @@ 'url': 'https://github.com/android/compose-samples', 'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8', 'folder': 'android/compose-samples/crane', - # TODO(b/173176042): Fix recompilation - 'skip_recompilation': True, }), # TODO(b/173167253): Check if monkey testing works. App({ @@ -353,8 +353,6 @@ 'url': 'https://github.com/android/compose-samples', 'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8', 'folder': 'android/compose-samples/jetcaster', - # TODO(b/173176042): Fix recompilation - 'skip_recompilation': True, }), # TODO(b/173167253): Check if monkey testing works. App({ @@ -366,8 +364,6 @@ 'url': 'https://github.com/android/compose-samples', 'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8', 'folder': 'android/compose-samples/jetchat', - # TODO(b/173176042): Fix recompilation - 'skip_recompilation': True, }), # TODO(b/173167253): Check if monkey testing works. App({ @@ -379,8 +375,6 @@ 'url': 'https://github.com/android/compose-samples', 'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8', 'folder': 'android/compose-samples/jetnews', - # TODO(b/173176042): Fix recompilation - 'skip_recompilation': True, }), # TODO(b/173167253): Check if monkey testing works. App({ @@ -392,8 +386,6 @@ 'url': 'https://github.com/android/compose-samples', 'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8', 'folder': 'android/compose-samples/jetsnack', - # TODO(b/173176042): Fix recompilation - 'skip_recompilation': True, }), # TODO(b/173167253): Check if monkey testing works. App({ @@ -405,8 +397,6 @@ 'url': 'https://github.com/android/compose-samples', 'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8', 'folder': 'android/compose-samples/jetsurvey', - # TODO(b/173176042): Fix recompilation - 'skip_recompilation': True, }), # TODO(b/173167253): Check if monkey testing works. App({ @@ -418,8 +408,6 @@ 'url': 'https://github.com/android/compose-samples', 'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8', 'folder': 'android/compose-samples/owl', - # TODO(b/173176042): Fix recompilation - 'skip_recompilation': True, }), # TODO(b/173167253): Check if monkey testing works. App({ @@ -431,9 +419,17 @@ 'url': 'https://github.com/android/compose-samples', 'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8', 'folder': 'android/compose-samples/rally', - # TODO(b/173176042): Fix recompilation - 'skip_recompilation': True, }), + App({ + 'id': 'youtube_15_33', + 'name': 'youtube_15_33', + 'dump_app': 'dump.zip', + 'apk_app': 'YouTubeRelease_unsigned.apk', + 'folder': 'youtube_15_33', + 'internal': True, + # TODO(b/181629268): Fix recompilation + 'skip_recompilation': True, + }) ] @@ -453,8 +449,11 @@ f.write(line) -def download_app(app_sha): - utils.DownloadFromGoogleCloudStorage(app_sha) +def download_app(app_sha, internal, quiet=False): + if internal: + utils.DownloadFromX20(app_sha) + else: + utils.DownloadFromGoogleCloudStorage(app_sha, quiet=quiet) def is_logging_enabled_for(app, options): @@ -503,11 +502,12 @@ def get_results_for_app(app, options, temp_dir): app_folder = app.folder if app.folder else app.name + "_" + app.revision - app_dir = os.path.join(utils.OPENSOURCE_DUMPS_DIR, app_folder) + app_dir = (os.path.join(utils.INTERNAL_DUMPS_DIR, app_folder) if app.internal + else os.path.join(utils.OPENSOURCE_DUMPS_DIR, app_folder)) if not os.path.exists(app_dir) and not options.golem: # Download the app from google storage. - download_app(app_dir + ".tar.gz.sha1") + download_app(app_dir + ".tar.gz.sha1", app.internal) # Ensure that the dumps are in place assert os.path.isfile(dump_for_app(app_dir, app)), "Could not find dump " \ @@ -789,6 +789,11 @@ warn(' {}-#{}: {}'.format(shrinker, compilation_index, build_status)) continue + if options.golem: + print('%s(RunTimeRaw): %s ms' % (app.name, result.get('duration'))) + print('%s(CodeSize): %s' % (app.name, result.get('dex_size'))) + continue + print(' {}-#{}:'.format(shrinker, compilation_index)) dex_size = result.get('dex_size') msg = ' dex size: {}'.format(dex_size) @@ -839,10 +844,17 @@ help='What app collection to run', choices=[collection.name for collection in APP_COLLECTIONS], action='append') + result.add_option('--app-logging-filter', '--app_logging_filter', + help='The apps for which to turn on logging', + action='append') result.add_option('--bot', help='Running on bot, use third_party dependency.', default=False, action='store_true') + result.add_option('--generate-golem-config', '--generate_golem_config', + help='Generate a new config for golem.', + default=False, + action='store_true') result.add_option('--debug-agent', help='Enable Java debug agent and suspend compilation ' '(default disabled)', @@ -861,15 +873,16 @@ action='store_true') result.add_option('--hash', help='The commit of R8 to use') + result.add_option('--internal', + help='Run internal apps if set, otherwise run opensource', + default=False, + action='store_true') result.add_option('--keystore', help='Path to app.keystore', default=os.path.join(utils.TOOLS_DIR, 'debug.keystore')) result.add_option('--keystore-password', '--keystore_password', help='Password for app.keystore', default='android') - result.add_option('--app-logging-filter', '--app_logging_filter', - help='The apps for which to turn on logging', - action='append') result.add_option('--monkey', help='Whether to install and run app(s) with monkey', default=False, @@ -934,7 +947,7 @@ del options.app del options.app_collection else: - options.apps = APPS + options.apps = [app for app in APPS if app.internal == options.internal] if options.app_logging_filter: for app_name in options.app_logging_filter: @@ -960,6 +973,65 @@ return (options, args) +def print_indented(s, indent): + print(' ' * indent + s) + + +def get_sha256(gz_file): + with open(gz_file, 'rb') as f: + bytes = f.read() # read entire file as bytes + return hashlib.sha256(bytes).hexdigest(); + + +def get_sha_from_file(sha_file): + with open(sha_file, 'r') as f: + return f.readlines()[0] + + +def print_golem_config(options): + print('// AUTOGENERATED FILE from tools/run_on_app_dump.py in R8 repo') + print('part of r8_config;') + print('') + print('final Suite dumpsSuite = new Suite("OpenSourceAppDumps");') + print('') + print('createOpenSourceAppBenchmarks() {') + print_indented('final cpus = ["Lenovo M90"];', 2) + for app in options.apps: + if app.folder and not app.internal: + indentation = 2; + print_indented('{', indentation) + indentation = 4 + print_indented('final name = "%s";' % app.name, indentation) + print_indented('final benchmark =', indentation) + print_indented( + 'new StandardBenchmark(name, [Metric.RunTimeRaw, Metric.CodeSize]);', + indentation + 4) + print_indented( + 'final options = benchmark.addTargets(noImplementation, ["R8"]);', + indentation) + print_indented('options.cpus = cpus;', indentation) + print_indented('options.isScript = true;', indentation) + print_indented('options.fromRevision = 9700;', indentation); + print_indented('options.mainFile = "tools/run_on_app_dump.py "', indentation) + print_indented('"--golem --shrinker r8 --app %s";' % app.name, indentation + 4) + app_gz = os.path.join(utils.OPENSOURCE_DUMPS_DIR, app.folder + '.tar.gz') + app_sha = app_gz + '.sha1' + # Golem uses a sha256 of the file in the cache, and you need to specify that. + download_app(app_sha, app.internal, quiet=True) + sha256 = get_sha256(app_gz) + sha = get_sha_from_file(app_sha) + print_indented('final resource = BenchmarkResource("",', indentation) + print_indented('type: BenchmarkResourceType.Storage,', indentation + 4) + print_indented('uri: "gs://r8-deps/%s",' % sha, indentation + 4) + print_indented('hash:', indentation + 4) + print_indented('"%s",' % sha256, indentation + 8) + print_indented('extract: "gz");', indentation + 4); + print_indented('options.resources.add(resource);', indentation) + print_indented('dumpsSuite.addBenchmark(name);', indentation) + indentation = 2 + print_indented('}', indentation) + print('}') + def main(argv): (options, args) = parse_options(argv) @@ -969,13 +1041,16 @@ print(options.shrinker) if options.golem: - golem.link_third_party() options.disable_assertions = True options.no_build = True options.r8_compilation_steps = 1 options.quiet = True options.no_logging = True + if options.generate_golem_config: + print_golem_config(options) + return 0 + with utils.TempDir() as temp_dir: if options.hash: # Download r8-<hash>.jar from
diff --git a/tools/test.py b/tools/test.py index 546a25f..9e2a3e2 100755 --- a/tools/test.py +++ b/tools/test.py
@@ -192,6 +192,9 @@ print('Building desugared library.') with utils.TempDir() as checkout_dir: archive_desugar_jdk_libs.CloneDesugaredLibrary('google', checkout_dir) + # Make sure bazel is extracted in third_party. + utils.DownloadFromGoogleCloudStorage(utils.BAZEL_SHA_FILE) + utils.DownloadFromGoogleCloudStorage(utils.JAVA8_SHA_FILE) (library_jar, maven_zip) = archive_desugar_jdk_libs.BuildDesugaredLibrary(checkout_dir) desugar_jdk_libs = os.path.join(desugar_jdk_libs_dir, os.path.basename(library_jar)) shutil.copyfile(library_jar, desugar_jdk_libs)
diff --git a/tools/utils.py b/tools/utils.py index 8b7acb7..1820991 100644 --- a/tools/utils.py +++ b/tools/utils.py
@@ -79,6 +79,7 @@ # TODO(b/152155164): Remove this when all apps has been migrated. OPENSOURCE_APPS_FOLDER = os.path.join(THIRD_PARTY, 'opensource_apps') OPENSOURCE_DUMPS_DIR = os.path.join(THIRD_PARTY, 'opensource-apps') +INTERNAL_DUMPS_DIR = os.path.join(THIRD_PARTY, 'internal-apps') BAZEL_SHA_FILE = os.path.join(THIRD_PARTY, 'bazel.tar.gz.sha1') BAZEL_TOOL = os.path.join(THIRD_PARTY, 'bazel') JAVA8_SHA_FILE = os.path.join(THIRD_PARTY, 'openjdk', 'jdk8', 'linux-x86.tar.gz.sha1') @@ -237,15 +238,19 @@ PrintCmd(cmd) subprocess.check_call(cmd) -def DownloadFromGoogleCloudStorage(sha1_file, bucket='r8-deps', auth=False): +def DownloadFromGoogleCloudStorage(sha1_file, bucket='r8-deps', auth=False, + quiet=False): suffix = '.bat' if IsWindows() else '' download_script = 'download_from_google_storage%s' % suffix cmd = [download_script] if not auth: cmd.append('-n') cmd.extend(['-b', bucket, '-u', '-s', sha1_file]) - PrintCmd(cmd) - subprocess.check_call(cmd) + if not quiet: + PrintCmd(cmd) + subprocess.check_call(cmd) + else: + subprocess.check_output(cmd) def get_sha1(filename): sha1 = hashlib.sha1()