[ApiModel] Allow higher api type references without inlining
Bug: 207601948
Change-Id: I557feef557b15f77fb043254393f41642e3fb8c6
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 900e79c..25429fd 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -822,21 +822,4 @@
return checksumSupplier;
}
- public ComputedApiLevel getApiReferenceLevel(
- AppView<?> appView, AndroidApiLevelCompute apiLevelCompute) {
- // The api level of a class is the max level of it's members, super class and interfaces.
- return getMembersApiReferenceLevel(
- apiLevelCompute.computeApiLevelForDefinition(
- allImmediateSupertypes(), apiLevelCompute.getPlatformApiLevelOrUnknown(appView)));
- }
-
- public ComputedApiLevel getMembersApiReferenceLevel(ComputedApiLevel memberLevel) {
- for (DexEncodedMember<?, ?> member : members()) {
- memberLevel = memberLevel.max(member.getApiLevel());
- if (memberLevel.isUnknownApiLevel()) {
- return memberLevel;
- }
- }
- return memberLevel;
- }
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDifferentApiReferenceLevel.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDifferentApiReferenceLevel.java
index 1f953f4..6a083f0 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDifferentApiReferenceLevel.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDifferentApiReferenceLevel.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.horizontalclassmerging.policies;
+import static com.android.tools.r8.utils.AndroidApiLevelUtils.getApiReferenceLevelForMerging;
+
import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
import com.android.tools.r8.androidapi.ComputedApiLevel;
import com.android.tools.r8.graph.AppView;
@@ -37,6 +39,6 @@
@Override
public ComputedApiLevel getMergeKey(DexProgramClass clazz) {
assert enableApiCallerIdentification;
- return clazz.getApiReferenceLevel(appView, apiLevelCompute);
+ return getApiReferenceLevelForMerging(appView, apiLevelCompute, clazz);
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
index 227cbf7..a5b539b 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -220,14 +220,6 @@
}
private void setMaxApiReferenceLevel(DexReference reference) {
- if (reference.isDexMember()) {
- maxApiReferenceLevel =
- maxApiReferenceLevel.max(
- apiLevelCompute.computeApiLevelForDefinition(
- reference.asDexMember(),
- appView.dexItemFactory(),
- apiLevelCompute.getPlatformApiLevelOrUnknown(appView)));
- }
maxApiReferenceLevel =
maxApiReferenceLevel.max(
apiLevelCompute.computeApiLevelForLibraryReference(
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 9396f50..9822a54 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.utils.AndroidApiLevelUtils.getApiReferenceLevelForMerging;
import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
import com.android.tools.r8.androidapi.ComputedApiLevel;
@@ -531,8 +532,10 @@
// Only merge if api reference level of source class is equal to target class. The check is
// somewhat expensive.
if (appView.options().apiModelingOptions().enableApiCallerIdentification) {
- ComputedApiLevel sourceApiLevel = sourceClass.getApiReferenceLevel(appView, apiLevelCompute);
- ComputedApiLevel targetApiLevel = targetClass.getApiReferenceLevel(appView, apiLevelCompute);
+ ComputedApiLevel sourceApiLevel =
+ getApiReferenceLevelForMerging(appView, apiLevelCompute, sourceClass);
+ ComputedApiLevel targetApiLevel =
+ getApiReferenceLevelForMerging(appView, apiLevelCompute, targetClass);
if (!sourceApiLevel.equals(targetApiLevel)) {
if (Log.ENABLED) {
AbortReason.API_REFERENCE_LEVEL.printLogMessageForClass(sourceClass);
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
index 73cf09f..089ccce 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
@@ -6,6 +6,9 @@
import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
import com.android.tools.r8.androidapi.ComputedApiLevel;
+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.LibraryMethod;
import com.android.tools.r8.graph.ProgramMethod;
@@ -19,12 +22,38 @@
if (caller.getHolderType() == inlinee.getHolderType()) {
return true;
}
+ // For inlining we only measure if the code has invokes into the library.
return caller
.getDefinition()
- .getApiLevel()
+ .getApiLevelForCode()
.isGreaterThanOrEqualTo(inlinee.getDefinition().getApiLevelForCode());
}
+ public static ComputedApiLevel getApiReferenceLevelForMerging(
+ AppView<?> appView, AndroidApiLevelCompute apiLevelCompute, DexProgramClass clazz) {
+ // The api level of a class is the max level of it's members, super class and interfaces.
+ return getMembersApiReferenceLevelForMerging(
+ clazz,
+ apiLevelCompute.computeApiLevelForDefinition(
+ clazz.allImmediateSupertypes(), apiLevelCompute.getPlatformApiLevelOrUnknown(appView)));
+ }
+
+ private static ComputedApiLevel getMembersApiReferenceLevelForMerging(
+ DexProgramClass clazz, ComputedApiLevel memberLevel) {
+ // Based on b/138781768#comment57 there is almost no penalty for having an unknown reference
+ // as long as we are not invoking or accessing a field on it. Therefore we can disregard static
+ // types of fields and only consider method code api levels.
+ for (DexEncodedMethod method : clazz.methods()) {
+ if (method.hasCode()) {
+ memberLevel = memberLevel.max(method.getApiLevelForCode());
+ }
+ if (memberLevel.isUnknownApiLevel()) {
+ return memberLevel;
+ }
+ }
+ return memberLevel;
+ }
+
public static boolean isApiSafeForMemberRebinding(
LibraryMethod method,
AndroidApiLevelCompute androidApiLevelCompute,
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java
index 7c4226c..c41c5e2 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java
@@ -44,14 +44,7 @@
.enableInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
.addHorizontallyMergedClassesInspector(
- inspector -> {
- if (parameters.isDexRuntime()
- && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L_MR1)) {
- inspector.assertClassesMerged(A.class, B.class);
- } else {
- inspector.assertNoClassesMerged();
- }
- })
+ inspector -> inspector.assertClassesMerged(A.class, B.class))
.apply(ApiModelingTestHelper::enableApiCallerIdentification)
.apply(ApiModelingTestHelper::disableCheckAllApiReferencesAreNotUnknown)
.apply(setMockApiLevelForClass(Api.class, AndroidApiLevel.L_MR1))
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldTypeReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldTypeReferenceTest.java
index 149c93e..302cca5 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldTypeReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldTypeReferenceTest.java
@@ -53,9 +53,9 @@
if (Reference.methodFromMethod(readApiField).equals(method)
|| Reference.methodFromMethod(setApiField).equals(method)) {
if (parameters.isCfRuntime()) {
- assertEquals(AndroidApiLevel.L_MR1, apiLevel);
+ assertEquals(AndroidApiLevel.B, apiLevel);
} else {
- assertEquals(AndroidApiLevel.L_MR1.max(parameters.getApiLevel()), apiLevel);
+ assertEquals(AndroidApiLevel.B.max(parameters.getApiLevel()), apiLevel);
}
}
}))
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMethodTypeReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMethodTypeReferenceTest.java
index d3d9572..c4308ed 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMethodTypeReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMethodTypeReferenceTest.java
@@ -53,9 +53,9 @@
if (Reference.methodFromMethod(readApi).equals(method)
|| Reference.methodFromMethod(setApi).equals(method)) {
if (parameters.isCfRuntime()) {
- assertEquals(AndroidApiLevel.L_MR1, apiLevel);
+ assertEquals(AndroidApiLevel.B, apiLevel);
} else {
- assertEquals(AndroidApiLevel.L_MR1.max(parameters.getApiLevel()), apiLevel);
+ assertEquals(AndroidApiLevel.B.max(parameters.getApiLevel()), apiLevel);
}
}
}))
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelTypeReferenceInvokeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelTypeReferenceInvokeTest.java
index 01e9dc7..ef4737d 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelTypeReferenceInvokeTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelTypeReferenceInvokeTest.java
@@ -51,8 +51,9 @@
.enableInliningAnnotations()
.addAndroidBuildVersion()
.compile()
- // TODO(b/207601948): We should only inline from api level M.
- .inspect(verifyThat(parameters, apiCaller).inlinedInto(apiCallerCaller))
+ .inspect(
+ verifyThat(parameters, apiCaller)
+ .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.M))
.addRunClasspathClasses(LibraryClass.class)
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLinesIf(