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',