Merge commit 'd2c333f958b9987fb598dca4df09975210c13035' into dev-release
diff --git a/.gitignore b/.gitignore index 052f0a6..17af682 100644 --- a/.gitignore +++ b/.gitignore
@@ -183,6 +183,10 @@ third_party/opensource-apps/applymapping.tar.gz third_party/opensource-apps/chanu third_party/opensource-apps/chanu.tar.gz +third_party/opensource-apps/empty-activity +third_party/opensource-apps/empty-activity.tar.gz +third_party/opensource-apps/empty-compose-activity +third_party/opensource-apps/empty-compose-activity.tar.gz third_party/opensource-apps/friendlyeats third_party/opensource-apps/friendlyeats.tar.gz third_party/opensource-apps/iosched
diff --git a/build.gradle b/build.gradle index 2045d5a..46548e4 100644 --- a/build.gradle +++ b/build.gradle
@@ -2,7 +2,6 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import dx.DexMergerTask import dx.DxTask import net.ltgt.gradle.errorprone.CheckSeverity @@ -21,9 +20,6 @@ gradlePluginPortal() jcenter() } - dependencies { - classpath 'com.github.jengelman.gradle.plugins:shadow:5.2.0' - } } plugins { @@ -270,6 +266,8 @@ examplesTestNGRunnerCompile group: 'org.testng', name: 'testng', version: testngVersion testCompile sourceSets.examples.output testCompile "junit:junit:$junitVersion" + testCompile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" + testCompile "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion" testCompile group: 'org.smali', name: 'smali', version: smaliVersion testCompile files('third_party/jasmin/jasmin-2.4.jar') testCompile files('third_party/jdwp-tests/apache-harmony-jdwp-tests-host.jar') @@ -290,8 +288,6 @@ apiUsageSampleCompile "com.google.guava:guava:$guavaVersion" kotlinR8TestResourcesCompileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" errorprone("com.google.errorprone:error_prone_core:$errorproneVersion") - testImplementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" - testImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion" } def r8LibPath = "$buildDir/libs/r8lib.jar" @@ -741,56 +737,56 @@ } } -static mergeServiceFiles(ShadowJar task) { - // Everything under META-INF is not included by default. - // Should include before 'relocate' so that the service file path and its content - // are properly relocated as well. - task.mergeServiceFiles { - include 'META-INF/services/*' +def repackageDepFile(file) { + if (file.getName().endsWith('.jar')) { + return zipTree(file).matching { + exclude '**/module-info.class' + exclude 'META-INF/maven/**' + exclude 'META-INF/LICENSE.txt' + exclude 'META-INF/MANIFEST.MF' + } + } else { + return fileTree(file) } } -task repackageDeps(type: ShadowJar) { +task repackageDeps(type: Jar) { dependsOn downloadCloudDeps - configurations = [project.configurations.runtimeClasspath] - mergeServiceFiles(it) - exclude { it.getRelativePath().getPathString().endsWith("module-info.class") } - exclude { it.getRelativePath().getPathString().startsWith("META-INF/maven/") } - exclude { it.getRelativePath().getPathString().equals("META-INF/LICENSE.txt") } - baseName 'deps_all' + dependsOn project.configurations.runtimeClasspath + project.configurations.runtimeClasspath.forEach { + from repackageDepFile(it) + } + archiveFileName = 'deps_all.jar' } -task repackageTestDeps(type: ShadowJar) { +task repackageTestDeps(type: Jar) { dependsOn downloadCloudDeps - configurations = [project.configurations.testCompile] - mergeServiceFiles(it) - exclude { it.getRelativePath().getPathString().endsWith("module-info.class") } - exclude { it.getRelativePath().getPathString().startsWith("META-INF/maven/") } - baseName 'test_deps_all' + dependsOn project.configurations.testCompile + project.configurations.testCompile.forEach { + from repackageDepFile(it) + } + archiveFileName = 'test_deps_all.jar' } -task repackageSources(type: ShadowJar) { +task repackageSources(type: Jar) { // If this fails then remove all generated folders from // build/classes/java/test that is not {com,dalvik} from sourceSets.main.output - mergeServiceFiles(it) - baseName 'sources_main' + archiveFileName = 'sources_main.jar' } -task repackageSources11(type: ShadowJar) { +task repackageSources11(type: Jar) { from sourceSets.main11.output - mergeServiceFiles(it) - baseName 'sources_main_11' + archiveFileName = 'sources_main_11.jar' } -def r8CreateTask(name, baseNameName, sources, includeSwissArmyKnife) { - return tasks.create("r8Create${name}", ShadowJar) { +def r8CreateTask(name, baseName, sources, includeSwissArmyKnife) { + return tasks.create("r8Create${name}", Jar) { + dependsOn sources from consolidatedLicense.outputs.files - from sources + from sources.collect { zipTree(it) } exclude "$buildDir/classes/**" - baseName baseNameName - classifier = null - version = null + archiveFileName = baseName if (includeSwissArmyKnife) { manifest { attributes 'Main-Class': 'com.android.tools.r8.SwissArmyKnife' @@ -846,7 +842,7 @@ inputs.files ([repackageSources.outputs, repackageDeps.outputs]) def r8Task = r8CreateTask( 'WithDeps', - 'r8_with_deps', + 'r8_with_deps.jar', repackageSources.outputs.files + repackageDeps.outputs.files, true) dependsOn r8Task @@ -859,7 +855,7 @@ inputs.files ([repackageSources11.outputs, repackageDeps.outputs]) def r8Task = r8CreateTask( 'WithDeps11', - 'r8_with_deps_11', + 'r8_with_deps_11.jar', repackageSources11.outputs.files + repackageDeps.outputs.files, true) dependsOn r8Task @@ -885,7 +881,7 @@ inputs.files repackageSources.outputs def r8Task = r8CreateTask( 'WithoutDeps', - 'r8_without_deps', + 'r8_without_deps.jar', repackageSources.outputs.files, true) dependsOn r8Task @@ -907,7 +903,7 @@ inputs.files repackageSources.outputs def r8Task = r8CreateTask( 'NoManifestWithoutDeps', - 'r8_no_manifest_without_deps', + 'r8_no_manifest_without_deps.jar', repackageSources.outputs.files, false) dependsOn r8Task @@ -919,7 +915,7 @@ inputs.files ([repackageSources.outputs, repackageDeps.outputs]) def r8Task = r8CreateTask( 'NoManifestWithDeps', - 'r8_no_manifest_with_deps', + 'r8_no_manifest_with_deps.jar', repackageSources.outputs.files + repackageDeps.outputs.files, false) dependsOn r8Task @@ -943,10 +939,10 @@ outputs.file "${buildDir}/libs/r8_no_manifest.jar" } -task D8(type: ShadowJar) { +task D8(type: Jar) { dependsOn r8 - from r8.outputs.files[0] - baseName 'd8' + from zipTree(r8.outputs.files[0]) + archiveFileName = 'd8.jar' manifest { attributes 'Main-Class': 'com.android.tools.r8.D8' } @@ -1015,8 +1011,8 @@ destinationDir file('build/libs') } -task testJarSources(type: ShadowJar, dependsOn: [testClasses, buildLibraryDesugarConversions]) { - baseName = "r8testsbase" +task testJarSources(type: Jar, dependsOn: [testClasses, buildLibraryDesugarConversions]) { + archiveFileName = "r8testsbase.jar" from sourceSets.test.output // We only want to include tests that use R8 when generating keep rules for applymapping. include "com/android/tools/r8/**" @@ -1152,7 +1148,7 @@ task jctfCommonJar(type: Jar) { from sourceSets.jctfCommon.output - baseName 'jctfCommon' + archiveFileName = 'jctfCommon.jar' } artifacts { @@ -1186,13 +1182,13 @@ task buildCfSegments(type: Jar, dependsOn: downloadDeps) { from sourceSets.cfSegments.output - baseName 'cf_segments' + archiveFileName = 'cf_segments.jar' destinationDir file('build/libs') } task buildR8ApiUsageSample(type: Jar) { from sourceSets.apiUsageSample.output - baseName 'r8_api_usage_sample' + archiveFileName = 'r8_api_usage_sample.jar' destinationDir file('tests') } @@ -1763,7 +1759,7 @@ } task buildPreNJdwpTestsJar(type: Jar) { - baseName = 'jdwp-tests-preN' + archiveFileName = 'jdwp-tests-preN.jar' from zipTree('third_party/jdwp-tests/apache-harmony-jdwp-tests-host.jar') // Exclude the classes containing java8 exclude 'org/apache/harmony/jpda/tests/jdwp/InterfaceType/*.class'
diff --git a/infra/config/global/generated/luci-scheduler.cfg b/infra/config/global/generated/luci-scheduler.cfg index bf973f3..6d92824 100644 --- a/infra/config/global/generated/luci-scheduler.cfg +++ b/infra/config/global/generated/luci-scheduler.cfg
@@ -98,6 +98,7 @@ triggering_policy { kind: GREEDY_BATCHING max_concurrent_invocations: 4 + max_batch_size: 1 } buildbucket { server: "cr-buildbucket.appspot.com" @@ -126,6 +127,7 @@ triggering_policy { kind: GREEDY_BATCHING max_concurrent_invocations: 4 + max_batch_size: 1 } buildbucket { server: "cr-buildbucket.appspot.com" @@ -154,6 +156,7 @@ triggering_policy { kind: GREEDY_BATCHING max_concurrent_invocations: 4 + max_batch_size: 1 } buildbucket { server: "cr-buildbucket.appspot.com" @@ -182,6 +185,7 @@ triggering_policy { kind: GREEDY_BATCHING max_concurrent_invocations: 4 + max_batch_size: 1 } buildbucket { server: "cr-buildbucket.appspot.com" @@ -210,6 +214,7 @@ triggering_policy { kind: GREEDY_BATCHING max_concurrent_invocations: 4 + max_batch_size: 1 } buildbucket { server: "cr-buildbucket.appspot.com" @@ -238,6 +243,7 @@ triggering_policy { kind: GREEDY_BATCHING max_concurrent_invocations: 4 + max_batch_size: 1 } buildbucket { server: "cr-buildbucket.appspot.com" @@ -266,6 +272,7 @@ triggering_policy { kind: GREEDY_BATCHING max_concurrent_invocations: 4 + max_batch_size: 1 } buildbucket { server: "cr-buildbucket.appspot.com" @@ -294,6 +301,7 @@ triggering_policy { kind: GREEDY_BATCHING max_concurrent_invocations: 4 + max_batch_size: 1 } buildbucket { server: "cr-buildbucket.appspot.com" @@ -322,6 +330,7 @@ triggering_policy { kind: GREEDY_BATCHING max_concurrent_invocations: 4 + max_batch_size: 1 } buildbucket { server: "cr-buildbucket.appspot.com" @@ -350,6 +359,7 @@ triggering_policy { kind: GREEDY_BATCHING max_concurrent_invocations: 4 + max_batch_size: 1 } buildbucket { server: "cr-buildbucket.appspot.com" @@ -378,6 +388,7 @@ triggering_policy { kind: GREEDY_BATCHING max_concurrent_invocations: 4 + max_batch_size: 1 } buildbucket { server: "cr-buildbucket.appspot.com" @@ -434,6 +445,7 @@ triggering_policy { kind: GREEDY_BATCHING max_concurrent_invocations: 4 + max_batch_size: 1 } buildbucket { server: "cr-buildbucket.appspot.com" @@ -462,6 +474,7 @@ triggering_policy { kind: GREEDY_BATCHING max_concurrent_invocations: 4 + max_batch_size: 1 } buildbucket { server: "cr-buildbucket.appspot.com" @@ -490,6 +503,7 @@ triggering_policy { kind: GREEDY_BATCHING max_concurrent_invocations: 4 + max_batch_size: 1 } buildbucket { server: "cr-buildbucket.appspot.com" @@ -546,6 +560,7 @@ triggering_policy { kind: GREEDY_BATCHING max_concurrent_invocations: 4 + max_batch_size: 1 } buildbucket { server: "cr-buildbucket.appspot.com" @@ -574,6 +589,7 @@ triggering_policy { kind: GREEDY_BATCHING max_concurrent_invocations: 4 + max_batch_size: 1 } buildbucket { server: "cr-buildbucket.appspot.com" @@ -602,6 +618,7 @@ triggering_policy { kind: GREEDY_BATCHING max_concurrent_invocations: 4 + max_batch_size: 1 } buildbucket { server: "cr-buildbucket.appspot.com" @@ -630,6 +647,7 @@ triggering_policy { kind: GREEDY_BATCHING max_concurrent_invocations: 4 + max_batch_size: 1 } buildbucket { server: "cr-buildbucket.appspot.com"
diff --git a/infra/config/global/generated/project.cfg b/infra/config/global/generated/project.cfg index 525fcd4..b87c601 100644 --- a/infra/config/global/generated/project.cfg +++ b/infra/config/global/generated/project.cfg
@@ -7,7 +7,7 @@ name: "r8" access: "group:all" lucicfg { - version: "1.30.4" + version: "1.30.5" package_dir: ".." config_dir: "generated" entry_point: "main.star"
diff --git a/infra/config/global/main.star b/infra/config/global/main.star index a438dbc..02b45d5 100755 --- a/infra/config/global/main.star +++ b/infra/config/global/main.star
@@ -173,6 +173,7 @@ else ["main-gitiles-trigger"] triggering_policy = triggering_policy or scheduler.policy( kind = scheduler.GREEDY_BATCHING_KIND, + max_batch_size = 1 if release else None, max_concurrent_invocations = 4) luci.builder(
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java index 660b1ef..01e1163 100644 --- a/src/main/java/com/android/tools/r8/GenerateLintFiles.java +++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -128,13 +128,7 @@ private CfCode buildEmptyThrowingCfCode(DexMethod method) { CfInstruction insn[] = {new CfConstNull(), new CfThrow()}; - return new CfCode( - method.holder, - 1, - method.proto.parameters.size() + 1, - Arrays.asList(insn), - Collections.emptyList(), - Collections.emptyList()); + return new CfCode(method.holder, 1, method.proto.parameters.size() + 1, Arrays.asList(insn)); } private void addMethodsToHeaderJar(
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index 171d1bc..5e43f29 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java
@@ -630,6 +630,7 @@ new BridgeHoisting(appViewWithLiveness).run(); assert Inliner.verifyAllSingleCallerMethodsHaveBeenPruned(appViewWithLiveness); + assert Inliner.verifyAllMultiCallerInlinedMethodsHaveBeenPruned(appView); assert appView.allMergedClasses().verifyAllSourcesPruned(appViewWithLiveness); assert appView.validateUnboxedEnumsHaveBeenPruned();
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 644ed7d..c4bbada 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
@@ -75,6 +75,9 @@ NamingLens namingLens, LensCodeRewriterUtils rewriter, MethodVisitor visitor) { + if (context.getName().toSourceString().equals("recordNewFieldSignature")) { + System.currentTimeMillis(); + } DexCallSite rewrittenCallSite = rewriter.rewriteCallSite(callSite, context); DexMethodHandle bootstrapMethod = rewrittenCallSite.bootstrapMethod; List<DexValue> bootstrapArgs = rewrittenCallSite.bootstrapArgs;
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 a48fb5c..7c7824d 100644 --- a/src/main/java/com/android/tools/r8/graph/CfCode.java +++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -149,6 +149,7 @@ private final List<CfTryCatch> tryCatchRanges; private final List<LocalVariableInfo> localVariables; private StackMapStatus stackMapStatus = StackMapStatus.NOT_VERIFIED; + private final com.android.tools.r8.position.Position diagnosticPosition; public CfCode( DexType originalHolder, int maxStack, int maxLocals, List<CfInstruction> instructions) { @@ -168,12 +169,31 @@ List<CfInstruction> instructions, List<CfTryCatch> tryCatchRanges, List<LocalVariableInfo> localVariables) { + this( + originalHolder, + maxStack, + maxLocals, + instructions, + tryCatchRanges, + localVariables, + com.android.tools.r8.position.Position.UNKNOWN); + } + + public CfCode( + DexType originalHolder, + int maxStack, + int maxLocals, + List<CfInstruction> instructions, + List<CfTryCatch> tryCatchRanges, + List<LocalVariableInfo> localVariables, + com.android.tools.r8.position.Position diagnosticPosition) { this.originalHolder = originalHolder; this.maxStack = maxStack; this.maxLocals = maxLocals; this.instructions = instructions; this.tryCatchRanges = tryCatchRanges; this.localVariables = localVariables; + this.diagnosticPosition = diagnosticPosition; } @Override @@ -208,6 +228,10 @@ return stackMapStatus; } + public com.android.tools.r8.position.Position getDiagnosticPosition() { + return diagnosticPosition; + } + public void setMaxLocals(int newMaxLocals) { maxLocals = newMaxLocals; }
diff --git a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java index e25cb47..8803346 100644 --- a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java +++ b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
@@ -134,7 +134,10 @@ @Override public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) { - DefaultInstanceInitializerSourceCode source = new DefaultInstanceInitializerSourceCode(method); + DexMethod originalMethod = + appView.graphLens().getOriginalMethodSignature(method.getReference()); + DefaultInstanceInitializerSourceCode source = + new DefaultInstanceInitializerSourceCode(originalMethod); return IRBuilder.create(method, appView, source, origin).build(method); } @@ -148,8 +151,10 @@ Position callerPosition, Origin origin, RewrittenPrototypeDescription protoChanges) { + DexMethod originalMethod = + appView.graphLens().getOriginalMethodSignature(method.getReference()); DefaultInstanceInitializerSourceCode source = - new DefaultInstanceInitializerSourceCode(method, callerPosition); + new DefaultInstanceInitializerSourceCode(originalMethod, callerPosition); return IRBuilder.createForInlining( method, appView, codeLens, source, origin, valueNumberGenerator, protoChanges) .build(context); @@ -379,16 +384,16 @@ static class DefaultInstanceInitializerSourceCode extends SyntheticStraightLineSourceCode { - DefaultInstanceInitializerSourceCode(ProgramMethod method) { + DefaultInstanceInitializerSourceCode(DexMethod method) { this(method, null); } - DefaultInstanceInitializerSourceCode(ProgramMethod method, Position callerPosition) { + DefaultInstanceInitializerSourceCode(DexMethod method, Position callerPosition) { super( getInstructionBuilders(), SyntheticPosition.builder() .setLine(0) - .setMethod(method.getReference()) + .setMethod(method) .setCallerPosition(callerPosition) .build()); }
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 4b5152b..289f255 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -65,7 +65,6 @@ import com.android.tools.r8.naming.MemberNaming.MethodSignature; import com.android.tools.r8.naming.MemberNaming.Signature; import com.android.tools.r8.naming.NamingLens; -import com.android.tools.r8.position.MethodPosition; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.ConsumerUtils; @@ -82,7 +81,6 @@ import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.function.BiConsumer; @@ -505,6 +503,11 @@ return !isPrivateMethod() && isVirtualMethod(); } + public boolean isNonStaticPrivateMethod() { + checkIfObsolete(); + return isInstance() && isPrivate(); + } + /** * Returns true if this method can be invoked via invoke-virtual, invoke-super or invoke-interface * and is non-abstract. @@ -958,9 +961,7 @@ getReference().holder, 1 + BooleanUtils.intValue(negate), getReference().getArity() + 1, - Arrays.asList(instructions), - Collections.emptyList(), - Collections.emptyList()); + Arrays.asList(instructions)); } public DexCode buildInstanceOfDexCode(DexType type, boolean negate) { @@ -1078,13 +1079,7 @@ .add(new CfConstString(message)) .add(new CfInvoke(Opcodes.INVOKESPECIAL, exceptionInitMethod, false)) .add(new CfThrow()); - return new CfCode( - getReference().holder, - 3, - locals, - instructionBuilder.build(), - Collections.emptyList(), - Collections.emptyList()); + return new CfCode(getReference().holder, 3, locals, instructionBuilder.build()); } public DexEncodedMethod toTypeSubstitutedMethod(DexMethod method) { @@ -1241,10 +1236,6 @@ return code == null ? "<no code>" : code.toString(this, null); } - public MethodPosition getPosition() { - return new MethodPosition(getReference().asMethodReference()); - } - @Override public boolean isDexEncodedMethod() { checkIfObsolete();
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 a6c1c5c..4c76629 100644 --- a/src/main/java/com/android/tools/r8/graph/GraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -421,11 +421,30 @@ return lookupMethod(method, context.getReference(), Type.VIRTUAL); } - /** Lookup a rebound or non-rebound method reference using the current graph lens. */ - public abstract MethodLookupResult lookupMethod(DexMethod method, DexMethod context, Type type); + public final MethodLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) { + return lookupMethod(method, context, type, null); + } + + /** + * Lookup a rebound or non-rebound method reference using the current graph lens. + * + * @param codeLens Specifies the graph lens which has already been applied to the code object. The + * lookup procedure will not recurse beyond this graph lens to ensure that each mapping is + * applied at most once. + * <p>Note: since the compiler currently inserts {@link ClearCodeRewritingGraphLens} it is + * generally valid to pass null for the {@param codeLens}. The removal of {@link + * ClearCodeRewritingGraphLens} is tracked by b/202368283. After this is removed, the compiler + * should generally use the result of calling {@link AppView#codeLens()}. + */ + public abstract MethodLookupResult lookupMethod( + DexMethod method, DexMethod context, Type type, GraphLens codeLens); protected abstract MethodLookupResult internalLookupMethod( - DexMethod reference, DexMethod context, Type type, LookupMethodContinuation continuation); + DexMethod reference, + DexMethod context, + Type type, + GraphLens codeLens, + LookupMethodContinuation continuation); interface LookupMethodContinuation { @@ -435,21 +454,31 @@ public abstract RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition( DexMethod method); - /** Lookup a rebound or non-rebound field reference using the current graph lens. */ - public DexField lookupField(DexField field) { - // Lookup the field using the graph lens and return the (non-rebound) reference from the lookup - // result. - return lookupFieldResult(field).getReference(); + public final DexField lookupField(DexField field) { + return lookupField(field, null); } /** Lookup a rebound or non-rebound field reference using the current graph lens. */ - public FieldLookupResult lookupFieldResult(DexField field) { + public DexField lookupField(DexField field, GraphLens codeLens) { + // Lookup the field using the graph lens and return the (non-rebound) reference from the lookup + // result. + return lookupFieldResult(field, codeLens).getReference(); + } + + /** Lookup a rebound or non-rebound field reference using the current graph lens. */ + public final FieldLookupResult lookupFieldResult(DexField field) { // Lookup the field using the graph lens and return the lookup result. - return internalLookupField(field, x -> x); + return lookupFieldResult(field, null); + } + + /** Lookup a rebound or non-rebound field reference using the current graph lens. */ + public final FieldLookupResult lookupFieldResult(DexField field, GraphLens codeLens) { + // Lookup the field using the graph lens and return the lookup result. + return internalLookupField(field, codeLens, x -> x); } protected abstract FieldLookupResult internalLookupField( - DexField reference, LookupFieldContinuation continuation); + DexField reference, GraphLens codeLens, LookupFieldContinuation continuation); interface LookupFieldContinuation { @@ -759,7 +788,8 @@ } @Override - public MethodLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) { + public MethodLookupResult lookupMethod( + DexMethod method, DexMethod context, Type type, GraphLens codeLens) { if (method.getHolderType().isArrayType()) { assert lookupType(method.getReturnType()) == method.getReturnType(); assert method.getParameters().stream() @@ -770,7 +800,7 @@ .build(); } assert method.getHolderType().isClassType(); - return internalLookupMethod(method, context, type, result -> result); + return internalLookupMethod(method, context, type, codeLens, result -> result); } @Override @@ -810,18 +840,33 @@ @Override protected FieldLookupResult internalLookupField( - DexField reference, LookupFieldContinuation continuation) { + DexField reference, GraphLens codeLens, LookupFieldContinuation continuation) { + if (this == codeLens) { + return getIdentityLens().internalLookupField(reference, codeLens, continuation); + } return previousLens.internalLookupField( - reference, previous -> continuation.lookupField(internalDescribeLookupField(previous))); + reference, + codeLens, + previous -> continuation.lookupField(internalDescribeLookupField(previous))); } @Override protected MethodLookupResult internalLookupMethod( - DexMethod reference, DexMethod context, Type type, LookupMethodContinuation continuation) { + DexMethod reference, + DexMethod context, + Type type, + GraphLens codeLens, + LookupMethodContinuation continuation) { + if (this == codeLens) { + GraphLens identityLens = getIdentityLens(); + return identityLens.internalLookupMethod( + reference, context, type, identityLens, continuation); + } return previousLens.internalLookupMethod( reference, internalGetPreviousMethodSignature(context), type, + codeLens, previous -> continuation.lookupMethod(internalDescribeLookupMethod(previous, context))); } @@ -912,7 +957,9 @@ } @Override - public MethodLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) { + public MethodLookupResult lookupMethod( + DexMethod method, DexMethod context, Type type, GraphLens codeLens) { + assert codeLens == null || codeLens.isIdentityLens(); return MethodLookupResult.builder(this).setReference(method).setType(type).build(); } @@ -924,7 +971,7 @@ @Override protected FieldLookupResult internalLookupField( - DexField reference, LookupFieldContinuation continuation) { + DexField reference, GraphLens codeLens, LookupFieldContinuation continuation) { // Passes the field reference back to the next graph lens. The identity lens intentionally // does not set the rebound field reference, since it does not know what that is. return continuation.lookupField( @@ -933,11 +980,14 @@ @Override protected MethodLookupResult internalLookupMethod( - DexMethod reference, DexMethod context, Type type, LookupMethodContinuation continuation) { + DexMethod reference, + DexMethod context, + Type type, + GraphLens codeLens, + LookupMethodContinuation continuation) { // Passes the method reference back to the next graph lens. The identity lens intentionally // does not set the rebound method reference, since it does not know what that is. - return continuation.lookupMethod( - MethodLookupResult.builder(this).setReference(reference).setType(type).build()); + return continuation.lookupMethod(lookupMethod(reference, context, type, codeLens)); } @Override @@ -1004,8 +1054,8 @@ @Override protected FieldLookupResult internalLookupField( - DexField reference, LookupFieldContinuation continuation) { - return getIdentityLens().internalLookupField(reference, continuation); + DexField reference, GraphLens codeLens, LookupFieldContinuation continuation) { + return getIdentityLens().internalLookupField(reference, codeLens, continuation); } @Override @@ -1015,8 +1065,15 @@ @Override protected MethodLookupResult internalLookupMethod( - DexMethod reference, DexMethod context, Type type, LookupMethodContinuation continuation) { - return getIdentityLens().internalLookupMethod(reference, context, type, continuation); + DexMethod reference, + DexMethod context, + Type type, + GraphLens codeLens, + LookupMethodContinuation continuation) { + assert codeLens == null || codeLens == this; + GraphLens identityLens = getIdentityLens(); + return identityLens.internalLookupMethod( + reference, context, type, identityLens, continuation); } @Override
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 2403804..ae16c5a 100644 --- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java +++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -62,6 +62,8 @@ import com.android.tools.r8.naming.ClassNameMapper; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.position.MethodPosition; +import com.android.tools.r8.position.TextPosition; +import com.android.tools.r8.position.TextRange; import com.android.tools.r8.shaking.ProguardConfiguration; import com.android.tools.r8.shaking.ProguardKeepAttributes; import com.android.tools.r8.utils.ExceptionUtils; @@ -368,6 +370,8 @@ private final LazyCfCode code; private final DexMethod method; private final Origin origin; + private int minLine = Integer.MAX_VALUE; + private int maxLine = -1; MethodCodeVisitor( JarApplicationReader application, @@ -406,7 +410,7 @@ throw new CompilationError( "Absent Code attribute in method that is not native or abstract", origin, - new MethodPosition(method.asMethodReference())); + MethodPosition.create(method.asMethodReference(), getDiagnosticPosition())); } code.setCode( new CfCode( @@ -415,7 +419,20 @@ maxLocals, instructions, tryCatchRanges, - localVariables)); + localVariables, + getDiagnosticPosition())); + } + + private com.android.tools.r8.position.Position getDiagnosticPosition() { + if (minLine == Integer.MAX_VALUE) { + return com.android.tools.r8.position.Position.UNKNOWN; + } else if (minLine == maxLine) { + return new TextPosition(0, minLine, TextPosition.UNKNOWN_COLUMN); + } else { + return new TextRange( + new TextPosition(0, minLine, TextPosition.UNKNOWN_COLUMN), + new TextPosition(0, maxLine, TextPosition.UNKNOWN_COLUMN)); + } } @Override @@ -1015,6 +1032,8 @@ @Override public void visitLineNumber(int line, Label start) { + minLine = Math.min(line, minLine); + maxLine = Math.max(line, maxLine); if (debugParsingOptions.lineInfo) { instructions.add( new CfPosition(
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java index cf866d4..9bd0c4a 100644 --- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java +++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -15,7 +15,6 @@ import com.android.tools.r8.kotlin.KotlinMethodLevelInfo; import com.android.tools.r8.logging.Log; import com.android.tools.r8.origin.Origin; -import com.android.tools.r8.position.MethodPosition; import com.android.tools.r8.shaking.AppInfoWithLiveness; /** Type representing a method definition in the programs compilation unit and its holder. */ @@ -171,8 +170,4 @@ public KotlinMethodLevelInfo getKotlinInfo() { return getDefinition().getKotlinInfo(); } - - public MethodPosition getPosition() { - return getDefinition().getPosition(); - } }
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java index 8ac7c59..09d477d 100644 --- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java +++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -40,6 +40,10 @@ continuation = TraversalContinuation.BREAK; } + public GraphLens getCodeLens() { + return appView.codeLens(); + } + public final T getContext() { return context; } @@ -69,7 +73,7 @@ public void registerInvokeSpecial(DexMethod method) { DexClassAndMethod context = getMethodContext(); - Invoke.Type type = Invoke.Type.fromInvokeSpecial(method, context, appView); + Invoke.Type type = Invoke.Type.fromInvokeSpecial(method, context, appView, getCodeLens()); if (type.isDirect()) { registerInvokeDirect(method); } else {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java index b1219c4..bb8f046 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
@@ -42,7 +42,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.ListIterator; import java.util.Set; @@ -136,12 +135,7 @@ SyntheticPosition.builder().setLine(0).setMethod(syntheticMethodReference).build(); List<CfInstruction> instructions = buildInstructions(callerPosition); return new CfCode( - syntheticMethodReference.getHolderType(), - maxStack, - maxLocals, - instructions, - Collections.emptyList(), - Collections.emptyList()); + syntheticMethodReference.getHolderType(), maxStack, maxLocals, instructions); } private List<CfInstruction> buildInstructions(Position callerPosition) {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/deadlock/SingleCallerInformation.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/deadlock/SingleCallerInformation.java index 0e35bf3..95f0385 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/deadlock/SingleCallerInformation.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/deadlock/SingleCallerInformation.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.graph.UseRegistry; +import com.android.tools.r8.ir.conversion.callgraph.CallGraph; import com.android.tools.r8.utils.ThreadUtils; import com.android.tools.r8.utils.collections.ProgramMethodMap; import java.util.IdentityHashMap; @@ -26,9 +27,9 @@ * since computing single caller information for such methods is expensive (it involves computing * the possible dispatch targets for each virtual invoke). * - * <p>Unlike the {@link com.android.tools.r8.ir.conversion.CallGraph} that is used to determine if a - * method can be single caller inlined, this considers a method that is called from multiple call - * sites in the same method to have a single caller. + * <p>Unlike the {@link CallGraph} that is used to determine if a method can be single caller + * inlined, this considers a method that is called from multiple call sites in the same method to + * have a single caller. */ // TODO(b/205611444): account for -keep rules. public class SingleCallerInformation {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java index 3aa2dac..b5eec8c 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -31,9 +31,9 @@ import com.android.tools.r8.ir.code.SafeCheckCast; import com.android.tools.r8.ir.code.StaticGet; import com.android.tools.r8.ir.code.Value; -import com.android.tools.r8.ir.conversion.CallGraph.Node; import com.android.tools.r8.ir.conversion.IRConverter; import com.android.tools.r8.ir.conversion.MethodProcessor; +import com.android.tools.r8.ir.conversion.callgraph.Node; import com.android.tools.r8.ir.optimize.Inliner; import com.android.tools.r8.ir.optimize.Inliner.Reason; import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java index 8c71936..337b88b 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.ir.code.InvokeMethod; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.MethodProcessor; +import com.android.tools.r8.ir.optimize.DefaultInliningOracle; import com.android.tools.r8.ir.optimize.Inliner.Reason; import com.android.tools.r8.ir.optimize.inliner.InliningReasonStrategy; @@ -38,6 +39,7 @@ InvokeMethod invoke, ProgramMethod target, ProgramMethod context, + DefaultInliningOracle oracle, MethodProcessor methodProcessor) { if (references.isAbstractGeneratedMessageLiteBuilder(context.getHolder()) && invoke.isInvokeSuper()) { @@ -48,7 +50,7 @@ } return references.isDynamicMethod(target) || references.isDynamicMethodBridge(target) ? computeInliningReasonForDynamicMethod(invoke, target, context) - : parent.computeInliningReason(invoke, target, context, methodProcessor); + : parent.computeInliningReason(invoke, target, context, oracle, methodProcessor); } private Reason computeInliningReasonForDynamicMethod(
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldGet.java b/src/main/java/com/android/tools/r8/ir/code/FieldGet.java index 3f62f1f..aecb3eb 100644 --- a/src/main/java/com/android/tools/r8/ir/code/FieldGet.java +++ b/src/main/java/com/android/tools/r8/ir/code/FieldGet.java
@@ -4,7 +4,11 @@ package com.android.tools.r8.ir.code; +import com.android.tools.r8.graph.DexField; + public interface FieldGet { + DexField getField(); + Value outValue(); }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java deleted file mode 100644 index 6ed9ef6..0000000 --- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java +++ /dev/null
@@ -1,303 +0,0 @@ -// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -package com.android.tools.r8.ir.conversion; - -import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexEncodedMethod; -import com.android.tools.r8.graph.ProgramMethod; -import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator.CycleEliminationResult; -import com.android.tools.r8.ir.conversion.CallSiteInformation.CallGraphBasedCallSiteInformation; -import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.android.tools.r8.utils.collections.ProgramMethodSet; -import com.google.common.collect.Sets; -import java.util.Iterator; -import java.util.Set; -import java.util.TreeSet; -import java.util.function.Consumer; -import java.util.function.Predicate; - -/** - * Call graph representation. - * - * <p>Each node in the graph contain the methods called and the calling methods. For virtual and - * interface calls all potential calls from subtypes are recorded. - * - * <p>Only methods in the program - not library methods - are represented. - * - * <p>The directional edges are represented as sets of nodes in each node (called methods and - * callees). - * - * <p>A call from method <code>a</code> to method <code>b</code> is only present once no matter how - * many calls of <code>a</code> there are in <code>a</code>. - * - * <p>Recursive calls are not present. - */ -public class CallGraph { - - public static class Node implements Comparable<Node> { - - public static Node[] EMPTY_ARRAY = {}; - - private final ProgramMethod method; - private int numberOfCallSites = 0; - - // Outgoing calls from this method. - private final Set<Node> callees = new TreeSet<>(); - - // Incoming calls to this method. - private final Set<Node> callers = new TreeSet<>(); - - // Incoming field read edges to this method (i.e., the set of methods that read a field written - // by the current method). - private final Set<Node> readers = new TreeSet<>(); - - // Outgoing field read edges from this method (i.e., the set of methods that write a field read - // by the current method). - private final Set<Node> writers = new TreeSet<>(); - - public Node(ProgramMethod method) { - this.method = method; - } - - public void addCallerConcurrently(Node caller) { - addCallerConcurrently(caller, false); - } - - public void addCallerConcurrently(Node caller, boolean likelySpuriousCallEdge) { - if (caller != this && !likelySpuriousCallEdge) { - boolean changedCallers; - synchronized (callers) { - changedCallers = callers.add(caller); - numberOfCallSites++; - } - if (changedCallers) { - synchronized (caller.callees) { - caller.callees.add(this); - } - // Avoid redundant field read edges (call edges are considered stronger). - removeReaderConcurrently(caller); - } - } else { - synchronized (callers) { - numberOfCallSites++; - } - } - } - - public void addReaderConcurrently(Node reader) { - if (reader != this) { - synchronized (callers) { - if (callers.contains(reader)) { - // Avoid redundant field read edges (call edges are considered stronger). - return; - } - boolean readersChanged; - synchronized (readers) { - readersChanged = readers.add(reader); - } - if (readersChanged) { - synchronized (reader.writers) { - reader.writers.add(this); - } - } - } - } - } - - private void removeReaderConcurrently(Node reader) { - synchronized (readers) { - readers.remove(reader); - } - synchronized (reader.writers) { - reader.writers.remove(this); - } - } - - public void removeCaller(Node caller) { - boolean callersChanged = callers.remove(caller); - assert callersChanged; - boolean calleesChanged = caller.callees.remove(this); - assert calleesChanged; - assert !hasReader(caller); - } - - public void removeReader(Node reader) { - boolean readersChanged = readers.remove(reader); - assert readersChanged; - boolean writersChanged = reader.writers.remove(this); - assert writersChanged; - assert !hasCaller(reader); - } - - public void cleanCalleesAndWritersForRemoval() { - assert callers.isEmpty(); - assert readers.isEmpty(); - for (Node callee : callees) { - boolean changed = callee.callers.remove(this); - assert changed; - } - for (Node writer : writers) { - boolean changed = writer.readers.remove(this); - assert changed; - } - } - - public void cleanCallersAndReadersForRemoval() { - assert callees.isEmpty(); - assert writers.isEmpty(); - for (Node caller : callers) { - boolean changed = caller.callees.remove(this); - assert changed; - } - for (Node reader : readers) { - boolean changed = reader.writers.remove(this); - assert changed; - } - } - - public Set<Node> getCallersWithDeterministicOrder() { - return callers; - } - - public Set<Node> getCalleesWithDeterministicOrder() { - return callees; - } - - public Set<Node> getReadersWithDeterministicOrder() { - return readers; - } - - public Set<Node> getWritersWithDeterministicOrder() { - return writers; - } - - public int getNumberOfCallSites() { - return numberOfCallSites; - } - - public boolean hasCallee(Node method) { - return callees.contains(method); - } - - public boolean hasCaller(Node method) { - return callers.contains(method); - } - - public boolean hasReader(Node method) { - return readers.contains(method); - } - - public boolean hasWriter(Node method) { - return writers.contains(method); - } - - public boolean isRoot() { - return callers.isEmpty() && readers.isEmpty(); - } - - public boolean isLeaf() { - return callees.isEmpty() && writers.isEmpty(); - } - - @Override - public int compareTo(Node other) { - return getProgramMethod().getReference().compareTo(other.getProgramMethod().getReference()); - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("MethodNode for: "); - builder.append(getProgramMethod().toSourceString()); - builder.append(" ("); - builder.append(callees.size()); - builder.append(" callees, "); - builder.append(callers.size()); - builder.append(" callers"); - builder.append(", invoke count ").append(numberOfCallSites); - builder.append(")."); - builder.append(System.lineSeparator()); - if (callees.size() > 0) { - builder.append("Callees:"); - builder.append(System.lineSeparator()); - for (Node call : callees) { - builder.append(" "); - builder.append(call.getProgramMethod().toSourceString()); - builder.append(System.lineSeparator()); - } - } - if (callers.size() > 0) { - builder.append("Callers:"); - builder.append(System.lineSeparator()); - for (Node caller : callers) { - builder.append(" "); - builder.append(caller.getProgramMethod().toSourceString()); - builder.append(System.lineSeparator()); - } - } - return builder.toString(); - } - - public DexEncodedMethod getMethod() { - return method.getDefinition(); - } - - public ProgramMethod getProgramMethod() { - return method; - } - } - - final Set<Node> nodes; - final CycleEliminationResult cycleEliminationResult; - - CallGraph(Set<Node> nodes) { - this(nodes, null); - } - - CallGraph(Set<Node> nodes, CycleEliminationResult cycleEliminationResult) { - this.nodes = nodes; - this.cycleEliminationResult = cycleEliminationResult; - } - - static CallGraphBuilder builder(AppView<AppInfoWithLiveness> appView) { - return new CallGraphBuilder(appView); - } - - CallSiteInformation createCallSiteInformation(AppView<AppInfoWithLiveness> appView) { - // Don't leverage single/dual call site information when we are not tree shaking. - return appView.options().isShrinking() - ? new CallGraphBasedCallSiteInformation(appView, this) - : CallSiteInformation.empty(); - } - - public boolean isEmpty() { - return nodes.isEmpty(); - } - - public ProgramMethodSet extractLeaves() { - return extractNodes(Node::isLeaf, Node::cleanCallersAndReadersForRemoval); - } - - public ProgramMethodSet extractRoots() { - return extractNodes(Node::isRoot, Node::cleanCalleesAndWritersForRemoval); - } - - private ProgramMethodSet extractNodes(Predicate<Node> predicate, Consumer<Node> clean) { - ProgramMethodSet result = ProgramMethodSet.create(); - Set<Node> removed = Sets.newIdentityHashSet(); - Iterator<Node> nodeIterator = nodes.iterator(); - while (nodeIterator.hasNext()) { - Node node = nodeIterator.next(); - if (predicate.test(node)) { - result.add(node.getProgramMethod()); - nodeIterator.remove(); - removed.add(node); - } - } - removed.forEach(clean); - assert !result.isEmpty(); - return result; - } -}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java deleted file mode 100644 index 58bfafa..0000000 --- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java +++ /dev/null
@@ -1,808 +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.conversion; - -import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; - -import com.android.tools.r8.errors.CompilationError; -import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexCallSite; -import com.android.tools.r8.graph.DexClass; -import com.android.tools.r8.graph.DexClassAndMethod; -import com.android.tools.r8.graph.DexEncodedMethod; -import com.android.tools.r8.graph.DexField; -import com.android.tools.r8.graph.DexMethod; -import com.android.tools.r8.graph.DexProgramClass; -import com.android.tools.r8.graph.DexType; -import com.android.tools.r8.graph.FieldAccessInfo; -import com.android.tools.r8.graph.FieldAccessInfoCollection; -import com.android.tools.r8.graph.GraphLens.MethodLookupResult; -import com.android.tools.r8.graph.LookupResult; -import com.android.tools.r8.graph.MethodResolutionResult; -import com.android.tools.r8.graph.ProgramField; -import com.android.tools.r8.graph.ProgramMethod; -import com.android.tools.r8.graph.UseRegistry; -import com.android.tools.r8.ir.code.Invoke; -import com.android.tools.r8.ir.conversion.CallGraph.Node; -import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator.CycleEliminationResult; -import com.android.tools.r8.logging.Log; -import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.android.tools.r8.utils.Timing; -import com.android.tools.r8.utils.collections.ProgramMethodSet; -import com.google.common.collect.Iterators; -import com.google.common.collect.Sets; -import java.util.ArrayDeque; -import java.util.Collection; -import java.util.Deque; -import java.util.IdentityHashMap; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Predicate; - -abstract class CallGraphBuilderBase { - - final AppView<AppInfoWithLiveness> appView; - private final FieldAccessInfoCollection<?> fieldAccessInfoCollection; - final Map<DexMethod, Node> nodes = new IdentityHashMap<>(); - private final Map<DexMethod, ProgramMethodSet> possibleProgramTargetsCache = - new ConcurrentHashMap<>(); - - CallGraphBuilderBase(AppView<AppInfoWithLiveness> appView) { - this.appView = appView; - this.fieldAccessInfoCollection = appView.appInfo().getFieldAccessInfoCollection(); - } - - public CallGraph build(ExecutorService executorService, Timing timing) throws ExecutionException { - timing.begin("Build IR processing order constraints"); - timing.begin("Build call graph"); - populateGraph(executorService); - assert verifyNoRedundantFieldReadEdges(); - timing.end(); - assert verifyAllMethodsWithCodeExists(); - - appView.withGeneratedMessageLiteBuilderShrinker( - shrinker -> shrinker.preprocessCallGraphBeforeCycleElimination(nodes)); - - timing.begin("Cycle elimination"); - // Sort the nodes for deterministic cycle elimination. - Set<Node> nodesWithDeterministicOrder = Sets.newTreeSet(nodes.values()); - CycleEliminator cycleEliminator = new CycleEliminator(); - CycleEliminationResult cycleEliminationResult = - cycleEliminator.breakCycles(nodesWithDeterministicOrder); - timing.end(); - timing.end(); - assert cycleEliminator.breakCycles(nodesWithDeterministicOrder).numberOfRemovedCallEdges() - == 0; // The cycles should be gone. - - return new CallGraph(nodesWithDeterministicOrder, cycleEliminationResult); - } - - abstract void populateGraph(ExecutorService executorService) throws ExecutionException; - - /** Verify that there are no field read edges in the graph if there is also a call graph edge. */ - private boolean verifyNoRedundantFieldReadEdges() { - for (Node writer : nodes.values()) { - for (Node reader : writer.getReadersWithDeterministicOrder()) { - assert !writer.hasCaller(reader); - } - } - return true; - } - - Node getOrCreateNode(ProgramMethod method) { - synchronized (nodes) { - return nodes.computeIfAbsent(method.getReference(), ignore -> new Node(method)); - } - } - - abstract boolean verifyAllMethodsWithCodeExists(); - - class InvokeExtractor extends UseRegistry<ProgramMethod> { - - private final Node currentMethod; - private final Predicate<ProgramMethod> targetTester; - - InvokeExtractor(Node currentMethod, Predicate<ProgramMethod> targetTester) { - super(appView, currentMethod.getProgramMethod()); - this.currentMethod = currentMethod; - this.targetTester = targetTester; - } - - private void addClassInitializerTarget(DexProgramClass clazz) { - assert clazz != null; - if (clazz.hasClassInitializer()) { - addCallEdge(clazz.getProgramClassInitializer(), false); - } - } - - private void addClassInitializerTarget(DexType type) { - assert type.isClassType(); - DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type)); - if (clazz != null) { - addClassInitializerTarget(clazz); - } - } - - private void addCallEdge(ProgramMethod callee, boolean likelySpuriousCallEdge) { - if (!targetTester.test(callee)) { - return; - } - if (callee.getDefinition().isAbstract()) { - // Not a valid target. - return; - } - if (callee.getDefinition().isNative()) { - // We don't care about calls to native methods. - return; - } - if (!appView.getKeepInfo(callee).isInliningAllowed(appView.options())) { - // Since the callee is kept and optimizations are disallowed, we cannot inline it into the - // caller, and we also cannot collect any optimization info for the method. Therefore, we - // drop the call edge to reduce the total number of call graph edges, which should lead to - // fewer call graph cycles. - return; - } - getOrCreateNode(callee).addCallerConcurrently(currentMethod, likelySpuriousCallEdge); - } - - private void addFieldReadEdge(DexEncodedMethod writer) { - addFieldReadEdge(writer.asProgramMethod(appView)); - } - - private void addFieldReadEdge(ProgramMethod writer) { - assert !writer.getDefinition().isAbstract(); - if (!targetTester.test(writer)) { - return; - } - getOrCreateNode(writer).addReaderConcurrently(currentMethod); - } - - private void processInvoke(Invoke.Type originalType, DexMethod originalMethod) { - ProgramMethod context = currentMethod.getProgramMethod(); - MethodLookupResult result = - appView.graphLens().lookupMethod(originalMethod, context.getReference(), originalType); - DexMethod method = result.getReference(); - Invoke.Type type = result.getType(); - if (type == Invoke.Type.INTERFACE || type == Invoke.Type.VIRTUAL) { - // For virtual and interface calls add all potential targets that could be called. - MethodResolutionResult resolutionResult = - appView.appInfo().resolveMethod(method, type == Invoke.Type.INTERFACE); - DexEncodedMethod target = resolutionResult.getSingleTarget(); - if (target != null) { - processInvokeWithDynamicDispatch(type, target, context); - } - } else { - ProgramMethod singleTarget = - appView.appInfo().lookupSingleProgramTarget(type, method, context, appView); - if (singleTarget != null) { - assert !context.getDefinition().isBridge() - || singleTarget.getDefinition() != context.getDefinition(); - // For static invokes, the class could be initialized. - if (type.isStatic()) { - addClassInitializerTarget(singleTarget.getHolder()); - } - addCallEdge(singleTarget, false); - } - } - } - - private void processInvokeWithDynamicDispatch( - Invoke.Type type, DexEncodedMethod encodedTarget, ProgramMethod context) { - DexMethod target = encodedTarget.getReference(); - DexClass clazz = appView.definitionFor(target.holder); - if (clazz == null) { - assert false : "Unable to lookup holder of `" + target.toSourceString() + "`"; - return; - } - - if (!appView.options().testing.addCallEdgesForLibraryInvokes) { - if (clazz.isLibraryClass()) { - // Likely to have many possible targets. - return; - } - } - - boolean isInterface = type == Invoke.Type.INTERFACE; - ProgramMethodSet possibleProgramTargets = - possibleProgramTargetsCache.computeIfAbsent( - target, - method -> { - MethodResolutionResult resolution = - appView.appInfo().resolveMethod(method, isInterface); - if (resolution.isVirtualTarget()) { - LookupResult lookupResult = - resolution.lookupVirtualDispatchTargets( - context.getHolder(), appView.appInfo()); - if (lookupResult.isLookupResultSuccess()) { - ProgramMethodSet targets = ProgramMethodSet.create(); - lookupResult - .asLookupResultSuccess() - .forEach( - methodTarget -> { - if (methodTarget.isProgramMethod()) { - targets.add(methodTarget.asProgramMethod()); - } - }, - lambdaTarget -> { - // The call target will ultimately be the implementation method. - DexClassAndMethod implementationMethod = - lambdaTarget.getImplementationMethod(); - if (implementationMethod.isProgramMethod()) { - targets.add(implementationMethod.asProgramMethod()); - } - }); - return targets; - } - } - return null; - }); - if (possibleProgramTargets != null) { - boolean likelySpuriousCallEdge = - possibleProgramTargets.size() - >= appView.options().callGraphLikelySpuriousCallEdgeThreshold; - for (ProgramMethod possibleTarget : possibleProgramTargets) { - addCallEdge(possibleTarget, likelySpuriousCallEdge); - } - } - } - - private void processFieldRead(DexField reference) { - if (!reference.holder.isClassType()) { - return; - } - - ProgramField field = appView.appInfo().resolveField(reference).getProgramField(); - if (field == null || appView.appInfo().isPinned(field)) { - return; - } - - // Each static field access implicitly triggers the class initializer. - if (field.getAccessFlags().isStatic()) { - addClassInitializerTarget(field.getHolder()); - } - - FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(field.getReference()); - if (fieldAccessInfo != null && fieldAccessInfo.hasKnownWriteContexts()) { - if (fieldAccessInfo.getNumberOfWriteContexts() == 1) { - fieldAccessInfo.forEachWriteContext(this::addFieldReadEdge); - } - } - } - - private void processFieldWrite(DexField reference) { - if (reference.getHolderType().isClassType()) { - ProgramField field = appView.appInfo().resolveField(reference).getProgramField(); - if (field != null && field.getAccessFlags().isStatic()) { - // Each static field access implicitly triggers the class initializer. - addClassInitializerTarget(field.getHolder()); - } - } - } - - private void processInitClass(DexType type) { - DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type)); - if (clazz == null) { - assert false; - return; - } - addClassInitializerTarget(clazz); - } - - @Override - public void registerInitClass(DexType clazz) { - processInitClass(clazz); - } - - @Override - public void registerInvokeVirtual(DexMethod method) { - processInvoke(Invoke.Type.VIRTUAL, method); - } - - @Override - public void registerInvokeDirect(DexMethod method) { - processInvoke(Invoke.Type.DIRECT, method); - } - - @Override - public void registerInvokeStatic(DexMethod method) { - processInvoke(Invoke.Type.STATIC, method); - } - - @Override - public void registerInvokeInterface(DexMethod method) { - processInvoke(Invoke.Type.INTERFACE, method); - } - - @Override - public void registerInvokeSuper(DexMethod method) { - processInvoke(Invoke.Type.SUPER, method); - } - - @Override - public void registerInstanceFieldRead(DexField field) { - processFieldRead(field); - } - - @Override - public void registerInstanceFieldWrite(DexField field) { - processFieldWrite(field); - } - - @Override - public void registerNewInstance(DexType type) { - if (type.isClassType()) { - addClassInitializerTarget(type); - } - } - - @Override - public void registerStaticFieldRead(DexField field) { - processFieldRead(field); - } - - @Override - public void registerStaticFieldWrite(DexField field) { - processFieldWrite(field); - } - - @Override - public void registerTypeReference(DexType type) {} - - @Override - public void registerInstanceOf(DexType type) {} - - @Override - public void registerCallSite(DexCallSite callSite) { - registerMethodHandle( - callSite.bootstrapMethod, MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY); - } - } - - static class CycleEliminator { - - static final String CYCLIC_FORCE_INLINING_MESSAGE = - "Unable to satisfy force inlining constraints due to cyclic force inlining"; - - private static class CallEdge { - - private final Node caller; - private final Node callee; - - CallEdge(Node caller, Node callee) { - this.caller = caller; - this.callee = callee; - } - } - - static class StackEntryInfo { - - final int index; - final Node predecessor; - - boolean processed; - - StackEntryInfo(int index, Node predecessor) { - this.index = index; - this.predecessor = predecessor; - } - } - - static class CycleEliminationResult { - - private Map<DexEncodedMethod, ProgramMethodSet> removedCallEdges; - - CycleEliminationResult(Map<DexEncodedMethod, ProgramMethodSet> removedCallEdges) { - this.removedCallEdges = removedCallEdges; - } - - void forEachRemovedCaller(ProgramMethod callee, Consumer<ProgramMethod> fn) { - removedCallEdges.getOrDefault(callee.getDefinition(), ProgramMethodSet.empty()).forEach(fn); - } - - int numberOfRemovedCallEdges() { - int numberOfRemovedCallEdges = 0; - for (ProgramMethodSet nodes : removedCallEdges.values()) { - numberOfRemovedCallEdges += nodes.size(); - } - return numberOfRemovedCallEdges; - } - } - - // DFS stack. - private Deque<Node> stack = new ArrayDeque<>(); - - // Nodes on the DFS stack. - private Map<Node, StackEntryInfo> stackEntryInfo = new IdentityHashMap<>(); - - // Subset of the DFS stack, where the nodes on the stack are class initializers. - // - // This stack is used to efficiently compute if there is a class initializer on the stack. - private Deque<Node> clinitCallStack = new ArrayDeque<>(); - - // Subset of the DFS stack, where the nodes on the stack satisfy that the edge from the - // predecessor to the node itself is a field read edge. - // - // This stack is used to efficiently compute if there is a field read edge inside a cycle when - // a cycle is found. - private Deque<Node> writerStack = new ArrayDeque<>(); - - // Set of nodes that have been visited entirely. - private Set<Node> marked = Sets.newIdentityHashSet(); - - // Call edges that should be removed when the caller has been processed. These are not removed - // directly since that would lead to ConcurrentModificationExceptions. - private Map<Node, Set<Node>> calleesToBeRemoved = new IdentityHashMap<>(); - - // Field read edges that should be removed when the reader has been processed. These are not - // removed directly since that would lead to ConcurrentModificationExceptions. - private Map<Node, Set<Node>> writersToBeRemoved = new IdentityHashMap<>(); - - // Mapping from callee to the set of callers that were removed from the callee. - private Map<DexEncodedMethod, ProgramMethodSet> removedCallEdges = new IdentityHashMap<>(); - - // Set of nodes from which cycle elimination must be rerun to ensure that all cycles will be - // removed. - private LinkedHashSet<Node> revisit = new LinkedHashSet<>(); - - CycleEliminationResult breakCycles(Collection<Node> roots) { - // Break cycles in this call graph by removing edges causing cycles. We do this in a fixpoint - // because the algorithm does not guarantee that all cycles will be removed from the graph - // when we remove an edge in the middle of a cycle that contains another cycle. - do { - traverse(roots); - roots = revisit; - prepareForNewTraversal(); - } while (!roots.isEmpty()); - - CycleEliminationResult result = new CycleEliminationResult(removedCallEdges); - if (Log.ENABLED) { - Log.info(getClass(), "# call graph cycles broken: %s", result.numberOfRemovedCallEdges()); - } - reset(); - return result; - } - - private void prepareForNewTraversal() { - assert calleesToBeRemoved.isEmpty(); - assert clinitCallStack.isEmpty(); - assert stack.isEmpty(); - assert stackEntryInfo.isEmpty(); - assert writersToBeRemoved.isEmpty(); - assert writerStack.isEmpty(); - marked.clear(); - revisit = new LinkedHashSet<>(); - } - - private void reset() { - assert clinitCallStack.isEmpty(); - assert marked.isEmpty(); - assert revisit.isEmpty(); - assert stack.isEmpty(); - assert stackEntryInfo.isEmpty(); - assert writerStack.isEmpty(); - removedCallEdges = new IdentityHashMap<>(); - } - - private static class WorkItem { - boolean isNode() { - return false; - } - - NodeWorkItem asNode() { - return null; - } - - boolean isIterator() { - return false; - } - - IteratorWorkItem asIterator() { - return null; - } - } - - private static class NodeWorkItem extends WorkItem { - private final Node node; - - NodeWorkItem(Node node) { - this.node = node; - } - - @Override - boolean isNode() { - return true; - } - - @Override - NodeWorkItem asNode() { - return this; - } - } - - private static class IteratorWorkItem extends WorkItem { - private final Node callerOrReader; - private final Iterator<Node> calleesAndWriters; - - IteratorWorkItem(Node callerOrReader, Iterator<Node> calleesAndWriters) { - this.callerOrReader = callerOrReader; - this.calleesAndWriters = calleesAndWriters; - } - - @Override - boolean isIterator() { - return true; - } - - @Override - IteratorWorkItem asIterator() { - return this; - } - } - - private void traverse(Collection<Node> roots) { - Deque<WorkItem> workItems = new ArrayDeque<>(roots.size()); - for (Node node : roots) { - workItems.addLast(new NodeWorkItem(node)); - } - while (!workItems.isEmpty()) { - WorkItem workItem = workItems.removeFirst(); - if (workItem.isNode()) { - Node node = workItem.asNode().node; - if (marked.contains(node)) { - // Already visited all nodes that can be reached from this node. - continue; - } - - Node predecessor = stack.isEmpty() ? null : stack.peek(); - push(node, predecessor); - - // The callees and writers must be sorted before calling traverse recursively. - // This ensures that cycles are broken the same way across multiple compilations. - Iterator<Node> calleesAndWriterIterator = - Iterators.concat( - node.getCalleesWithDeterministicOrder().iterator(), - node.getWritersWithDeterministicOrder().iterator()); - workItems.addFirst(new IteratorWorkItem(node, calleesAndWriterIterator)); - } else { - assert workItem.isIterator(); - IteratorWorkItem iteratorWorkItem = workItem.asIterator(); - Node newCallerOrReader = - iterateCalleesAndWriters( - iteratorWorkItem.calleesAndWriters, iteratorWorkItem.callerOrReader); - if (newCallerOrReader != null) { - // We did not finish the work on this iterator, so add it again. - workItems.addFirst(iteratorWorkItem); - workItems.addFirst(new NodeWorkItem(newCallerOrReader)); - } else { - assert !iteratorWorkItem.calleesAndWriters.hasNext(); - pop(iteratorWorkItem.callerOrReader); - marked.add(iteratorWorkItem.callerOrReader); - - Collection<Node> calleesToBeRemovedFromCaller = - calleesToBeRemoved.remove(iteratorWorkItem.callerOrReader); - if (calleesToBeRemovedFromCaller != null) { - calleesToBeRemovedFromCaller.forEach( - callee -> { - callee.removeCaller(iteratorWorkItem.callerOrReader); - recordCallEdgeRemoval(iteratorWorkItem.callerOrReader, callee); - }); - } - - Collection<Node> writersToBeRemovedFromReader = - writersToBeRemoved.remove(iteratorWorkItem.callerOrReader); - if (writersToBeRemovedFromReader != null) { - writersToBeRemovedFromReader.forEach( - writer -> writer.removeReader(iteratorWorkItem.callerOrReader)); - } - } - } - } - } - - private Node iterateCalleesAndWriters( - Iterator<Node> calleeOrWriterIterator, Node callerOrReader) { - while (calleeOrWriterIterator.hasNext()) { - Node calleeOrWriter = calleeOrWriterIterator.next(); - StackEntryInfo calleeOrWriterStackEntryInfo = stackEntryInfo.get(calleeOrWriter); - boolean foundCycle = calleeOrWriterStackEntryInfo != null; - if (!foundCycle) { - return calleeOrWriter; - } - - // Found a cycle that needs to be eliminated. If it is a field read edge, then remove it - // right away. - boolean isFieldReadEdge = calleeOrWriter.hasReader(callerOrReader); - if (isFieldReadEdge) { - removeFieldReadEdge(callerOrReader, calleeOrWriter); - continue; - } - - // Otherwise, it is a call edge. Check if there is a field read edge in the cycle, and if - // so, remove that edge. - if (!writerStack.isEmpty() - && removeIncomingEdgeOnStack( - writerStack.peek(), - calleeOrWriter, - calleeOrWriterStackEntryInfo, - this::removeFieldReadEdge)) { - continue; - } - - // It is a call edge and the cycle does not contain any field read edges. - // If it is a call edge to a <clinit>, then remove it. - if (calleeOrWriter.getMethod().isClassInitializer()) { - // Calls to class initializers are always safe to remove. - assert callEdgeRemovalIsSafe(callerOrReader, calleeOrWriter); - removeCallEdge(callerOrReader, calleeOrWriter); - continue; - } - - // Otherwise, check if there is a call edge to a <clinit> method in the cycle, and if so, - // remove that edge. - if (!clinitCallStack.isEmpty() - && removeIncomingEdgeOnStack( - clinitCallStack.peek(), - calleeOrWriter, - calleeOrWriterStackEntryInfo, - this::removeCallEdge)) { - continue; - } - - // Otherwise, we remove the call edge if it is safe according to force inlining. - if (callEdgeRemovalIsSafe(callerOrReader, calleeOrWriter)) { - // Break the cycle by removing the edge node->calleeOrWriter. - // Need to remove `calleeOrWriter` from `node.callees` using the iterator to prevent a - // ConcurrentModificationException. - removeCallEdge(callerOrReader, calleeOrWriter); - continue; - } - - // The call edge cannot be removed due to force inlining. Find another call edge in the - // cycle that can safely be removed instead. - LinkedList<Node> cycle = extractCycle(calleeOrWriter); - - // Break the cycle by finding an edge that can be removed without breaking force - // inlining. If that is not possible, this call fails with a compilation error. - CallEdge edge = findCallEdgeForRemoval(cycle); - - // The edge will be null if this cycle has already been eliminated as a result of - // another cycle elimination. - if (edge != null) { - assert callEdgeRemovalIsSafe(edge.caller, edge.callee); - - // Break the cycle by removing the edge caller->callee. - removeCallEdge(edge.caller, edge.callee); - revisit.add(edge.callee); - } - - // Recover the stack. - recoverStack(cycle); - } - return null; - } - - private void push(Node node, Node predecessor) { - stack.push(node); - assert !stackEntryInfo.containsKey(node); - stackEntryInfo.put(node, new StackEntryInfo(stack.size() - 1, predecessor)); - if (predecessor != null) { - if (node.getMethod().isClassInitializer() && node.hasCaller(predecessor)) { - clinitCallStack.push(node); - } else if (predecessor.getWritersWithDeterministicOrder().contains(node)) { - writerStack.push(node); - } - } - } - - private void pop(Node node) { - Node popped = stack.pop(); - assert popped == node; - assert stackEntryInfo.containsKey(node); - stackEntryInfo.remove(node); - if (clinitCallStack.peek() == popped) { - assert writerStack.peek() != popped; - clinitCallStack.pop(); - } else if (writerStack.peek() == popped) { - writerStack.pop(); - } - } - - private void removeCallEdge(Node caller, Node callee) { - calleesToBeRemoved.computeIfAbsent(caller, ignore -> Sets.newIdentityHashSet()).add(callee); - } - - private void removeFieldReadEdge(Node reader, Node writer) { - writersToBeRemoved.computeIfAbsent(reader, ignore -> Sets.newIdentityHashSet()).add(writer); - } - - private boolean removeIncomingEdgeOnStack( - Node target, - Node currentCalleeOrWriter, - StackEntryInfo currentCalleeOrWriterStackEntryInfo, - BiConsumer<Node, Node> edgeRemover) { - StackEntryInfo targetStackEntryInfo = stackEntryInfo.get(target); - boolean cycleContainsTarget = - targetStackEntryInfo.index > currentCalleeOrWriterStackEntryInfo.index; - if (cycleContainsTarget) { - assert verifyCycleSatisfies( - currentCalleeOrWriter, - cycle -> cycle.contains(target) && cycle.contains(targetStackEntryInfo.predecessor)); - if (!targetStackEntryInfo.processed) { - edgeRemover.accept(targetStackEntryInfo.predecessor, target); - revisit.add(target); - targetStackEntryInfo.processed = true; - } - return true; - } - return false; - } - - private LinkedList<Node> extractCycle(Node entry) { - LinkedList<Node> cycle = new LinkedList<>(); - do { - assert !stack.isEmpty(); - cycle.add(stack.pop()); - } while (cycle.getLast() != entry); - return cycle; - } - - private boolean verifyCycleSatisfies(Node entry, Predicate<LinkedList<Node>> predicate) { - LinkedList<Node> cycle = extractCycle(entry); - assert predicate.test(cycle); - recoverStack(cycle); - return true; - } - - private CallEdge findCallEdgeForRemoval(LinkedList<Node> extractedCycle) { - Node callee = extractedCycle.getLast(); - for (Node caller : extractedCycle) { - if (caller.hasWriter(callee)) { - // Not a call edge. - assert !caller.hasCallee(callee); - assert !callee.hasCaller(caller); - callee = caller; - continue; - } - if (!caller.hasCallee(callee)) { - // No need to break any edges since this cycle has already been broken previously. - assert !callee.hasCaller(caller); - return null; - } - if (callEdgeRemovalIsSafe(caller, callee)) { - return new CallEdge(caller, callee); - } - callee = caller; - } - throw new CompilationError(CYCLIC_FORCE_INLINING_MESSAGE); - } - - private static boolean callEdgeRemovalIsSafe(Node callerOrReader, Node calleeOrWriter) { - // All call edges where the callee is a method that should be force inlined must be kept, - // to guarantee that the IR converter will process the callee before the caller. - assert calleeOrWriter.hasCaller(callerOrReader); - return !calleeOrWriter.getMethod().getOptimizationInfo().forceInline(); - } - - private void recordCallEdgeRemoval(Node caller, Node callee) { - removedCallEdges - .computeIfAbsent(callee.getMethod(), ignore -> ProgramMethodSet.create(2)) - .add(caller.getProgramMethod()); - } - - private void recoverStack(LinkedList<Node> extractedCycle) { - Iterator<Node> descendingIt = extractedCycle.descendingIterator(); - while (descendingIt.hasNext()) { - stack.push(descendingIt.next()); - } - } - } -}
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 b358828..34142c2 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
@@ -371,13 +371,19 @@ localVariablesTable.add(info); } } + com.android.tools.r8.position.Position diagnosticPosition = + com.android.tools.r8.position.Position.UNKNOWN; + if (method.getCode().isCfCode()) { + diagnosticPosition = method.getCode().asCfCode().getDiagnosticPosition(); + } return new CfCode( method.getHolderType(), stackHeightTracker.maxHeight, registerAllocator.registersUsed(), instructions, tryCatchRanges, - localVariablesTable); + localVariablesTable, + diagnosticPosition); } private static boolean isNopInstruction(Instruction instruction, BasicBlock nextBlock) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java index 22b279b..5737361 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
@@ -9,6 +9,7 @@ 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.conversion.callgraph.CallSiteInformation; import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer; import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer.D8CfInstructionDesugaringEventConsumer; import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
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 dc97590..327326c 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
@@ -678,6 +678,7 @@ this::waveDone, timing, executorService); + lastWaveDone(postMethodProcessorBuilder, executorService); assert appView.graphLens() == graphLensForPrimaryOptimizationPass; timing.end(); } @@ -713,9 +714,6 @@ libraryMethodOverrideAnalysis.finish(); } - ConsumerUtils.acceptIfNotNull( - inliner, inliner -> inliner.enqueueMethodsForReprocessing(postMethodProcessorBuilder)); - if (!options.debug) { new TrivialFieldAccessReprocessor(appView.withLiveness(), postMethodProcessorBuilder) .run(executorService, feedback, timing); @@ -848,6 +846,14 @@ } } + private void lastWaveDone( + PostMethodProcessor.Builder postMethodProcessorBuilder, ExecutorService executorService) + throws ExecutionException { + if (inliner != null) { + inliner.onLastWaveDone(postMethodProcessorBuilder, executorService, timing); + } + } + public void addWaveDoneAction(com.android.tools.r8.utils.Action action) { if (!appView.enableWholeProgramOptimizations()) { throw new Unreachable("addWaveDoneAction() should never be used in D8."); @@ -1532,6 +1538,10 @@ enumUnboxer.analyzeEnums(code, conversionOptions); } + if (inliner != null) { + inliner.recordCallEdgesForMultiCallerInlining(method, code, methodProcessor, timing); + } + if (libraryMethodOverrideAnalysis != null) { timing.begin("Analyze library method overrides"); libraryMethodOverrideAnalysis.analyze(code);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java index d0b0349..dbe72e8 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
@@ -5,6 +5,7 @@ import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext; import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.conversion.callgraph.CallSiteInformation; public abstract class MethodProcessor { @@ -12,6 +13,10 @@ return false; } + public PrimaryMethodProcessor asPrimaryMethodProcessor() { + return null; + } + public boolean isPostMethodProcessor() { return false; }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessorWithWave.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessorWithWave.java index f0c766d..c3953a2 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessorWithWave.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessorWithWave.java
@@ -4,6 +4,7 @@ package com.android.tools.r8.ir.conversion; import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.conversion.callgraph.CallSiteInformation; import com.android.tools.r8.utils.collections.ProgramMethodSet; public abstract class MethodProcessorWithWave extends MethodProcessor {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java index c0f6e90..6e69b3c 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -15,6 +15,8 @@ import com.android.tools.r8.graph.GraphLens; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.conversion.PrimaryMethodProcessor.MethodAction; +import com.android.tools.r8.ir.conversion.callgraph.CallGraph; +import com.android.tools.r8.ir.conversion.callgraph.PartialCallGraphBuilder; import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed; import com.android.tools.r8.logging.Log; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -36,9 +38,7 @@ private final Deque<ProgramMethodSet> waves; private final ProgramMethodSet processed = ProgramMethodSet.create(); - private PostMethodProcessor( - AppView<AppInfoWithLiveness> appView, - CallGraph callGraph) { + private PostMethodProcessor(AppView<AppInfoWithLiveness> appView, CallGraph callGraph) { this.processorContext = appView.createProcessorContext(); this.waves = createWaves(callGraph); }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java index be8f65e..c1ce6d2 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
@@ -8,7 +8,9 @@ import com.android.tools.r8.contexts.CompilationContext.ProcessorContext; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.ProgramMethod; -import com.android.tools.r8.ir.conversion.CallGraph.Node; +import com.android.tools.r8.ir.conversion.callgraph.CallGraph; +import com.android.tools.r8.ir.conversion.callgraph.CallSiteInformation; +import com.android.tools.r8.ir.conversion.callgraph.Node; import com.android.tools.r8.logging.Log; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.InternalOptions; @@ -19,7 +21,6 @@ import java.util.ArrayDeque; import java.util.Collection; import java.util.Deque; -import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -27,7 +28,7 @@ * A {@link MethodProcessor} that processes methods in the whole program in a bottom-up manner, * i.e., from leaves to roots. */ -class PrimaryMethodProcessor extends MethodProcessorWithWave { +public class PrimaryMethodProcessor extends MethodProcessorWithWave { interface WaveStartAction { @@ -46,9 +47,7 @@ private ProcessorContext processorContext; - private PrimaryMethodProcessor( - AppView<AppInfoWithLiveness> appView, - CallGraph callGraph) { + private PrimaryMethodProcessor(AppView<AppInfoWithLiveness> appView, CallGraph callGraph) { this.appView = appView; this.callSiteInformation = callGraph.createCallSiteInformation(appView); this.waves = createWaves(appView, callGraph); @@ -74,6 +73,11 @@ } @Override + public PrimaryMethodProcessor asPrimaryMethodProcessor() { + return this; + } + + @Override public boolean shouldApplyCodeRewritings(ProgramMethod method) { assert !wave.contains(method); return !method.getDefinition().isProcessed(); @@ -87,7 +91,7 @@ private Deque<ProgramMethodSet> createWaves(AppView<?> appView, CallGraph callGraph) { InternalOptions options = appView.options(); Deque<ProgramMethodSet> waves = new ArrayDeque<>(); - Set<Node> nodes = callGraph.nodes; + Collection<Node> nodes = callGraph.getNodes(); int waveCount = 1; while (!nodes.isEmpty()) { ProgramMethodSet wave = callGraph.extractLeaves();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallGraph.java new file mode 100644 index 0000000..5560d5b --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallGraph.java
@@ -0,0 +1,95 @@ +// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.conversion.callgraph; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.ir.conversion.callgraph.CallSiteInformation.CallGraphBasedCallSiteInformation; +import com.android.tools.r8.ir.conversion.callgraph.CycleEliminator.CycleEliminationResult; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.collections.ProgramMethodSet; +import com.google.common.collect.Sets; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * Call graph representation. + * + * <p>Each node in the graph contain the methods called and the calling methods. For virtual and + * interface calls all potential calls from subtypes are recorded. + * + * <p>Only methods in the program - not library methods - are represented. + * + * <p>The directional edges are represented as sets of nodes in each node (called methods and + * callees). + * + * <p>A call from method <code>a</code> to method <code>b</code> is only present once no matter how + * many calls of <code>a</code> there are in <code>a</code>. + * + * <p>Recursive calls are not present. + */ +public class CallGraph extends CallGraphBase<Node> { + + private final CycleEliminationResult cycleEliminationResult; + + CallGraph(Map<DexMethod, Node> nodes) { + this(nodes, null); + } + + CallGraph(Map<DexMethod, Node> nodes, CycleEliminationResult cycleEliminationResult) { + super(nodes); + this.cycleEliminationResult = cycleEliminationResult; + } + + public static CallGraphBuilder builder(AppView<AppInfoWithLiveness> appView) { + return new CallGraphBuilder(appView); + } + + public static CallGraph createForTesting(Collection<Node> nodes) { + return new CallGraph( + nodes.stream() + .collect( + Collectors.toMap( + node -> node.getProgramMethod().getReference(), Function.identity()))); + } + + public CallSiteInformation createCallSiteInformation(AppView<AppInfoWithLiveness> appView) { + // Don't leverage single/dual call site information when we are not tree shaking. + return appView.options().isShrinking() + ? new CallGraphBasedCallSiteInformation(appView, this) + : CallSiteInformation.empty(); + } + + public ProgramMethodSet extractLeaves() { + return extractNodes(Node::isLeaf, Node::cleanCallersAndReadersForRemoval); + } + + public ProgramMethodSet extractRoots() { + return extractNodes(Node::isRoot, Node::cleanCalleesAndWritersForRemoval); + } + + private ProgramMethodSet extractNodes(Predicate<Node> predicate, Consumer<Node> clean) { + ProgramMethodSet result = ProgramMethodSet.create(); + Set<Node> removed = Sets.newIdentityHashSet(); + Iterator<Node> nodeIterator = nodes.values().iterator(); + while (nodeIterator.hasNext()) { + Node node = nodeIterator.next(); + if (predicate.test(node)) { + result.add(node.getProgramMethod()); + nodeIterator.remove(); + removed.add(node); + } + } + removed.forEach(clean); + assert !result.isEmpty(); + return result; + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallGraphBase.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallGraphBase.java new file mode 100644 index 0000000..3996903 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallGraphBase.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.conversion.callgraph; + +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.ProgramMethod; +import java.util.Collection; +import java.util.Map; + +public abstract class CallGraphBase<N extends NodeBase<N>> { + + final Map<DexMethod, N> nodes; + + public CallGraphBase(Map<DexMethod, N> nodes) { + this.nodes = nodes; + } + + public boolean isEmpty() { + return nodes.isEmpty(); + } + + public N getNode(ProgramMethod method) { + return nodes.get(method.getReference()); + } + + public Collection<N> getNodes() { + return nodes.values(); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallGraphBuilder.java similarity index 73% rename from src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java rename to src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallGraphBuilder.java index 4be3d83..cc1f711 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallGraphBuilder.java
@@ -2,7 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -package com.android.tools.r8.ir.conversion; +package com.android.tools.r8.ir.conversion.callgraph; import static com.google.common.base.Predicates.alwaysTrue; @@ -15,9 +15,9 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; -public class CallGraphBuilder extends CallGraphBuilderBase { +public class CallGraphBuilder extends IRProcessingCallGraphBuilderBase { - CallGraphBuilder(AppView<AppInfoWithLiveness> appView) { + public CallGraphBuilder(AppView<AppInfoWithLiveness> appView) { super(appView); } @@ -31,7 +31,14 @@ } private void processMethod(ProgramMethod method) { - method.registerCodeReferences(new InvokeExtractor(getOrCreateNode(method), alwaysTrue())); + IRProcessingCallGraphUseRegistry<Node> registry = + new IRProcessingCallGraphUseRegistry<>( + appView, + getOrCreateNode(method), + this::getOrCreateNode, + possibleProgramTargetsCache, + alwaysTrue()); + method.registerCodeReferences(registry); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallGraphBuilderBase.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallGraphBuilderBase.java new file mode 100644 index 0000000..1c9b944 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallGraphBuilderBase.java
@@ -0,0 +1,32 @@ +// 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.conversion.callgraph; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.collections.ProgramMethodSet; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public abstract class CallGraphBuilderBase<N extends NodeBase<N>> { + + protected final AppView<AppInfoWithLiveness> appView; + + protected final Map<DexMethod, N> nodes = new ConcurrentHashMap<>(); + protected final Map<DexMethod, ProgramMethodSet> possibleProgramTargetsCache = + new ConcurrentHashMap<>(); + + public CallGraphBuilderBase(AppView<AppInfoWithLiveness> appView) { + this.appView = appView; + } + + protected abstract N createNode(ProgramMethod method); + + protected N getOrCreateNode(ProgramMethod method) { + return nodes.computeIfAbsent(method.getReference(), ignore -> createNode(method)); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java similarity index 80% rename from src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java rename to src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java index 5d93463..a46a6bc 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java
@@ -1,13 +1,12 @@ // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -package com.android.tools.r8.ir.conversion; +package com.android.tools.r8.ir.conversion.callgraph; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo; import com.android.tools.r8.graph.ProgramMethod; -import com.android.tools.r8.ir.conversion.CallGraph.Node; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.classhierarchy.MethodOverridesCollector; import com.android.tools.r8.utils.collections.ProgramMethodSet; @@ -24,7 +23,7 @@ */ public abstract boolean hasSingleCallSite(ProgramMethod method); - public abstract boolean hasDoubleCallSite(ProgramMethod method); + public abstract boolean isMultiCallerInlineCandidate(ProgramMethod method); public abstract void unsetCallSiteInformation(ProgramMethod method); @@ -42,7 +41,7 @@ } @Override - public boolean hasDoubleCallSite(ProgramMethod method) { + public boolean isMultiCallerInlineCandidate(ProgramMethod method) { return false; } @@ -54,8 +53,8 @@ static class CallGraphBasedCallSiteInformation extends CallSiteInformation { - private final Set<DexMethod> singleCallSite = Sets.newIdentityHashSet(); - private final Set<DexMethod> doubleCallSite = Sets.newIdentityHashSet(); + private final Set<DexMethod> singleCallerMethods = Sets.newIdentityHashSet(); + private final Set<DexMethod> multiCallerInlineCandidates = Sets.newIdentityHashSet(); CallGraphBasedCallSiteInformation(AppView<AppInfoWithLiveness> appView, CallGraph graph) { ProgramMethodSet pinned = @@ -67,7 +66,7 @@ appView.getKeepInfo(method).isPinned(appView.options()) || appView.appInfo().isMethodTargetedByInvokeDynamic(method)); - for (Node node : graph.nodes) { + for (Node node : graph.getNodes()) { ProgramMethod method = node.getProgramMethod(); DexMethod reference = method.getReference(); @@ -90,9 +89,9 @@ int numberOfCallSites = node.getNumberOfCallSites(); if (numberOfCallSites == 1) { - singleCallSite.add(reference); - } else if (numberOfCallSites == 2) { - doubleCallSite.add(reference); + singleCallerMethods.add(reference); + } else if (numberOfCallSites > 1) { + multiCallerInlineCandidates.add(reference); } } } @@ -105,7 +104,7 @@ */ @Override public boolean hasSingleCallSite(ProgramMethod method) { - return singleCallSite.contains(method.getReference()); + return singleCallerMethods.contains(method.getReference()); } /** @@ -115,14 +114,14 @@ * library method this always returns false. */ @Override - public boolean hasDoubleCallSite(ProgramMethod method) { - return doubleCallSite.contains(method.getReference()); + public boolean isMultiCallerInlineCandidate(ProgramMethod method) { + return multiCallerInlineCandidates.contains(method.getReference()); } @Override public void unsetCallSiteInformation(ProgramMethod method) { - singleCallSite.remove(method.getReference()); - doubleCallSite.remove(method.getReference()); + singleCallerMethods.remove(method.getReference()); + multiCallerInlineCandidates.remove(method.getReference()); } } }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CycleEliminator.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CycleEliminator.java new file mode 100644 index 0000000..c96def4 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CycleEliminator.java
@@ -0,0 +1,458 @@ +// 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.conversion.callgraph; + +import com.android.tools.r8.errors.CompilationError; +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.logging.Log; +import com.android.tools.r8.utils.collections.ProgramMethodSet; +import com.google.common.collect.Iterators; +import com.google.common.collect.Sets; +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.Deque; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Predicate; + +public class CycleEliminator { + + public static final String CYCLIC_FORCE_INLINING_MESSAGE = + "Unable to satisfy force inlining constraints due to cyclic force inlining"; + + private static class CallEdge { + + private final Node caller; + private final Node callee; + + CallEdge(Node caller, Node callee) { + this.caller = caller; + this.callee = callee; + } + } + + static class StackEntryInfo { + + final int index; + final Node predecessor; + + boolean processed; + + StackEntryInfo(int index, Node predecessor) { + this.index = index; + this.predecessor = predecessor; + } + } + + public static class CycleEliminationResult { + + private Map<DexEncodedMethod, ProgramMethodSet> removedCallEdges; + + CycleEliminationResult(Map<DexEncodedMethod, ProgramMethodSet> removedCallEdges) { + this.removedCallEdges = removedCallEdges; + } + + public int numberOfRemovedCallEdges() { + int numberOfRemovedCallEdges = 0; + for (ProgramMethodSet nodes : removedCallEdges.values()) { + numberOfRemovedCallEdges += nodes.size(); + } + return numberOfRemovedCallEdges; + } + } + + // DFS stack. + private Deque<Node> stack = new ArrayDeque<>(); + + // Nodes on the DFS stack. + private Map<Node, StackEntryInfo> stackEntryInfo = new IdentityHashMap<>(); + + // Subset of the DFS stack, where the nodes on the stack are class initializers. + // + // This stack is used to efficiently compute if there is a class initializer on the stack. + private Deque<Node> clinitCallStack = new ArrayDeque<>(); + + // Subset of the DFS stack, where the nodes on the stack satisfy that the edge from the + // predecessor to the node itself is a field read edge. + // + // This stack is used to efficiently compute if there is a field read edge inside a cycle when + // a cycle is found. + private Deque<Node> writerStack = new ArrayDeque<>(); + + // Set of nodes that have been visited entirely. + private Set<Node> marked = Sets.newIdentityHashSet(); + + // Call edges that should be removed when the caller has been processed. These are not removed + // directly since that would lead to ConcurrentModificationExceptions. + private Map<Node, Set<Node>> calleesToBeRemoved = new IdentityHashMap<>(); + + // Field read edges that should be removed when the reader has been processed. These are not + // removed directly since that would lead to ConcurrentModificationExceptions. + private Map<Node, Set<Node>> writersToBeRemoved = new IdentityHashMap<>(); + + // Mapping from callee to the set of callers that were removed from the callee. + private Map<DexEncodedMethod, ProgramMethodSet> removedCallEdges = new IdentityHashMap<>(); + + // Set of nodes from which cycle elimination must be rerun to ensure that all cycles will be + // removed. + private LinkedHashSet<Node> revisit = new LinkedHashSet<>(); + + public CycleEliminationResult breakCycles(Collection<Node> roots) { + // Break cycles in this call graph by removing edges causing cycles. We do this in a fixpoint + // because the algorithm does not guarantee that all cycles will be removed from the graph + // when we remove an edge in the middle of a cycle that contains another cycle. + do { + traverse(roots); + roots = revisit; + prepareForNewTraversal(); + } while (!roots.isEmpty()); + + CycleEliminationResult result = new CycleEliminationResult(removedCallEdges); + if (Log.ENABLED) { + Log.info(getClass(), "# call graph cycles broken: %s", result.numberOfRemovedCallEdges()); + } + reset(); + return result; + } + + private void prepareForNewTraversal() { + assert calleesToBeRemoved.isEmpty(); + assert clinitCallStack.isEmpty(); + assert stack.isEmpty(); + assert stackEntryInfo.isEmpty(); + assert writersToBeRemoved.isEmpty(); + assert writerStack.isEmpty(); + marked.clear(); + revisit = new LinkedHashSet<>(); + } + + private void reset() { + assert clinitCallStack.isEmpty(); + assert marked.isEmpty(); + assert revisit.isEmpty(); + assert stack.isEmpty(); + assert stackEntryInfo.isEmpty(); + assert writerStack.isEmpty(); + removedCallEdges = new IdentityHashMap<>(); + } + + private static class WorkItem { + boolean isNode() { + return false; + } + + NodeWorkItem asNode() { + return null; + } + + boolean isIterator() { + return false; + } + + IteratorWorkItem asIterator() { + return null; + } + } + + private static class NodeWorkItem extends WorkItem { + private final Node node; + + NodeWorkItem(Node node) { + this.node = node; + } + + @Override + boolean isNode() { + return true; + } + + @Override + NodeWorkItem asNode() { + return this; + } + } + + private static class IteratorWorkItem extends WorkItem { + private final Node callerOrReader; + private final Iterator<Node> calleesAndWriters; + + IteratorWorkItem(Node callerOrReader, Iterator<Node> calleesAndWriters) { + this.callerOrReader = callerOrReader; + this.calleesAndWriters = calleesAndWriters; + } + + @Override + boolean isIterator() { + return true; + } + + @Override + IteratorWorkItem asIterator() { + return this; + } + } + + private void traverse(Collection<Node> roots) { + Deque<WorkItem> workItems = new ArrayDeque<>(roots.size()); + for (Node node : roots) { + workItems.addLast(new NodeWorkItem(node)); + } + while (!workItems.isEmpty()) { + WorkItem workItem = workItems.removeFirst(); + if (workItem.isNode()) { + Node node = workItem.asNode().node; + if (marked.contains(node)) { + // Already visited all nodes that can be reached from this node. + continue; + } + + Node predecessor = stack.isEmpty() ? null : stack.peek(); + push(node, predecessor); + + // The callees and writers must be sorted before calling traverse recursively. + // This ensures that cycles are broken the same way across multiple compilations. + Iterator<Node> calleesAndWriterIterator = + Iterators.concat( + node.getCalleesWithDeterministicOrder().iterator(), + node.getWritersWithDeterministicOrder().iterator()); + workItems.addFirst(new IteratorWorkItem(node, calleesAndWriterIterator)); + } else { + assert workItem.isIterator(); + IteratorWorkItem iteratorWorkItem = workItem.asIterator(); + Node newCallerOrReader = + iterateCalleesAndWriters( + iteratorWorkItem.calleesAndWriters, iteratorWorkItem.callerOrReader); + if (newCallerOrReader != null) { + // We did not finish the work on this iterator, so add it again. + workItems.addFirst(iteratorWorkItem); + workItems.addFirst(new NodeWorkItem(newCallerOrReader)); + } else { + assert !iteratorWorkItem.calleesAndWriters.hasNext(); + pop(iteratorWorkItem.callerOrReader); + marked.add(iteratorWorkItem.callerOrReader); + + Collection<Node> calleesToBeRemovedFromCaller = + calleesToBeRemoved.remove(iteratorWorkItem.callerOrReader); + if (calleesToBeRemovedFromCaller != null) { + calleesToBeRemovedFromCaller.forEach( + callee -> { + callee.removeCaller(iteratorWorkItem.callerOrReader); + recordCallEdgeRemoval(iteratorWorkItem.callerOrReader, callee); + }); + } + + Collection<Node> writersToBeRemovedFromReader = + writersToBeRemoved.remove(iteratorWorkItem.callerOrReader); + if (writersToBeRemovedFromReader != null) { + writersToBeRemovedFromReader.forEach( + writer -> writer.removeReader(iteratorWorkItem.callerOrReader)); + } + } + } + } + } + + private Node iterateCalleesAndWriters( + Iterator<Node> calleeOrWriterIterator, Node callerOrReader) { + while (calleeOrWriterIterator.hasNext()) { + Node calleeOrWriter = calleeOrWriterIterator.next(); + StackEntryInfo calleeOrWriterStackEntryInfo = stackEntryInfo.get(calleeOrWriter); + boolean foundCycle = calleeOrWriterStackEntryInfo != null; + if (!foundCycle) { + return calleeOrWriter; + } + + // Found a cycle that needs to be eliminated. If it is a field read edge, then remove it + // right away. + boolean isFieldReadEdge = calleeOrWriter.hasReader(callerOrReader); + if (isFieldReadEdge) { + removeFieldReadEdge(callerOrReader, calleeOrWriter); + continue; + } + + // Otherwise, it is a call edge. Check if there is a field read edge in the cycle, and if + // so, remove that edge. + if (!writerStack.isEmpty() + && removeIncomingEdgeOnStack( + writerStack.peek(), + calleeOrWriter, + calleeOrWriterStackEntryInfo, + this::removeFieldReadEdge)) { + continue; + } + + // It is a call edge and the cycle does not contain any field read edges. + // If it is a call edge to a <clinit>, then remove it. + if (calleeOrWriter.getMethod().isClassInitializer()) { + // Calls to class initializers are always safe to remove. + assert callEdgeRemovalIsSafe(callerOrReader, calleeOrWriter); + removeCallEdge(callerOrReader, calleeOrWriter); + continue; + } + + // Otherwise, check if there is a call edge to a <clinit> method in the cycle, and if so, + // remove that edge. + if (!clinitCallStack.isEmpty() + && removeIncomingEdgeOnStack( + clinitCallStack.peek(), + calleeOrWriter, + calleeOrWriterStackEntryInfo, + this::removeCallEdge)) { + continue; + } + + // Otherwise, we remove the call edge if it is safe according to force inlining. + if (callEdgeRemovalIsSafe(callerOrReader, calleeOrWriter)) { + // Break the cycle by removing the edge node->calleeOrWriter. + // Need to remove `calleeOrWriter` from `node.callees` using the iterator to prevent a + // ConcurrentModificationException. + removeCallEdge(callerOrReader, calleeOrWriter); + continue; + } + + // The call edge cannot be removed due to force inlining. Find another call edge in the + // cycle that can safely be removed instead. + LinkedList<Node> cycle = extractCycle(calleeOrWriter); + + // Break the cycle by finding an edge that can be removed without breaking force + // inlining. If that is not possible, this call fails with a compilation error. + CallEdge edge = findCallEdgeForRemoval(cycle); + + // The edge will be null if this cycle has already been eliminated as a result of + // another cycle elimination. + if (edge != null) { + assert callEdgeRemovalIsSafe(edge.caller, edge.callee); + + // Break the cycle by removing the edge caller->callee. + removeCallEdge(edge.caller, edge.callee); + revisit.add(edge.callee); + } + + // Recover the stack. + recoverStack(cycle); + } + return null; + } + + private void push(Node node, Node predecessor) { + stack.push(node); + assert !stackEntryInfo.containsKey(node); + stackEntryInfo.put(node, new StackEntryInfo(stack.size() - 1, predecessor)); + if (predecessor != null) { + if (node.getMethod().isClassInitializer() && node.hasCaller(predecessor)) { + clinitCallStack.push(node); + } else if (predecessor.getWritersWithDeterministicOrder().contains(node)) { + writerStack.push(node); + } + } + } + + private void pop(Node node) { + Node popped = stack.pop(); + assert popped == node; + assert stackEntryInfo.containsKey(node); + stackEntryInfo.remove(node); + if (clinitCallStack.peek() == popped) { + assert writerStack.peek() != popped; + clinitCallStack.pop(); + } else if (writerStack.peek() == popped) { + writerStack.pop(); + } + } + + private void removeCallEdge(Node caller, Node callee) { + calleesToBeRemoved.computeIfAbsent(caller, ignore -> Sets.newIdentityHashSet()).add(callee); + } + + private void removeFieldReadEdge(Node reader, Node writer) { + writersToBeRemoved.computeIfAbsent(reader, ignore -> Sets.newIdentityHashSet()).add(writer); + } + + private boolean removeIncomingEdgeOnStack( + Node target, + Node currentCalleeOrWriter, + StackEntryInfo currentCalleeOrWriterStackEntryInfo, + BiConsumer<Node, Node> edgeRemover) { + StackEntryInfo targetStackEntryInfo = stackEntryInfo.get(target); + boolean cycleContainsTarget = + targetStackEntryInfo.index > currentCalleeOrWriterStackEntryInfo.index; + if (cycleContainsTarget) { + assert verifyCycleSatisfies( + currentCalleeOrWriter, + cycle -> cycle.contains(target) && cycle.contains(targetStackEntryInfo.predecessor)); + if (!targetStackEntryInfo.processed) { + edgeRemover.accept(targetStackEntryInfo.predecessor, target); + revisit.add(target); + targetStackEntryInfo.processed = true; + } + return true; + } + return false; + } + + private LinkedList<Node> extractCycle(Node entry) { + LinkedList<Node> cycle = new LinkedList<>(); + do { + assert !stack.isEmpty(); + cycle.add(stack.pop()); + } while (cycle.getLast() != entry); + return cycle; + } + + private boolean verifyCycleSatisfies(Node entry, Predicate<LinkedList<Node>> predicate) { + LinkedList<Node> cycle = extractCycle(entry); + assert predicate.test(cycle); + recoverStack(cycle); + return true; + } + + private CallEdge findCallEdgeForRemoval(LinkedList<Node> extractedCycle) { + Node callee = extractedCycle.getLast(); + for (Node caller : extractedCycle) { + if (caller.hasWriter(callee)) { + // Not a call edge. + assert !caller.hasCallee(callee); + assert !callee.hasCaller(caller); + callee = caller; + continue; + } + if (!caller.hasCallee(callee)) { + // No need to break any edges since this cycle has already been broken previously. + assert !callee.hasCaller(caller); + return null; + } + if (callEdgeRemovalIsSafe(caller, callee)) { + return new CallEdge(caller, callee); + } + callee = caller; + } + throw new CompilationError(CYCLIC_FORCE_INLINING_MESSAGE); + } + + private static boolean callEdgeRemovalIsSafe(Node callerOrReader, Node calleeOrWriter) { + // All call edges where the callee is a method that should be force inlined must be kept, + // to guarantee that the IR converter will process the callee before the caller. + assert calleeOrWriter.hasCaller(callerOrReader); + return !calleeOrWriter.getMethod().getOptimizationInfo().forceInline(); + } + + private void recordCallEdgeRemoval(Node caller, Node callee) { + removedCallEdges + .computeIfAbsent(callee.getMethod(), ignore -> ProgramMethodSet.create(2)) + .add(caller.getProgramMethod()); + } + + private void recoverStack(LinkedList<Node> extractedCycle) { + Iterator<Node> descendingIt = extractedCycle.descendingIterator(); + while (descendingIt.hasNext()) { + stack.push(descendingIt.next()); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/IRProcessingCallGraphBuilderBase.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/IRProcessingCallGraphBuilderBase.java new file mode 100644 index 0000000..a580f04 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/IRProcessingCallGraphBuilderBase.java
@@ -0,0 +1,65 @@ +// 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.conversion.callgraph; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.conversion.callgraph.CycleEliminator.CycleEliminationResult; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.Timing; +import com.google.common.collect.Sets; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; + +abstract class IRProcessingCallGraphBuilderBase extends CallGraphBuilderBase<Node> { + + IRProcessingCallGraphBuilderBase(AppView<AppInfoWithLiveness> appView) { + super(appView); + } + + public CallGraph build(ExecutorService executorService, Timing timing) throws ExecutionException { + timing.begin("Build IR processing order constraints"); + timing.begin("Build call graph"); + populateGraph(executorService); + assert verifyNoRedundantFieldReadEdges(); + timing.end(); + assert verifyAllMethodsWithCodeExists(); + + appView.withGeneratedMessageLiteBuilderShrinker( + shrinker -> shrinker.preprocessCallGraphBeforeCycleElimination(nodes)); + + timing.begin("Cycle elimination"); + // Sort the nodes for deterministic cycle elimination. + Set<Node> nodesWithDeterministicOrder = Sets.newTreeSet(nodes.values()); + CycleEliminator cycleEliminator = new CycleEliminator(); + CycleEliminationResult cycleEliminationResult = + cycleEliminator.breakCycles(nodesWithDeterministicOrder); + timing.end(); + timing.end(); + assert cycleEliminator.breakCycles(nodesWithDeterministicOrder).numberOfRemovedCallEdges() + == 0; // The cycles should be gone. + + return new CallGraph(nodes, cycleEliminationResult); + } + + @Override + protected Node createNode(ProgramMethod method) { + return new Node(method); + } + + abstract void populateGraph(ExecutorService executorService) throws ExecutionException; + + /** Verify that there are no field read edges in the graph if there is also a call graph edge. */ + private boolean verifyNoRedundantFieldReadEdges() { + for (Node writer : nodes.values()) { + for (Node reader : writer.getReadersWithDeterministicOrder()) { + assert !writer.hasCaller(reader); + } + } + return true; + } + + abstract boolean verifyAllMethodsWithCodeExists(); +}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/IRProcessingCallGraphUseRegistry.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/IRProcessingCallGraphUseRegistry.java new file mode 100644 index 0000000..b88c9d0 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/IRProcessingCallGraphUseRegistry.java
@@ -0,0 +1,164 @@ +// 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.conversion.callgraph; + +import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexCallSite; +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.FieldAccessInfo; +import com.android.tools.r8.graph.FieldAccessInfoCollection; +import com.android.tools.r8.graph.ProgramField; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.collections.ProgramMethodSet; +import java.util.Map; +import java.util.function.Function; +import java.util.function.Predicate; + +public class IRProcessingCallGraphUseRegistry<N extends NodeBase<N>> extends InvokeExtractor<N> { + + private final FieldAccessInfoCollection<?> fieldAccessInfoCollection; + + IRProcessingCallGraphUseRegistry( + AppView<AppInfoWithLiveness> appView, + N currentMethod, + Function<ProgramMethod, N> nodeFactory, + Map<DexMethod, ProgramMethodSet> possibleProgramTargetsCache, + Predicate<ProgramMethod> targetTester) { + super(appView, currentMethod, nodeFactory, possibleProgramTargetsCache, targetTester); + this.fieldAccessInfoCollection = appView.appInfo().getFieldAccessInfoCollection(); + } + + protected void addClassInitializerTarget(DexProgramClass clazz) { + assert clazz != null; + if (clazz.hasClassInitializer()) { + addCallEdge(clazz.getProgramClassInitializer(), false); + } + } + + protected void addClassInitializerTarget(DexType type) { + assert type.isClassType(); + DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type)); + if (clazz != null) { + addClassInitializerTarget(clazz); + } + } + + private void addFieldReadEdge(ProgramMethod writer) { + assert !writer.getDefinition().isAbstract(); + if (!targetTester.test(writer)) { + return; + } + nodeFactory.apply(writer).addReaderConcurrently(currentMethod); + } + + private void processFieldRead(DexField reference) { + DexField rewrittenReference = appView.graphLens().lookupField(reference, getCodeLens()); + if (!rewrittenReference.getHolderType().isClassType()) { + return; + } + + ProgramField field = appView.appInfo().resolveField(rewrittenReference).getProgramField(); + if (field == null || appView.appInfo().isPinned(field)) { + return; + } + + // Each static field access implicitly triggers the class initializer. + if (field.getAccessFlags().isStatic()) { + addClassInitializerTarget(field.getHolder()); + } + + FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(field.getReference()); + if (fieldAccessInfo != null && fieldAccessInfo.hasKnownWriteContexts()) { + if (fieldAccessInfo.getNumberOfWriteContexts() == 1) { + fieldAccessInfo.forEachWriteContext(this::addFieldReadEdge); + } + } + } + + private void processFieldWrite(DexField reference) { + DexField rewrittenReference = appView.graphLens().lookupField(reference, getCodeLens()); + if (!rewrittenReference.getHolderType().isClassType()) { + return; + } + + ProgramField field = appView.appInfo().resolveField(rewrittenReference).getProgramField(); + if (field == null || appView.appInfo().isPinned(field)) { + return; + } + + // Each static field access implicitly triggers the class initializer. + if (field.getAccessFlags().isStatic()) { + addClassInitializerTarget(field.getHolder()); + } + } + + private void processInitClass(DexType type) { + DexType rewrittenType = appView.graphLens().lookupType(type); + DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(rewrittenType)); + if (clazz == null) { + assert false; + return; + } + addClassInitializerTarget(clazz); + } + + @Override + protected void processSingleTarget(ProgramMethod singleTarget, ProgramMethod context) { + super.processSingleTarget(singleTarget, context); + if (singleTarget.getAccessFlags().isStatic()) { + addClassInitializerTarget(singleTarget.getHolder()); + } + } + + @Override + public void registerInitClass(DexType clazz) { + processInitClass(clazz); + } + + @Override + public void registerInstanceFieldRead(DexField field) { + processFieldRead(field); + } + + @Override + public void registerInstanceFieldWrite(DexField field) { + processFieldWrite(field); + } + + @Override + public void registerInstanceOf(DexType type) {} + + @Override + public void registerNewInstance(DexType type) { + if (type.isClassType()) { + addClassInitializerTarget(type); + } + } + + @Override + public void registerStaticFieldRead(DexField field) { + processFieldRead(field); + } + + @Override + public void registerStaticFieldWrite(DexField field) { + processFieldWrite(field); + } + + @Override + public void registerTypeReference(DexType type) {} + + @Override + public void registerCallSite(DexCallSite callSite) { + registerMethodHandle( + callSite.bootstrapMethod, MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/InvokeExtractor.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/InvokeExtractor.java new file mode 100644 index 0000000..ecd06de --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/InvokeExtractor.java
@@ -0,0 +1,215 @@ +// 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.conversion.callgraph; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexCallSite; +import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexClassAndMethod; +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.GraphLens.MethodLookupResult; +import com.android.tools.r8.graph.LookupResult; +import com.android.tools.r8.graph.MethodResolutionResult; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.graph.UseRegistry; +import com.android.tools.r8.ir.code.Invoke; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.collections.ProgramMethodSet; +import java.util.Map; +import java.util.function.Function; +import java.util.function.Predicate; + +public class InvokeExtractor<N extends NodeBase<N>> extends UseRegistry<ProgramMethod> { + + protected final AppView<AppInfoWithLiveness> appView; + protected final N currentMethod; + protected final Function<ProgramMethod, N> nodeFactory; + protected final Map<DexMethod, ProgramMethodSet> possibleProgramTargetsCache; + protected final Predicate<ProgramMethod> targetTester; + + public InvokeExtractor( + AppView<AppInfoWithLiveness> appView, + N currentMethod, + Function<ProgramMethod, N> nodeFactory, + Map<DexMethod, ProgramMethodSet> possibleProgramTargetsCache, + Predicate<ProgramMethod> targetTester) { + super(appView, currentMethod.getProgramMethod()); + this.appView = appView; + this.currentMethod = currentMethod; + this.nodeFactory = nodeFactory; + this.possibleProgramTargetsCache = possibleProgramTargetsCache; + this.targetTester = targetTester; + } + + protected void addCallEdge(ProgramMethod callee, boolean likelySpuriousCallEdge) { + if (!targetTester.test(callee)) { + return; + } + if (callee.getDefinition().isAbstract()) { + // Not a valid target. + return; + } + if (callee.getDefinition().isNative()) { + // We don't care about calls to native methods. + return; + } + if (!appView.getKeepInfo(callee).isInliningAllowed(appView.options())) { + // Since the callee is kept and optimizations are disallowed, we cannot inline it into the + // caller, and we also cannot collect any optimization info for the method. Therefore, we + // drop the call edge to reduce the total number of call graph edges, which should lead to + // fewer call graph cycles. + return; + } + nodeFactory.apply(callee).addCallerConcurrently(currentMethod, likelySpuriousCallEdge); + } + + private void processInvoke(Invoke.Type originalType, DexMethod originalMethod) { + ProgramMethod context = currentMethod.getProgramMethod(); + MethodLookupResult result = + appView + .graphLens() + .lookupMethod(originalMethod, context.getReference(), originalType, getCodeLens()); + DexMethod method = result.getReference(); + Invoke.Type type = result.getType(); + if (type == Invoke.Type.INTERFACE || type == Invoke.Type.VIRTUAL) { + // For virtual and interface calls add all potential targets that could be called. + MethodResolutionResult resolutionResult = + appView.appInfo().resolveMethod(method, type == Invoke.Type.INTERFACE); + DexClassAndMethod target = resolutionResult.getResolutionPair(); + if (target != null) { + processInvokeWithDynamicDispatch(type, target, context); + } + } else { + ProgramMethod singleTarget = + appView.appInfo().lookupSingleProgramTarget(type, method, context, appView); + if (singleTarget != null) { + processSingleTarget(singleTarget, context); + } + } + } + + protected void processSingleTarget(ProgramMethod singleTarget, ProgramMethod context) { + assert !context.getDefinition().isBridge() + || singleTarget.getDefinition() != context.getDefinition(); + addCallEdge(singleTarget, false); + } + + protected void processInvokeWithDynamicDispatch( + Invoke.Type type, DexClassAndMethod encodedTarget, ProgramMethod context) { + DexMethod target = encodedTarget.getReference(); + DexClass clazz = encodedTarget.getHolder(); + if (!appView.options().testing.addCallEdgesForLibraryInvokes) { + if (clazz.isLibraryClass()) { + // Likely to have many possible targets. + return; + } + } + + boolean isInterface = type == Invoke.Type.INTERFACE; + ProgramMethodSet possibleProgramTargets = + possibleProgramTargetsCache.computeIfAbsent( + target, + method -> { + MethodResolutionResult resolution = + appView.appInfo().resolveMethod(method, isInterface); + if (resolution.isVirtualTarget()) { + LookupResult lookupResult = + resolution.lookupVirtualDispatchTargets(context.getHolder(), appView.appInfo()); + if (lookupResult.isLookupResultSuccess()) { + ProgramMethodSet targets = ProgramMethodSet.create(); + lookupResult + .asLookupResultSuccess() + .forEach( + methodTarget -> { + if (methodTarget.isProgramMethod()) { + targets.add(methodTarget.asProgramMethod()); + } + }, + lambdaTarget -> { + // The call target will ultimately be the implementation method. + DexClassAndMethod implementationMethod = + lambdaTarget.getImplementationMethod(); + if (implementationMethod.isProgramMethod()) { + targets.add(implementationMethod.asProgramMethod()); + } + }); + return targets; + } + } + return null; + }); + if (possibleProgramTargets != null) { + boolean likelySpuriousCallEdge = + possibleProgramTargets.size() + >= appView.options().callGraphLikelySpuriousCallEdgeThreshold; + for (ProgramMethod possibleTarget : possibleProgramTargets) { + addCallEdge(possibleTarget, likelySpuriousCallEdge); + } + } + } + + @Override + public void registerCallSite(DexCallSite callSite) { + registerMethodHandle( + callSite.bootstrapMethod, MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY); + } + + @Override + public void registerInvokeDirect(DexMethod method) { + processInvoke(Invoke.Type.DIRECT, method); + } + + @Override + public void registerInvokeInterface(DexMethod method) { + processInvoke(Invoke.Type.INTERFACE, method); + } + + @Override + public void registerInvokeStatic(DexMethod method) { + processInvoke(Invoke.Type.STATIC, method); + } + + @Override + public void registerInvokeSuper(DexMethod method) { + processInvoke(Invoke.Type.SUPER, method); + } + + @Override + public void registerInvokeVirtual(DexMethod method) { + processInvoke(Invoke.Type.VIRTUAL, method); + } + + @Override + public void registerInitClass(DexType type) { + // Intentionally empty. This use registry is only tracing method calls. + } + + @Override + public void registerInstanceFieldRead(DexField field) { + // Intentionally empty. This use registry is only tracing method calls. + } + + @Override + public void registerInstanceFieldWrite(DexField field) { + // Intentionally empty. This use registry is only tracing method calls. + } + + @Override + public void registerStaticFieldRead(DexField field) { + // Intentionally empty. This use registry is only tracing method calls. + } + + @Override + public void registerStaticFieldWrite(DexField field) { + // Intentionally empty. This use registry is only tracing method calls. + } + + @Override + public void registerTypeReference(DexType type) { + // Intentionally empty. This use registry is only tracing method calls. + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/Node.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/Node.java new file mode 100644 index 0000000..7752ad9 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/Node.java
@@ -0,0 +1,215 @@ +// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.conversion.callgraph; + +import com.android.tools.r8.graph.ProgramMethod; +import java.util.Set; +import java.util.TreeSet; + +public class Node extends NodeBase<Node> implements Comparable<Node> { + + public static Node[] EMPTY_ARRAY = {}; + + private int numberOfCallSites = 0; + + // Outgoing calls from this method. + private final Set<Node> callees = new TreeSet<>(); + + // Incoming calls to this method. + private final Set<Node> callers = new TreeSet<>(); + + // Incoming field read edges to this method (i.e., the set of methods that read a field written + // by the current method). + private final Set<Node> readers = new TreeSet<>(); + + // Outgoing field read edges from this method (i.e., the set of methods that write a field read + // by the current method). + private final Set<Node> writers = new TreeSet<>(); + + public Node(ProgramMethod method) { + super(method); + } + + public void addCallerConcurrently(Node caller) { + addCallerConcurrently(caller, false); + } + + @Override + public void addCallerConcurrently(Node caller, boolean likelySpuriousCallEdge) { + if (caller != this && !likelySpuriousCallEdge) { + boolean changedCallers; + synchronized (callers) { + changedCallers = callers.add(caller); + numberOfCallSites++; + } + if (changedCallers) { + synchronized (caller.callees) { + caller.callees.add(this); + } + // Avoid redundant field read edges (call edges are considered stronger). + removeReaderConcurrently(caller); + } + } else { + synchronized (callers) { + numberOfCallSites++; + } + } + } + + @Override + public void addReaderConcurrently(Node reader) { + if (reader != this) { + synchronized (callers) { + if (callers.contains(reader)) { + // Avoid redundant field read edges (call edges are considered stronger). + return; + } + boolean readersChanged; + synchronized (readers) { + readersChanged = readers.add(reader); + } + if (readersChanged) { + synchronized (reader.writers) { + reader.writers.add(this); + } + } + } + } + } + + private void removeReaderConcurrently(Node reader) { + synchronized (readers) { + readers.remove(reader); + } + synchronized (reader.writers) { + reader.writers.remove(this); + } + } + + public void removeCaller(Node caller) { + boolean callersChanged = callers.remove(caller); + assert callersChanged; + boolean calleesChanged = caller.callees.remove(this); + assert calleesChanged; + assert !hasReader(caller); + } + + public void removeReader(Node reader) { + boolean readersChanged = readers.remove(reader); + assert readersChanged; + boolean writersChanged = reader.writers.remove(this); + assert writersChanged; + assert !hasCaller(reader); + } + + public void cleanCalleesAndWritersForRemoval() { + assert callers.isEmpty(); + assert readers.isEmpty(); + for (Node callee : callees) { + boolean changed = callee.callers.remove(this); + assert changed; + } + for (Node writer : writers) { + boolean changed = writer.readers.remove(this); + assert changed; + } + } + + public void cleanCallersAndReadersForRemoval() { + assert callees.isEmpty(); + assert writers.isEmpty(); + for (Node caller : callers) { + boolean changed = caller.callees.remove(this); + assert changed; + } + for (Node reader : readers) { + boolean changed = reader.writers.remove(this); + assert changed; + } + } + + public Set<Node> getCallersWithDeterministicOrder() { + return callers; + } + + public Set<Node> getCalleesWithDeterministicOrder() { + return callees; + } + + public Set<Node> getReadersWithDeterministicOrder() { + return readers; + } + + public Set<Node> getWritersWithDeterministicOrder() { + return writers; + } + + public int getNumberOfCallSites() { + return numberOfCallSites; + } + + public boolean hasCallee(Node method) { + return callees.contains(method); + } + + public boolean hasCaller(Node method) { + return callers.contains(method); + } + + public boolean hasReader(Node method) { + return readers.contains(method); + } + + public boolean hasWriter(Node method) { + return writers.contains(method); + } + + public boolean isRoot() { + return callers.isEmpty() && readers.isEmpty(); + } + + public boolean isLeaf() { + return callees.isEmpty() && writers.isEmpty(); + } + + @Override + public int compareTo(Node other) { + return getProgramMethod().getReference().compareTo(other.getProgramMethod().getReference()); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("MethodNode for: "); + builder.append(getProgramMethod().toSourceString()); + builder.append(" ("); + builder.append(callees.size()); + builder.append(" callees, "); + builder.append(callers.size()); + builder.append(" callers"); + builder.append(", invoke count ").append(numberOfCallSites); + builder.append(")."); + builder.append(System.lineSeparator()); + if (callees.size() > 0) { + builder.append("Callees:"); + builder.append(System.lineSeparator()); + for (Node call : callees) { + builder.append(" "); + builder.append(call.getProgramMethod().toSourceString()); + builder.append(System.lineSeparator()); + } + } + if (callers.size() > 0) { + builder.append("Callers:"); + builder.append(System.lineSeparator()); + for (Node caller : callers) { + builder.append(" "); + builder.append(caller.getProgramMethod().toSourceString()); + builder.append(System.lineSeparator()); + } + } + return builder.toString(); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/NodeBase.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/NodeBase.java new file mode 100644 index 0000000..2f59f65 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/NodeBase.java
@@ -0,0 +1,29 @@ +// 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.conversion.callgraph; + +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.ProgramMethod; + +public abstract class NodeBase<N extends NodeBase<N>> { + + private final ProgramMethod method; + + public NodeBase(ProgramMethod method) { + this.method = method; + } + + public abstract void addCallerConcurrently(N caller, boolean likelySpuriousCallEdge); + + public abstract void addReaderConcurrently(N reader); + + public DexEncodedMethod getMethod() { + return getProgramMethod().getDefinition(); + } + + public ProgramMethod getProgramMethod() { + return method; + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PartialCallGraphBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/PartialCallGraphBuilder.java similarity index 68% rename from src/main/java/com/android/tools/r8/ir/conversion/PartialCallGraphBuilder.java rename to src/main/java/com/android/tools/r8/ir/conversion/callgraph/PartialCallGraphBuilder.java index e094867..afab94b 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/PartialCallGraphBuilder.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/PartialCallGraphBuilder.java
@@ -1,7 +1,8 @@ // 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.conversion; + +package com.android.tools.r8.ir.conversion.callgraph; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.ProgramMethod; @@ -11,11 +12,11 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; -public class PartialCallGraphBuilder extends CallGraphBuilderBase { +public class PartialCallGraphBuilder extends IRProcessingCallGraphBuilderBase { private final ProgramMethodSet seeds; - PartialCallGraphBuilder(AppView<AppInfoWithLiveness> appView, ProgramMethodSet seeds) { + public PartialCallGraphBuilder(AppView<AppInfoWithLiveness> appView, ProgramMethodSet seeds) { super(appView); assert seeds != null && !seeds.isEmpty(); this.seeds = seeds; @@ -27,7 +28,14 @@ } private void processMethod(ProgramMethod method) { - method.registerCodeReferences(new InvokeExtractor(getOrCreateNode(method), seeds::contains)); + IRProcessingCallGraphUseRegistry<Node> registry = + new IRProcessingCallGraphUseRegistry<>( + appView, + getOrCreateNode(method), + this::getOrCreateNode, + possibleProgramTargetsCache, + seeds::contains); + method.registerCodeReferences(registry); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java index b0abf2a..ddcb68d 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java
@@ -30,8 +30,6 @@ new CfStackInstruction(Opcode.Dup), new CfInvoke(Opcodes.INVOKESPECIAL, lambda.constructor, false), new CfStaticFieldWrite(lambda.lambdaField, lambda.lambdaField), - new CfReturnVoid()), - ImmutableList.of(), - ImmutableList.of()); + new CfReturnVoid())); } }
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 a8d8b14..69cc40b 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
@@ -31,6 +31,7 @@ import com.android.tools.r8.ir.desugar.records.RecordDesugaring; import com.android.tools.r8.ir.desugar.stringconcat.StringConcatInstructionDesugaring; import com.android.tools.r8.ir.desugar.twr.TwrInstructionDesugaring; +import com.android.tools.r8.position.MethodPosition; import com.android.tools.r8.utils.IntBox; import com.android.tools.r8.utils.ListUtils; import com.android.tools.r8.utils.SetUtils; @@ -49,6 +50,8 @@ private final AppView<?> appView; private final List<CfInstructionDesugaring> desugarings = new ArrayList<>(); + // A special collection of desugarings that yield to all other desugarings. + private final List<CfInstructionDesugaring> yieldingDesugarings = new ArrayList<>(); private final NestBasedAccessDesugaring nestBasedAccessDesugaring; private final RecordDesugaring recordRewriter; @@ -89,7 +92,7 @@ backportedMethodRewriter = new BackportedMethodRewriter(appView); } if (appView.options().apiModelingOptions().enableOutliningOfMethods) { - desugarings.add(new ApiInvokeOutlinerDesugaring(appView, apiLevelCompute)); + yieldingDesugarings.add(new ApiInvokeOutlinerDesugaring(appView, apiLevelCompute)); } if (appView.options().enableTryWithResourcesDesugaring()) { desugarings.add(new TwrInstructionDesugaring(appView)); @@ -167,7 +170,7 @@ new StringDiagnostic( "Unsupported attempt to desugar non-CF code", method.getOrigin(), - method.getPosition())); + MethodPosition.create(method))); } } @@ -267,7 +270,38 @@ ProgramMethod context, MethodProcessingContext methodProcessingContext) { // TODO(b/177810578): Migrate other cf-to-cf based desugaring here. - Iterator<CfInstructionDesugaring> iterator = desugarings.iterator(); + Collection<CfInstruction> replacement = + applyDesugaring( + instruction, + freshLocalProvider, + localStackAllocator, + eventConsumer, + context, + methodProcessingContext, + desugarings.iterator()); + if (replacement != null) { + return replacement; + } + // If we made it here there it is because a yielding desugaring reported that it needs + // desugaring and no other desugaring happened. + return applyDesugaring( + instruction, + freshLocalProvider, + localStackAllocator, + eventConsumer, + context, + methodProcessingContext, + yieldingDesugarings.iterator()); + } + + private Collection<CfInstruction> applyDesugaring( + CfInstruction instruction, + FreshLocalProvider freshLocalProvider, + LocalStackAllocator localStackAllocator, + CfInstructionDesugaringEventConsumer eventConsumer, + ProgramMethod context, + MethodProcessingContext methodProcessingContext, + Iterator<CfInstructionDesugaring> iterator) { while (iterator.hasNext()) { CfInstructionDesugaring desugaring = iterator.next(); Collection<CfInstruction> replacement = @@ -310,7 +344,9 @@ private boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) { return Iterables.any( - desugarings, desugaring -> desugaring.needsDesugaring(instruction, context)); + desugarings, desugaring -> desugaring.needsDesugaring(instruction, context)) + || Iterables.any( + yieldingDesugarings, desugaring -> desugaring.needsDesugaring(instruction, context)); } private boolean verifyNoOtherDesugaringNeeded(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java index 62c0445..d56be1a 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
@@ -31,6 +31,10 @@ import com.google.common.collect.ImmutableList; import java.util.Collection; +/** + * This desugaring will outline calls to library methods that are introduced after the min-api + * level. For classes introduced after the min-api level see ApiReferenceStubber. + */ public class ApiInvokeOutlinerDesugaring implements CfInstructionDesugaring { private final AppView<?> appView; @@ -55,7 +59,7 @@ if (computedApiLevel.isGreaterThan(appView.computedMinApiLevel())) { return desugarLibraryCall( methodProcessingContext.createUniqueContext(), - instruction, + instruction.asInvoke(), computedApiLevel, dexItemFactory); } @@ -92,14 +96,6 @@ apiLevelCompute.computeApiLevelForLibraryReference( cfInvoke.getMethod(), ComputedApiLevel.unknown()); if (apiLevel.isGreaterThan(appView.computedMinApiLevel())) { - ComputedApiLevel holderApiLevel = - apiLevelCompute.computeApiLevelForLibraryReference( - holderType, ComputedApiLevel.unknown()); - if (holderApiLevel.isGreaterThan(appView.computedMinApiLevel())) { - // Do not outline where the holder is unknown or introduced later then min api. - // TODO(b/208978971): Describe where mocking is done when landing. - return appView.computedMinApiLevel(); - } return apiLevel; } return appView.computedMinApiLevel(); @@ -107,11 +103,12 @@ private Collection<CfInstruction> desugarLibraryCall( UniqueContext context, - CfInstruction instruction, + CfInvoke invoke, ComputedApiLevel computedApiLevel, DexItemFactory factory) { - DexMethod method = instruction.asInvoke().getMethod(); - ProgramMethod programMethod = ensureOutlineMethod(context, method, computedApiLevel, factory); + DexMethod method = invoke.getMethod(); + ProgramMethod programMethod = + ensureOutlineMethod(context, method, computedApiLevel, factory, invoke); return ImmutableList.of(new CfInvoke(INVOKESTATIC, programMethod.getReference(), false)); } @@ -119,12 +116,13 @@ UniqueContext context, DexMethod apiMethod, ComputedApiLevel apiLevel, - DexItemFactory factory) { + DexItemFactory factory, + CfInvoke invoke) { DexClass libraryHolder = appView.definitionFor(apiMethod.getHolderType()); assert libraryHolder != null; - DexEncodedMethod libraryApiMethodDefinition = libraryHolder.lookupMethod(apiMethod); - DexProto proto = - factory.prependHolderToProtoIf(apiMethod, libraryApiMethodDefinition.isVirtualMethod()); + boolean isVirtualMethod = invoke.isInvokeVirtual() || invoke.isInvokeInterface(); + assert verifyLibraryHolderAndInvoke(libraryHolder, apiMethod, isVirtualMethod); + DexProto proto = factory.prependHolderToProtoIf(apiMethod, isVirtualMethod); return appView .getSyntheticItems() .createMethod( @@ -145,18 +143,25 @@ .setApiLevelForCode(apiLevel) .setCode( m -> { - if (libraryApiMethodDefinition.isStatic()) { - return ForwardMethodBuilder.builder(factory) - .setStaticTarget(apiMethod, libraryHolder.isInterface()) - .setStaticSource(apiMethod) - .build(); - } else { + if (isVirtualMethod) { return ForwardMethodBuilder.builder(factory) .setVirtualTarget(apiMethod, libraryHolder.isInterface()) .setNonStaticSource(apiMethod) .build(); + } else { + return ForwardMethodBuilder.builder(factory) + .setStaticTarget(apiMethod, libraryHolder.isInterface()) + .setStaticSource(apiMethod) + .build(); } }); }); } + + private boolean verifyLibraryHolderAndInvoke( + DexClass libraryHolder, DexMethod apiMethod, boolean isVirtualInvoke) { + DexEncodedMethod libraryApiMethodDefinition = libraryHolder.lookupMethod(apiMethod); + return libraryApiMethodDefinition == null + || libraryApiMethodDefinition.isVirtualMethod() == isVirtualInvoke; + } }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodGenerators.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodGenerators.java index ee175f9..cd385e8 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodGenerators.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodGenerators.java
@@ -60,13 +60,7 @@ false), new CfReturn(ValueType.OBJECT)); - return new CfCode( - method.holder, - 4, - formalCount, - builder.build(), - ImmutableList.of(), - ImmutableList.of()); + return new CfCode(method.holder, 4, formalCount, builder.build()); } public static CfCode generateMapOf( @@ -111,12 +105,6 @@ false), new CfReturn(ValueType.OBJECT)); - return new CfCode( - method.holder, - 7, - formalCount * 2, - builder.build(), - ImmutableList.of(), - ImmutableList.of()); + return new CfCode(method.holder, 7, formalCount * 2, builder.build()); } }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecification.java new file mode 100644 index 0000000..6a6a2d4 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecification.java
@@ -0,0 +1,170 @@ +// 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.desugaredlibrary.humanspecification; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClassAndMethod; +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexReference; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.ir.desugar.PrefixRewritingMapper; +import com.android.tools.r8.ir.desugar.PrefixRewritingMapper.DesugarPrefixRewritingMapper; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.InternalOptions; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class HumanDesugaredLibrarySpecification { + + private final boolean libraryCompilation; + private final HumanTopLevelFlags topLevelFlags; + private final HumanRewritingFlags rewritingFlags; + private final PrefixRewritingMapper prefixRewritingMapper; + + public static HumanDesugaredLibrarySpecification withOnlyRewritePrefixForTesting( + Map<String, String> prefix, InternalOptions options) { + return new HumanDesugaredLibrarySpecification( + HumanTopLevelFlags.empty(), + HumanRewritingFlags.withOnlyRewritePrefixForTesting(prefix, options), + true, + options.itemFactory); + } + + public static HumanDesugaredLibrarySpecification empty() { + return new HumanDesugaredLibrarySpecification( + HumanTopLevelFlags.empty(), HumanRewritingFlags.empty(), false, null) { + + @Override + public boolean isSupported(DexReference reference, AppView<?> appView) { + return false; + } + + @Override + public boolean isEmptyConfiguration() { + return true; + } + }; + } + + public HumanDesugaredLibrarySpecification( + HumanTopLevelFlags topLevelFlags, + HumanRewritingFlags rewritingFlags, + boolean libraryCompilation, + DexItemFactory factory) { + this.libraryCompilation = libraryCompilation; + this.topLevelFlags = topLevelFlags; + this.rewritingFlags = rewritingFlags; + this.prefixRewritingMapper = + rewritingFlags.getRewritePrefix().isEmpty() + ? PrefixRewritingMapper.empty() + : new DesugarPrefixRewritingMapper( + rewritingFlags.getRewritePrefix(), factory, libraryCompilation); + } + + public boolean supportAllCallbacksFromLibrary() { + return topLevelFlags.supportAllCallbacksFromLibrary(); + } + + public PrefixRewritingMapper getPrefixRewritingMapper() { + return prefixRewritingMapper; + } + + public AndroidApiLevel getRequiredCompilationApiLevel() { + return topLevelFlags.getRequiredCompilationAPILevel(); + } + + public boolean isLibraryCompilation() { + return libraryCompilation; + } + + public String getSynthesizedLibraryClassesPackagePrefix() { + return topLevelFlags.getSynthesizedLibraryClassesPackagePrefix(); + } + + public HumanTopLevelFlags getTopLevelFlags() { + return topLevelFlags; + } + + public HumanRewritingFlags getRewritingFlags() { + return rewritingFlags; + } + + public String getIdentifier() { + return topLevelFlags.getIdentifier(); + } + + public Map<String, String> getRewritePrefix() { + return rewritingFlags.getRewritePrefix(); + } + + public boolean hasEmulatedLibraryInterfaces() { + return !getEmulateLibraryInterface().isEmpty(); + } + + public Map<DexType, DexType> getEmulateLibraryInterface() { + return rewritingFlags.getEmulateLibraryInterface(); + } + + public boolean isSupported(DexReference reference, AppView<?> appView) { + return prefixRewritingMapper.hasRewrittenType(reference.getContextType(), appView); + } + + // If the method is retargeted, answers the retargeted method, else null. + public DexMethod retargetMethod(DexEncodedMethod method, AppView<?> appView) { + Map<DexMethod, DexType> retargetCoreLibMember = rewritingFlags.getRetargetCoreLibMember(); + DexType dexType = retargetCoreLibMember.get(method.getReference()); + if (dexType != null) { + return appView + .dexItemFactory() + .createMethod( + dexType, + appView.dexItemFactory().prependHolderToProto(method.getReference()), + method.getName()); + } + return null; + } + + public DexMethod retargetMethod(DexClassAndMethod method, AppView<?> appView) { + return retargetMethod(method.getDefinition(), appView); + } + + public Map<DexMethod, DexType> getRetargetCoreLibMember() { + return rewritingFlags.getRetargetCoreLibMember(); + } + + public Map<DexType, DexType> getBackportCoreLibraryMember() { + return rewritingFlags.getBackportCoreLibraryMember(); + } + + public Map<DexType, DexType> getCustomConversions() { + return rewritingFlags.getCustomConversions(); + } + + public Set<DexType> getWrapperConversions() { + return rewritingFlags.getWrapperConversions(); + } + + public Set<DexMethod> getDontRewriteInvocation() { + return rewritingFlags.getDontRewriteInvocation(); + } + + public Set<DexType> getDontRetargetLibMember() { + return rewritingFlags.getDontRetargetLibMember(); + } + + public List<String> getExtraKeepRules() { + return topLevelFlags.getExtraKeepRules(); + } + + public String getJsonSource() { + return topLevelFlags.getJsonSource(); + } + + public boolean isEmptyConfiguration() { + return false; + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java new file mode 100644 index 0000000..a612177 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
@@ -0,0 +1,236 @@ +// 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.desugaredlibrary.humanspecification; + +import com.android.tools.r8.StringResource; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.ExceptionDiagnostic; +import com.android.tools.r8.utils.Reporter; +import com.android.tools.r8.utils.StringDiagnostic; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +public class HumanDesugaredLibrarySpecificationParser { + + static final String IDENTIFIER_KEY = "identifier"; + static final String REQUIRED_COMPILATION_API_LEVEL_KEY = "required_compilation_api_level"; + static final String SYNTHESIZED_LIBRARY_CLASSES_PACKAGE_PREFIX_KEY = + "synthesized_library_classes_package_prefix"; + + static final String COMMON_FLAGS_KEY = "common_flags"; + static final String LIBRARY_FLAGS_KEY = "library_flags"; + static final String PROGRAM_FLAGS_KEY = "program_flags"; + + static final String API_LEVEL_BELOW_OR_EQUAL_KEY = "api_level_below_or_equal"; + static final String WRAPPER_CONVERSION_KEY = "wrapper_conversion"; + static final String CUSTOM_CONVERSION_KEY = "custom_conversion"; + static final String REWRITE_PREFIX_KEY = "rewrite_prefix"; + static final String RETARGET_LIB_MEMBER_KEY = "retarget_lib_member"; + static final String EMULATE_INTERFACE_KEY = "emulate_interface"; + static final String DONT_REWRITE_KEY = "dont_rewrite"; + static final String DONT_RETARGET_LIB_MEMBER_KEY = "dont_retarget_lib_member"; + static final String BACKPORT_KEY = "backport"; + static final String SHRINKER_CONFIG_KEY = "shrinker_config"; + static final String SUPPORT_ALL_CALLBACKS_FROM_LIBRARY_KEY = "support_all_callbacks_from_library"; + + private final DexItemFactory dexItemFactory; + private final Reporter reporter; + private final boolean libraryCompilation; + private final int minAPILevel; + + private Origin origin; + private JsonObject jsonConfig; + + public HumanDesugaredLibrarySpecificationParser( + DexItemFactory dexItemFactory, + Reporter reporter, + boolean libraryCompilation, + int minAPILevel) { + this.dexItemFactory = dexItemFactory; + this.reporter = reporter; + this.minAPILevel = minAPILevel; + this.libraryCompilation = libraryCompilation; + } + + public DexItemFactory dexItemFactory() { + return dexItemFactory; + } + + public Reporter reporter() { + return reporter; + } + + public JsonObject getJsonConfig() { + return jsonConfig; + } + + public Origin getOrigin() { + assert origin != null; + return origin; + } + + JsonElement required(JsonObject json, String key) { + if (!json.has(key)) { + throw reporter.fatalError( + new StringDiagnostic( + "Invalid desugared library configuration. Expected required key '" + key + "'", + origin)); + } + return json.get(key); + } + + public HumanDesugaredLibrarySpecification parse(StringResource stringResource) { + return parse(stringResource, builder -> {}); + } + + public HumanDesugaredLibrarySpecification parse( + StringResource stringResource, Consumer<HumanTopLevelFlags.Builder> topLevelFlagAmender) { + String jsonConfigString = parseJson(stringResource); + + HumanTopLevelFlags topLevelFlags = parseTopLevelFlags(jsonConfigString, topLevelFlagAmender); + + HumanRewritingFlags legacyRewritingFlags = parseRewritingFlags(); + + HumanDesugaredLibrarySpecification config = + new HumanDesugaredLibrarySpecification( + topLevelFlags, legacyRewritingFlags, libraryCompilation, dexItemFactory); + origin = null; + return config; + } + + String parseJson(StringResource stringResource) { + setOrigin(stringResource); + String jsonConfigString; + try { + jsonConfigString = stringResource.getString(); + JsonParser parser = new JsonParser(); + jsonConfig = parser.parse(jsonConfigString).getAsJsonObject(); + } catch (Exception e) { + throw reporter.fatalError(new ExceptionDiagnostic(e, origin)); + } + return jsonConfigString; + } + + void setOrigin(StringResource stringResource) { + origin = stringResource.getOrigin(); + assert origin != null; + } + + private HumanRewritingFlags parseRewritingFlags() { + HumanRewritingFlags.Builder builder = + HumanRewritingFlags.builder(dexItemFactory, reporter, origin); + JsonElement commonFlags = required(jsonConfig, COMMON_FLAGS_KEY); + JsonElement libraryFlags = required(jsonConfig, LIBRARY_FLAGS_KEY); + JsonElement programFlags = required(jsonConfig, PROGRAM_FLAGS_KEY); + parseFlagsList(commonFlags.getAsJsonArray(), builder); + parseFlagsList( + libraryCompilation ? libraryFlags.getAsJsonArray() : programFlags.getAsJsonArray(), + builder); + return builder.build(); + } + + HumanTopLevelFlags parseTopLevelFlags( + String jsonConfigString, Consumer<HumanTopLevelFlags.Builder> topLevelFlagAmender) { + HumanTopLevelFlags.Builder builder = HumanTopLevelFlags.builder(); + + builder.setJsonSource(jsonConfigString); + + String identifier = required(jsonConfig, IDENTIFIER_KEY).getAsString(); + builder.setDesugaredLibraryIdentifier(identifier); + builder.setSynthesizedLibraryClassesPackagePrefix( + required(jsonConfig, SYNTHESIZED_LIBRARY_CLASSES_PACKAGE_PREFIX_KEY).getAsString()); + + int required_compilation_api_level = + required(jsonConfig, REQUIRED_COMPILATION_API_LEVEL_KEY).getAsInt(); + builder.setRequiredCompilationAPILevel( + AndroidApiLevel.getAndroidApiLevel(required_compilation_api_level)); + if (jsonConfig.has(SHRINKER_CONFIG_KEY)) { + JsonArray jsonKeepRules = jsonConfig.get(SHRINKER_CONFIG_KEY).getAsJsonArray(); + List<String> extraKeepRules = new ArrayList<>(jsonKeepRules.size()); + for (JsonElement keepRule : jsonKeepRules) { + extraKeepRules.add(keepRule.getAsString()); + } + builder.setExtraKeepRules(extraKeepRules); + } + + if (jsonConfig.has(SUPPORT_ALL_CALLBACKS_FROM_LIBRARY_KEY)) { + boolean supportAllCallbacksFromLibrary = + jsonConfig.get(SUPPORT_ALL_CALLBACKS_FROM_LIBRARY_KEY).getAsBoolean(); + builder.setSupportAllCallbacksFromLibrary(supportAllCallbacksFromLibrary); + } + + topLevelFlagAmender.accept(builder); + + return builder.build(); + } + + private void parseFlagsList(JsonArray jsonFlags, HumanRewritingFlags.Builder builder) { + for (JsonElement jsonFlagSet : jsonFlags) { + JsonObject flag = jsonFlagSet.getAsJsonObject(); + int api_level_below_or_equal = required(flag, API_LEVEL_BELOW_OR_EQUAL_KEY).getAsInt(); + if (minAPILevel <= api_level_below_or_equal) { + parseFlags(flag, builder); + } + } + } + + void parseFlags(JsonObject jsonFlagSet, HumanRewritingFlags.Builder builder) { + if (jsonFlagSet.has(REWRITE_PREFIX_KEY)) { + for (Map.Entry<String, JsonElement> rewritePrefix : + jsonFlagSet.get(REWRITE_PREFIX_KEY).getAsJsonObject().entrySet()) { + builder.putRewritePrefix(rewritePrefix.getKey(), rewritePrefix.getValue().getAsString()); + } + } + if (jsonFlagSet.has(RETARGET_LIB_MEMBER_KEY)) { + for (Map.Entry<String, JsonElement> retarget : + jsonFlagSet.get(RETARGET_LIB_MEMBER_KEY).getAsJsonObject().entrySet()) { + builder.putRetargetCoreLibMember(retarget.getKey(), retarget.getValue().getAsString()); + } + } + if (jsonFlagSet.has(BACKPORT_KEY)) { + for (Map.Entry<String, JsonElement> backport : + jsonFlagSet.get(BACKPORT_KEY).getAsJsonObject().entrySet()) { + builder.putBackportCoreLibraryMember(backport.getKey(), backport.getValue().getAsString()); + } + } + if (jsonFlagSet.has(EMULATE_INTERFACE_KEY)) { + for (Map.Entry<String, JsonElement> itf : + jsonFlagSet.get(EMULATE_INTERFACE_KEY).getAsJsonObject().entrySet()) { + builder.putEmulateLibraryInterface(itf.getKey(), itf.getValue().getAsString()); + } + } + if (jsonFlagSet.has(CUSTOM_CONVERSION_KEY)) { + for (Map.Entry<String, JsonElement> conversion : + jsonFlagSet.get(CUSTOM_CONVERSION_KEY).getAsJsonObject().entrySet()) { + builder.putCustomConversion(conversion.getKey(), conversion.getValue().getAsString()); + } + } + if (jsonFlagSet.has(WRAPPER_CONVERSION_KEY)) { + for (JsonElement wrapper : jsonFlagSet.get(WRAPPER_CONVERSION_KEY).getAsJsonArray()) { + builder.addWrapperConversion(wrapper.getAsString()); + } + } + if (jsonFlagSet.has(DONT_REWRITE_KEY)) { + JsonArray dontRewrite = jsonFlagSet.get(DONT_REWRITE_KEY).getAsJsonArray(); + for (JsonElement rewrite : dontRewrite) { + builder.addDontRewriteInvocation(rewrite.getAsString()); + } + } + if (jsonFlagSet.has(DONT_RETARGET_LIB_MEMBER_KEY)) { + JsonArray dontRetarget = jsonFlagSet.get(DONT_RETARGET_LIB_MEMBER_KEY).getAsJsonArray(); + for (JsonElement rewrite : dontRetarget) { + builder.addDontRetargetLibMember(rewrite.getAsString()); + } + } + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java new file mode 100644 index 0000000..bf87cd8 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
@@ -0,0 +1,354 @@ +// 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.desugaredlibrary.humanspecification; + +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexMethod; +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.origin.Origin; +import com.android.tools.r8.utils.DescriptorUtils; +import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.Reporter; +import com.android.tools.r8.utils.StringDiagnostic; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import com.google.common.collect.Sets.SetView; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +public class HumanRewritingFlags { + + private final Map<String, String> rewritePrefix; + private final Map<DexType, DexType> emulateLibraryInterface; + private final Map<DexMethod, DexType> retargetCoreLibMember; + private final Map<DexType, DexType> backportCoreLibraryMember; + private final Map<DexType, DexType> customConversions; + private final Set<DexMethod> dontRewriteInvocation; + private final Set<DexType> dontRetargetLibMember; + private final Set<DexType> wrapperConversions; + + HumanRewritingFlags( + Map<String, String> rewritePrefix, + Map<DexType, DexType> emulateLibraryInterface, + Map<DexMethod, DexType> retargetCoreLibMember, + Map<DexType, DexType> backportCoreLibraryMember, + Map<DexType, DexType> customConversions, + Set<DexMethod> dontRewriteInvocation, + Set<DexType> dontRetargetLibMember, + Set<DexType> wrapperConversions) { + this.rewritePrefix = rewritePrefix; + this.emulateLibraryInterface = emulateLibraryInterface; + this.retargetCoreLibMember = retargetCoreLibMember; + this.backportCoreLibraryMember = backportCoreLibraryMember; + this.customConversions = customConversions; + this.dontRewriteInvocation = dontRewriteInvocation; + this.dontRetargetLibMember = dontRetargetLibMember; + this.wrapperConversions = wrapperConversions; + } + + public static HumanRewritingFlags empty() { + return new HumanRewritingFlags( + ImmutableMap.of(), + ImmutableMap.of(), + ImmutableMap.of(), + ImmutableMap.of(), + ImmutableMap.of(), + ImmutableSet.of(), + ImmutableSet.of(), + ImmutableSet.of()); + } + + public static HumanRewritingFlags withOnlyRewritePrefixForTesting( + Map<String, String> prefix, InternalOptions options) { + Builder builder = builder(options.dexItemFactory(), options.reporter, Origin.unknown()); + prefix.forEach(builder::putRewritePrefix); + return builder.build(); + } + + public static Builder builder(DexItemFactory dexItemFactory, Reporter reporter, Origin origin) { + return new Builder(dexItemFactory, reporter, origin); + } + + public Builder newBuilder(DexItemFactory dexItemFactory, Reporter reporter, Origin origin) { + return new Builder( + dexItemFactory, + reporter, + origin, + rewritePrefix, + emulateLibraryInterface, + retargetCoreLibMember, + backportCoreLibraryMember, + customConversions, + dontRewriteInvocation, + dontRetargetLibMember, + wrapperConversions); + } + + public Map<String, String> getRewritePrefix() { + return rewritePrefix; + } + + public Map<DexType, DexType> getEmulateLibraryInterface() { + return emulateLibraryInterface; + } + + public Map<DexMethod, DexType> getRetargetCoreLibMember() { + return retargetCoreLibMember; + } + + public Map<DexType, DexType> getBackportCoreLibraryMember() { + return backportCoreLibraryMember; + } + + public Map<DexType, DexType> getCustomConversions() { + return customConversions; + } + + public Set<DexMethod> getDontRewriteInvocation() { + return dontRewriteInvocation; + } + + public Set<DexType> getDontRetargetLibMember() { + return dontRetargetLibMember; + } + + public Set<DexType> getWrapperConversions() { + return wrapperConversions; + } + + public static class Builder { + + private static final String SEPARATORS = "\\s+|,\\s+|#|\\(|\\)"; + + private final DexItemFactory factory; + private final Reporter reporter; + private final Origin origin; + + private final Map<String, String> rewritePrefix; + private final Map<DexType, DexType> emulateLibraryInterface; + private final Map<DexMethod, DexType> retargetCoreLibMember; + private final Map<DexType, DexType> backportCoreLibraryMember; + private final Map<DexType, DexType> customConversions; + private final Set<DexMethod> dontRewriteInvocation; + private final Set<DexType> dontRetargetLibMember; + private final Set<DexType> wrapperConversions; + + Builder(DexItemFactory factory, Reporter reporter, Origin origin) { + this( + factory, + reporter, + origin, + new HashMap<>(), + new IdentityHashMap<>(), + new IdentityHashMap<>(), + new IdentityHashMap<>(), + new IdentityHashMap<>(), + Sets.newIdentityHashSet(), + Sets.newIdentityHashSet(), + Sets.newIdentityHashSet()); + } + + Builder( + DexItemFactory factory, + Reporter reporter, + Origin origin, + Map<String, String> rewritePrefix, + Map<DexType, DexType> emulateLibraryInterface, + Map<DexMethod, DexType> retargetCoreLibMember, + Map<DexType, DexType> backportCoreLibraryMember, + Map<DexType, DexType> customConversions, + Set<DexMethod> dontRewriteInvocation, + Set<DexType> dontRetargetLibMember, + Set<DexType> wrapperConversions) { + this.factory = factory; + this.reporter = reporter; + this.origin = origin; + this.rewritePrefix = new HashMap<>(rewritePrefix); + this.emulateLibraryInterface = new IdentityHashMap<>(emulateLibraryInterface); + this.retargetCoreLibMember = new IdentityHashMap<>(retargetCoreLibMember); + this.backportCoreLibraryMember = new IdentityHashMap<>(backportCoreLibraryMember); + this.customConversions = new IdentityHashMap<>(customConversions); + this.dontRewriteInvocation = Sets.newIdentityHashSet(); + this.dontRewriteInvocation.addAll(dontRewriteInvocation); + this.dontRetargetLibMember = Sets.newIdentityHashSet(); + this.dontRetargetLibMember.addAll(dontRetargetLibMember); + this.wrapperConversions = Sets.newIdentityHashSet(); + this.wrapperConversions.addAll(wrapperConversions); + } + + // Utility to set values. + private <K, V> void put(Map<K, V> map, K key, V value, String desc) { + if (map.containsKey(key) && !map.get(key).equals(value)) { + throw reporter.fatalError( + new StringDiagnostic( + "Invalid desugared library configuration. " + + " Duplicate assignment of key: '" + + key + + "' in sections for '" + + desc + + "'", + origin)); + } + map.put(key, value); + } + + public Builder putRewritePrefix(String prefix, String rewrittenPrefix) { + put( + rewritePrefix, + prefix, + rewrittenPrefix, + HumanDesugaredLibrarySpecificationParser.REWRITE_PREFIX_KEY); + return this; + } + + public Builder putEmulateLibraryInterface( + String emulateLibraryItf, String rewrittenEmulateLibraryItf) { + DexType interfaceType = stringClassToDexType(emulateLibraryItf); + DexType rewrittenType = stringClassToDexType(rewrittenEmulateLibraryItf); + putEmulateLibraryInterface(interfaceType, rewrittenType); + return this; + } + + public Builder putEmulateLibraryInterface(DexType interfaceType, DexType rewrittenType) { + put( + emulateLibraryInterface, + interfaceType, + rewrittenType, + HumanDesugaredLibrarySpecificationParser.EMULATE_INTERFACE_KEY); + return this; + } + + public Builder putCustomConversion(String type, String conversionHolder) { + DexType dexType = stringClassToDexType(type); + DexType conversionType = stringClassToDexType(conversionHolder); + putCustomConversion(dexType, conversionType); + return this; + } + + public Builder putCustomConversion(DexType dexType, DexType conversionType) { + put( + customConversions, + dexType, + conversionType, + HumanDesugaredLibrarySpecificationParser.CUSTOM_CONVERSION_KEY); + return this; + } + + public Builder addWrapperConversion(String type) { + DexType dexType = stringClassToDexType(type); + addWrapperConversion(dexType); + return this; + } + + public Builder addWrapperConversion(DexType dexType) { + wrapperConversions.add(dexType); + return this; + } + + public Builder putRetargetCoreLibMember(String retarget, String rewrittenRetarget) { + DexMethod key = parseMethod(retarget); + DexType rewrittenType = stringClassToDexType(rewrittenRetarget); + putRetargetCoreLibMember(key, rewrittenType); + return this; + } + + public Builder putRetargetCoreLibMember(DexMethod key, DexType rewrittenType) { + put( + retargetCoreLibMember, + key, + rewrittenType, + HumanDesugaredLibrarySpecificationParser.RETARGET_LIB_MEMBER_KEY); + return this; + } + + public Builder putBackportCoreLibraryMember(String backport, String rewrittenBackport) { + DexType backportType = stringClassToDexType(backport); + DexType rewrittenBackportType = stringClassToDexType(rewrittenBackport); + putBackportCoreLibraryMember(backportType, rewrittenBackportType); + return this; + } + + public Builder putBackportCoreLibraryMember( + DexType backportType, DexType rewrittenBackportType) { + put( + backportCoreLibraryMember, + backportType, + rewrittenBackportType, + HumanDesugaredLibrarySpecificationParser.BACKPORT_KEY); + return this; + } + + public Builder addDontRewriteInvocation(String dontRewriteInvocation) { + DexMethod dontRewrite = parseMethod(dontRewriteInvocation); + addDontRewriteInvocation(dontRewrite); + return this; + } + + public Builder addDontRewriteInvocation(DexMethod dontRewrite) { + this.dontRewriteInvocation.add(dontRewrite); + return this; + } + + public Builder addDontRetargetLibMember(String dontRetargetLibMember) { + addDontRetargetLibMember(stringClassToDexType(dontRetargetLibMember)); + return this; + } + + public Builder addDontRetargetLibMember(DexType dontRetargetLibMember) { + this.dontRetargetLibMember.add(dontRetargetLibMember); + return this; + } + + private DexMethod parseMethod(String signature) { + String[] split = signature.split(SEPARATORS); + assert split.length >= 3; + DexType returnType = factory.createType(DescriptorUtils.javaTypeToDescriptor(split[0])); + DexType holderType = factory.createType(DescriptorUtils.javaTypeToDescriptor(split[1])); + DexString name = factory.createString(split[2]); + DexType[] argTypes = new DexType[split.length - 3]; + for (int i = 3; i < split.length; i++) { + argTypes[i - 3] = factory.createType(DescriptorUtils.javaTypeToDescriptor(split[i])); + } + DexProto proto = factory.createProto(returnType, argTypes); + return factory.createMethod(holderType, proto, name); + } + + private DexType stringClassToDexType(String stringClass) { + return factory.createType(DescriptorUtils.javaTypeToDescriptor(stringClass)); + } + + public HumanRewritingFlags build() { + validate(); + return new HumanRewritingFlags( + ImmutableMap.copyOf(rewritePrefix), + ImmutableMap.copyOf(emulateLibraryInterface), + ImmutableMap.copyOf(retargetCoreLibMember), + ImmutableMap.copyOf(backportCoreLibraryMember), + ImmutableMap.copyOf(customConversions), + ImmutableSet.copyOf(dontRewriteInvocation), + ImmutableSet.copyOf(dontRetargetLibMember), + ImmutableSet.copyOf(wrapperConversions)); + } + + private void validate() { + SetView<DexType> dups = Sets.intersection(customConversions.keySet(), wrapperConversions); + if (!dups.isEmpty()) { + throw reporter.fatalError( + new StringDiagnostic( + "Invalid desugared library configuration. " + + "Duplicate types in custom conversions and wrapper conversions: " + + String.join( + ", ", dups.stream().map(DexType::toString).collect(Collectors.toSet())), + origin)); + } + } + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanTopLevelFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanTopLevelFlags.java new file mode 100644 index 0000000..80a13b5 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanTopLevelFlags.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.ir.desugar.desugaredlibrary.humanspecification; + +import com.android.tools.r8.utils.AndroidApiLevel; +import com.google.common.collect.ImmutableList; +import java.util.List; + +public class HumanTopLevelFlags { + + private final AndroidApiLevel requiredCompilationAPILevel; + private final String synthesizedLibraryClassesPackagePrefix; + private final String identifier; + private final String jsonSource; + // Setting supportAllCallbacksFromLibrary reduces the number of generated call-backs, + // more specifically: + // - no call-back is generated for emulated interface method overrides (forEach, etc.) + // - no call-back is generated inside the desugared library itself. + // Such setting decreases significantly the desugared library dex file, but virtual calls from + // within the library to desugared library classes instances as receiver may be incorrect, for + // example the method forEach in Iterable may be executed over a concrete implementation. + private final boolean supportAllCallbacksFromLibrary; + private final List<String> extraKeepRules; + + HumanTopLevelFlags( + AndroidApiLevel requiredCompilationAPILevel, + String synthesizedLibraryClassesPackagePrefix, + String identifier, + String jsonSource, + boolean supportAllCallbacksFromLibrary, + List<String> extraKeepRules) { + this.requiredCompilationAPILevel = requiredCompilationAPILevel; + this.synthesizedLibraryClassesPackagePrefix = synthesizedLibraryClassesPackagePrefix; + this.identifier = identifier; + this.jsonSource = jsonSource; + this.supportAllCallbacksFromLibrary = supportAllCallbacksFromLibrary; + this.extraKeepRules = extraKeepRules; + } + + public static HumanTopLevelFlags empty() { + return new HumanTopLevelFlags( + AndroidApiLevel.B, "unused", null, null, true, ImmutableList.of()); + } + + public static HumanTopLevelFlags testing() { + return new HumanTopLevelFlags( + AndroidApiLevel.B, "unused", "testing", null, true, ImmutableList.of()); + } + + public static Builder builder() { + return new Builder(); + } + + public AndroidApiLevel getRequiredCompilationAPILevel() { + return requiredCompilationAPILevel; + } + + public String getSynthesizedLibraryClassesPackagePrefix() { + return synthesizedLibraryClassesPackagePrefix; + } + + public String getIdentifier() { + return identifier; + } + + public String getJsonSource() { + return jsonSource; + } + + public boolean supportAllCallbacksFromLibrary() { + return supportAllCallbacksFromLibrary; + } + + public List<String> getExtraKeepRules() { + return extraKeepRules; + } + + public static class Builder { + + private AndroidApiLevel requiredCompilationAPILevel; + private String synthesizedLibraryClassesPackagePrefix; + private String identifier; + private String jsonSource; + private Boolean supportAllCallbacksFromLibrary; + private List<String> extraKeepRules; + + Builder() {} + + public Builder setRequiredCompilationAPILevel(AndroidApiLevel requiredCompilationAPILevel) { + this.requiredCompilationAPILevel = requiredCompilationAPILevel; + return this; + } + + public Builder setSynthesizedLibraryClassesPackagePrefix(String prefix) { + this.synthesizedLibraryClassesPackagePrefix = prefix.replace('.', '/'); + return this; + } + + public Builder setDesugaredLibraryIdentifier(String identifier) { + this.identifier = identifier; + return this; + } + + public Builder setJsonSource(String jsonSource) { + this.jsonSource = jsonSource; + return this; + } + + public Builder setSupportAllCallbacksFromLibrary(boolean supportAllCallbacksFromLibrary) { + this.supportAllCallbacksFromLibrary = supportAllCallbacksFromLibrary; + return this; + } + + public Builder setExtraKeepRules(List<String> rules) { + extraKeepRules = rules; + return this; + } + + public HumanTopLevelFlags build() { + assert synthesizedLibraryClassesPackagePrefix != null; + assert supportAllCallbacksFromLibrary != null; + return new HumanTopLevelFlags( + requiredCompilationAPILevel, + synthesizedLibraryClassesPackagePrefix, + identifier, + jsonSource, + supportAllCallbacksFromLibrary, + extraKeepRules); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecification.java new file mode 100644 index 0000000..69c82a9 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecification.java
@@ -0,0 +1,64 @@ +// 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.desugaredlibrary.humanspecification; + +import com.android.tools.r8.origin.Origin; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import java.util.Map; + +public class MultiAPILevelHumanDesugaredLibrarySpecification { + + private final Origin origin; + private final HumanTopLevelFlags topLevelFlags; + private final Int2ObjectMap<HumanRewritingFlags> commonFlags; + private final Int2ObjectMap<HumanRewritingFlags> libraryFlags; + private final Int2ObjectMap<HumanRewritingFlags> programFlags; + + public MultiAPILevelHumanDesugaredLibrarySpecification( + Origin origin, + HumanTopLevelFlags topLevelFlags, + Int2ObjectMap<HumanRewritingFlags> commonFlags, + Int2ObjectMap<HumanRewritingFlags> libraryFlags, + Int2ObjectMap<HumanRewritingFlags> programFlags) { + this.origin = origin; + this.topLevelFlags = topLevelFlags; + this.commonFlags = commonFlags; + this.libraryFlags = libraryFlags; + this.programFlags = programFlags; + } + + public Origin getOrigin() { + return origin; + } + + public HumanTopLevelFlags getTopLevelFlags() { + return topLevelFlags; + } + + public Int2ObjectMap<HumanRewritingFlags> getCommonFlags() { + return commonFlags; + } + + public Int2ObjectMap<HumanRewritingFlags> getLibraryFlags() { + return libraryFlags; + } + + public Int2ObjectMap<HumanRewritingFlags> getProgramFlags() { + return programFlags; + } + + public Map<Integer, HumanRewritingFlags> getCommonFlagsForTesting() { + return commonFlags; + } + + public Map<Integer, HumanRewritingFlags> getLibraryFlagsForTesting() { + return libraryFlags; + } + + public Map<Integer, HumanRewritingFlags> getProgramFlagsForTesting() { + return programFlags; + } + +}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationFlagDeduplicator.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationFlagDeduplicator.java new file mode 100644 index 0000000..c79fad3 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationFlagDeduplicator.java
@@ -0,0 +1,162 @@ +// 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.desugaredlibrary.humanspecification; + +import com.android.tools.r8.graph.DexItem; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.utils.Reporter; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.IntArraySet; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +public class MultiAPILevelHumanDesugaredLibrarySpecificationFlagDeduplicator { + + public static void deduplicateFlags( + MultiAPILevelHumanDesugaredLibrarySpecification specification, + DexItemFactory factory, + Reporter reporter) { + + IntArraySet apis = new IntArraySet(); + apis.addAll(specification.getCommonFlags().keySet()); + apis.addAll(specification.getLibraryFlags().keySet()); + apis.addAll(specification.getProgramFlags().keySet()); + + for (Integer api : apis) { + deduplicateFlags(specification, factory, reporter, api); + } + } + + private static void deduplicateFlags( + MultiAPILevelHumanDesugaredLibrarySpecification specification, + DexItemFactory factory, + Reporter reporter, + int api) { + + Int2ObjectMap<HumanRewritingFlags> commonFlags = specification.getCommonFlags(); + Int2ObjectMap<HumanRewritingFlags> libraryFlags = specification.getLibraryFlags(); + Int2ObjectMap<HumanRewritingFlags> programFlags = specification.getProgramFlags(); + + HumanRewritingFlags library = libraryFlags.get(api); + HumanRewritingFlags program = programFlags.get(api); + + if (library == null || program == null) { + return; + } + + Origin origin = specification.getOrigin(); + HumanRewritingFlags.Builder commonBuilder = + commonFlags.get(api) == null + ? HumanRewritingFlags.builder(factory, reporter, origin) + : commonFlags.get(api).newBuilder(factory, reporter, origin); + HumanRewritingFlags.Builder libraryBuilder = + HumanRewritingFlags.builder(factory, reporter, origin); + HumanRewritingFlags.Builder programBuilder = + HumanRewritingFlags.builder(factory, reporter, origin); + + // Iterate over all library/program flags, add them in common if also in the other, else add + // them to library/program. + deduplicateFlags(library, program, commonBuilder, libraryBuilder); + deduplicateFlags(program, library, commonBuilder, programBuilder); + + commonFlags.put(api, commonBuilder.build()); + libraryFlags.put(api, libraryBuilder.build()); + programFlags.put(api, programBuilder.build()); + } + + private static void deduplicateFlags( + HumanRewritingFlags flags, + HumanRewritingFlags otherFlags, + HumanRewritingFlags.Builder commonBuilder, + HumanRewritingFlags.Builder builder) { + deduplicateRewritePrefix(flags, otherFlags, commonBuilder, builder); + + deduplicateFlags( + flags.getEmulateLibraryInterface(), + otherFlags.getEmulateLibraryInterface(), + commonBuilder::putEmulateLibraryInterface, + builder::putEmulateLibraryInterface); + deduplicateFlags( + flags.getRetargetCoreLibMember(), + otherFlags.getRetargetCoreLibMember(), + commonBuilder::putRetargetCoreLibMember, + builder::putRetargetCoreLibMember); + deduplicateFlags( + flags.getBackportCoreLibraryMember(), + otherFlags.getBackportCoreLibraryMember(), + commonBuilder::putBackportCoreLibraryMember, + builder::putBackportCoreLibraryMember); + deduplicateFlags( + flags.getCustomConversions(), + otherFlags.getCustomConversions(), + commonBuilder::putCustomConversion, + builder::putCustomConversion); + + deduplicateFlags( + flags.getDontRewriteInvocation(), + otherFlags.getDontRewriteInvocation(), + commonBuilder::addDontRewriteInvocation, + builder::addDontRewriteInvocation); + deduplicateFlags( + flags.getDontRetargetLibMember(), + otherFlags.getDontRetargetLibMember(), + commonBuilder::addDontRetargetLibMember, + builder::addDontRetargetLibMember); + deduplicateFlags( + flags.getWrapperConversions(), + otherFlags.getWrapperConversions(), + commonBuilder::addWrapperConversion, + builder::addWrapperConversion); + } + + private static void deduplicateRewritePrefix( + HumanRewritingFlags flags, + HumanRewritingFlags otherFlags, + HumanRewritingFlags.Builder commonBuilder, + HumanRewritingFlags.Builder builder) { + flags + .getRewritePrefix() + .forEach( + (k, v) -> { + if (otherFlags.getRewritePrefix().get(k) != null + && otherFlags.getRewritePrefix().get(k).equals(v)) { + commonBuilder.putRewritePrefix(k, v); + } else { + builder.putRewritePrefix(k, v); + } + }); + } + + private static <T extends DexItem> void deduplicateFlags( + Map<T, DexType> flags, + Map<T, DexType> otherFlags, + BiConsumer<T, DexType> common, + BiConsumer<T, DexType> specific) { + flags.forEach( + (k, v) -> { + if (otherFlags.get(k) == v) { + common.accept(k, v); + } else { + specific.accept(k, v); + } + }); + } + + private static <T extends DexItem> void deduplicateFlags( + Set<T> flags, Set<T> otherFlags, Consumer<T> common, Consumer<T> specific) { + flags.forEach( + e -> { + if (otherFlags.contains(e)) { + common.accept(e); + } else { + specific.accept(e); + } + }); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.java new file mode 100644 index 0000000..fd76244 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.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.ir.desugar.desugaredlibrary.humanspecification; + +import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.*; +import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.IDENTIFIER_KEY; + +import com.android.tools.r8.DiagnosticsHandler; +import com.android.tools.r8.StringConsumer; +import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.graph.DexItem; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexType; +import com.google.common.collect.Sets; +import com.google.gson.Gson; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +public class MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter { + + public static void export( + MultiAPILevelHumanDesugaredLibrarySpecification specification, StringConsumer output) { + new MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter() + .internalExport(specification, output); + } + + private void internalExport( + MultiAPILevelHumanDesugaredLibrarySpecification humanSpec, StringConsumer output) { + HashMap<String, Object> toJson = new LinkedHashMap<>(); + toJson.put(IDENTIFIER_KEY, humanSpec.getTopLevelFlags().getIdentifier()); + toJson.put( + REQUIRED_COMPILATION_API_LEVEL_KEY, + humanSpec.getTopLevelFlags().getRequiredCompilationAPILevel().getLevel()); + toJson.put( + SYNTHESIZED_LIBRARY_CLASSES_PACKAGE_PREFIX_KEY, + humanSpec.getTopLevelFlags().getSynthesizedLibraryClassesPackagePrefix()); + toJson.put( + SUPPORT_ALL_CALLBACKS_FROM_LIBRARY_KEY, + humanSpec.getTopLevelFlags().supportAllCallbacksFromLibrary()); + + toJson.put(COMMON_FLAGS_KEY, rewritingFlagsToString(humanSpec.getCommonFlags())); + toJson.put(PROGRAM_FLAGS_KEY, rewritingFlagsToString(humanSpec.getProgramFlags())); + toJson.put(LIBRARY_FLAGS_KEY, rewritingFlagsToString(humanSpec.getLibraryFlags())); + + toJson.put(SHRINKER_CONFIG_KEY, humanSpec.getTopLevelFlags().getExtraKeepRules()); + + Gson gson = new Gson(); + String export = gson.toJson(toJson); + output.accept(export, new DiagnosticsHandler() {}); + } + + private List<Object> rewritingFlagsToString( + Int2ObjectMap<HumanRewritingFlags> rewritingFlagsMap) { + ArrayList<Object> list = new ArrayList<>(); + rewritingFlagsMap.forEach( + (apiBelowOrEqual, flags) -> { + HashMap<String, Object> toJson = new LinkedHashMap<>(); + toJson.put(API_LEVEL_BELOW_OR_EQUAL_KEY, apiBelowOrEqual); + if (!flags.getRewritePrefix().isEmpty()) { + toJson.put(REWRITE_PREFIX_KEY, new TreeMap<>(flags.getRewritePrefix())); + } + if (!flags.getEmulateLibraryInterface().isEmpty()) { + toJson.put(EMULATE_INTERFACE_KEY, mapToString(flags.getEmulateLibraryInterface())); + } + if (!flags.getDontRewriteInvocation().isEmpty()) { + toJson.put(DONT_REWRITE_KEY, setToString(flags.getDontRewriteInvocation())); + } + if (!flags.getRetargetCoreLibMember().isEmpty()) { + toJson.put(RETARGET_LIB_MEMBER_KEY, mapToString(flags.getRetargetCoreLibMember())); + } + if (!flags.getDontRetargetLibMember().isEmpty()) { + toJson.put(DONT_RETARGET_LIB_MEMBER_KEY, setToString(flags.getDontRetargetLibMember())); + } + if (!flags.getBackportCoreLibraryMember().isEmpty()) { + toJson.put(BACKPORT_KEY, mapToString(flags.getBackportCoreLibraryMember())); + } + if (!flags.getWrapperConversions().isEmpty()) { + toJson.put(WRAPPER_CONVERSION_KEY, setToString(flags.getWrapperConversions())); + } + if (!flags.getCustomConversions().isEmpty()) { + toJson.put(CUSTOM_CONVERSION_KEY, mapToString(flags.getCustomConversions())); + } + list.add(toJson); + }); + return list; + } + + private Set<String> setToString(Set<? extends DexItem> set) { + Set<String> stringSet = Sets.newHashSet(); + set.forEach(e -> stringSet.add(toString(e))); + return stringSet; + } + + private Map<String, String> mapToString(Map<? extends DexItem, ? extends DexItem> map) { + Map<String, String> stringMap = new TreeMap<>(); + map.forEach((k, v) -> stringMap.put(toString(k), toString(v))); + return stringMap; + } + + private String toString(DexItem o) { + if (o instanceof DexType) { + return o.toString(); + } + if (o instanceof DexMethod) { + DexMethod method = (DexMethod) o; + StringBuilder sb = + new StringBuilder() + .append(method.getReturnType()) + .append(" ") + .append(method.getHolderType()) + .append("#") + .append(method.getName()) + .append("("); + for (int i = 0; i < method.getParameters().size(); i++) { + sb.append(method.getParameter(i)); + if (i != method.getParameters().size() - 1) { + sb.append(", "); + } + } + sb.append(")"); + return sb.toString(); + } + throw new Unreachable(); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationParser.java new file mode 100644 index 0000000..140609d --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationParser.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.desugar.desugaredlibrary.humanspecification; + +import com.android.tools.r8.StringResource; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.utils.Reporter; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; + +public class MultiAPILevelHumanDesugaredLibrarySpecificationParser + extends HumanDesugaredLibrarySpecificationParser { + + public MultiAPILevelHumanDesugaredLibrarySpecificationParser( + DexItemFactory dexItemFactory, Reporter reporter) { + super(dexItemFactory, reporter, false, 1); + } + + public MultiAPILevelHumanDesugaredLibrarySpecification parseMultiLevelConfiguration( + StringResource stringResource) { + + String jsonConfigString = parseJson(stringResource); + + HumanTopLevelFlags topLevelFlags = parseTopLevelFlags(jsonConfigString, builder -> {}); + + Int2ObjectMap<HumanRewritingFlags> commonFlags = parseAllFlags(COMMON_FLAGS_KEY); + Int2ObjectMap<HumanRewritingFlags> libraryFlags = parseAllFlags(LIBRARY_FLAGS_KEY); + Int2ObjectMap<HumanRewritingFlags> programFlags = parseAllFlags(PROGRAM_FLAGS_KEY); + + return new MultiAPILevelHumanDesugaredLibrarySpecification( + getOrigin(), topLevelFlags, commonFlags, libraryFlags, programFlags); + } + + private Int2ObjectMap<HumanRewritingFlags> parseAllFlags(String flagKey) { + JsonElement jsonFlags = required(getJsonConfig(), flagKey); + Int2ObjectMap<HumanRewritingFlags> flags = new Int2ObjectArrayMap<>(); + for (JsonElement jsonFlagSet : jsonFlags.getAsJsonArray()) { + JsonObject flag = jsonFlagSet.getAsJsonObject(); + int api_level_below_or_equal = required(flag, API_LEVEL_BELOW_OR_EQUAL_KEY).getAsInt(); + HumanRewritingFlags.Builder builder = + flags.containsKey(api_level_below_or_equal) + ? flags + .get(api_level_below_or_equal) + .newBuilder(dexItemFactory(), reporter(), getOrigin()) + : HumanRewritingFlags.builder(dexItemFactory(), reporter(), getOrigin()); + parseFlags(flag, builder); + flags.put(api_level_below_or_equal, builder.build()); + } + return flags; + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecification.java index 6198ce5..3da0b97 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecification.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecification.java
@@ -68,6 +68,14 @@ rewritingFlags.getRewritePrefix(), factory, libraryCompilation); } + public LegacyTopLevelFlags getTopLevelFlags() { + return topLevelFlags; + } + + public LegacyRewritingFlags getRewritingFlags() { + return rewritingFlags; + } + public boolean supportAllCallbacksFromLibrary() { return topLevelFlags.supportAllCallbacksFromLibrary(); }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/MultiAPILevelLegacyDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/MultiAPILevelLegacyDesugaredLibrarySpecification.java index 76a0b07..2226f88 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/MultiAPILevelLegacyDesugaredLibrarySpecification.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/MultiAPILevelLegacyDesugaredLibrarySpecification.java
@@ -4,23 +4,47 @@ package com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification; +import com.android.tools.r8.origin.Origin; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; public class MultiAPILevelLegacyDesugaredLibrarySpecification { + private final Origin origin; private final LegacyTopLevelFlags topLevelFlags; private final Int2ObjectMap<LegacyRewritingFlags> commonFlags; private final Int2ObjectMap<LegacyRewritingFlags> libraryFlags; private final Int2ObjectMap<LegacyRewritingFlags> programFlags; public MultiAPILevelLegacyDesugaredLibrarySpecification( + Origin origin, LegacyTopLevelFlags topLevelFlags, Int2ObjectMap<LegacyRewritingFlags> commonFlags, Int2ObjectMap<LegacyRewritingFlags> libraryFlags, Int2ObjectMap<LegacyRewritingFlags> programFlags) { + this.origin = origin; this.topLevelFlags = topLevelFlags; this.commonFlags = commonFlags; this.libraryFlags = libraryFlags; this.programFlags = programFlags; } + + public Origin getOrigin() { + return origin; + } + + public LegacyTopLevelFlags getTopLevelFlags() { + return topLevelFlags; + } + + public Int2ObjectMap<LegacyRewritingFlags> getCommonFlags() { + return commonFlags; + } + + public Int2ObjectMap<LegacyRewritingFlags> getLibraryFlags() { + return libraryFlags; + } + + public Int2ObjectMap<LegacyRewritingFlags> getProgramFlags() { + return programFlags; + } }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/MultiAPILevelLegacyDesugaredLibrarySpecificationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/MultiAPILevelLegacyDesugaredLibrarySpecificationParser.java index 708b300..447272e 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/MultiAPILevelLegacyDesugaredLibrarySpecificationParser.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/MultiAPILevelLegacyDesugaredLibrarySpecificationParser.java
@@ -32,7 +32,7 @@ Int2ObjectMap<LegacyRewritingFlags> programFlags = parseAllFlags(PROGRAM_FLAGS_KEY); return new MultiAPILevelLegacyDesugaredLibrarySpecification( - topLevelFlags, commonFlags, libraryFlags, programFlags); + getOrigin(), topLevelFlags, commonFlags, libraryFlags, programFlags); } private Int2ObjectMap<LegacyRewritingFlags> parseAllFlags(String flagKey) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java new file mode 100644 index 0000000..14898fc --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java
@@ -0,0 +1,226 @@ +// 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.desugaredlibrary.specificationconversion; + +import com.android.tools.r8.StringConsumer; +import com.android.tools.r8.StringResource; +import com.android.tools.r8.dex.ApplicationReader; +import com.android.tools.r8.graph.DexApplication; +import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexClassAndMethod; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexMethod; +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.DirectMappedDexApplication; +import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecification; +import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags; +import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanTopLevelFlags; +import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.MultiAPILevelHumanDesugaredLibrarySpecification; +import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.MultiAPILevelHumanDesugaredLibrarySpecificationFlagDeduplicator; +import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter; +import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyDesugaredLibrarySpecification; +import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyRewritingFlags; +import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyTopLevelFlags; +import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.MultiAPILevelLegacyDesugaredLibrarySpecification; +import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.MultiAPILevelLegacyDesugaredLibrarySpecificationParser; +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.InternalOptions; +import com.android.tools.r8.utils.Pair; +import com.android.tools.r8.utils.ThreadUtils; +import com.android.tools.r8.utils.Timing; +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; + +public class LegacyToHumanSpecificationConverter implements SpecificationConverter { + + @Override + public void convertAllAPILevels( + StringResource inputSpecification, Path androidLib, StringConsumer output) + throws IOException { + InternalOptions options = new InternalOptions(); + MultiAPILevelLegacyDesugaredLibrarySpecification legacySpec = + new MultiAPILevelLegacyDesugaredLibrarySpecificationParser( + options.dexItemFactory(), options.reporter) + .parseMultiLevelConfiguration(inputSpecification); + MultiAPILevelHumanDesugaredLibrarySpecification humanSpec = + convertAllAPILevels(legacySpec, androidLib, options); + MultiAPILevelHumanDesugaredLibrarySpecificationFlagDeduplicator.deduplicateFlags( + humanSpec, options.dexItemFactory(), options.reporter); + MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.export(humanSpec, output); + } + + public MultiAPILevelHumanDesugaredLibrarySpecification convertAllAPILevels( + MultiAPILevelLegacyDesugaredLibrarySpecification legacySpec, + Path androidLib, + InternalOptions options) + throws IOException { + Origin origin = legacySpec.getOrigin(); + DexApplication app = readApp(androidLib, options); + HumanTopLevelFlags humanTopLevelFlags = convertTopLevelFlags(legacySpec.getTopLevelFlags()); + Int2ObjectArrayMap<HumanRewritingFlags> commonFlags = + convertRewritingFlagMap(legacySpec.getCommonFlags(), app, origin); + Int2ObjectArrayMap<HumanRewritingFlags> programFlags = + convertRewritingFlagMap(legacySpec.getProgramFlags(), app, origin); + Int2ObjectArrayMap<HumanRewritingFlags> libraryFlags = + convertRewritingFlagMap(legacySpec.getLibraryFlags(), app, origin); + + legacyLibraryFlagHacks(libraryFlags, app, origin); + + return new MultiAPILevelHumanDesugaredLibrarySpecification( + origin, humanTopLevelFlags, commonFlags, libraryFlags, programFlags); + } + + public HumanDesugaredLibrarySpecification convert( + LegacyDesugaredLibrarySpecification legacySpec, Path androidLib, InternalOptions options) + throws IOException { + DexApplication app = readApp(androidLib, options); + HumanTopLevelFlags humanTopLevelFlags = convertTopLevelFlags(legacySpec.getTopLevelFlags()); + + // The origin is not maintained in non multi-level specifications. + // It should not matter since the origin is used to report invalid specifications, and + // converting non multi-level specifications should be performed only with *valid* + // specifications in practical cases. + Origin origin = Origin.unknown(); + + HumanRewritingFlags humanRewritingFlags = + convertRewritingFlags(legacySpec.getRewritingFlags(), app, origin); + return new HumanDesugaredLibrarySpecification( + humanTopLevelFlags, + humanRewritingFlags, + legacySpec.isLibraryCompilation(), + app.dexItemFactory()); + } + + private void legacyLibraryFlagHacks( + Int2ObjectArrayMap<HumanRewritingFlags> libraryFlags, DexApplication app, Origin origin) { + int level = AndroidApiLevel.N_MR1.getLevel(); + HumanRewritingFlags humanRewritingFlags = libraryFlags.get(level); + HumanRewritingFlags.Builder builder = + humanRewritingFlags.newBuilder(app.dexItemFactory(), app.options.reporter, origin); + DexItemFactory itemFactory = app.dexItemFactory(); + + // TODO(b/177977763): This is only a workaround rewriting invokes of j.u.Arrays.deepEquals0 + // to j.u.DesugarArrays.deepEquals0. + DexString name = itemFactory.createString("deepEquals0"); + DexProto proto = + itemFactory.createProto( + itemFactory.booleanType, itemFactory.objectType, itemFactory.objectType); + DexMethod source = + itemFactory.createMethod(itemFactory.createType(itemFactory.arraysDescriptor), proto, name); + DexType target = itemFactory.createType("Ljava/util/DesugarArrays;"); + builder.putRetargetCoreLibMember(source, target); + + // TODO(b/181629049): This is only a workaround rewriting invokes of + // j.u.TimeZone.getTimeZone taking a java.time.ZoneId. + 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.createType("Ljava/util/DesugarTimeZone;"); + builder.putRetargetCoreLibMember(source, target); + + libraryFlags.put(level, builder.build()); + } + + private DirectMappedDexApplication readApp(Path androidLib, InternalOptions options) + throws IOException { + AndroidApp androidApp = AndroidApp.builder().addLibraryFile(androidLib).build(); + ApplicationReader applicationReader = + new ApplicationReader(androidApp, options, Timing.empty()); + ExecutorService executorService = ThreadUtils.getExecutorService(options); + return applicationReader.read(executorService).toDirect(); + } + + private Int2ObjectArrayMap<HumanRewritingFlags> convertRewritingFlagMap( + Int2ObjectMap<LegacyRewritingFlags> libFlags, DexApplication app, Origin origin) { + Int2ObjectArrayMap<HumanRewritingFlags> map = new Int2ObjectArrayMap<>(); + libFlags.forEach((key, flags) -> map.put((int) key, convertRewritingFlags(flags, app, origin))); + return map; + } + + private HumanRewritingFlags convertRewritingFlags( + LegacyRewritingFlags flags, DexApplication app, Origin origin) { + HumanRewritingFlags.Builder builder = + HumanRewritingFlags.builder(app.dexItemFactory(), app.options.reporter, origin); + + flags.getRewritePrefix().forEach(builder::putRewritePrefix); + flags.getEmulateLibraryInterface().forEach(builder::putEmulateLibraryInterface); + flags.getBackportCoreLibraryMember().forEach(builder::putBackportCoreLibraryMember); + flags.getCustomConversions().forEach(builder::putCustomConversion); + flags.getDontRetargetLibMember().forEach(builder::addDontRetargetLibMember); + flags.getWrapperConversions().forEach(builder::addWrapperConversion); + + flags + .getRetargetCoreLibMember() + .forEach((name, typeMap) -> convertRetargetCoreLibMember(builder, app, name, typeMap)); + flags + .getDontRewriteInvocation() + .forEach(pair -> convertDontRewriteInvocation(builder, app, pair)); + + return builder.build(); + } + + private void convertDontRewriteInvocation( + HumanRewritingFlags.Builder builder, DexApplication app, Pair<DexType, DexString> pair) { + DexClass dexClass = app.definitionFor(pair.getFirst()); + assert dexClass != null; + List<DexClassAndMethod> methodsWithName = findMethodsWithName(pair.getSecond(), dexClass); + for (DexClassAndMethod dexClassAndMethod : methodsWithName) { + builder.addDontRewriteInvocation(dexClassAndMethod.getReference()); + } + } + + private void convertRetargetCoreLibMember( + HumanRewritingFlags.Builder builder, + DexApplication app, + DexString name, + Map<DexType, DexType> typeMap) { + typeMap.forEach( + (type, rewrittenType) -> { + DexClass dexClass = app.definitionFor(type); + assert dexClass != null; + List<DexClassAndMethod> methodsWithName = findMethodsWithName(name, dexClass); + for (DexClassAndMethod dexClassAndMethod : methodsWithName) { + builder.putRetargetCoreLibMember(dexClassAndMethod.getReference(), rewrittenType); + } + }); + } + + private List<DexClassAndMethod> findMethodsWithName(DexString methodName, DexClass clazz) { + List<DexClassAndMethod> found = new ArrayList<>(); + clazz.forEachClassMethodMatching(definition -> definition.getName() == methodName, found::add); + assert !found.isEmpty() + : "Should have found a method (library specifications) for " + + clazz.toSourceString() + + "." + + methodName + + ". Maybe the library used for the compilation should be newer."; + return found; + } + + private HumanTopLevelFlags convertTopLevelFlags(LegacyTopLevelFlags topLevelFlags) { + return HumanTopLevelFlags.builder() + .setDesugaredLibraryIdentifier(topLevelFlags.getIdentifier()) + .setExtraKeepRules(topLevelFlags.getExtraKeepRules()) + .setJsonSource(topLevelFlags.getJsonSource()) + .setRequiredCompilationAPILevel(topLevelFlags.getRequiredCompilationAPILevel()) + .setSupportAllCallbacksFromLibrary(topLevelFlags.supportAllCallbacksFromLibrary()) + .setSynthesizedLibraryClassesPackagePrefix( + topLevelFlags.getSynthesizedLibraryClassesPackagePrefix()) + .build(); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/SpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/SpecificationConverter.java new file mode 100644 index 0000000..85a8c8b --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/SpecificationConverter.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.desugaredlibrary.specificationconversion; + +import com.android.tools.r8.StringConsumer; +import com.android.tools.r8.StringResource; +import java.io.IOException; +import java.nio.file.Path; + +public interface SpecificationConverter { + + void convertAllAPILevels( + StringResource inputSpecification, Path androidLib, StringConsumer output) throws IOException; +}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java index a4800c3..616cc07 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
@@ -481,9 +481,7 @@ ImmutableList.of( new CfInitClass(iface.getType()), new CfStackInstruction(Opcode.Pop), - new CfReturnVoid()), - ImmutableList.of(), - ImmutableList.of()); + new CfReturnVoid())); } DexEncodedField clinitField = ensureStaticClinitFieldToTriggerInterfaceInitialization(iface); @@ -497,9 +495,7 @@ isWide ? new CfStackInstruction(Opcode.Pop2) : new CfStackInstruction(Opcode.Pop), - new CfReturnVoid()), - ImmutableList.of(), - ImmutableList.of()); + new CfReturnVoid())); }); }
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 d612c5f..35e919e 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
@@ -39,15 +39,14 @@ import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason; import com.android.tools.r8.ir.optimize.Inliner.Reason; import com.android.tools.r8.ir.optimize.Inliner.RetryAction; -import com.android.tools.r8.ir.optimize.info.OptimizationFeedback; +import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider; import com.android.tools.r8.ir.optimize.inliner.InliningReasonStrategy; import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter; -import com.android.tools.r8.logging.Log; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.shaking.MainDexInfo; import com.android.tools.r8.synthesis.SyntheticItems; import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.InternalOptions.InlinerOptions; -import com.android.tools.r8.utils.Timing; import com.google.common.collect.Sets; import java.util.ArrayList; import java.util.BitSet; @@ -57,8 +56,8 @@ public final class DefaultInliningOracle implements InliningOracle, InliningStrategy { private final AppView<AppInfoWithLiveness> appView; - private final Inliner inliner; private final InlinerOptions inlinerOptions; + private final MainDexInfo mainDexInfo; private final ProgramMethod method; private final MethodProcessor methodProcessor; private final InliningReasonStrategy reasonStrategy; @@ -66,21 +65,25 @@ DefaultInliningOracle( AppView<AppInfoWithLiveness> appView, - Inliner inliner, InliningReasonStrategy inliningReasonStrategy, ProgramMethod method, MethodProcessor methodProcessor, int inliningInstructionAllowance) { this.appView = appView; - this.inliner = inliner; this.inlinerOptions = appView.options().inlinerOptions(); this.reasonStrategy = inliningReasonStrategy; + this.mainDexInfo = appView.appInfo().getMainDexInfo(); this.method = method; this.methodProcessor = methodProcessor; this.instructionAllowance = inliningInstructionAllowance; } @Override + public AppView<AppInfoWithLiveness> appView() { + return appView; + } + + @Override public boolean isForcedInliningOracle() { return false; } @@ -190,19 +193,18 @@ // Don't inline code with references beyond root main dex classes into a root main dex class. // If we do this it can increase the size of the main dex dependent classes. if (reason != Reason.FORCE - && inliner.mainDexInfo.disallowInliningIntoContext( + && mainDexInfo.disallowInliningIntoContext( appView, method, singleTarget, appView.getSyntheticItems())) { whyAreYouNotInliningReporter.reportInlineeRefersToClassesNotInMainDex(); return false; } assert reason != Reason.FORCE - || !inliner.mainDexInfo.disallowInliningIntoContext( + || !mainDexInfo.disallowInliningIntoContext( appView, method, singleTarget, appView.getSyntheticItems()); return true; } - private boolean satisfiesRequirementsForSimpleInlining( - InvokeMethod invoke, ProgramMethod target) { + public boolean satisfiesRequirementsForSimpleInlining(InvokeMethod invoke, ProgramMethod target) { // If we are looking for a simple method, only inline if actually simple. Code code = target.getDefinition().getCode(); int instructionLimit = @@ -252,17 +254,19 @@ @Override public InlineResult computeInlining( + IRCode code, InvokeMethod invoke, SingleResolutionResult resolutionResult, ProgramMethod singleTarget, ProgramMethod context, ClassInitializationAnalysis classInitializationAnalysis, + InliningIRProvider inliningIRProvider, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) { if (isSingleTargetInvalid(invoke, singleTarget, whyAreYouNotInliningReporter)) { return null; } - if (inliner.neverInline(invoke, resolutionResult, singleTarget, whyAreYouNotInliningReporter)) { + if (neverInline(invoke, resolutionResult, singleTarget, whyAreYouNotInliningReporter)) { if (singleTarget.getDefinition().getOptimizationInfo().forceInline()) { throw new Unreachable( "Unexpected attempt to force inline method `" @@ -275,7 +279,7 @@ } Reason reason = - reasonStrategy.computeInliningReason(invoke, singleTarget, context, methodProcessor); + reasonStrategy.computeInliningReason(invoke, singleTarget, context, this, methodProcessor); if (reason == Reason.NEVER) { return null; } @@ -300,8 +304,60 @@ return null; } - return invoke.computeInlining( - singleTarget, reason, this, classInitializationAnalysis, whyAreYouNotInliningReporter); + InlineAction action = + invoke.computeInlining( + singleTarget, reason, this, classInitializationAnalysis, whyAreYouNotInliningReporter); + if (action == null) { + return null; + } + + if (!setDowncastTypeIfNeeded(appView, action, invoke, singleTarget, context)) { + return null; + } + + // Make sure constructor inlining is legal. + if (singleTarget.getDefinition().isInstanceInitializer() + && !canInlineInstanceInitializer( + code, + invoke.asInvokeDirect(), + singleTarget, + inliningIRProvider, + whyAreYouNotInliningReporter)) { + return null; + } + + return action; + } + + private boolean neverInline( + InvokeMethod invoke, + SingleResolutionResult resolutionResult, + ProgramMethod singleTarget, + WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) { + AppInfoWithLiveness appInfo = appView.appInfo(); + DexMethod singleTargetReference = singleTarget.getReference(); + if (!appView.getKeepInfo(singleTarget).isInliningAllowed(appView.options())) { + whyAreYouNotInliningReporter.reportPinned(); + return true; + } + + if (appInfo.isNeverInlineMethod(singleTargetReference)) { + whyAreYouNotInliningReporter.reportMarkedAsNeverInline(); + return true; + } + + if (appInfo.noSideEffects.containsKey(invoke.getInvokedMethod()) + || appInfo.noSideEffects.containsKey(resolutionResult.getResolvedMethod().getReference()) + || appInfo.noSideEffects.containsKey(singleTargetReference)) { + return !singleTarget.getDefinition().getOptimizationInfo().forceInline(); + } + + if (!appView.testing().allowInliningOfSynthetics + && appView.getSyntheticItems().isSyntheticClass(singleTarget.getHolder())) { + return true; + } + + return false; } public InlineAction computeForInvokeWithReceiver( @@ -407,17 +463,6 @@ } @Override - public void ensureMethodProcessed( - ProgramMethod target, IRCode inlinee, OptimizationFeedback feedback) { - if (!target.getDefinition().isProcessed()) { - if (Log.ENABLED) { - Log.verbose(getClass(), "Forcing extra inline on " + target.toSourceString()); - } - inliner.performInlining(target, inlinee, feedback, methodProcessor, Timing.empty()); - } - } - - @Override public boolean allowInliningOfInvokeInInlinee( InlineAction action, int inliningDepth, @@ -440,9 +485,14 @@ @Override public boolean canInlineInstanceInitializer( IRCode code, - IRCode inlinee, InvokeDirect invoke, + ProgramMethod singleTarget, + InliningIRProvider inliningIRProvider, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) { + boolean removeInnerFramesIfThrowingNpe = false; + IRCode inlinee = + inliningIRProvider.getInliningIR(invoke, singleTarget, removeInnerFramesIfThrowingNpe); + // In the Java VM Specification section "4.10.2.4. Instance Initialization Methods and // Newly Created Objects" it says: // @@ -453,13 +503,14 @@ // Allow inlining a constructor into a constructor of the same class, as the constructor code // is expected to adhere to the VM specification. DexType callerMethodHolder = method.getHolderType(); - DexType calleeMethodHolder = inlinee.method().getHolderType(); + DexType calleeMethodHolder = singleTarget.getHolderType(); // Forwarding constructor calls that target a constructor in the same class can always be // inlined. if (method.getDefinition().isInstanceInitializer() && callerMethodHolder == calleeMethodHolder && invoke.getReceiver() == code.getThis()) { + inliningIRProvider.cacheInliningIR(invoke, inlinee); return true; } @@ -548,6 +599,7 @@ } inlinee.returnMarkingColor(markingColor); + inliningIRProvider.cacheInliningIR(invoke, inlinee); return true; }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java index 0e26fa3..493b052 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -17,7 +17,7 @@ import com.android.tools.r8.ir.optimize.Inliner.InlineResult; import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason; import com.android.tools.r8.ir.optimize.Inliner.Reason; -import com.android.tools.r8.ir.optimize.info.OptimizationFeedback; +import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider; import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter; import com.android.tools.r8.shaking.AppInfoWithLiveness; import java.util.Map; @@ -38,6 +38,11 @@ } @Override + public AppView<AppInfoWithLiveness> appView() { + return appView; + } + + @Override public boolean isForcedInliningOracle() { return true; } @@ -63,13 +68,22 @@ @Override public InlineResult computeInlining( + IRCode code, InvokeMethod invoke, SingleResolutionResult resolutionResult, ProgramMethod singleTarget, ProgramMethod context, ClassInitializationAnalysis classInitializationAnalysis, + InliningIRProvider inliningIRProvider, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) { - return computeForInvoke(invoke, resolutionResult, whyAreYouNotInliningReporter); + InlineAction action = computeForInvoke(invoke, resolutionResult, whyAreYouNotInliningReporter); + if (action == null) { + return null; + } + if (!setDowncastTypeIfNeeded(appView, action, invoke, singleTarget, context)) { + return null; + } + return action; } private InlineAction computeForInvoke( @@ -87,13 +101,6 @@ } @Override - public void ensureMethodProcessed( - ProgramMethod target, IRCode inlinee, OptimizationFeedback feedback) { - // Do nothing. If the method is not yet processed, we still should - // be able to build IR for inlining, though. - } - - @Override public boolean allowInliningOfInvokeInInlinee( InlineAction action, int inliningDepth, @@ -105,8 +112,9 @@ @Override public boolean canInlineInstanceInitializer( IRCode code, - IRCode inlinee, InvokeDirect invoke, + ProgramMethod singleTarget, + InliningIRProvider inliningIRProvider, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) { return true; }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java index b4a067e..2c185bf 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -8,7 +8,6 @@ import static com.google.common.base.Predicates.not; import com.android.tools.r8.androidapi.AvailableApiExceptions; -import com.android.tools.r8.graph.AccessControl; import com.android.tools.r8.graph.AccessFlags; import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; @@ -61,6 +60,7 @@ import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.shaking.MainDexInfo; +import com.android.tools.r8.utils.ConsumerUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.InternalOptions.InlinerOptions; import com.android.tools.r8.utils.IteratorUtils; @@ -79,6 +79,9 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.function.Consumer; public class Inliner { @@ -91,6 +94,8 @@ // due to not being processed at the time of inlining. private final LongLivedProgramMethodSetBuilder<ProgramMethodSet> singleInlineCallers; + private final MultiCallerInliner multiCallerInliner; + // The set of methods that have been single caller inlined in the current wave. These need to be // pruned when the wave ends. private final Map<DexProgramClass, ProgramMethodSet> singleCallerInlinedMethodsInWave = @@ -108,6 +113,7 @@ this.converter = converter; this.lensCodeRewriter = lensCodeRewriter; this.mainDexInfo = appView.appInfo().getMainDexInfo(); + this.multiCallerInliner = new MultiCallerInliner(appView); this.singleInlineCallers = LongLivedProgramMethodSetBuilder.createConcurrentForIdentitySet(appView.graphLens()); availableApiExceptions = @@ -116,41 +122,6 @@ : null; } - boolean neverInline( - InvokeMethod invoke, - SingleResolutionResult resolutionResult, - ProgramMethod singleTarget, - WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) { - AppInfoWithLiveness appInfo = appView.appInfo(); - DexMethod singleTargetReference = singleTarget.getReference(); - if (!appView.getKeepInfo(singleTarget).isInliningAllowed(appView.options())) { - whyAreYouNotInliningReporter.reportPinned(); - return true; - } - - if (appInfo.isNeverInlineMethod(singleTargetReference)) { - whyAreYouNotInliningReporter.reportMarkedAsNeverInline(); - return true; - } - - if (appInfo.noSideEffects.containsKey(invoke.getInvokedMethod()) - || appInfo.noSideEffects.containsKey(resolutionResult.getResolvedMethod().getReference()) - || appInfo.noSideEffects.containsKey(singleTargetReference)) { - return !singleTarget.getDefinition().getOptimizationInfo().forceInline(); - } - - if (!appView.testing().allowInliningOfSynthetics - && appView.getSyntheticItems().isSyntheticClass(singleTarget.getHolder())) { - return true; - } - - return false; - } - - boolean isDoubleInliningEnabled(MethodProcessor methodProcessor) { - return methodProcessor.isPostMethodProcessor(); - } - private ConstraintWithTarget instructionAllowedForInlining( Instruction instruction, InliningConstraints inliningConstraints, ProgramMethod context) { ConstraintWithTarget result = instruction.inliningConstraint(inliningConstraints, context); @@ -203,16 +174,9 @@ return false; } - public void enqueueMethodsForReprocessing( - PostMethodProcessor.Builder postMethodProcessorBuilder) { - // The double inline callers are always rewritten up until the graph lens of the primary - // optimization pass, so we can safely merge them into the methods to reprocess (which may be - // rewritten with a newer graph lens). - postMethodProcessorBuilder - .getMethodsToReprocessBuilder() - .rewrittenWithLens(appView) - .merge(singleInlineCallers); - singleInlineCallers.clear(); + public void recordCallEdgesForMultiCallerInlining( + ProgramMethod method, IRCode code, MethodProcessor methodProcessor, Timing timing) { + multiCallerInliner.recordCallEdgesForMultiCallerInlining(method, code, methodProcessor, timing); } /** @@ -521,12 +485,14 @@ FORCE, // Inlinee is marked for forced inlining (bridge method or renamed constructor). ALWAYS, // Inlinee is marked for inlining due to alwaysinline directive. SINGLE_CALLER, // Inlinee has precisely one caller. - DUAL_CALLER, // Inlinee has precisely two callers. + // Inlinee has multiple callers and should not be inlined. Only used during the primary + // optimization pass. + MULTI_CALLER_CANDIDATE, SIMPLE, // Inlinee has simple code suitable for inlining. NEVER; // Inlinee must not be inlined. public boolean mustBeInlined() { - // TODO(118734615): Include SINGLE_CALLER and DUAL_CALLER here as well? + // TODO(118734615): Include SINGLE_CALLER here as well? return this == FORCE || this == ALWAYS; } } @@ -551,6 +517,8 @@ private boolean shouldSynthesizeInitClass; private boolean shouldSynthesizeNullCheckForReceiver; + private DexProgramClass downcastClass; + InlineAction(ProgramMethod target, Invoke invoke, Reason reason) { this.target = target; this.invoke = invoke; @@ -562,6 +530,14 @@ return this; } + DexProgramClass getDowncastClass() { + return downcastClass; + } + + void setDowncastClass(DexProgramClass downcastClass) { + this.downcastClass = downcastClass; + } + void setShouldSynthesizeInitClass() { assert !shouldSynthesizeNullCheckForReceiver; shouldSynthesizeInitClass = true; @@ -850,6 +826,7 @@ IRCode code, Map<? extends InvokeMethod, InliningInfo> invokesToInline, InliningIRProvider inliningIRProvider, + MethodProcessor methodProcessor, Timing timing) { ForcedInliningOracle oracle = new ForcedInliningOracle(appView, method, invokesToInline); performInliningImpl( @@ -859,6 +836,7 @@ code, OptimizationFeedbackIgnore.getInstance(), inliningIRProvider, + methodProcessor, timing); } @@ -894,7 +872,8 @@ InliningIRProvider inliningIRProvider = new InliningIRProvider(appView, method, code, methodProcessor); assert inliningIRProvider.verifyIRCacheIsEmpty(); - performInliningImpl(oracle, oracle, method, code, feedback, inliningIRProvider, timing); + performInliningImpl( + oracle, oracle, method, code, feedback, inliningIRProvider, methodProcessor, timing); } public InliningReasonStrategy createDefaultInliningReasonStrategy( @@ -924,7 +903,6 @@ InliningReasonStrategy inliningReasonStrategy) { return new DefaultInliningOracle( appView, - this, inliningReasonStrategy, method, methodProcessor, @@ -938,6 +916,7 @@ IRCode code, OptimizationFeedback feedback, InliningIRProvider inliningIRProvider, + MethodProcessor methodProcessor, Timing timing) { AssumeRemover assumeRemover = new AssumeRemover(appView, code); Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet(); @@ -990,11 +969,13 @@ : WhyAreYouNotInliningReporter.createFor(singleTarget, appView, context); InlineResult inlineResult = oracle.computeInlining( + code, invoke, resolutionResult, singleTarget, context, classInitializationAnalysis, + inliningIRProvider, whyAreYouNotInliningReporter); if (inlineResult == null) { assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag(); @@ -1007,11 +988,8 @@ } InlineAction action = inlineResult.asInlineAction(); - - DexProgramClass downcastClass = getDowncastTypeIfNeeded(strategy, invoke, singleTarget); - if (downcastClass != null - && AccessControl.isClassAccessible(downcastClass, context, appView) - .isPossiblyFalse()) { + if (action.reason == Reason.MULTI_CALLER_CANDIDATE) { + assert methodProcessor.isPrimaryMethodProcessor(); continue; } @@ -1036,18 +1014,8 @@ continue; } - // If this code did not go through the full pipeline, apply inlining to make sure - // that force inline targets get processed. - strategy.ensureMethodProcessed(singleTarget, inlinee.code, feedback); - - // Make sure constructor inlining is legal. - assert !singleTargetMethod.isClassInitializer(); - if (singleTargetMethod.isInstanceInitializer() - && !strategy.canInlineInstanceInitializer( - code, inlinee.code, invoke.asInvokeDirect(), whyAreYouNotInliningReporter)) { - assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag(); - continue; - } + // Verify this code went through the full pipeline. + assert singleTarget.getDefinition().isProcessed(); // Mark AssumeDynamicType instruction for the out-value for removal, if any. Value outValue = invoke.outValue(); @@ -1062,7 +1030,12 @@ iterator.previous(); strategy.markInlined(inlinee); iterator.inlineInvoke( - appView, code, inlinee.code, blockIterator, blocksToRemove, downcastClass); + appView, + code, + inlinee.code, + blockIterator, + blocksToRemove, + action.getDowncastClass()); if (inlinee.reason == Reason.SINGLE_CALLER) { assert converter.isInWave(); @@ -1077,7 +1050,7 @@ } classInitializationAnalysis.notifyCodeHasChanged(); - postProcessInlineeBlocks(code, inlinee.code, blockIterator, block, timing); + postProcessInlineeBlocks(code, blockIterator, block, blocksToRemove, timing); // The synthetic and bridge flags are maintained only if the inlinee has also these flags. if (context.getDefinition().isBridge() && !inlinee.code.method().isBridge()) { @@ -1180,33 +1153,38 @@ /** Applies member rebinding to the inlinee and inserts assume instructions. */ private void postProcessInlineeBlocks( IRCode code, - IRCode inlinee, BasicBlockIterator blockIterator, BasicBlock block, + Set<BasicBlock> blocksToRemove, Timing timing) { BasicBlock state = IteratorUtils.peekNext(blockIterator); - Set<BasicBlock> inlineeBlocks = SetUtils.newIdentityHashSet(inlinee.blocks); + Set<BasicBlock> inlineeBlocks = Sets.newIdentityHashSet(); // Run member value propagation on the inlinee blocks. - rewindBlockIteratorToFirstInlineeBlock(blockIterator, block); - applyMemberValuePropagationToInlinee(code, blockIterator, block, inlineeBlocks); + rewindBlockIterator( + blockIterator, + block, + inlineeBlock -> { + if (!blocksToRemove.contains(inlineeBlock)) { + inlineeBlocks.add(inlineeBlock); + } + }); + applyMemberValuePropagationToInlinee(code, blockIterator, inlineeBlocks); // Add non-null IRs only to the inlinee blocks. - insertAssumeInstructions(code, blockIterator, block, inlineeBlocks, timing); + rewindBlockIterator(blockIterator, block); + insertAssumeInstructions(code, blockIterator, inlineeBlocks, timing); // Restore the old state of the iterator. - rewindBlockIteratorToFirstInlineeBlock(blockIterator, state); - // TODO(b/72693244): need a test where refined env in inlinee affects the caller. + rewindBlockIterator(blockIterator, state); } private void insertAssumeInstructions( IRCode code, BasicBlockIterator blockIterator, - BasicBlock block, Set<BasicBlock> inlineeBlocks, Timing timing) { - rewindBlockIteratorToFirstInlineeBlock(blockIterator, block); new AssumeInserter(appView) .insertAssumeInstructionsInBlocks(code, blockIterator, inlineeBlocks::contains, timing); assert !blockIterator.hasNext(); @@ -1215,9 +1193,7 @@ private void applyMemberValuePropagationToInlinee( IRCode code, ListIterator<BasicBlock> blockIterator, - BasicBlock block, Set<BasicBlock> inlineeBlocks) { - assert IteratorUtils.peekNext(blockIterator) == block; Set<Value> affectedValues = Sets.newIdentityHashSet(); new MemberValuePropagation(appView) .run(code, blockIterator, affectedValues, inlineeBlocks::contains); @@ -1227,13 +1203,23 @@ assert !blockIterator.hasNext(); } - private void rewindBlockIteratorToFirstInlineeBlock( - ListIterator<BasicBlock> blockIterator, BasicBlock firstInlineeBlock) { + private void rewindBlockIterator(ListIterator<BasicBlock> blockIterator, BasicBlock callerBlock) { + rewindBlockIterator(blockIterator, callerBlock, ConsumerUtils.emptyConsumer()); + } + + private void rewindBlockIterator( + ListIterator<BasicBlock> blockIterator, + BasicBlock callerBlock, + Consumer<BasicBlock> consumer) { // Move the cursor back to where the first inlinee block was added. - while (blockIterator.hasPrevious() && blockIterator.previous() != firstInlineeBlock) { - // Do nothing. + while (blockIterator.hasPrevious()) { + BasicBlock previous = blockIterator.previous(); + if (previous == callerBlock) { + break; + } + consumer.accept(previous); } - assert IteratorUtils.peekNext(blockIterator) == firstInlineeBlock; + assert IteratorUtils.peekNext(blockIterator) == callerBlock; } public void enqueueMethodForReprocessing(ProgramMethod method) { @@ -1242,6 +1228,7 @@ public void onMethodPruned(ProgramMethod method) { onMethodCodePruned(method); + multiCallerInliner.onMethodPruned(method); } public void onMethodCodePruned(ProgramMethod method) { @@ -1281,6 +1268,24 @@ singleCallerInlinedMethodsInWave.clear(); } + public void onLastWaveDone( + PostMethodProcessor.Builder postMethodProcessorBuilder, + ExecutorService executorService, + Timing timing) + throws ExecutionException { + postMethodProcessorBuilder + .getMethodsToReprocessBuilder() + .rewrittenWithLens(appView) + .merge( + singleInlineCallers + .rewrittenWithLens(appView) + .removeIf( + appView, + method -> method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite())); + singleInlineCallers.clear(); + multiCallerInliner.onLastWaveDone(postMethodProcessorBuilder, executorService, timing); + } + public static boolean verifyAllSingleCallerMethodsHaveBeenPruned( AppView<AppInfoWithLiveness> appView) { for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) { @@ -1298,4 +1303,16 @@ assert singleCallerInlinedPrunedMethodsForTesting.contains(method); return true; } + + public static boolean verifyAllMultiCallerInlinedMethodsHaveBeenPruned(AppView<?> appView) { + for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) { + for (DexEncodedMethod method : clazz.methods()) { + if (method.hasCode() && method.getOptimizationInfo().isMultiCallerMethod()) { + // TODO(b/142300882): Ensure soundness of multi caller inlining. + // assert false; + } + } + } + return true; + } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java index f839991..ddc31c2 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
@@ -7,9 +7,11 @@ import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis; +import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.InvokeMethod; import com.android.tools.r8.ir.optimize.Inliner.InlineResult; import com.android.tools.r8.ir.optimize.Inliner.Reason; +import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider; import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter; /** @@ -30,10 +32,12 @@ WhyAreYouNotInliningReporter whyAreYouNotInliningReporter); InlineResult computeInlining( + IRCode code, InvokeMethod invoke, SingleResolutionResult resolutionResult, ProgramMethod singleTarget, ProgramMethod context, ClassInitializationAnalysis classInitializationAnalysis, + InliningIRProvider inliningIRProvider, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java index b6d33ea..564fcf8 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
@@ -4,19 +4,26 @@ package com.android.tools.r8.ir.optimize; +import com.android.tools.r8.graph.AccessControl; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.ProgramMethod; 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.InvokeDirect; import com.android.tools.r8.ir.code.InvokeMethod; +import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.optimize.Inliner.InlineAction; import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason; -import com.android.tools.r8.ir.optimize.info.OptimizationFeedback; +import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider; import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter; +import com.android.tools.r8.shaking.AppInfoWithLiveness; interface InliningStrategy { + AppView<AppInfoWithLiveness> appView(); + boolean allowInliningOfInvokeInInlinee( InlineAction action, int inliningDepth, @@ -24,8 +31,9 @@ boolean canInlineInstanceInitializer( IRCode code, - IRCode inlinee, InvokeDirect invoke, + ProgramMethod singleTarget, + InliningIRProvider inliningIRProvider, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter); /** Return true if there is still budget for inlining into this method. */ @@ -47,7 +55,41 @@ /** Inform the strategy that the inlinee has been inlined. */ void markInlined(InlineeWithReason inlinee); - void ensureMethodProcessed(ProgramMethod target, IRCode inlinee, OptimizationFeedback feedback); + default boolean setDowncastTypeIfNeeded( + AppView<AppInfoWithLiveness> appView, + InlineAction action, + InvokeMethod invoke, + ProgramMethod singleTarget, + ProgramMethod context) { + DexProgramClass downcastClass = getDowncastTypeIfNeeded(invoke, singleTarget); + if (downcastClass != null) { + if (AccessControl.isClassAccessible(downcastClass, context, appView).isPossiblyFalse()) { + return false; + } + action.setDowncastClass(downcastClass); + } + return true; + } + + default DexProgramClass getDowncastTypeIfNeeded(InvokeMethod invoke, ProgramMethod target) { + if (invoke.isInvokeMethodWithReceiver()) { + // If the invoke has a receiver but the actual type of the receiver is different from the + // computed target holder, inlining requires a downcast of the receiver. In case we don't know + // the exact type of the receiver we use the static type of the receiver. + Value receiver = invoke.asInvokeMethodWithReceiver().getReceiver(); + if (!receiver.getType().isClassType()) { + return target.getHolder(); + } + + ClassTypeElement receiverType = + getReceiverTypeOrDefault(invoke, receiver.getType().asClassType()); + ClassTypeElement targetType = target.getHolderType().toTypeElement(appView()).asClassType(); + if (!receiverType.lessThanOrEqualUpToNullability(targetType, appView())) { + return target.getHolder(); + } + } + return null; + } ClassTypeElement getReceiverTypeOrDefault(InvokeMethod invoke, ClassTypeElement defaultValue); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java new file mode 100644 index 0000000..afe3547 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.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.optimize; + +import static com.android.tools.r8.ir.optimize.info.OptimizationFeedback.getSimpleFeedback; +import static com.android.tools.r8.utils.MapUtils.ignoreKey; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.GraphLens; +import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis; +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.conversion.MethodProcessor; +import com.android.tools.r8.ir.conversion.PostMethodProcessor; +import com.android.tools.r8.ir.optimize.Inliner.InlineAction; +import com.android.tools.r8.ir.optimize.Inliner.InlineResult; +import com.android.tools.r8.ir.optimize.Inliner.Reason; +import com.android.tools.r8.ir.optimize.inliner.FixedInliningReasonStrategy; +import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider; +import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter; +import com.android.tools.r8.ir.optimize.inliner.multicallerinliner.MultiCallerInlinerCallGraph; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.IntBox; +import com.android.tools.r8.utils.LazyBox; +import com.android.tools.r8.utils.Timing; +import com.android.tools.r8.utils.collections.LongLivedProgramMethodSetBuilder; +import com.android.tools.r8.utils.collections.ProgramMethodMap; +import com.android.tools.r8.utils.collections.ProgramMethodMultiset; +import com.android.tools.r8.utils.collections.ProgramMethodSet; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; + +// TODO(b/142300882): If a method is selected for multi caller inlining, then if it is reprocessed +// and we inline into it, we should potentially disable multi caller inlining for that method (or +// we should disallow inlining into it). +public class MultiCallerInliner { + + private final AppView<AppInfoWithLiveness> appView; + + // Maps each method to the set of inlineable call sites targeting the method, or Optional.empty() + // if we have stopped tracking the inlineable call sites. + private final ProgramMethodMap<Optional<ProgramMethodMultiset>> multiInlineCallEdges = + ProgramMethodMap.createConcurrent(); + + private final int[] multiCallerInliningInstructionLimits; + + MultiCallerInliner(AppView<AppInfoWithLiveness> appView) { + this.appView = appView; + this.multiCallerInliningInstructionLimits = + appView.options().inlinerOptions().multiCallerInliningInstructionLimits; + } + + void recordCallEdgesForMultiCallerInlining( + ProgramMethod method, IRCode code, MethodProcessor methodProcessor, Timing timing) { + if (!methodProcessor.isPrimaryMethodProcessor()) { + return; + } + + timing.time( + "Multi caller inliner: Record call edges", + () -> recordCallEdgesForMultiCallerInlining(method, code, methodProcessor)); + } + + private void recordCallEdgesForMultiCallerInlining( + ProgramMethod method, IRCode code, MethodProcessor methodProcessor) { + LazyBox<DefaultInliningOracle> lazyOracle = + new LazyBox<>( + () -> { + int inliningInstructionAllowance = Integer.MAX_VALUE; + return new DefaultInliningOracle( + appView, + new FixedInliningReasonStrategy(Reason.MULTI_CALLER_CANDIDATE), + method, + methodProcessor, + inliningInstructionAllowance); + }); + for (InvokeMethod invoke : code.<InvokeMethod>instructions(Instruction::isInvokeMethod)) { + // Don't attempt to multi caller inline constructors. To determine if a constructor is + // eligible for inlining, the inliner builds IR for the constructor, which we want to avoid + // here for build speed. + if (invoke.isInvokeConstructor(appView.dexItemFactory())) { + continue; + } + + SingleResolutionResult resolutionResult = + appView + .appInfo() + .resolveMethod(invoke.getInvokedMethod(), invoke.getInterfaceBit()) + .asSingleResolution(); + if (resolutionResult == null + || resolutionResult.isAccessibleFrom(method, appView).isPossiblyFalse()) { + continue; + } + + ProgramMethod singleTarget = invoke.lookupSingleProgramTarget(appView, method); + if (singleTarget == null + || !methodProcessor.getCallSiteInformation().isMultiCallerInlineCandidate(singleTarget)) { + continue; + } + + InlineResult inlineResult = + lazyOracle + .computeIfAbsent() + .computeInlining( + code, + invoke, + resolutionResult, + singleTarget, + method, + ClassInitializationAnalysis.trivial(), + InliningIRProvider.getThrowingInstance(), + NopWhyAreYouNotInliningReporter.getInstance()); + if (inlineResult == null || inlineResult.isRetryAction()) { + stopTrackingCallSitesForMethod(singleTarget); + continue; + } + + InlineAction action = inlineResult.asInlineAction(); + assert action.reason == Reason.MULTI_CALLER_CANDIDATE; + recordCallEdgeForMultiCallerInlining(method, singleTarget, methodProcessor); + } + } + + void recordCallEdgeForMultiCallerInlining( + ProgramMethod method, ProgramMethod singleTarget, MethodProcessor methodProcessor) { + Optional<ProgramMethodMultiset> value = + multiInlineCallEdges.computeIfAbsent( + singleTarget, ignoreKey(() -> Optional.of(ProgramMethodMultiset.createConcurrent()))); + + // If we are not tracking the callers for the single target, then just return. In this case, we + // have previously found that the single target is ineligible for multi caller inlining. + if (!value.isPresent()) { + return; + } + + // Record that we have seen a call site that dispatched to the single target which is eligible + // for inlining. + ProgramMethodMultiset callers = value.get(); + callers.add(method); + + // We track up to n call sites, where n is the size of multiCallerInliningInstructionLimits. + if (callers.size() > multiCallerInliningInstructionLimits.length) { + stopTrackingCallSitesForMethodIfDefinitelyIneligibleForMultiCallerInlining( + method, singleTarget, methodProcessor, callers); + } + } + + private void stopTrackingCallSitesForMethodIfDefinitelyIneligibleForMultiCallerInlining( + ProgramMethod method, + ProgramMethod singleTarget, + MethodProcessor methodProcessor, + ProgramMethodMultiset callers) { + // First remove the call sites that no longer exist due to single caller inlining. + callers.removeIf(caller -> caller.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite()); + + // Then compute the minimum number of call sites that are guaranteed to be present at the end + // of the primary optimization pass. + IntBox minimumCallers = new IntBox(); + callers.forEachEntry( + (caller, calls) -> { + // If these call sites are inside a method that has a single caller, then the call sites + // could potentially disappear as a result of single caller inlining, so don't include + // them. + if (!methodProcessor.getCallSiteInformation().hasSingleCallSite(caller)) { + minimumCallers.increment(calls); + } + }); + + // If the threshold is definitely exceeded, then mark as ineligible for multi caller inlining. + if (minimumCallers.get() > multiCallerInliningInstructionLimits.length) { + stopTrackingCallSitesForMethod(singleTarget); + } + } + + private void stopTrackingCallSitesForMethod(ProgramMethod method) { + multiInlineCallEdges.put(method, Optional.empty()); + } + + void onMethodPruned(ProgramMethod method) { + assert !multiInlineCallEdges.containsKey(method); + } + + public void onLastWaveDone( + PostMethodProcessor.Builder postMethodProcessorBuilder, + ExecutorService executorService, + Timing timing) + throws ExecutionException { + timing.begin("Multi caller inliner"); + MultiCallerInlinerCallGraph callGraph = + timing.time( + "Call graph construction", + () -> MultiCallerInlinerCallGraph.builder(appView).build(executorService)); + LongLivedProgramMethodSetBuilder<ProgramMethodSet> multiInlineCallers = + timing.time("Needs inlining analysis", () -> computeMultiInlineCallerMethods(callGraph)); + postMethodProcessorBuilder + .getMethodsToReprocessBuilder() + .rewrittenWithLens(appView) + .merge(multiInlineCallers); + timing.end(); + } + + private LongLivedProgramMethodSetBuilder<ProgramMethodSet> computeMultiInlineCallerMethods( + MultiCallerInlinerCallGraph callGraph) { + // The multi inline callers are always rewritten up until the graph lens of the primary + // optimization pass, so we can safely merge them into the methods to reprocess (which may be + // rewritten with a newer graph lens). + GraphLens currentGraphLens = appView.graphLens(); + LongLivedProgramMethodSetBuilder<ProgramMethodSet> multiInlineCallers = + LongLivedProgramMethodSetBuilder.createForIdentitySet(currentGraphLens); + multiInlineCallEdges.forEach( + (singleTarget, value) -> { + if (singleTarget.getDefinition().isLibraryMethodOverride().isPossiblyTrue()) { + return; + } + + if (!value.isPresent()) { + return; + } + + if (singleTarget.getDefinition().isInstance() + && !appView.appInfo().isInstantiatedDirectlyOrIndirectly(singleTarget.getHolder())) { + return; + } + + ProgramMethodMultiset callers = value.get(); + callers.removeIf( + method -> method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite()); + if (callers.size() == 0 || callers.size() > multiCallerInliningInstructionLimits.length) { + return; + } + + int numberOfCallSites = callGraph.getNode(singleTarget).getNumberOfCallSites(); + // TODO(b/142300882): The number of call sites according to the call graph should + // generally be >= the number of calls that the multi caller inliner has seen. This may + // not hold when calls are removed due to identical block prefix/suffix sharing, however. + // When this happens, the inliner may think that it can inline all call sites found in + // the call graph, although this may not actually be true. + if (numberOfCallSites < callers.size()) { + return; + } + if (callers.size() < numberOfCallSites) { + // Can't inline all call sites. + return; + } + + int multiCallerInliningInstructionLimit = + multiCallerInliningInstructionLimits[callers.size() - 1]; + if (!singleTarget + .getDefinition() + .getCode() + .estimatedSizeForInliningAtMost(multiCallerInliningInstructionLimit)) { + // Multi caller inlining could lead to a size increase according to the heuristic. + return; + } + callers.forEachEntry( + (caller, count) -> { + if (!caller.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite()) { + multiInlineCallers.add(caller, currentGraphLens); + } + }); + getSimpleFeedback().setMultiCallerMethod(singleTarget); + }); + multiInlineCallEdges.clear(); + return multiInlineCallers; + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java index b3f74c5..0e3dfdb 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
@@ -20,6 +20,7 @@ import com.android.tools.r8.ir.analysis.value.SingleValue; import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState; import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.FieldGet; import com.android.tools.r8.ir.code.FieldInstruction; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.InitClass; @@ -91,6 +92,10 @@ private interface FieldValue { + default ExistingValue asExistingValue() { + return null; + } + void eliminateRedundantRead(InstructionListIterator it, FieldInstruction redundant); } @@ -103,6 +108,11 @@ } @Override + public ExistingValue asExistingValue() { + return this; + } + + @Override public void eliminateRedundantRead(InstructionListIterator it, FieldInstruction redundant) { affectedValues.addAll(redundant.value().affectedValues()); redundant.value().replaceUsers(value); @@ -110,6 +120,10 @@ value.uniquePhiUsers().forEach(Phi::removeTrivialPhi); } + public Value getValue() { + return value; + } + @Override public String toString() { return "ExistingValue(v" + value.getNumber() + ")"; @@ -411,7 +425,7 @@ FieldAndObject fieldAndObject = new FieldAndObject(field.getReference(), object); FieldValue replacement = activeState.getInstanceFieldValue(fieldAndObject); if (replacement != null) { - assumeRemover.markAssumeDynamicTypeUsersForRemoval(instanceGet.outValue()); + markAssumeDynamicTypeUsersForRemoval(instanceGet, replacement, assumeRemover); replacement.eliminateRedundantRead(it, instanceGet); return; } @@ -430,6 +444,21 @@ } } + private void markAssumeDynamicTypeUsersForRemoval( + FieldGet fieldGet, FieldValue replacement, AssumeRemover assumeRemover) { + ExistingValue existingValue = replacement.asExistingValue(); + if (existingValue == null + || !existingValue + .getValue() + .isDefinedByInstructionSatisfying( + definition -> + definition.isFieldGet() + && definition.asFieldGet().getField().getType() + == fieldGet.getField().getType())) { + assumeRemover.markAssumeDynamicTypeUsersForRemoval(fieldGet.outValue()); + } + } + private void handleInstancePut(InstancePut instancePut, DexClassAndField field) { // An instance-put instruction can potentially write the given field on all objects because of // aliases. @@ -480,7 +509,7 @@ FieldValue replacement = activeState.getStaticFieldValue(field.getReference()); if (replacement != null) { - assumeRemover.markAssumeDynamicTypeUsersForRemoval(staticGet.outValue()); + markAssumeDynamicTypeUsersForRemoval(staticGet, replacement, assumeRemover); replacement.eliminateRedundantRead(instructionIterator, staticGet); return; }
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 aede4f7..d317655 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
@@ -401,7 +401,7 @@ } inliner.performForcedInlining( - method, code, directMethodCalls, inliningIRProvider, Timing.empty()); + method, code, directMethodCalls, inliningIRProvider, methodProcessor, 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 @@ -450,7 +450,7 @@ } if (!directMethodCalls.isEmpty()) { inliner.performForcedInlining( - method, code, directMethodCalls, inliningIRProvider, Timing.empty()); + method, code, directMethodCalls, inliningIRProvider, methodProcessor, Timing.empty()); } } while (!directMethodCalls.isEmpty()); } @@ -504,7 +504,7 @@ if (!methodCallsOnInstance.isEmpty()) { inliner.performForcedInlining( - method, code, methodCallsOnInstance, inliningIRProvider, Timing.empty()); + method, code, methodCallsOnInstance, inliningIRProvider, methodProcessor, Timing.empty()); } else { assert indirectMethodCallsOnInstance.stream() .filter(method -> method.getDefinition().getOptimizationInfo().mayHaveSideEffects())
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java index 353d8b7..3875ddf 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -1154,12 +1154,15 @@ DexClass targetHolder = singleTarget.getHolder(); if (targetHolder.isProgramClass()) { if (targetHolder.isEnum() && singleTarget.getDefinition().isInstanceInitializer()) { - if (code.context().getHolder() == targetHolder && code.method().isClassInitializer()) { - // The enum instance initializer is allowed to be called only from the enum clinit. - return Reason.ELIGIBLE; - } else { + // The enum instance initializer is only allowed to be called from an initializer of the + // enum itself. + if (code.context().getHolder() != targetHolder || !code.method().isInitializer()) { return Reason.INVALID_INIT; } + if (code.method().isInstanceInitializer() && !invoke.getFirstArgument().isThis()) { + return Reason.INVALID_INIT; + } + return Reason.ELIGIBLE; } // Check if this is a checkNotNull() user. In this case, we can create a copy of the method
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java index 182b415..096c2cf 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
@@ -40,7 +40,6 @@ import com.android.tools.r8.utils.ConsumerUtils; import com.google.common.collect.ImmutableList; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Set; import org.objectweb.asm.Opcodes; @@ -271,13 +270,7 @@ int maxStack = 4; int maxLocals = 0; - return new CfCode( - sharedUtilityClassType, - maxStack, - maxLocals, - instructions, - Collections.emptyList(), - Collections.emptyList()); + return new CfCode(sharedUtilityClassType, maxStack, maxLocals, instructions); } private DexEncodedMethod createValuesMethod( @@ -325,9 +318,7 @@ Opcodes.INVOKESTATIC, dexItemFactory.javaLangSystemMethods.arraycopy, false), // return result new CfLoad(ValueType.OBJECT, resultLocalSlot), - new CfReturn(ValueType.OBJECT)), - Collections.emptyList(), - Collections.emptyList()); + new CfReturn(ValueType.OBJECT))); } private static DexProgramClass findDeterministicContextType(Set<DexProgramClass> contexts) {
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 1ce6568..09b1cc3 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
@@ -157,6 +157,11 @@ } @Override + public boolean isMultiCallerMethod() { + return false; + } + + @Override public boolean forceInline() { return false; }
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 b2d7be0..a4667c9 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
@@ -22,7 +22,7 @@ implements MemberOptimizationInfo<MutableMethodOptimizationInfo> { enum InlinePreference { - NeverInline, + MultiCallerInline, ForceInline, Default } @@ -84,6 +84,8 @@ public abstract BitSet getUnusedArguments(); + public abstract boolean isMultiCallerMethod(); + public abstract boolean forceInline(); public abstract boolean checksNullReceiverBeforeAnySideEffect();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java index 968cc66..62db1f4 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
@@ -468,6 +468,11 @@ } @Override + public boolean isMultiCallerMethod() { + return inlining == InlinePreference.MultiCallerInline; + } + + @Override public boolean forceInline() { return inlining == InlinePreference.ForceInline; } @@ -644,13 +649,18 @@ inlining = InlinePreference.ForceInline; } - // TODO(b/140214568): Should be package-private. - public void unsetForceInline() { - // For concurrent scenarios we should allow the flag to be already unset - assert inlining == InlinePreference.Default || inlining == InlinePreference.ForceInline; + void unsetForceInline() { inlining = InlinePreference.Default; } + void setMultiCallerMethod() { + if (inlining == InlinePreference.Default) { + inlining = InlinePreference.MultiCallerInline; + } else { + assert inlining == InlinePreference.ForceInline; + } + } + void markCheckNullReceiverBeforeAnySideEffect(boolean mark) { setFlag(CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG, mark); }
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 5fd4800..c6cd18d 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
@@ -67,6 +67,10 @@ } } + public void setMultiCallerMethod(ProgramMethod method) { + method.getDefinition().getMutableOptimizationInfo().setMultiCallerMethod(); + } + // METHOD OPTIMIZATION INFO. @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java index ef00071..3a9c668 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
@@ -9,8 +9,9 @@ import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.code.InvokeMethod; -import com.android.tools.r8.ir.conversion.CallSiteInformation; import com.android.tools.r8.ir.conversion.MethodProcessor; +import com.android.tools.r8.ir.conversion.callgraph.CallSiteInformation; +import com.android.tools.r8.ir.optimize.DefaultInliningOracle; import com.android.tools.r8.ir.optimize.Inliner.Reason; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.InternalOptions.InlinerOptions; @@ -33,6 +34,7 @@ InvokeMethod invoke, ProgramMethod target, ProgramMethod context, + DefaultInliningOracle oracle, MethodProcessor methodProcessor) { DexEncodedMethod targetMethod = target.getDefinition(); DexMethod targetReference = target.getReference(); @@ -54,9 +56,10 @@ if (isSingleCallerInliningTarget(target)) { return Reason.SINGLE_CALLER; } - if (isDoubleInliningTarget(target)) { - assert methodProcessor.isPrimaryMethodProcessor(); - return Reason.DUAL_CALLER; + if (isMultiCallerInlineCandidate(invoke, target, oracle, methodProcessor)) { + return methodProcessor.isPrimaryMethodProcessor() + ? Reason.MULTI_CALLER_CANDIDATE + : Reason.ALWAYS; } return Reason.SIMPLE; } @@ -75,11 +78,20 @@ return true; } - private boolean isDoubleInliningTarget(ProgramMethod candidate) { - return callSiteInformation.hasDoubleCallSite(candidate) - && candidate - .getDefinition() - .getCode() - .estimatedSizeForInliningAtMost(options.getDoubleInliningInstructionLimit()); + private boolean isMultiCallerInlineCandidate( + InvokeMethod invoke, + ProgramMethod singleTarget, + DefaultInliningOracle oracle, + MethodProcessor methodProcessor) { + if (oracle.satisfiesRequirementsForSimpleInlining(invoke, singleTarget)) { + return false; + } + if (methodProcessor.isPrimaryMethodProcessor()) { + return callSiteInformation.isMultiCallerInlineCandidate(singleTarget); + } + if (methodProcessor.isPostMethodProcessor()) { + return singleTarget.getOptimizationInfo().isMultiCallerMethod(); + } + return false; } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/FixedInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/FixedInliningReasonStrategy.java index aed242b..ec998a7 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/FixedInliningReasonStrategy.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/FixedInliningReasonStrategy.java
@@ -7,6 +7,7 @@ import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.code.InvokeMethod; import com.android.tools.r8.ir.conversion.MethodProcessor; +import com.android.tools.r8.ir.optimize.DefaultInliningOracle; import com.android.tools.r8.ir.optimize.Inliner.Reason; public class FixedInliningReasonStrategy implements InliningReasonStrategy { @@ -22,6 +23,7 @@ InvokeMethod invoke, ProgramMethod target, ProgramMethod context, + DefaultInliningOracle oracle, MethodProcessor methodProcessor) { return reason; }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java index 1387311..677ccdd 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
@@ -4,6 +4,7 @@ package com.android.tools.r8.ir.optimize.inliner; +import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.code.IRCode; @@ -24,6 +25,13 @@ private final Map<InvokeMethod, IRCode> cache = new IdentityHashMap<>(); + private InliningIRProvider() { + this.appView = null; + this.context = null; + this.valueNumberGenerator = null; + this.methodProcessor = null; + } + public InliningIRProvider( AppView<?> appView, ProgramMethod context, IRCode code, MethodProcessor methodProcessor) { this.appView = appView; @@ -32,6 +40,42 @@ this.methodProcessor = methodProcessor; } + public static InliningIRProvider getThrowingInstance() { + return new InliningIRProvider() { + @Override + public IRCode getInliningIR( + InvokeMethod invoke, ProgramMethod method, boolean removeInnerFramesIfNpe) { + throw new Unreachable(); + } + + @Override + public IRCode getAndCacheInliningIR( + InvokeMethod invoke, ProgramMethod method, boolean removeInnerFrameIfThrowingNpe) { + throw new Unreachable(); + } + + @Override + public void cacheInliningIR(InvokeMethod invoke, IRCode code) { + throw new Unreachable(); + } + + @Override + public MethodProcessor getMethodProcessor() { + throw new Unreachable(); + } + + @Override + public boolean verifyIRCacheIsEmpty() { + throw new Unreachable(); + } + + @Override + public boolean shouldApplyCodeRewritings(ProgramMethod method) { + throw new Unreachable(); + } + }; + } + public IRCode getInliningIR( InvokeMethod invoke, ProgramMethod method, boolean removeInnerFramesIfNpe) { IRCode cached = cache.remove(invoke);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningReasonStrategy.java index 00ff53c..c749be9 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningReasonStrategy.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningReasonStrategy.java
@@ -7,6 +7,7 @@ import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.code.InvokeMethod; import com.android.tools.r8.ir.conversion.MethodProcessor; +import com.android.tools.r8.ir.optimize.DefaultInliningOracle; import com.android.tools.r8.ir.optimize.Inliner.Reason; public interface InliningReasonStrategy { @@ -15,5 +16,6 @@ InvokeMethod invoke, ProgramMethod target, ProgramMethod context, + DefaultInliningOracle oracle, MethodProcessor methodProcessor); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/multicallerinliner/MultiCallerInlinerCallGraph.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/multicallerinliner/MultiCallerInlinerCallGraph.java new file mode 100644 index 0000000..9f77a05 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/multicallerinliner/MultiCallerInlinerCallGraph.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.ir.optimize.inliner.multicallerinliner; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.ir.conversion.callgraph.CallGraphBase; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import java.util.Map; + +public class MultiCallerInlinerCallGraph extends CallGraphBase<MultiCallerInlinerNode> { + + MultiCallerInlinerCallGraph(Map<DexMethod, MultiCallerInlinerNode> nodes) { + super(nodes); + } + + public static MultiCallerInlinerCallGraphBuilder builder(AppView<AppInfoWithLiveness> appView) { + return new MultiCallerInlinerCallGraphBuilder(appView); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/multicallerinliner/MultiCallerInlinerCallGraphBuilder.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/multicallerinliner/MultiCallerInlinerCallGraphBuilder.java new file mode 100644 index 0000000..d54fc87 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/multicallerinliner/MultiCallerInlinerCallGraphBuilder.java
@@ -0,0 +1,45 @@ +// 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.inliner.multicallerinliner; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.conversion.callgraph.CallGraphBuilderBase; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.ThreadUtils; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; + +public class MultiCallerInlinerCallGraphBuilder + extends CallGraphBuilderBase<MultiCallerInlinerNode> { + + MultiCallerInlinerCallGraphBuilder(AppView<AppInfoWithLiveness> appView) { + super(appView); + } + + @Override + protected MultiCallerInlinerNode createNode(ProgramMethod method) { + return new MultiCallerInlinerNode(method); + } + + public MultiCallerInlinerCallGraph build(ExecutorService executorService) + throws ExecutionException { + ThreadUtils.processItems(appView.appInfo().classes(), this::processClass, executorService); + return new MultiCallerInlinerCallGraph(nodes); + } + + private void processClass(DexProgramClass clazz) { + clazz.forEachProgramMethodMatching(DexEncodedMethod::hasCode, this::processMethod); + } + + private void processMethod(ProgramMethod method) { + MultiCallerInlinerInvokeRegistry registry = + new MultiCallerInlinerInvokeRegistry( + appView, getOrCreateNode(method), this::getOrCreateNode, possibleProgramTargetsCache); + method.registerCodeReferences(registry); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/multicallerinliner/MultiCallerInlinerInvokeRegistry.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/multicallerinliner/MultiCallerInlinerInvokeRegistry.java new file mode 100644 index 0000000..e60c8b5 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/multicallerinliner/MultiCallerInlinerInvokeRegistry.java
@@ -0,0 +1,45 @@ +// 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.inliner.multicallerinliner; + +import static com.google.common.base.Predicates.alwaysTrue; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClassAndMethod; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.GraphLens; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.code.Invoke.Type; +import com.android.tools.r8.ir.conversion.callgraph.InvokeExtractor; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.collections.ProgramMethodSet; +import java.util.Map; +import java.util.function.Function; + +public class MultiCallerInlinerInvokeRegistry extends InvokeExtractor<MultiCallerInlinerNode> { + + MultiCallerInlinerInvokeRegistry( + AppView<AppInfoWithLiveness> appView, + MultiCallerInlinerNode currentMethod, + Function<ProgramMethod, MultiCallerInlinerNode> nodeFactory, + Map<DexMethod, ProgramMethodSet> possibleProgramTargetsCache) { + super(appView, currentMethod, nodeFactory, possibleProgramTargetsCache, alwaysTrue()); + } + + @Override + public GraphLens getCodeLens() { + return appView.graphLens(); + } + + @Override + protected void processInvokeWithDynamicDispatch( + Type type, DexClassAndMethod resolutionResult, ProgramMethod context) { + // Skip calls that dispatch to library methods or library method overrides. + if (resolutionResult.isProgramMethod() + && resolutionResult.getDefinition().isLibraryMethodOverride().isPossiblyFalse()) { + super.processInvokeWithDynamicDispatch(type, resolutionResult, context); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/multicallerinliner/MultiCallerInlinerNode.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/multicallerinliner/MultiCallerInlinerNode.java new file mode 100644 index 0000000..c08b1a7 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/multicallerinliner/MultiCallerInlinerNode.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.optimize.inliner.multicallerinliner; + +import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.conversion.callgraph.NodeBase; +import java.util.concurrent.atomic.AtomicInteger; + +public class MultiCallerInlinerNode extends NodeBase<MultiCallerInlinerNode> { + + private final AtomicInteger numberOfCallSites = new AtomicInteger(); + + public MultiCallerInlinerNode(ProgramMethod method) { + super(method); + } + + @Override + public void addCallerConcurrently(MultiCallerInlinerNode caller, boolean likelySpuriousCallEdge) { + assert !getMethod().isClassInitializer(); + numberOfCallSites.incrementAndGet(); + } + + @Override + public void addReaderConcurrently(MultiCallerInlinerNode reader) { + throw new Unreachable(); + } + + public int getNumberOfCallSites() { + return numberOfCallSites.get(); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticCfCodeProvider.java index 05f88a1..8e166d1 100644 --- a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticCfCodeProvider.java +++ b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticCfCodeProvider.java
@@ -5,11 +5,9 @@ package com.android.tools.r8.ir.synthetic; import com.android.tools.r8.cf.code.CfInstruction; -import com.android.tools.r8.cf.code.CfTryCatch; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; import com.android.tools.r8.graph.DexType; -import com.google.common.collect.ImmutableList; import java.util.List; public abstract class SyntheticCfCodeProvider { @@ -29,13 +27,7 @@ public abstract CfCode generateCfCode(); protected CfCode standardCfCodeFromInstructions(List<CfInstruction> instructions) { - return new CfCode( - holder, - defaultMaxStack(), - defaultMaxLocals(), - instructions, - defaultTryCatchs(), - ImmutableList.of()); + return new CfCode(holder, defaultMaxStack(), defaultMaxLocals(), instructions); } protected int defaultMaxStack() { @@ -45,8 +37,4 @@ protected int defaultMaxLocals() { return 16; } - - protected List<CfTryCatch> defaultTryCatchs() { - return ImmutableList.of(); - } }
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java index 2e21467..0ad986c 100644 --- a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java +++ b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
@@ -13,9 +13,11 @@ import com.android.tools.r8.code.Instruction; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.Code; +import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexString; +import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.DexValue; import com.android.tools.r8.graph.DexValue.DexItemBasedValueString; import com.android.tools.r8.graph.DexValue.DexValueString; @@ -23,6 +25,7 @@ import com.android.tools.r8.ir.desugar.records.RecordCfToCfRewriter; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.shaking.ProguardClassFilter; +import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.ListUtils; import com.android.tools.r8.utils.ThreadUtils; import java.util.List; @@ -94,19 +97,36 @@ cnst.BBBB = getRenamedStringLiteral(cnst.getString()); } } - } else { - assert code.isCfCode(); + } else if (code.isCfCode()) { for (CfInstruction instruction : code.asCfCode().getInstructions()) { if (instruction.isConstString()) { CfConstString cnst = instruction.asConstString(); cnst.setString(getRenamedStringLiteral(cnst.getString())); } } + } else { + assert code.isCfWritableCode() || code.isDexWritableCode(); } } private DexString getRenamedStringLiteral(DexString originalLiteral) { - DexString rewrittenString = lens.lookupDescriptorForJavaTypeName(originalLiteral.toString()); + String descriptor = + DescriptorUtils.javaTypeToDescriptorIfValidJavaType(originalLiteral.toString()); + if (descriptor == null) { + return originalLiteral; + } + DexType type = appView.dexItemFactory().createType(descriptor); + DexType originalType = appView.graphLens().getOriginalType(type); + if (originalType != type) { + // The type has changed to something clashing with the string. + return originalLiteral; + } + DexType rewrittenType = appView.graphLens().lookupType(type); + DexClass clazz = appView.appInfo().definitionForWithoutExistenceAssert(rewrittenType); + if (clazz == null || clazz.isNotProgramClass()) { + return originalLiteral; + } + DexString rewrittenString = lens.lookupClassDescriptor(rewrittenType); return rewrittenString == null ? originalLiteral : appView.dexItemFactory().createString(descriptorToJavaType(rewrittenString.toString()));
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java index cc64864..bf18111 100644 --- a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java +++ b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
@@ -34,7 +34,7 @@ ClassRenaming classRenaming, MethodRenaming methodRenaming, FieldRenaming fieldRenaming) { - super(appView.dexItemFactory(), classRenaming.classRenaming); + super(appView.dexItemFactory()); this.appView = appView; this.packageRenaming = classRenaming.packageRenaming; renaming.putAll(classRenaming.classRenaming);
diff --git a/src/main/java/com/android/tools/r8/naming/NamingLens.java b/src/main/java/com/android/tools/r8/naming/NamingLens.java index b6e94ee..5b24eb9 100644 --- a/src/main/java/com/android/tools/r8/naming/NamingLens.java +++ b/src/main/java/com/android/tools/r8/naming/NamingLens.java
@@ -21,8 +21,6 @@ import com.android.tools.r8.utils.InternalOptions; import com.google.common.collect.Sets; import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; import java.util.Set; /** @@ -42,8 +40,6 @@ public abstract DexString lookupDescriptor(DexType type); - public abstract DexString lookupDescriptorForJavaTypeName(String typeName); - public DexString lookupClassDescriptor(DexType type) { assert type.isClassType(); return internalLookupClassDescriptor(type); @@ -185,13 +181,9 @@ public abstract static class NonIdentityNamingLens extends NamingLens { private final DexItemFactory dexItemFactory; - private final Map<String, DexString> typeStringMapping; - protected NonIdentityNamingLens( - DexItemFactory dexItemFactory, Map<DexType, DexString> typeMapping) { + protected NonIdentityNamingLens(DexItemFactory dexItemFactory) { this.dexItemFactory = dexItemFactory; - typeStringMapping = new HashMap<>(); - typeMapping.forEach((k, v) -> typeStringMapping.put(k.toSourceString(), v)); } protected DexItemFactory dexItemFactory() { @@ -211,11 +203,6 @@ assert type.isClassType(); return lookupClassDescriptor(type); } - - @Override - public DexString lookupDescriptorForJavaTypeName(String typeName) { - return typeStringMapping.get(typeName); - } } private static final class IdentityLens extends NamingLens { @@ -230,11 +217,6 @@ } @Override - public DexString lookupDescriptorForJavaTypeName(String typeName) { - return null; - } - - @Override protected DexString internalLookupClassDescriptor(DexType type) { return type.descriptor; }
diff --git a/src/main/java/com/android/tools/r8/naming/PrefixRewritingNamingLens.java b/src/main/java/com/android/tools/r8/naming/PrefixRewritingNamingLens.java index 04f235f..5ab9772 100644 --- a/src/main/java/com/android/tools/r8/naming/PrefixRewritingNamingLens.java +++ b/src/main/java/com/android/tools/r8/naming/PrefixRewritingNamingLens.java
@@ -12,7 +12,6 @@ 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 type prefixes. public class PrefixRewritingNamingLens extends NonIdentityNamingLens { @@ -34,7 +33,7 @@ } public PrefixRewritingNamingLens(NamingLens namingLens, AppView<?> appView) { - super(appView.dexItemFactory(), new IdentityHashMap<>()); + super(appView.dexItemFactory()); this.appView = appView; this.namingLens = namingLens; this.options = appView.options(); @@ -96,18 +95,6 @@ } @Override - public DexString lookupDescriptorForJavaTypeName(String typeName) { - if (appView.rewritePrefix.shouldRewriteTypeName(typeName)) { - DexType rewrittenType = - appView.rewritePrefix.rewrittenType(dexItemFactory().createType(typeName), appView); - if (rewrittenType != null) { - return rewrittenType.descriptor; - } - } - return namingLens.lookupDescriptorForJavaTypeName(typeName); - } - - @Override public String lookupPackageName(String packageName) { // Used for resource shrinking. // Desugared libraries do not have resources.
diff --git a/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java b/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java index f48cfc7..fe2654f 100644 --- a/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java +++ b/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java
@@ -13,7 +13,6 @@ 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 { @@ -34,7 +33,7 @@ } public RecordRewritingNamingLens(NamingLens namingLens, AppView<?> appView) { - super(appView.dexItemFactory(), new IdentityHashMap<>()); + super(appView.dexItemFactory()); this.namingLens = namingLens; factory = appView.dexItemFactory(); } @@ -75,14 +74,6 @@ } @Override - public DexString lookupDescriptorForJavaTypeName(String typeName) { - if (typeName.equals(factory.recordType.toSourceString())) { - return factory.recordTagType.descriptor; - } - return namingLens.lookupDescriptorForJavaTypeName(typeName); - } - - @Override public boolean hasPrefixRewritingLogic() { return namingLens.hasPrefixRewritingLogic(); }
diff --git a/src/main/java/com/android/tools/r8/position/MethodPosition.java b/src/main/java/com/android/tools/r8/position/MethodPosition.java index fa86ac8..12a7d8d 100644 --- a/src/main/java/com/android/tools/r8/position/MethodPosition.java +++ b/src/main/java/com/android/tools/r8/position/MethodPosition.java
@@ -4,7 +4,9 @@ package com.android.tools.r8.position; import com.android.tools.r8.Keep; +import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.references.MethodReference; import com.android.tools.r8.references.TypeReference; import java.util.List; @@ -15,14 +17,41 @@ public class MethodPosition implements Position { private final MethodReference method; + private final Position textPosition; @Deprecated public MethodPosition(DexMethod method) { this(method.asMethodReference()); } + @Deprecated public MethodPosition(MethodReference method) { + this(method, Position.UNKNOWN); + } + + private MethodPosition(MethodReference method, Position textPosition) { this.method = method; + this.textPosition = textPosition; + } + + public static MethodPosition create(ProgramMethod method) { + return create(method.getDefinition()); + } + + public static MethodPosition create(DexEncodedMethod method) { + Position position = UNKNOWN; + if (method.hasCode() && method.getCode().isCfCode()) { + position = method.getCode().asCfCode().getDiagnosticPosition(); + } + return create(method.getReference().asMethodReference(), position); + } + + public static MethodPosition create(MethodReference method) { + return new MethodPosition(method, Position.UNKNOWN); + } + + public static MethodPosition create(MethodReference method, Position position) { + return new MethodPosition(method, position); } /** The method */ @@ -51,6 +80,10 @@ .collect(Collectors.toList()); } + public Position getTextPosition() { + return textPosition; + } + @Override public String toString() { return method.toString();
diff --git a/src/main/java/com/android/tools/r8/relocator/SimplePackagesRewritingMapper.java b/src/main/java/com/android/tools/r8/relocator/SimplePackagesRewritingMapper.java index 3a5902a..7afb7e3 100644 --- a/src/main/java/com/android/tools/r8/relocator/SimplePackagesRewritingMapper.java +++ b/src/main/java/com/android/tools/r8/relocator/SimplePackagesRewritingMapper.java
@@ -102,7 +102,7 @@ Map<DexType, DexString> typeMappings, Map<String, String> packageMappings, DexItemFactory factory) { - super(factory, typeMappings); + super(factory); this.typeMappings = typeMappings; this.packageMappings = packageMappings; }
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 7765953..c7a2bab 100644 --- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java +++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1017,6 +1017,7 @@ markLambdaAsInstantiated(descriptor, context); transitionMethodsForInstantiatedLambda(descriptor); callSites.computeIfAbsent(callSite, ignore -> ProgramMethodSet.create()).add(context); + descriptor.captures.forEach(type -> markTypeAsLive(type, context)); // For call sites representing a lambda, we link the targeted method // or field as if it were referenced from the current method.
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 2a06ead..6d51c04 100644 --- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java +++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -7,6 +7,7 @@ import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; import static com.android.tools.r8.ir.code.Invoke.Type.DIRECT; import static com.android.tools.r8.ir.code.Invoke.Type.STATIC; +import static com.android.tools.r8.ir.code.Invoke.Type.VIRTUAL; import static com.android.tools.r8.utils.AndroidApiLevelUtils.getApiReferenceLevelForMerging; import com.android.tools.r8.androidapi.AndroidApiLevelCompute; @@ -1061,7 +1062,7 @@ } } - DexEncodedMethod resultingDirectMethod; + DexEncodedMethod resultingMethod; if (source.accessFlags.isInterface()) { // Moving a default interface method into its subtype. This method could be hit directly // via an invoke-super instruction from any of the transitive subtypes of this interface, @@ -1070,7 +1071,7 @@ // method name that does not collide with one in the hierarchy of this class. MemberPool<DexMethod> methodPoolForTarget = methodPoolCollection.buildForHierarchy(target, executorService, timing); - resultingDirectMethod = + resultingMethod = renameMethod( virtualMethod, method -> @@ -1079,26 +1080,32 @@ MethodSignatureEquivalence.get().wrap(method)), Rename.ALWAYS, appView.dexItemFactory().prependHolderToProto(virtualMethod.getReference())); - makeStatic(resultingDirectMethod); + makeStatic(resultingMethod); // Update method pool collection now that we are adding a new public method. - methodPoolForTarget.seen(resultingDirectMethod.getReference()); + methodPoolForTarget.seen(resultingMethod.getReference()); } else { // This virtual method could be called directly from a sub class via an invoke-super in- - // struction. Therefore, we translate this virtual method into a direct method, such that - // relevant invoke-super instructions can be rewritten into invoke-direct instructions. - resultingDirectMethod = - renameMethod(virtualMethod, availableMethodSignatures, Rename.ALWAYS); - makePrivate(resultingDirectMethod); + // struction. Therefore, we translate this virtual method into an instance method with a + // unique name, such that relevant invoke-super instructions can be rewritten to target + // this method directly. + resultingMethod = renameMethod(virtualMethod, availableMethodSignatures, Rename.ALWAYS); + if (appView.options().getProguardConfiguration().isAccessModificationAllowed()) { + makePublic(resultingMethod); + } else { + makePrivate(resultingMethod); + } } - add(directMethods, resultingDirectMethod, MethodSignatureEquivalence.get()); + add( + resultingMethod.belongsToDirectPool() ? directMethods : virtualMethods, + resultingMethod, + MethodSignatureEquivalence.get()); // Record that invoke-super instructions in the target class should be redirected to the // newly created direct method. - redirectSuperCallsInTarget( - virtualMethod.getReference(), resultingDirectMethod.getReference()); - blockRedirectionOfSuperCalls(resultingDirectMethod.getReference()); + redirectSuperCallsInTarget(virtualMethod, resultingMethod); + blockRedirectionOfSuperCalls(resultingMethod.getReference()); if (shadowedBy == null) { // In addition to the newly added direct method, create a virtual method such that we do @@ -1106,15 +1113,14 @@ // Note that this method is added independently of whether it will actually be used. If // it turns out that the method is never used, it will be removed by the final round // of tree shaking. - shadowedBy = buildBridgeMethod(virtualMethod, resultingDirectMethod); + shadowedBy = buildBridgeMethod(virtualMethod, resultingMethod); deferredRenamings.recordCreationOfBridgeMethod( virtualMethod.getReference(), shadowedBy.getReference()); add(virtualMethods, shadowedBy, MethodSignatureEquivalence.get()); } deferredRenamings.map(virtualMethod.getReference(), shadowedBy.getReference()); - deferredRenamings.recordMove( - virtualMethod.getReference(), resultingDirectMethod.getReference()); + deferredRenamings.recordMove(virtualMethod.getReference(), resultingMethod.getReference()); } if (abortMerge) { @@ -1369,7 +1375,11 @@ return synthesizedBridges; } - private void redirectSuperCallsInTarget(DexMethod oldTarget, DexMethod newTarget) { + private void redirectSuperCallsInTarget( + DexEncodedMethod oldTarget, DexEncodedMethod newTarget) { + DexMethod oldTargetReference = oldTarget.getReference(); + DexMethod newTargetReference = newTarget.getReference(); + Type newTargetType = newTarget.isNonPrivateVirtualMethod() ? VIRTUAL : DIRECT; if (source.accessFlags.isInterface()) { // If we merge a default interface method from interface I to its subtype C, then we need // to rewrite invocations on the form "invoke-super I.m()" to "invoke-direct C.m$I()". @@ -1379,21 +1389,23 @@ // if I has a supertype J. This is due to the fact that invoke-super instructions that // resolve to a method on an interface never hit an implementation below that interface. deferredRenamings.mapVirtualMethodToDirectInType( - oldTarget, - prototypeChanges -> new MethodLookupResult(newTarget, null, STATIC, prototypeChanges), + oldTargetReference, + prototypeChanges -> + new MethodLookupResult(newTargetReference, null, STATIC, prototypeChanges), target.type); } else { // If we merge class B into class C, and class C contains an invocation super.m(), then it - // is insufficient to rewrite "invoke-super B.m()" to "invoke-direct C.m$B()" (the method - // C.m$B denotes the direct method that has been created in C for B.m). In particular, there - // might be an instruction "invoke-super A.m()" in C that resolves to B.m at runtime (A is - // a superclass of B), which also needs to be rewritten to "invoke-direct C.m$B()". + // is insufficient to rewrite "invoke-super B.m()" to "invoke-{direct,virtual} C.m$B()" (the + // method C.m$B denotes the direct/virtual method that has been created in C for B.m). In + // particular, there might be an instruction "invoke-super A.m()" in C that resolves to B.m + // at runtime (A is a superclass of B), which also needs to be rewritten to + // "invoke-{direct,virtual} C.m$B()". // // We handle this by adding a mapping for [target] and all of its supertypes. DexProgramClass holder = target; while (holder != null && holder.isProgramClass()) { DexMethod signatureInHolder = - application.dexItemFactory.createMethod(holder.type, oldTarget.proto, oldTarget.name); + oldTargetReference.withHolder(holder, appView.dexItemFactory()); // Only rewrite the invoke-super call if it does not lead to a NoSuchMethodError. boolean resolutionSucceeds = holder.lookupVirtualMethod(signatureInHolder) != null @@ -1402,7 +1414,8 @@ deferredRenamings.mapVirtualMethodToDirectInType( signatureInHolder, prototypeChanges -> - new MethodLookupResult(newTarget, null, DIRECT, prototypeChanges), + new MethodLookupResult( + newTargetReference, null, newTargetType, prototypeChanges), target.type); } else { break; @@ -1416,7 +1429,7 @@ Set<DexType> mergedTypes = mergedClasses.getKeys(holder.type); for (DexType type : mergedTypes) { DexMethod signatureInType = - application.dexItemFactory.createMethod(type, oldTarget.proto, oldTarget.name); + oldTargetReference.withHolder(type, appView.dexItemFactory()); // Resolution would have succeeded if the method used to be in [type], or if one of // its super classes declared the method. boolean resolutionSucceededBeforeMerge = @@ -1426,7 +1439,8 @@ deferredRenamings.mapVirtualMethodToDirectInType( signatureInType, prototypeChanges -> - new MethodLookupResult(newTarget, null, DIRECT, prototypeChanges), + new MethodLookupResult( + newTargetReference, null, newTargetType, prototypeChanges), target.type); } } @@ -1469,13 +1483,17 @@ accessFlags.setSynthetic(); accessFlags.unsetAbstract(); - assert invocationTarget.isPrivateMethod() == !invocationTarget.isStatic(); + assert invocationTarget.isStatic() + || invocationTarget.isNonPrivateVirtualMethod() + || invocationTarget.isNonStaticPrivateMethod(); SynthesizedBridgeCode code = new SynthesizedBridgeCode( newMethod, appView.graphLens().getOriginalMethodSignature(method.getReference()), invocationTarget.getReference(), - invocationTarget.isPrivateMethod() ? DIRECT : STATIC, + invocationTarget.isStatic() + ? STATIC + : (invocationTarget.isNonPrivateVirtualMethod() ? VIRTUAL : DIRECT), target.isInterface()); // Add the bridge to the list of synthesized bridges such that the method signatures will @@ -1694,6 +1712,14 @@ method.accessFlags.setPrivate(); } + private static void makePublic(DexEncodedMethod method) { + MethodAccessFlags accessFlags = method.getAccessFlags(); + assert !accessFlags.isAbstract(); + accessFlags.unsetPrivate(); + accessFlags.unsetProtected(); + accessFlags.setPublic(); + } + private static class VerticalClassMergerTreeFixer extends TreeFixerBase { private final AppView<AppInfoWithLiveness> appView; @@ -1959,10 +1985,11 @@ } @Override - public MethodLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) { + public MethodLookupResult lookupMethod( + DexMethod method, DexMethod context, Type type, GraphLens codeLens) { // First look up the method using the existing graph lens (for example, the type will have // changed if the method was publicized by ClassAndMemberPublicizer). - MethodLookupResult lookup = appView.graphLens().lookupMethod(method, context, type); + MethodLookupResult lookup = appView.graphLens().lookupMethod(method, context, type, codeLens); // Then check if there is a renaming due to the vertical class merger. DexMethod newMethod = lensBuilder.methodMap.get(lookup.getReference()); if (newMethod == null) { @@ -1979,7 +2006,7 @@ DexClass clazz = appInfo.definitionFor(newMethod.holder); if (clazz != null && !clazz.accessFlags.isInterface()) { assert appInfo.definitionFor(method.holder).accessFlags.isInterface(); - methodLookupResultBuilder.setType(Type.VIRTUAL); + methodLookupResultBuilder.setType(VIRTUAL); } } return methodLookupResultBuilder.build(); @@ -1999,7 +2026,7 @@ } @Override - public DexField lookupField(DexField field) { + public DexField lookupField(DexField field, GraphLens codeLens) { return lensBuilder.fieldMap.getOrDefault(field, field); } @@ -2087,14 +2114,14 @@ @Override public void registerInvokeVirtual(DexMethod method) { MethodLookupResult lookup = - appView.graphLens().lookupMethod(method, getContext().getReference(), Type.VIRTUAL); + appView.graphLens().lookupMethod(method, getContext().getReference(), VIRTUAL); checkMethodReference(lookup.getReference(), OptionalBool.FALSE); } @Override public void registerInvokeDirect(DexMethod method) { MethodLookupResult lookup = - appView.graphLens().lookupMethod(method, getContext().getReference(), Type.DIRECT); + appView.graphLens().lookupMethod(method, getContext().getReference(), DIRECT); checkMethodReference(lookup.getReference(), OptionalBool.UNKNOWN); } @@ -2255,7 +2282,7 @@ forwardSourceCodeBuilder .setReceiver(method.holder) .setOriginalMethod(originalMethod) - .setTargetReceiver(type == DIRECT ? method.holder : null) + .setTargetReceiver(type.isStatic() ? null : method.holder) .setTarget(invocationTarget) .setInvokeType(type) .setIsInterface(isInterface); @@ -2270,11 +2297,12 @@ case DIRECT: registry.registerInvokeDirect(invocationTarget); break; - case STATIC: registry.registerInvokeStatic(invocationTarget); break; - + case VIRTUAL: + registry.registerInvokeVirtual(invocationTarget); + break; default: throw new Unreachable("Unexpected invocation type: " + 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 9548f78..5cc5876 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java +++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -602,7 +602,11 @@ externalSyntheticTypePrefix, generators, appView, - equivalences::containsKey); + candidateType -> + equivalences.containsKey(candidateType) + || appView + .horizontallyMergedClasses() + .hasBeenMergedIntoDifferentType(candidateType)); equivalences.put(representativeType, group); } });
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 9a863be..24c6a60 100644 --- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java +++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -91,6 +91,10 @@ return true; } + public static int last(int[] array) { + return array[array.length - 1]; + } + public static <T> T last(T[] array) { return array[array.length - 1]; }
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 7db36b3..7bc38a4 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1317,10 +1317,8 @@ parseSystemPropertyForDevelopmentOrDefault( "com.android.tools.r8.inliningInstructionLimit", -1); - // This defines the limit of instructions in the inlinee - public int doubleInliningInstructionLimit = - parseSystemPropertyForDevelopmentOrDefault( - "com.android.tools.r8.doubleInliningInstructionLimit", -1); + public int[] multiCallerInliningInstructionLimits = + new int[] {Integer.MAX_VALUE, 28, 16, 12, 10}; // This defines how many instructions of inlinees we can inlinee overall. public int inliningInstructionAllowance = 1500; @@ -1356,20 +1354,6 @@ assert isGeneratingDex(); return 5; } - - public int getDoubleInliningInstructionLimit() { - // If a custom double inlining instruction limit is set, then use that. - if (doubleInliningInstructionLimit >= 0) { - return doubleInliningInstructionLimit; - } - // Allow 10 instructions when generating to class files. - if (isGeneratingClassFiles()) { - return 10; - } - // Allow the size of the dex code to be up to 20 bytes. - assert isGeneratingDex(); - return 20; - } } public class HorizontalClassMergerOptions {
diff --git a/src/main/java/com/android/tools/r8/utils/Timing.java b/src/main/java/com/android/tools/r8/utils/Timing.java index 5367c26..04d801c 100644 --- a/src/main/java/com/android/tools/r8/utils/Timing.java +++ b/src/main/java/com/android/tools/r8/utils/Timing.java
@@ -21,7 +21,6 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Stack; -import java.util.function.Supplier; public class Timing { @@ -380,7 +379,7 @@ } } - public <T> T time(String title, Supplier<T> supplier) { + public <T, E extends Exception> T time(String title, ThrowingSupplier<T, E> supplier) throws E { begin(title); try { return supplier.get();
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMultiset.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMultiset.java index ae8974c..cfaa16f 100644 --- a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMultiset.java +++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMultiset.java
@@ -9,9 +9,11 @@ import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.utils.ProgramMethodEquivalence; import com.google.common.base.Equivalence.Wrapper; +import com.google.common.collect.ConcurrentHashMultiset; import com.google.common.collect.HashMultiset; import com.google.common.collect.Multiset; import java.util.function.ObjIntConsumer; +import java.util.function.Predicate; public class ProgramMethodMultiset { @@ -21,10 +23,18 @@ this.backing = backing; } + public static ProgramMethodMultiset createConcurrent() { + return new ProgramMethodMultiset(ConcurrentHashMultiset.create()); + } + public static ProgramMethodMultiset createHash() { return new ProgramMethodMultiset(HashMultiset.create()); } + public void add(ProgramMethod method) { + backing.add(wrap(method)); + } + public void createAndAdd(DexProgramClass holder, DexEncodedMethod method, int occurrences) { backing.add(wrap(new ProgramMethod(holder, method)), occurrences); } @@ -33,6 +43,14 @@ backing.forEachEntry((wrapper, occurrences) -> consumer.accept(wrapper.get(), occurrences)); } + public boolean removeIf(Predicate<ProgramMethod> predicate) { + return backing.removeIf(wrapper -> predicate.test(wrapper.get())); + } + + public int size() { + return backing.size(); + } + private static Wrapper<ProgramMethod> wrap(ProgramMethod method) { return ProgramMethodEquivalence.get().wrap(method); }
diff --git a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java index 9b81396..b6bcf5c 100644 --- a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java +++ b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
@@ -130,6 +130,10 @@ return compilerVersion == version; } + public boolean isOneOf(KotlinCompilerVersion... versions) { + return Arrays.stream(versions).anyMatch(this::is); + } + public boolean isNot(KotlinCompilerVersion version) { return !is(version); }
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java index 45d03e7..f695dbb 100644 --- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java +++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -204,7 +204,7 @@ b -> b.addProguardConfiguration( getProguardOptionsNPlus(enableProguardCompatibilityMode), Origin.unknown())) - .withDexCheck(inspector -> checkLambdaCount(inspector, 2, "lambdadesugaringnplus")) + .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaringnplus")) .run(); } @@ -244,7 +244,7 @@ b -> b.addProguardConfiguration( getProguardOptionsNPlus(enableProguardCompatibilityMode), Origin.unknown())) - .withDexCheck(inspector -> checkLambdaCount(inspector, 2, "lambdadesugaringnplus")) + .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaringnplus")) .run(); }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java index 71bb770..e0afd34 100644 --- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java
@@ -7,11 +7,11 @@ import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass; import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer; import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod; +import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat; import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeFalse; @@ -22,7 +22,6 @@ import com.android.tools.r8.ToolHelper.DexVm.Version; import com.android.tools.r8.testing.AndroidBuildVersion; import com.android.tools.r8.utils.AndroidApiLevel; -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 java.lang.reflect.Method; @@ -81,27 +80,30 @@ .inspect( inspector -> { // No need to check further on CF. - Optional<FoundMethodSubject> synthesizedAddedOn23 = - inspector.allClasses().stream() - .flatMap(clazz -> clazz.allMethods().stream()) - .filter( - methodSubject -> - methodSubject.isSynthetic() - && invokesMethodWithName("addedOn23").matches(methodSubject)) - .findFirst(); - if (parameters.isCfRuntime() || parameters.getApiLevel().isLessThan(classApiLevel)) { - assertFalse(synthesizedAddedOn23.isPresent()); - assertEquals(3, inspector.allClasses().size()); - } else if (parameters.getApiLevel().isLessThan(methodApiLevel)) { + int classCount = + parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(methodApiLevel) + ? 4 + : 3; + assertEquals(classCount, inspector.allClasses().size()); + Method testMethod = TestClass.class.getDeclaredMethod("test"); + verifyThat(parameters, adeddOn23).isOutlinedFromUntil(testMethod, methodApiLevel); + if (parameters.isDexRuntime() + && parameters.getApiLevel().isLessThan(methodApiLevel)) { + // Verify that we invoke the synthesized outline addedOn23 twice. + Optional<FoundMethodSubject> synthesizedAddedOn23 = + inspector.allClasses().stream() + .flatMap(clazz -> clazz.allMethods().stream()) + .filter( + methodSubject -> + methodSubject.isSynthetic() + && invokesMethodWithName("addedOn23").matches(methodSubject)) + .findFirst(); assertTrue(synthesizedAddedOn23.isPresent()); - assertEquals(4, inspector.allClasses().size()); - ClassSubject testClass = inspector.clazz(TestClass.class); - assertThat(testClass, isPresent()); - MethodSubject testMethod = testClass.uniqueMethodWithName("test"); - assertThat(testMethod, isPresent()); + MethodSubject testMethodSubject = inspector.method(testMethod); + assertThat(testMethodSubject, isPresent()); assertEquals( 2, - testMethod + testMethodSubject .streamInstructions() .filter( instructionSubject -> { @@ -114,10 +116,6 @@ .equals(synthesizedAddedOn23.get().asMethodReference()); }) .count()); - } else { - // No outlining on this api level. - assertFalse(synthesizedAddedOn23.isPresent()); - assertEquals(3, inspector.allClasses().size()); } }); }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java index 436d2fc..2426afb 100644 --- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java
@@ -122,8 +122,7 @@ methodSubject.isSynthetic() && invokesMethodWithName("addedOn27").matches(methodSubject)) .collect(Collectors.toList()); - if (parameters.isCfRuntime() - || parameters.getApiLevel().isLessThan(libraryClassApiLevel)) { + if (parameters.isCfRuntime()) { assertTrue(outlinedAddedOn23.isEmpty()); assertTrue(outlinedAddedOn27.isEmpty()); assertEquals(3, inspector.allClasses().size());
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodAndStubClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodAndStubClassTest.java new file mode 100644 index 0000000..051f793 --- /dev/null +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodAndStubClassTest.java
@@ -0,0 +1,98 @@ +// 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.apimodel; + +import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass; +import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer; +import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod; +import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat; +import static org.junit.Assume.assumeFalse; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper.DexVm.Version; +import com.android.tools.r8.apimodel.ApiModelMockClassTest.TestClass; +import com.android.tools.r8.testing.AndroidBuildVersion; +import com.android.tools.r8.utils.AndroidApiLevel; +import java.lang.reflect.Method; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class ApiModelOutlineMethodAndStubClassTest extends TestBase { + + private final AndroidApiLevel libraryClassLevel = AndroidApiLevel.M; + private final AndroidApiLevel libraryMethodLevel = AndroidApiLevel.Q; + + @Parameter public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void testR8() throws Exception { + // TODO(b/197078995): Make this work on 12. + assumeFalse( + parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isEqualTo(Version.V12_0_0)); + boolean libraryClassNotStubbed = + parameters.isDexRuntime() + && parameters.getApiLevel().isGreaterThanOrEqualTo(libraryClassLevel); + Method apiMethod = LibraryClass.class.getDeclaredMethod("foo"); + testForR8(parameters.getBackend()) + .addProgramClasses(Main.class, TestClass.class) + .addLibraryClasses(LibraryClass.class) + .addDefaultRuntimeLibrary(parameters) + .setMinApi(parameters.getApiLevel()) + .addKeepMainRule(Main.class) + .addAndroidBuildVersion() + .apply(ApiModelingTestHelper::enableStubbingOfClasses) + .apply(ApiModelingTestHelper::enableOutliningOfMethods) + .apply(setMockApiLevelForClass(LibraryClass.class, libraryClassLevel)) + .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, libraryClassLevel)) + .apply(setMockApiLevelForMethod(apiMethod, libraryMethodLevel)) + .compile() + .applyIf( + parameters.isDexRuntime() + && parameters + .getRuntime() + .maxSupportedApiLevel() + .isGreaterThanOrEqualTo(libraryClassLevel), + b -> b.addBootClasspathClasses(LibraryClass.class)) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLinesIf(libraryClassNotStubbed, "LibraryClass::foo") + .assertSuccessWithOutputLinesIf(!libraryClassNotStubbed, "Hello World") + .inspect(verifyThat(parameters, LibraryClass.class).stubbedUntil(libraryClassLevel)) + .inspect( + verifyThat(parameters, apiMethod) + .isOutlinedFromUntil( + Main.class.getDeclaredMethod("main", String[].class), libraryMethodLevel)); + } + + // Only present from api level 23. + public static class LibraryClass { + + // Only present from api level 30 + public void foo() { + System.out.println("LibraryClass::foo"); + } + } + + public static class Main { + + public static void main(String[] args) { + if (AndroidBuildVersion.VERSION >= 23) { + new LibraryClass().foo(); + } else { + System.out.println("Hello World"); + } + } + } +}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodMissingClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodMissingClassTest.java index 4d6a526..9d0f7cf 100644 --- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodMissingClassTest.java +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodMissingClassTest.java
@@ -7,12 +7,12 @@ import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass; import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer; import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod; +import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat; import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeFalse; import com.android.tools.r8.NeverInline; @@ -20,11 +20,8 @@ import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.ToolHelper.DexVm.Version; -import com.android.tools.r8.references.MethodReference; -import com.android.tools.r8.references.Reference; import com.android.tools.r8.testing.AndroidBuildVersion; import com.android.tools.r8.utils.AndroidApiLevel; -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 java.lang.reflect.Method; @@ -59,7 +56,7 @@ !preMockApis && parameters.getApiLevel().isGreaterThanOrEqualTo(finalLibraryMethodLevel); boolean betweenMockApis = !preMockApis && !postMockApis; Method addedOn23 = LibraryClass.class.getMethod("addedOn23"); - Method adeddOn27 = LibraryClass.class.getMethod("addedOn27"); + Method addedOn27 = LibraryClass.class.getMethod("addedOn27"); testForR8(parameters.getBackend()) .addProgramClasses(Main.class, TestClass.class) .addLibraryClasses(LibraryClass.class) @@ -72,7 +69,7 @@ setMockApiLevelForDefaultInstanceInitializer( LibraryClass.class, initialLibraryMockLevel)) .apply(setMockApiLevelForMethod(addedOn23, initialLibraryMockLevel)) - .apply(setMockApiLevelForMethod(adeddOn27, finalLibraryMethodLevel)) + .apply(setMockApiLevelForMethod(addedOn27, finalLibraryMethodLevel)) .apply(ApiModelingTestHelper::enableOutliningOfMethods) .enableInliningAnnotations() .compile() @@ -103,27 +100,9 @@ assertEquals(3, inspector.allClasses().size()); return; } - ClassSubject testClass = inspector.clazz(TestClass.class); - assertThat(testClass, isPresent()); - MethodSubject testMethod = testClass.uniqueMethodWithName("test"); - assertThat(testMethod, isPresent()); - Optional<FoundMethodSubject> synthesizedAddedOn27 = - inspector.allClasses().stream() - .flatMap(clazz -> clazz.allMethods().stream()) - .filter( - methodSubject -> - methodSubject.isSynthetic() - && invokesMethodWithName("addedOn27").matches(methodSubject)) - .findFirst(); - Optional<FoundMethodSubject> synthesizedMissingAndReferenced = - inspector.allClasses().stream() - .flatMap(clazz -> clazz.allMethods().stream()) - .filter( - methodSubject -> - methodSubject.isSynthetic() - && invokesMethodWithName("missingAndReferenced") - .matches(methodSubject)) - .findFirst(); + Method testMethod = TestClass.class.getDeclaredMethod("test"); + MethodSubject testMethodSubject = inspector.method(testMethod); + assertThat(testMethodSubject, isPresent()); Optional<FoundMethodSubject> synthesizedMissingNotReferenced = inspector.allClasses().stream() .flatMap(clazz -> clazz.allMethods().stream()) @@ -134,64 +113,19 @@ .matches(methodSubject)) .findFirst(); assertFalse(synthesizedMissingNotReferenced.isPresent()); - if (parameters.getApiLevel().isLessThan(AndroidApiLevel.M)) { - assertEquals(3, inspector.allClasses().size()); - assertFalse(synthesizedAddedOn27.isPresent()); - assertFalse(synthesizedMissingAndReferenced.isPresent()); - } else if (parameters.getApiLevel().isLessThan(AndroidApiLevel.O_MR1)) { + verifyThat(parameters, addedOn23).isOutlinedFromUntil(testMethod, AndroidApiLevel.M); + verifyThat(parameters, addedOn27) + .isOutlinedFromUntil(testMethod, AndroidApiLevel.O_MR1); + verifyThat(parameters, LibraryClass.class.getDeclaredMethod("missingAndReferenced")) + .isOutlinedFrom(testMethod); + if (parameters.getApiLevel().isLessThan(AndroidApiLevel.O_MR1)) { assertEquals(5, inspector.allClasses().size()); - assertTrue(synthesizedAddedOn27.isPresent()); - inspectRewrittenToOutline(testMethod, synthesizedAddedOn27.get(), "addedOn27"); - assertTrue(synthesizedMissingAndReferenced.isPresent()); - inspectRewrittenToOutline( - testMethod, synthesizedMissingAndReferenced.get(), "missingAndReferenced"); } else { assertEquals(4, inspector.allClasses().size()); - assertFalse(synthesizedAddedOn27.isPresent()); - assertTrue(synthesizedMissingAndReferenced.isPresent()); - inspectRewrittenToOutline( - testMethod, synthesizedMissingAndReferenced.get(), "missingAndReferenced"); } }); } - private void inspectRewrittenToOutline( - MethodSubject callerSubject, FoundMethodSubject outline, String apiMethodName) - throws Exception { - // Check that the library reference is no longer present. - MethodReference libraryMethodReference = - Reference.methodFromMethod(LibraryClass.class.getDeclaredMethod(apiMethodName)); - assertFalse( - callerSubject - .streamInstructions() - .anyMatch( - instructionSubject -> { - if (!instructionSubject.isInvoke()) { - return false; - } - return instructionSubject - .getMethod() - .asMethodReference() - .equals(libraryMethodReference); - })); - MethodReference outlineReference = outline.getMethod().getReference().asMethodReference(); - assertEquals( - 1, - callerSubject - .streamInstructions() - .filter( - instructionSubject -> { - if (!instructionSubject.isInvoke()) { - return false; - } - return instructionSubject - .getMethod() - .asMethodReference() - .equals(outlineReference); - }) - .count()); - } - // Only present from api level 23. public static class LibraryClass {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java index c109def..70d4f36 100644 --- a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
@@ -4,10 +4,13 @@ package com.android.tools.r8.apimodel; +import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod; +import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static com.android.tools.r8.utils.codeinspector.Matchers.notIf; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import com.android.tools.r8.TestCompilerBuilder; @@ -20,12 +23,15 @@ import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.CodeMatchers; import com.android.tools.r8.utils.codeinspector.FoundClassSubject; +import com.android.tools.r8.utils.codeinspector.FoundMethodSubject; import com.android.tools.r8.utils.codeinspector.MethodSubject; import com.google.common.collect.ImmutableList; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.List; import java.util.function.BiConsumer; +import java.util.stream.Collectors; public abstract class ApiModelingTestHelper { @@ -223,5 +229,40 @@ assertThat(target, not(CodeMatchers.invokesMethod(candidate))); }; } + + ThrowingConsumer<CodeInspector, Exception> isOutlinedFromUntil( + Method method, AndroidApiLevel apiLevel) { + return parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(apiLevel) + ? isOutlinedFrom(method) + : isNotOutlinedFrom(method); + } + + ThrowingConsumer<CodeInspector, Exception> isOutlinedFrom(Method method) { + return inspector -> { + // Check that the call is in a synthetic class. + List<FoundMethodSubject> outlinedMethod = + inspector.allClasses().stream() + .flatMap(clazz -> clazz.allMethods().stream()) + .filter( + methodSubject -> + methodSubject.isSynthetic() + && invokesMethodWithName(methodOfInterest.getMethodName()) + .matches(methodSubject)) + .collect(Collectors.toList()); + assertEquals(1, outlinedMethod.size()); + // Assert that method invokes the outline + MethodSubject caller = inspector.method(method); + assertThat(caller, isPresent()); + assertThat(caller, invokesMethod(outlinedMethod.get(0))); + }; + } + + ThrowingConsumer<CodeInspector, Exception> isNotOutlinedFrom(Method method) { + return inspector -> { + MethodSubject caller = inspector.method(method); + assertThat(caller, isPresent()); + assertThat(caller, invokesMethodWithName(methodOfInterest.getMethodName())); + }; + } } }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTest.java b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTest.java index 7e969c8..596a3f1 100644 --- a/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTest.java +++ b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTest.java
@@ -4,8 +4,11 @@ package com.android.tools.r8.debuginfo; +import com.android.tools.r8.AlwaysInline; + public class DexPcWithDebugInfoForOverloadedMethodsTest { + @AlwaysInline private static void inlinee(String message) { if (System.currentTimeMillis() > 0) { throw new RuntimeException(message);
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java index b3c1c08..a2f71b4 100644 --- a/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java +++ b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
@@ -23,6 +23,7 @@ import com.android.tools.r8.ToolHelper.DexVm.Version; import com.android.tools.r8.references.Reference; import com.android.tools.r8.retrace.RetraceFrameResult; +import com.android.tools.r8.shaking.ProguardKeepAttributes; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; @@ -67,6 +68,8 @@ .addKeepMainRule(MAIN) .addKeepMethodRules(MAIN, "void overloaded(...)") .addKeepAttributeLineNumberTable() + .addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE) + .enableAlwaysInliningAnnotations() .setMinApi(parameters.getApiLevel()) .run(parameters.getRuntime(), MAIN) .assertFailureWithErrorThatMatches(containsString(EXPECTED)) @@ -88,13 +91,13 @@ Reference.methodFromMethod( MAIN.getDeclaredMethod("inlinee", String.class)), MINIFIED_LINE_POSITION, - 11, + 14, FILENAME_INLINE), LinePosition.create( Reference.methodFromMethod( MAIN.getDeclaredMethod("overloaded", String.class)), MINIFIED_LINE_POSITION, - 20, + 23, FILENAME_INLINE)); RetraceFrameResult retraceResult = throwingSubject
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java new file mode 100644 index 0000000..4d861a0 --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.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.desugar.desugaredlibrary.specification; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.StringResource; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags; +import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanTopLevelFlags; +import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.MultiAPILevelHumanDesugaredLibrarySpecification; +import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter; +import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.MultiAPILevelHumanDesugaredLibrarySpecificationParser; +import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.MultiAPILevelLegacyDesugaredLibrarySpecification; +import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.MultiAPILevelLegacyDesugaredLibrarySpecificationParser; +import com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.LegacyToHumanSpecificationConverter; +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.utils.Box; +import com.android.tools.r8.utils.InternalOptions; +import java.io.IOException; +import java.util.Map; +import org.junit.Assume; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class ConvertExportReadTest extends TestBase { + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + public ConvertExportReadTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testMultiLevel() throws IOException { + Assume.assumeTrue(ToolHelper.isLocalDevelopment()); + + LegacyToHumanSpecificationConverter converter = new LegacyToHumanSpecificationConverter(); + + InternalOptions options = new InternalOptions(); + + MultiAPILevelLegacyDesugaredLibrarySpecification spec = + new MultiAPILevelLegacyDesugaredLibrarySpecificationParser( + options.dexItemFactory(), options.reporter) + .parseMultiLevelConfiguration( + StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting())); + + MultiAPILevelHumanDesugaredLibrarySpecification humanSpec1 = + converter.convertAllAPILevels(spec, ToolHelper.getAndroidJar(31), options); + + Box<String> json = new Box<>(); + MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.export( + humanSpec1, (string, handler) -> json.set(string)); + MultiAPILevelHumanDesugaredLibrarySpecification humanSpec2 = + new MultiAPILevelHumanDesugaredLibrarySpecificationParser( + options.dexItemFactory(), options.reporter) + .parseMultiLevelConfiguration(StringResource.fromString(json.get(), Origin.unknown())); + + assertSpecEquals(humanSpec1, humanSpec2); + } + + private void assertSpecEquals( + MultiAPILevelHumanDesugaredLibrarySpecification humanSpec1, + MultiAPILevelHumanDesugaredLibrarySpecification humanSpec2) { + assertTopLevelFlagsEquals(humanSpec1.getTopLevelFlags(), humanSpec2.getTopLevelFlags()); + assertFlagMapEquals( + humanSpec1.getCommonFlagsForTesting(), humanSpec2.getCommonFlagsForTesting()); + assertFlagMapEquals( + humanSpec1.getLibraryFlagsForTesting(), humanSpec2.getLibraryFlagsForTesting()); + assertFlagMapEquals( + humanSpec1.getProgramFlagsForTesting(), humanSpec2.getProgramFlagsForTesting()); + } + + private void assertFlagMapEquals( + Map<Integer, HumanRewritingFlags> commonFlags1, + Map<Integer, HumanRewritingFlags> commonFlags2) { + assertEquals(commonFlags1.size(), commonFlags2.size()); + for (int integer : commonFlags1.keySet()) { + assertTrue(commonFlags2.containsKey(integer)); + assertFlagsEquals(commonFlags1.get(integer), commonFlags2.get(integer)); + } + } + + private void assertFlagsEquals( + HumanRewritingFlags humanRewritingFlags1, HumanRewritingFlags humanRewritingFlags2) { + assertEquals(humanRewritingFlags1.getRewritePrefix(), humanRewritingFlags2.getRewritePrefix()); + assertEquals( + humanRewritingFlags1.getBackportCoreLibraryMember(), + humanRewritingFlags2.getBackportCoreLibraryMember()); + assertEquals( + humanRewritingFlags1.getCustomConversions(), humanRewritingFlags2.getCustomConversions()); + assertEquals( + humanRewritingFlags1.getEmulateLibraryInterface(), + humanRewritingFlags2.getEmulateLibraryInterface()); + assertEquals( + humanRewritingFlags1.getRetargetCoreLibMember(), + humanRewritingFlags2.getRetargetCoreLibMember()); + + assertEquals( + humanRewritingFlags1.getDontRetargetLibMember(), + humanRewritingFlags2.getDontRetargetLibMember()); + assertEquals( + humanRewritingFlags1.getDontRewriteInvocation(), + humanRewritingFlags2.getDontRewriteInvocation()); + assertEquals( + humanRewritingFlags1.getWrapperConversions(), humanRewritingFlags2.getWrapperConversions()); + } + + private void assertTopLevelFlagsEquals( + HumanTopLevelFlags topLevelFlags1, HumanTopLevelFlags topLevelFlags2) { + assertEquals(topLevelFlags1.getExtraKeepRules(), topLevelFlags2.getExtraKeepRules()); + assertEquals(topLevelFlags1.getIdentifier(), topLevelFlags2.getIdentifier()); + assertEquals( + topLevelFlags1.getRequiredCompilationAPILevel().getLevel(), + topLevelFlags2.getRequiredCompilationAPILevel().getLevel()); + assertEquals( + topLevelFlags1.getSynthesizedLibraryClassesPackagePrefix(), + topLevelFlags2.getSynthesizedLibraryClassesPackagePrefix()); + } +}
diff --git a/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java b/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java new file mode 100644 index 0000000..6b593fd --- /dev/null +++ b/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java
@@ -0,0 +1,99 @@ +// 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.internal.opensourceapps; + +import static org.junit.Assume.assumeTrue; + +import com.android.tools.r8.R8TestBuilder; +import com.android.tools.r8.R8TestCompileResult; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.ZipUtils; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class TiviTest extends TestBase { + + private static Path outDirectory; + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + @BeforeClass + public static void setup() throws IOException { + assumeTrue(ToolHelper.isLocalDevelopment()); + outDirectory = getStaticTemp().newFolder().toPath(); + ZipUtils.unzip(Paths.get("third_party/opensource-apps/tivi/dump_app.zip"), outDirectory); + } + + @Test + public void testR8() throws Exception { + testForR8(Backend.DEX) + .addProgramFiles(outDirectory.resolve("program.jar")) + .apply(this::configure) + .compile(); + } + + @Test + public void testR8Recompilation() throws Exception { + R8TestCompileResult compileResult = + testForR8(Backend.CF) + .addProgramFiles(outDirectory.resolve("program.jar")) + .apply(this::configure) + .compile(); + testForR8(Backend.DEX) + .addProgramFiles(compileResult.writeToZip()) + .apply(this::configure) + .compile(); + } + + @Test + public void testR8Compat() throws Exception { + testForR8Compat(Backend.DEX) + .addProgramFiles(outDirectory.resolve("program.jar")) + .apply(this::configure) + .compile(); + } + + @Test + public void testR8CompatRecompilation() throws Exception { + R8TestCompileResult compileResult = + testForR8Compat(Backend.CF) + .addProgramFiles(outDirectory.resolve("program.jar")) + .apply(this::configure) + .compile(); + testForR8Compat(Backend.DEX) + .addProgramFiles(compileResult.writeToZip()) + .apply(this::configure) + .compile(); + } + + private void configure(R8TestBuilder<?> testBuilder) { + testBuilder + .addClasspathFiles(outDirectory.resolve("classpath.jar")) + .addLibraryFiles(outDirectory.resolve("library.jar")) + .addKeepRuleFiles(outDirectory.resolve("proguard.config")) + .setMinApi(AndroidApiLevel.M) + .allowDiagnosticMessages() + .allowUnusedDontWarnPatterns() + .allowUnusedProguardConfigurationRules(); + } +}
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java b/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java index a24f51f..35762f4 100644 --- a/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java +++ b/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java
@@ -15,7 +15,7 @@ import com.android.tools.r8.graph.GenericSignature.ClassSignature; import com.android.tools.r8.graph.MethodAccessFlags; import com.android.tools.r8.graph.ProgramMethod; -import com.android.tools.r8.ir.conversion.CallGraph.Node; +import com.android.tools.r8.ir.conversion.callgraph.Node; import com.android.tools.r8.origin.SynthesizedOrigin; import java.util.Collections;
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/CycleEliminationTest.java b/src/test/java/com/android/tools/r8/ir/conversion/CycleEliminationTest.java index 2611dcd..5a6c914 100644 --- a/src/test/java/com/android/tools/r8/ir/conversion/CycleEliminationTest.java +++ b/src/test/java/com/android/tools/r8/ir/conversion/CycleEliminationTest.java
@@ -4,6 +4,7 @@ package com.android.tools.r8.ir.conversion; +import static com.android.tools.r8.ir.optimize.info.OptimizationFeedback.getSimpleFeedback; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; @@ -12,8 +13,8 @@ import static org.junit.Assert.fail; import com.android.tools.r8.errors.CompilationError; -import com.android.tools.r8.ir.conversion.CallGraph.Node; -import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator; +import com.android.tools.r8.ir.conversion.callgraph.CycleEliminator; +import com.android.tools.r8.ir.conversion.callgraph.Node; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import java.util.ArrayList; @@ -163,7 +164,7 @@ if (configuration.forceInline.contains(node)) { node.getMethod().getMutableOptimizationInfo().markForceInline(); } else { - node.getMethod().getMutableOptimizationInfo().unsetForceInline(); + getSimpleFeedback().unsetForceInline(node.getProgramMethod()); } }
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java b/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java index 1459b20..030465c 100644 --- a/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java +++ b/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java
@@ -9,8 +9,9 @@ import static org.junit.Assert.assertTrue; import com.android.tools.r8.graph.DexEncodedMethod; -import com.android.tools.r8.ir.conversion.CallGraph.Node; -import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator; +import com.android.tools.r8.ir.conversion.callgraph.CallGraph; +import com.android.tools.r8.ir.conversion.callgraph.CycleEliminator; +import com.android.tools.r8.ir.conversion.callgraph.Node; import java.util.Set; import java.util.TreeSet; import org.junit.Test; @@ -46,7 +47,7 @@ nodes.add(n5); nodes.add(n6); - CallGraph cg = new CallGraph(nodes); + CallGraph cg = CallGraph.createForTesting(nodes); Set<DexEncodedMethod> wave = cg.extractLeaves().toDefinitionSet(); assertEquals(3, wave.size()); assertThat(wave, hasItem(n3.getMethod())); @@ -61,7 +62,7 @@ wave = cg.extractLeaves().toDefinitionSet(); assertEquals(1, wave.size()); assertThat(wave, hasItem(n1.getMethod())); - assertTrue(nodes.isEmpty()); + assertTrue(cg.isEmpty()); } @Test @@ -95,7 +96,7 @@ CycleEliminator cycleEliminator = new CycleEliminator(); assertEquals(1, cycleEliminator.breakCycles(nodes).numberOfRemovedCallEdges()); - CallGraph cg = new CallGraph(nodes); + CallGraph cg = CallGraph.createForTesting(nodes); Set<DexEncodedMethod> wave = cg.extractLeaves().toDefinitionSet(); assertEquals(3, wave.size()); assertThat(wave, hasItem(n3.getMethod())); @@ -112,7 +113,7 @@ wave = cg.extractLeaves().toDefinitionSet(); assertEquals(1, wave.size()); assertThat(wave, hasItem(n1.getMethod())); - assertTrue(nodes.isEmpty()); + assertTrue(cg.isEmpty()); } @Test @@ -141,7 +142,7 @@ nodes.add(n5); nodes.add(n6); - CallGraph callGraph = new CallGraph(nodes, null); + CallGraph callGraph = CallGraph.createForTesting(nodes); Set<DexEncodedMethod> wave = callGraph.extractRoots().toDefinitionSet(); assertEquals(2, wave.size()); assertThat(wave, hasItem(n1.getMethod())); @@ -156,7 +157,7 @@ assertEquals(2, wave.size()); assertThat(wave, hasItem(n3.getMethod())); assertThat(wave, hasItem(n4.getMethod())); - assertTrue(nodes.isEmpty()); + assertTrue(callGraph.isEmpty()); } @Test @@ -190,7 +191,7 @@ CycleEliminator cycleEliminator = new CycleEliminator(); assertEquals(1, cycleEliminator.breakCycles(nodes).numberOfRemovedCallEdges()); - CallGraph callGraph = new CallGraph(nodes, null); + CallGraph callGraph = CallGraph.createForTesting(nodes); Set<DexEncodedMethod> wave = callGraph.extractRoots().toDefinitionSet(); assertEquals(2, wave.size()); assertThat(wave, hasItem(n1.getMethod())); @@ -205,6 +206,6 @@ assertEquals(2, wave.size()); assertThat(wave, hasItem(n3.getMethod())); assertThat(wave, hasItem(n4.getMethod())); - assertTrue(nodes.isEmpty()); + assertTrue(callGraph.isEmpty()); } }
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java b/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java index 36acfe9..f5b16f4 100644 --- a/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java +++ b/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
@@ -15,7 +15,10 @@ import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.ProgramMethod; -import com.android.tools.r8.ir.conversion.CallGraph.Node; +import com.android.tools.r8.ir.conversion.callgraph.CallGraph; +import com.android.tools.r8.ir.conversion.callgraph.CallGraphBuilder; +import com.android.tools.r8.ir.conversion.callgraph.Node; +import com.android.tools.r8.ir.conversion.callgraph.PartialCallGraphBuilder; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.shaking.ProguardConfigurationParser; import com.android.tools.r8.utils.AndroidApp; @@ -58,12 +61,12 @@ @Test public void testFullGraph() throws Exception { CallGraph cg = new CallGraphBuilder(appView).build(executorService, Timing.empty()); - Node m1 = findNode(cg.nodes, "m1"); - Node m2 = findNode(cg.nodes, "m2"); - Node m3 = findNode(cg.nodes, "m3"); - Node m4 = findNode(cg.nodes, "m4"); - Node m5 = findNode(cg.nodes, "m5"); - Node m6 = findNode(cg.nodes, "m6"); + Node m1 = findNode(cg.getNodes(), "m1"); + Node m2 = findNode(cg.getNodes(), "m2"); + Node m3 = findNode(cg.getNodes(), "m3"); + Node m4 = findNode(cg.getNodes(), "m4"); + Node m5 = findNode(cg.getNodes(), "m5"); + Node m6 = findNode(cg.getNodes(), "m6"); assertNotNull(m1); assertNotNull(m2); assertNotNull(m3); @@ -85,7 +88,7 @@ wave = cg.extractLeaves().toDefinitionSet(); assertEquals(1, wave.size()); assertThat(wave, hasItem(m1.getMethod())); - assertTrue(cg.nodes.isEmpty()); + assertTrue(cg.isEmpty()); } @Test @@ -107,10 +110,10 @@ CallGraph pg = new PartialCallGraphBuilder(appView, seeds).build(executorService, Timing.empty()); - Node m1 = findNode(pg.nodes, "m1"); - Node m2 = findNode(pg.nodes, "m2"); - Node m4 = findNode(pg.nodes, "m4"); - Node m5 = findNode(pg.nodes, "m5"); + Node m1 = findNode(pg.getNodes(), "m1"); + Node m2 = findNode(pg.getNodes(), "m2"); + Node m4 = findNode(pg.getNodes(), "m4"); + Node m5 = findNode(pg.getNodes(), "m5"); assertNotNull(m1); assertNotNull(m2); assertNotNull(m4); @@ -132,7 +135,7 @@ wave.addAll(pg.extractRoots().toDefinitionSet()); assertEquals(1, wave.size()); assertThat(wave, hasItem(m4.getMethod())); - assertTrue(pg.nodes.isEmpty()); + assertTrue(pg.isEmpty()); } private Node findNode(Iterable<Node> nodes, String name) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java index 15d6a42..9b0bb90 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java
@@ -97,19 +97,19 @@ MethodSubject checkNull = mainSubject.uniqueMethodWithName("checkNull"); assertThat(checkNull, isPresent()); - assertEquals(1, countCallToParamNullCheck(checkNull)); + assertEquals(0, countCallToParamNullCheck(checkNull)); assertEquals(1, countPrintCall(checkNull)); - assertEquals(0, countThrow(checkNull)); + assertEquals(1, countThrow(checkNull)); MethodSubject paramCheck = mainSubject.uniqueMethodWithName("nonNullAfterParamCheck"); assertThat(paramCheck, isPresent()); assertEquals(1, countPrintCall(paramCheck)); - assertEquals(0, countThrow(paramCheck)); + assertEquals(1, countThrow(paramCheck)); paramCheck = mainSubject.uniqueMethodWithName("nonNullAfterParamCheckDifferently"); assertThat(paramCheck, isPresent()); assertEquals(1, countPrintCall(paramCheck)); - assertEquals(0, countThrow(paramCheck)); + assertEquals(1, countThrow(paramCheck)); } @Test
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java index 42bb70f..fba166a 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -333,7 +333,10 @@ assertCounters(INLINABLE, ALWAYS_INLINABLE, countInvokes(inspector, m)); m = clazz.method("int", "notInlinableDueToSideEffect", ImmutableList.of("inlining.A")); - assertCounters(INLINABLE, NEVER_INLINABLE, countInvokes(inspector, m)); + assertCounters( + parameters.isCfRuntime() ? INLINABLE : NEVER_INLINABLE, + NEVER_INLINABLE, + countInvokes(inspector, m)); m = clazz.method("int", "notInlinableOnThrow", ImmutableList.of("java.lang.Throwable")); assertCounters(ALWAYS_INLINABLE, NEVER_INLINABLE, countInvokes(inspector, m));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java index 52fcf43..6ea5d98 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -6,6 +6,7 @@ import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static com.android.tools.r8.utils.codeinspector.Matchers.notIf; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; @@ -272,7 +273,9 @@ Sets.newHashSet("java.lang.StringBuilder", "java.lang.RuntimeException"), collectTypes(clazz.uniqueMethodWithName("testInitNeverReturnsNormally"))); - assertThat(inspector.clazz(InvalidRootsTestClass.NeverReturnsNormally.class), isPresent()); + assertThat( + inspector.clazz(InvalidRootsTestClass.NeverReturnsNormally.class), + notIf(isPresent(), parameters.isCfRuntime())); assertThat( inspector.clazz(InvalidRootsTestClass.InitNeverReturnsNormally.class), not(isPresent()));
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 index 9ddf887..f3d6a4d 100644 --- 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
@@ -10,6 +10,7 @@ import static org.junit.Assert.assertTrue; 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; @@ -42,6 +43,7 @@ .addKeepMainRule(Main.class) .addHorizontallyMergedClassesInspector( inspector -> inspector.assertIsCompleteMergeGroup(A.class, B.class, C.class)) + .enableInliningAnnotations() .enableNeverClassInliningAnnotations() .setMinApi(parameters.getApiLevel()) .noMinification() @@ -80,6 +82,7 @@ @NeverClassInline static class A { + @NeverInline void m() { System.out.println("A"); } @@ -88,6 +91,7 @@ @NeverClassInline static class B { + @NeverInline void m() { System.out.println("B"); } @@ -96,6 +100,7 @@ @NeverClassInline static class C { + @NeverInline 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 index a2bfb2c..1c2705e 100644 --- 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
@@ -10,6 +10,7 @@ import static org.junit.Assert.assertTrue; 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; @@ -42,6 +43,7 @@ .addKeepMainRule(Main.class) .addHorizontallyMergedClassesInspector( inspector -> inspector.assertIsCompleteMergeGroup(A.class, B.class, C.class)) + .enableInliningAnnotations() .enableNeverClassInliningAnnotations() .setMinApi(parameters.getApiLevel()) .noMinification() @@ -82,6 +84,7 @@ @NeverClassInline static class A { + @NeverInline void m() { System.out.println("A"); } @@ -90,6 +93,7 @@ @NeverClassInline static class B { + @NeverInline void m() { System.out.println("B"); } @@ -98,6 +102,7 @@ @NeverClassInline static class C { + @NeverInline void m() { System.out.println("C"); }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/AvoidInliningRecursiveMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/AvoidInliningRecursiveMethodTest.java index 3a501a6..d231fdf 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/AvoidInliningRecursiveMethodTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/AvoidInliningRecursiveMethodTest.java
@@ -53,9 +53,7 @@ MethodSubject mainMethodSubject = classSubject.mainMethod(); assertThat(mainMethodSubject, isPresent()); - - // TODO(b/145276800): Should not inline recursive methods. - assertTrue(mainMethodSubject.streamInstructions().anyMatch(InstructionSubject::isIf)); + assertTrue(mainMethodSubject.streamInstructions().noneMatch(InstructionSubject::isIf)); } static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/DoubleInliningNullCheckTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/DoubleInliningNullCheckTest.java index 0edb4cf..94fba01 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/DoubleInliningNullCheckTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/DoubleInliningNullCheckTest.java
@@ -22,7 +22,7 @@ @Parameters(name = "{0}") public static TestParametersCollection params() { - return getTestParameters().withAllRuntimes().build(); + return getTestParameters().withAllRuntimesAndApiLevels().build(); } private final TestParameters parameters; @@ -37,16 +37,17 @@ .addInnerClasses(DoubleInliningNullCheckTest.class) .addKeepMainRule(TestClass.class) .noMinification() - .setMinApi(parameters.getRuntime()) + .setMinApi(parameters.getApiLevel()) .run(parameters.getRuntime(), TestClass.class) .assertSuccessWithOutputLines("true") - .inspect(codeInspector -> { - ClassSubject main = codeInspector.clazz(TestClass.class); - assertThat(main, isPresent()); - MethodSubject mainMethod = main.mainMethod(); - assertThat(mainMethod, isPresent()); - assertEquals(0, countCall(mainMethod, "checkParameterIsNotNull")); - }); + .inspect( + codeInspector -> { + ClassSubject main = codeInspector.clazz(TestClass.class); + assertThat(main, isPresent()); + MethodSubject mainMethod = main.mainMethod(); + assertThat(mainMethod, isPresent()); + assertEquals(0, countCall(mainMethod, "checkParameterIsNotNull")); + }); } static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineFunctionalInterfaceMethodImplementedByLambdasTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineFunctionalInterfaceMethodImplementedByLambdasTest.java index 8a999ff..587795f 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineFunctionalInterfaceMethodImplementedByLambdasTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineFunctionalInterfaceMethodImplementedByLambdasTest.java
@@ -5,6 +5,7 @@ package com.android.tools.r8.ir.optimize.inliner; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static com.android.tools.r8.utils.codeinspector.Matchers.notIf; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; @@ -52,7 +53,8 @@ assertThat(inspector.clazz(I.class), isPresent()); } - assertThat(inspector.clazz(A.class), not(isPresent())); + // When compiling to DEX, A.m() will be single caller inlined in the second optimization pass. + assertThat(inspector.clazz(A.class), notIf(isPresent(), parameters.isDexRuntime())); } static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningIntoVisibilityBridgeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningIntoVisibilityBridgeTest.java index dc74e0f..841c369 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningIntoVisibilityBridgeTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningIntoVisibilityBridgeTest.java
@@ -47,7 +47,7 @@ @Test public void test() throws Exception { - String expectedOutput = StringUtils.lines("Hello world", "Hello world", "Hello world"); + String expectedOutput = StringUtils.times(StringUtils.lines("Hello world"), 6); R8TestRunResult result = testForR8(parameters.getBackend()) @@ -91,8 +91,11 @@ static class TestClass { public static void main(String[] args) { - // Invoke method three times to prevent the synthetic bridge on InliningIntoVisibilityBridge- - // TestClassB from being inlined. + // Invoke method multiple times to prevent the synthetic bridge on + // InliningIntoVisibilityBridgeTestClassB from being inlined. + InliningIntoVisibilityBridgeTestClassC.method(); + InliningIntoVisibilityBridgeTestClassC.method(); + InliningIntoVisibilityBridgeTestClassC.method(); InliningIntoVisibilityBridgeTestClassC.method(); InliningIntoVisibilityBridgeTestClassC.method(); InliningIntoVisibilityBridgeTestClassC.method();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetFromExactReceiverTypeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetFromExactReceiverTypeTest.java index 7c9b7b7..e7b6931 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetFromExactReceiverTypeTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetFromExactReceiverTypeTest.java
@@ -10,6 +10,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertTrue; +import com.android.tools.r8.AlwaysInline; import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; import com.android.tools.r8.TestBase; @@ -32,7 +33,7 @@ @Parameters(name = "{0}") public static TestParametersCollection data() { - return getTestParameters().withAllRuntimes().build(); + return getTestParameters().withAllRuntimesAndApiLevels().build(); } public SingleTargetFromExactReceiverTypeTest(TestParameters parameters) { @@ -48,9 +49,10 @@ "-keepclassmembers class " + A.class.getTypeName() + " {", " void cannotBeInlinedDueToKeepRule();", "}") + .enableAlwaysInliningAnnotations() .enableNeverClassInliningAnnotations() .enableInliningAnnotations() - .setMinApi(parameters.getRuntime()) + .setMinApi(parameters.getApiLevel()) .compile() .inspect(this::verifyOnlyCanBeInlinedHasBeenInlined) .run(parameters.getRuntime(), TestClass.class) @@ -135,6 +137,7 @@ System.out.println("A.canBeInlined()"); } + @AlwaysInline public void canBeInlinedDueToAssume() { System.out.println("A.canBeInlinedDueToAssume()"); }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java index 615aa20..f3a61c4 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -205,8 +205,6 @@ assertEquals( Lists.newArrayList( "STATIC: String TrivialTestClass.next()", - "STATIC: void SimpleWithThrowingGetter.getInstance()", - "STATIC: void SimpleWithThrowingGetter.getInstance()", "SimpleWithThrowingGetter SimpleWithThrowingGetter.INSTANCE", "VIRTUAL: String SimpleWithThrowingGetter.bar(String)", "VIRTUAL: String SimpleWithThrowingGetter.foo()"),
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineChainTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineChainTest.java index da2f568..f72637c 100644 --- a/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineChainTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineChainTest.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.KotlinTestBase; import com.android.tools.r8.KotlinTestParameters; import com.android.tools.r8.TestParameters; +import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.MethodSubject; @@ -77,9 +78,13 @@ MethodSubject main = mainClass.mainMethod(); long checkParameterIsNotNull = countCall(main, "checkParameterIsNotNull"); long checkNotNullParameter = countCall(main, "checkNotNullParameter"); - if (kotlinParameters.is(KotlinCompilerVersion.KOTLINC_1_3_72)) { - assertEquals(1, checkParameterIsNotNull); + if (parameters.isDexRuntime() + && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.I)) { assertEquals(0, checkNotNullParameter); + assertEquals(0, checkParameterIsNotNull); + } else if (kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72)) { + assertEquals(0, checkNotNullParameter); + assertEquals(1, checkParameterIsNotNull); } else { assertEquals(1, checkNotNullParameter); assertEquals(0, checkParameterIsNotNull);
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java index a03ebb2..6d64e37 100644 --- a/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java
@@ -13,6 +13,7 @@ import com.android.tools.r8.KotlinTestBase; import com.android.tools.r8.KotlinTestParameters; import com.android.tools.r8.TestParameters; +import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.codeinspector.ClassSubject; @@ -90,7 +91,10 @@ @Test public void b139432507_isSupported() throws Exception { assumeTrue("Different inlining behavior on CF backend", parameters.isDexRuntime()); - testSingle("isSupported", kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72)); + testSingle( + "isSupported", + kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72) + && parameters.getApiLevel().isLessThan(AndroidApiLevel.I)); } @Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java index 8544744..3486422 100644 --- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
@@ -4,6 +4,10 @@ package com.android.tools.r8.kotlin; +import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72; +import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_4_20; +import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLIN_DEV; + import com.android.tools.r8.KotlinTestParameters; import com.android.tools.r8.TestParameters; import com.android.tools.r8.kotlin.TestKotlinClass.Visibility; @@ -212,8 +216,16 @@ .addOptionsModification(disableClassInliner)) .inspect( inspector -> { - ClassSubject dataClass = checkClassIsKept(inspector, TEST_DATA_CLASS.getClassName()); - checkMethodIsRemoved(dataClass, COPY_DEFAULT_METHOD); + // TODO(b/210828502): Investigate why Person is not removed with kotlin dev. + if (allowAccessModification + && !(kotlinc.isOneOf(KOTLINC_1_3_72, KOTLINC_1_4_20, KOTLIN_DEV) + && testParameters.isCfRuntime())) { + checkClassIsRemoved(inspector, TEST_DATA_CLASS.getClassName()); + } else { + ClassSubject dataClass = + checkClassIsKept(inspector, TEST_DATA_CLASS.getClassName()); + checkMethodIsRemoved(dataClass, COPY_DEFAULT_METHOD); + } }); } }
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java index 5a60483..c979be9 100644 --- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -556,11 +556,7 @@ PACKAGE_NAME, mainClass, testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) - .inspect( - inspector -> { - checkClassIsRemoved(inspector, testedClass.getClassName()); - checkClassIsRemoved(inspector, testedClass.getClassName()); - }); + .inspect(inspector -> checkClassIsRemoved(inspector, testedClass.getClassName())); } @Test
diff --git a/src/test/java/com/android/tools/r8/naming/adaptclassstrings/AdaptClassStringKeepTest.java b/src/test/java/com/android/tools/r8/naming/adaptclassstrings/AdaptClassStringKeepTest.java new file mode 100644 index 0000000..fd9cce7 --- /dev/null +++ b/src/test/java/com/android/tools/r8/naming/adaptclassstrings/AdaptClassStringKeepTest.java
@@ -0,0 +1,80 @@ +// 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.naming.adaptclassstrings; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assume.assumeTrue; + +import com.android.tools.r8.ProguardVersion; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.BooleanUtils; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class AdaptClassStringKeepTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameter(1) + public boolean isCompat; + + @Parameter(2) + public ProguardVersion proguardVersion; + + @Parameters(name = "{0}, isCompat: {1}") + public static List<Object[]> data() { + return buildParameters( + getTestParameters().withSystemRuntime().build(), + BooleanUtils.values(), + ProguardVersion.values()); + } + + @Test + public void testProguard() throws Exception { + assumeTrue(isCompat); + testForProguard(proguardVersion) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .addKeepRules("-adaptclassstrings") + .addDontWarn(AdaptClassStringKeepTest.class) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("com.android.tools.r8.naming.adaptclassstrings.a") + .inspect(inspector -> assertThat(inspector.clazz(Foo.class), isPresent())); + } + + @Test + public void testR8() throws Exception { + assumeTrue(proguardVersion == ProguardVersion.getLatest()); + (isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend())) + .addInnerClasses(getClass()) + .setMinApi(AndroidApiLevel.B) + .addKeepMainRule(Main.class) + .addKeepRules("-adaptclassstrings") + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines(Foo.class.getName()) + // TODO(b/210825389): We currently interpret -adaptclasstrings without pinning the class + .inspect(inspector -> assertThat(inspector.clazz(Foo.class), isAbsent())); + } + + public static class Foo {} + + public static class Main { + + public static void main(String[] args) { + System.out.println( + "com.android.tools.r8.naming.adaptclassstrings.AdaptClassStringKeepTest$Foo"); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/naming/adaptclassstrings/AdaptClassStringWithRepackagingTest.java b/src/test/java/com/android/tools/r8/naming/adaptclassstrings/AdaptClassStringWithRepackagingTest.java new file mode 100644 index 0000000..06557fa --- /dev/null +++ b/src/test/java/com/android/tools/r8/naming/adaptclassstrings/AdaptClassStringWithRepackagingTest.java
@@ -0,0 +1,52 @@ +// 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.naming.adaptclassstrings; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class AdaptClassStringWithRepackagingTest extends TestBase { + + @Parameter() public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .setMinApi(parameters.getApiLevel()) + .addKeepMainRule(Main.class) + .addKeepRules("-repackageclasses ''") + .addKeepRules("-adaptclassstrings") + .addKeepClassRulesWithAllowObfuscation(Foo.class) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("a") + .inspect(inspector -> assertThat(inspector.clazz(Foo.class), isPresentAndRenamed())); + } + + public static class Foo {} + + public static class Main { + + public static void main(String[] args) { + System.out.println( + "com.android.tools.r8.naming.adaptclassstrings.AdaptClassStringWithRepackagingTest$Foo"); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/naming/adaptclassstrings/RepackageMinificationNameClashTest.java b/src/test/java/com/android/tools/r8/naming/adaptclassstrings/RepackageMinificationNameClashTest.java new file mode 100644 index 0000000..1f23283 --- /dev/null +++ b/src/test/java/com/android/tools/r8/naming/adaptclassstrings/RepackageMinificationNameClashTest.java
@@ -0,0 +1,48 @@ +// 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.naming.adaptclassstrings; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +/** This is a regression test for b/210699098 */ +public class RepackageMinificationNameClashTest extends TestBase { + + @Parameter() public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .setMinApi(parameters.getApiLevel()) + .addKeepRules("-repackageclasses ''") + .addKeepRules("-adaptclassstrings") + .addKeepClassRulesWithAllowObfuscation(Foo.class) + .addKeepMainRule(Main.class) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("RepackageMinificationNameClashTest$Foo"); + } + + public static class Foo {} + + public static class Main { + + public static void main(String[] args) { + System.out.println("RepackageMinificationNameClashTest$Foo"); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingMethodTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingMethodTest.java index 3cb240b..57b70d2 100644 --- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingMethodTest.java +++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingMethodTest.java
@@ -11,7 +11,6 @@ import com.android.tools.r8.CompilationFailedException; import com.android.tools.r8.NeverInline; import com.android.tools.r8.NeverPropagateValue; -import com.android.tools.r8.NoVerticalClassMerging; import com.android.tools.r8.R8TestCompileResult; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; @@ -80,9 +79,7 @@ } } - private static final Class<?>[] LIBRARY_CLASSES = { - NoVerticalClassMerging.class, LibraryBase.class, LibrarySubclass.class - }; + private static final Class<?>[] LIBRARY_CLASSES = {LibraryBase.class, LibrarySubclass.class}; private static final Class<?>[] PROGRAM_CLASSES = { ProgramClass.class @@ -107,13 +104,15 @@ } private static R8TestCompileResult compileLibrary(Backend backend) - throws CompilationFailedException, IOException, ExecutionException { + throws CompilationFailedException, IOException { return testForR8(staticTemp, backend) .enableInliningAnnotations() .enableMemberValuePropagationAnnotations() .addProgramClasses(LIBRARY_CLASSES) .addKeepMainRule(LibrarySubclass.class) .addKeepClassAndDefaultConstructor(LibrarySubclass.class) + .addVerticallyMergedClassesInspector( + inspector -> inspector.assertMergedIntoSubtype(LibraryBase.class)) .setMinApi(AndroidApiLevel.B) .compile() .inspect(
diff --git a/src/test/java/com/android/tools/r8/resolution/InvokeSuperCallInStaticTest.java b/src/test/java/com/android/tools/r8/resolution/InvokeSuperCallInStaticTest.java index 09e9882..8d5a997 100644 --- a/src/test/java/com/android/tools/r8/resolution/InvokeSuperCallInStaticTest.java +++ b/src/test/java/com/android/tools/r8/resolution/InvokeSuperCallInStaticTest.java
@@ -84,6 +84,7 @@ .addProgramClasses(Base.class, Main.class) .addProgramClassFileData(getAWithRewrittenInvokeSpecialToBase()) .addKeepMainRule(Main.class) + .allowAccessModification() .setMinApi(parameters.getApiLevel()) .run(parameters.getRuntime(), Main.class) .assertSuccessWithOutputLines(EXPECTED)
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java index c6efef7..4726fe4 100644 --- a/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java +++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
@@ -3,9 +3,12 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.resolution; +import static org.junit.Assume.assumeTrue; + import com.android.tools.r8.AsmTestBase; +import com.android.tools.r8.R8TestBuilder; import com.android.tools.r8.TestParameters; -import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.TestShrinkerBuilder; import com.android.tools.r8.resolution.singletarget.Main; import com.android.tools.r8.resolution.singletarget.one.AbstractSubClass; import com.android.tools.r8.resolution.singletarget.one.AbstractTopClass; @@ -14,12 +17,14 @@ import com.android.tools.r8.resolution.singletarget.one.SubSubClassOne; import com.android.tools.r8.resolution.singletarget.one.SubSubClassThree; import com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo; +import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.StringUtils; import com.google.common.collect.ImmutableList; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; @RunWith(Parameterized.class) @@ -39,57 +44,26 @@ getBytesFromAsmClass(IrrelevantInterfaceWithDefaultDump::dump) ); - public static final String EXPECTED = - StringUtils.lines( - "SubSubClassOne", - "SubSubClassOne", - "AbstractTopClass", - "SubSubClassOne", - "AbstractTopClass", - "com.android.tools.r8.resolution.singletarget.one.AbstractSubClass", - "InterfaceWithDefault", - "InterfaceWithDefault", - "ICCE", - "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo", - "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo", - "AbstractTopClass", - "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo", - "AbstractTopClass", - "com.android.tools.r8.resolution.singletarget.one.AbstractSubClass", - "InterfaceWithDefault", - "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo", - "InterfaceWithDefault", - "InterfaceWithDefault", - "InterfaceWithDefault", - "ICCE", - "InterfaceWithDefault", - "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo", - "InterfaceWithDefault", - "InterfaceWithDefault", - "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo", - "InterfaceWithDefault", - "InterfaceWithDefault", - "InterfaceWithDefault", - "ICCE"); + @Parameter(0) + public boolean enableInliningAnnotations; - public final TestParameters parameters; + @Parameter(1) + public TestParameters parameters; - @Parameters(name = "{0}") - public static TestParametersCollection data() { - return getTestParameters().withAllRuntimesAndApiLevels().build(); - } - - public SingleTargetExecutionTest(TestParameters parameters) { - this.parameters = parameters; + @Parameters(name = "{1}, enable inlining annotations: {0}") + public static List<Object[]> data() { + return buildParameters( + BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build()); } @Test public void testReference() throws Exception { + assumeTrue(enableInliningAnnotations); testForRuntime(parameters) .addProgramClasses(CLASSES) .addProgramClassFileData(ASM_CLASSES) .run(parameters.getRuntime(), Main.class) - .assertSuccessWithOutput(EXPECTED); + .assertSuccessWithOutput(getExpectedOutput()); } @Test @@ -99,9 +73,51 @@ .addProgramClasses(CLASSES) .addProgramClassFileData(ASM_CLASSES) .addKeepMainRule(Main.class) + .applyIf( + enableInliningAnnotations, + R8TestBuilder::enableInliningAnnotations, + TestShrinkerBuilder::addInliningAnnotations) .enableNoHorizontalClassMergingAnnotations() .setMinApi(parameters.getApiLevel()) .run(parameters.getRuntime(), Main.class) - .assertSuccessWithOutput(EXPECTED); + .assertSuccessWithOutput(getExpectedOutput()); + } + + private String getExpectedOutput() { + String icceOrNot = + enableInliningAnnotations || !parameters.canUseDefaultAndStaticInterfaceMethods() + ? "ICCE" + : "InterfaceWithDefault"; + return StringUtils.lines( + "SubSubClassOne", + "SubSubClassOne", + "AbstractTopClass", + "SubSubClassOne", + "AbstractTopClass", + "com.android.tools.r8.resolution.singletarget.one.AbstractSubClass", + "InterfaceWithDefault", + "InterfaceWithDefault", + icceOrNot, + "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo", + "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo", + "AbstractTopClass", + "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo", + "AbstractTopClass", + "com.android.tools.r8.resolution.singletarget.one.AbstractSubClass", + "InterfaceWithDefault", + "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo", + "InterfaceWithDefault", + "InterfaceWithDefault", + "InterfaceWithDefault", + icceOrNot, + "InterfaceWithDefault", + "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo", + "InterfaceWithDefault", + "InterfaceWithDefault", + "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo", + "InterfaceWithDefault", + "InterfaceWithDefault", + "InterfaceWithDefault", + "ICCE"); } }
diff --git a/src/test/java/com/android/tools/r8/resolution/singletarget/one/InterfaceWithDefault.java b/src/test/java/com/android/tools/r8/resolution/singletarget/one/InterfaceWithDefault.java index f0faedd..b25ece9 100644 --- a/src/test/java/com/android/tools/r8/resolution/singletarget/one/InterfaceWithDefault.java +++ b/src/test/java/com/android/tools/r8/resolution/singletarget/one/InterfaceWithDefault.java
@@ -3,6 +3,8 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.resolution.singletarget.one; +import com.android.tools.r8.NeverInline; + public interface InterfaceWithDefault { // Avoid InterfaceWithDefault.class.getCanonicalName() as it may change during shrinking. @@ -16,6 +18,7 @@ System.out.println(TAG); } + @NeverInline default void overriddenInOtherInterface() { System.out.println(TAG); }
diff --git a/src/test/java/com/android/tools/r8/shaking/LambdaCaptureShrinkingTest.java b/src/test/java/com/android/tools/r8/shaking/LambdaCaptureShrinkingTest.java new file mode 100644 index 0000000..4efaeb4 --- /dev/null +++ b/src/test/java/com/android/tools/r8/shaking/LambdaCaptureShrinkingTest.java
@@ -0,0 +1,84 @@ +// 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.shaking; + +import static junit.framework.TestCase.assertTrue; + +import com.android.tools.r8.NoVerticalClassMerging; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.codeinspector.InstructionSubject; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +import java.util.Arrays; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class LambdaCaptureShrinkingTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters() + .withCfRuntimes() + .withDexRuntimes() + .withApiLevelsStartingAtIncluding(AndroidApiLevel.N) + .build(); + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .enableNoVerticalClassMergingAnnotations() + .setMinApi(parameters.getApiLevel()) + .compile() + .applyIf( + parameters.isCfRuntime(), + compileResult -> + compileResult.inspect( + inspector -> { + MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod(); + assertTrue( + mainMethodSubject + .streamInstructions() + .anyMatch(InstructionSubject::isInvokeDynamic)); + })) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("true"); + } + + static class Main { + + public static void main(String[] args) { + PredicateInterface f = new Predicate(); + System.out.println(Arrays.asList(args).stream().noneMatch(f::m)); + } + } + + @NoVerticalClassMerging + interface PredicateInterfaceBase { + boolean m(String item); + } + + @NoVerticalClassMerging + interface PredicateInterface extends PredicateInterfaceBase {} + + static class Predicate implements PredicateInterface { + + @Override + public boolean m(String item) { + return System.currentTimeMillis() > 0; + } + } +}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java index 6b01b51..12c0643 100644 --- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java +++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java
@@ -21,7 +21,7 @@ static class TestClass { - private static A field = new B(); + private static A field = System.currentTimeMillis() >= 0 ? new B() : null; public static void main(String[] args) { System.out.print(field.getClass().getName());
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java index c0ab377..a482eee 100644 --- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java +++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java
@@ -64,7 +64,7 @@ public static Collection<Object[]> data() { // We don't run this on Proguard, as Proguard does not merge A into B. return buildParameters( - getTestParameters().withAllRuntimes().build(), BooleanUtils.values()); + getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values()); } public void configure(R8FullTestBuilder builder) { @@ -105,7 +105,7 @@ "-keep class " + Unused.class.getTypeName(), getAdditionalKeepRules()) .noMinification() - .setMinApi(parameters.getRuntime()) + .setMinApi(parameters.getApiLevel()) .apply(this::configure) .run(parameters.getRuntime(), getTestClass()) .assertSuccessWithOutput(expected)
diff --git a/third_party/opensource-apps/empty-activity.tar.gz.sha1 b/third_party/opensource-apps/empty-activity.tar.gz.sha1 new file mode 100644 index 0000000..4244c67 --- /dev/null +++ b/third_party/opensource-apps/empty-activity.tar.gz.sha1
@@ -0,0 +1 @@ +ff20836a8bc101c9ec5ded3fe025013605453919 \ No newline at end of file
diff --git a/third_party/opensource-apps/empty-compose-activity.tar.gz.sha1 b/third_party/opensource-apps/empty-compose-activity.tar.gz.sha1 new file mode 100644 index 0000000..2f0c18e --- /dev/null +++ b/third_party/opensource-apps/empty-compose-activity.tar.gz.sha1
@@ -0,0 +1 @@ +a9ee18fef196b70fe4d5ca7e6a754383df0d1a59 \ No newline at end of file
diff --git a/third_party/protobuf-lite.tar.gz.sha1 b/third_party/protobuf-lite.tar.gz.sha1 index 6c757bb..77f4e56 100644 --- a/third_party/protobuf-lite.tar.gz.sha1 +++ b/third_party/protobuf-lite.tar.gz.sha1
@@ -1 +1 @@ -f5f3295899f7eecd937830fc8a0a35a4ef5b4083 \ No newline at end of file +047e2914d6898dc764803e7709eda60f7de1ecfa \ No newline at end of file
diff --git a/tools/build_sample_apk.py b/tools/build_sample_apk.py index a08a4ec..376c5c0 100755 --- a/tools/build_sample_apk.py +++ b/tools/build_sample_apk.py
@@ -159,7 +159,7 @@ for root, dirnames, filenames in os.walk(get_bin_path(app)): for filename in fnmatch.filter(filenames, '*.class'): files.append(os.path.join(root, filename)) - command = [DEFAULT_D8, + command = [DEFAULT_D8, '--', '--output', get_bin_path(app), '--classpath', utils.get_android_jar(api), '--min-api', str(api)]
diff --git a/tools/internal_test.py b/tools/internal_test.py index 47d236e..dae6247 100755 --- a/tools/internal_test.py +++ b/tools/internal_test.py
@@ -109,6 +109,7 @@ '--max-memory=%s' % int(record['oom-threshold'] * 0.85) ] +# TODO(b/210982978): Enable testing of min xmx again TEST_COMMANDS = [ # Run test.py internal testing. ['tools/test.py', '--only_internal', '--slow_tests', @@ -117,9 +118,7 @@ ['tools/run_on_app.py', '--run-all', '--out=out'], # Find min xmx for selected benchmark apps ['tools/gradle.py', 'r8lib'], -] + (map(find_min_xmx_command, BENCHMARK_APPS) - + map(compile_with_memory_max_command, BENCHMARK_APPS) - + map(compile_with_memory_min_command, BENCHMARK_APPS)) +] # Command timeout, in seconds. RUN_TIMEOUT = 3600 * 6
diff --git a/tools/run_on_app_dump.py b/tools/run_on_app_dump.py index 750a877..c55a6f7 100755 --- a/tools/run_on_app_dump.py +++ b/tools/run_on_app_dump.py
@@ -131,6 +131,24 @@ # not abstract 'compiler_properties': ['-Dcom.android.tools.r8.allowInvalidCfAccessFlags=true'] }), + App({ + 'id': 'com.example.myapplication', + 'name': 'empty-activity', + 'dump_app': 'dump_app.zip', + 'apk_app': 'app-release.apk', + 'url': 'https://github.com/christofferqa/empty_android_activity.git', + 'revision': '2d297ec3373dadb03cbae916b9feba4792563156', + 'folder': 'empty-activity', + }), + App({ + 'id': 'com.example.emptycomposeactivity', + 'name': 'empty-compose-activity', + 'dump_app': 'dump_app.zip', + 'apk_app': 'app-release.apk', + 'url': 'https://github.com/christofferqa/empty_android_compose_activity.git', + 'revision': '3c8111b8b7d6e9184049a07e2b96702d7b33d03e', + 'folder': 'empty-compose-activity', + }), # TODO(b/172539375): Monkey runner fails on recompilation. App({ 'id': 'com.google.firebase.example.fireeats',