Allow class inlining of merged singleton classes
Bug: 173337498
Change-Id: I317e3e77cb502f7a42b97cbabb6c080ab8739a4e
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index eb016fe..37b3796 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -224,8 +224,6 @@
anyInlinedMethods = true;
}
- assert inliningIRProvider.verifyIRCacheIsEmpty();
-
// Restore normality.
code.removeAllDeadAndTrivialPhis(affectedValues);
if (!affectedValues.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 4b15332..23d9242 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
@@ -25,11 +25,12 @@
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
import com.android.tools.r8.ir.code.AliasedValueConfiguration;
import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.CheckCast;
-import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.InstanceGet;
@@ -59,7 +60,6 @@
import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.OptionalBool;
-import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.Timing;
@@ -93,6 +93,7 @@
private Value eligibleInstance;
private DexProgramClass eligibleClass;
+ private ObjectState objectState;
private final Map<InvokeMethodWithReceiver, InliningInfo> methodCallsOnInstance =
new IdentityHashMap<>();
@@ -100,8 +101,6 @@
private final ProgramMethodSet indirectMethodCallsOnInstance = ProgramMethodSet.create();
private final Map<InvokeMethod, InliningInfo> extraMethodCalls
= new IdentityHashMap<>();
- private final List<Pair<InvokeMethod, Integer>> unusedArguments
- = new ArrayList<>();
private final Map<InvokeMethod, ProgramMethod> directInlinees = new IdentityHashMap<>();
private final List<ProgramMethod> indirectInlinees = new ArrayList<>();
@@ -191,6 +190,11 @@
if (eligibleClass == null) {
return EligibilityStatus.NOT_ELIGIBLE;
}
+ AbstractValue abstractValue = optimizationInfo.getAbstractValue();
+ objectState =
+ abstractValue.isSingleFieldValue()
+ ? abstractValue.asSingleFieldValue().getState()
+ : ObjectState.empty();
return EligibilityStatus.ELIGIBLE;
}
@@ -382,7 +386,6 @@
// Process inlining, includes the following steps:
//
// * remove linked assume instructions if any so that users of the eligible field are up-to-date.
- // * replace unused instance usages as arguments which are never used
// * inline extra methods if any, collect new direct method calls
// * inline direct methods if any
// * remove superclass initializer call and field reads
@@ -398,7 +401,6 @@
throws IllegalClassInlinerStateException {
// Verify that `eligibleInstance` is not aliased.
assert eligibleInstance == eligibleInstance.getAliasedValue();
- replaceUsagesAsUnusedArgument(code);
boolean anyInlinedMethods = forceInlineExtraMethodInvocations(code, inliningIRProvider);
if (anyInlinedMethods) {
@@ -412,8 +414,6 @@
}
assert extraMethodCalls.isEmpty()
: "Remaining extra method calls: " + StringUtils.join(extraMethodCalls.entrySet(), ", ");
- assert unusedArguments.isEmpty()
- : "Remaining unused arg: " + StringUtils.join(unusedArguments, ", ");
}
anyInlinedMethods |= forceInlineDirectMethodInvocations(code, inliningIRProvider);
@@ -431,26 +431,9 @@
methodCallsOnInstance.clear();
indirectMethodCallsOnInstance.clear();
extraMethodCalls.clear();
- unusedArguments.clear();
receivers.reset();
}
- private void replaceUsagesAsUnusedArgument(IRCode code) {
- for (Pair<InvokeMethod, Integer> unusedArgument : unusedArguments) {
- InvokeMethod invoke = unusedArgument.getFirst();
- BasicBlock block = invoke.getBlock();
-
- ConstNumber nullValue = code.createConstNull();
- nullValue.setPosition(invoke.getPosition());
- block.listIterator(code, invoke).add(nullValue);
- assert nullValue.getBlock() == block;
-
- int argIndex = unusedArgument.getSecond();
- invoke.replaceValue(argIndex, nullValue.outValue());
- }
- unusedArguments.clear();
- }
-
private boolean forceInlineExtraMethodInvocations(
IRCode code, InliningIRProvider inliningIRProvider) {
if (extraMethodCalls.isEmpty()) {
@@ -737,6 +720,7 @@
new TreeSet<>(Comparator.comparingInt(x -> x.outValue().getNumber()));
for (Instruction user : eligibleInstance.uniqueUsers()) {
if (user.isInstanceGet()) {
+ assert root.isNewInstance();
if (user.outValue().hasAnyUsers()) {
uniqueInstanceGetUsersWithDeterministicOrder.add(user.asInstanceGet());
} else {
@@ -748,6 +732,7 @@
if (user.isInstancePut()) {
// Skip in this iteration since these instructions are needed to properly calculate what
// value should field reads be replaced with.
+ assert root.isNewInstance();
continue;
}
@@ -1111,7 +1096,7 @@
assert root.isStaticGet();
return classInlinerMethodConstraint.isEligibleForStaticGetClassInlining(
- singleTarget, parameter);
+ appView, parameter, objectState);
}
private boolean isExtraMethodCall(InvokeMethod invoke) {
@@ -1136,10 +1121,6 @@
// Analyzes if a method invoke the eligible instance is passed to is eligible. In short,
// it can be eligible if:
//
- // -- eligible instance is passed as argument #N which is not used in the method,
- // such cases are collected in 'unusedArguments' parameter and later replaced
- // with 'null' value
- //
// -- eligible instance is passed as argument #N which is only used in the method to
// call a method on this object (we call it indirect method call), and method is
// eligible according to the same rules defined for direct method call eligibility
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java
index 0c6d335e..ff68dfd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java
@@ -4,8 +4,11 @@
package com.android.tools.r8.ir.optimize.classinliner.constraint;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsage;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
public class AlwaysFalseClassInlinerMethodConstraint implements ClassInlinerMethodConstraint {
@@ -34,7 +37,8 @@
}
@Override
- public boolean isEligibleForStaticGetClassInlining(ProgramMethod method, int parameter) {
+ public boolean isEligibleForStaticGetClassInlining(
+ AppView<AppInfoWithLiveness> appView, int parameter, ObjectState objectState) {
return false;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java
index c86f373..ce14bb4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java
@@ -4,8 +4,11 @@
package com.android.tools.r8.ir.optimize.classinliner.constraint;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsage;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
public interface ClassInlinerMethodConstraint {
@@ -15,7 +18,8 @@
boolean isEligibleForNewInstanceClassInlining(ProgramMethod method, int parameter);
- boolean isEligibleForStaticGetClassInlining(ProgramMethod method, int parameter);
+ boolean isEligibleForStaticGetClassInlining(
+ AppView<AppInfoWithLiveness> appView, int parameter, ObjectState objectState);
static AlwaysFalseClassInlinerMethodConstraint alwaysFalse() {
return AlwaysFalseClassInlinerMethodConstraint.getInstance();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java
index 5c384e8..8ad6b4a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java
@@ -4,12 +4,18 @@
package com.android.tools.r8.ir.optimize.classinliner.constraint;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
import com.android.tools.r8.ir.optimize.classinliner.analysis.AnalysisContext;
import com.android.tools.r8.ir.optimize.classinliner.analysis.AnalysisState;
import com.android.tools.r8.ir.optimize.classinliner.analysis.NonEmptyParameterUsage;
import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsage;
import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsagePerContext;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@@ -51,7 +57,8 @@
}
@Override
- public boolean isEligibleForStaticGetClassInlining(ProgramMethod method, int parameter) {
+ public boolean isEligibleForStaticGetClassInlining(
+ AppView<AppInfoWithLiveness> appView, int parameter, ObjectState objectState) {
AnalysisContext defaultContext = AnalysisContext.getDefaultContext();
ParameterUsage usage = usages.get(parameter).get(defaultContext);
if (usage.isBottom()) {
@@ -72,9 +79,13 @@
// We will not be able to remove the monitor instruction afterwards.
return false;
}
- if (!knownUsage.getFieldsReadFromParameter().isEmpty()) {
- // We don't know the value of the field.
- return false;
+ for (DexField fieldReadFromParameter : knownUsage.getFieldsReadFromParameter()) {
+ DexClass holder = appView.definitionFor(fieldReadFromParameter.getHolderType());
+ DexEncodedField definition = fieldReadFromParameter.lookupOnClass(holder);
+ if (definition == null
+ || !objectState.getAbstractFieldValue(definition).isSingleConstValue()) {
+ return false;
+ }
}
return true;
}