Reland "Allow class inlining of merged singleton classes"
This reverts commit d9a87124658bb6b40c0ed4ae4c24a84f3fac1067.
Change-Id: I004faadd720fd800b9581677334d93095c9850cc
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
index 8bd65e4..a512bd4 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
@@ -53,6 +53,10 @@
return false;
}
+ public SingleConstValue asSingleConstValue() {
+ return null;
+ }
+
public boolean isSingleConstClassValue() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstValue.java
index ecf9838..11d238a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstValue.java
@@ -10,4 +10,9 @@
public boolean isSingleConstValue() {
return true;
}
+
+ @Override
+ public SingleConstValue asSingleConstValue() {
+ return this;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 689514f..4c1d942 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -309,7 +309,11 @@
}
public void replace(Instruction newInstruction, IRCode code) {
- getBlock().listIterator(code, this).replaceCurrentInstruction(newInstruction);
+ replace(newInstruction, code, null);
+ }
+
+ public void replace(Instruction newInstruction, IRCode code, Set<Value> affectedValues) {
+ getBlock().listIterator(code, this).replaceCurrentInstruction(newInstruction, affectedValues);
}
/**
diff --git a/src/main/java/com/android/tools/r8/ir/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..91a2d83 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
@@ -10,6 +10,7 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AccessControl;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -25,16 +26,19 @@
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.analysis.value.SingleConstValue;
import com.android.tools.r8.ir.code.AliasedValueConfiguration;
import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.CheckCast;
-import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.InstanceGet;
import com.android.tools.r8.ir.code.InstancePut;
import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InstructionOrPhi;
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
@@ -59,14 +63,11 @@
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;
import com.android.tools.r8.utils.WorkList;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Comparator;
@@ -93,6 +94,7 @@
private Value eligibleInstance;
private DexProgramClass eligibleClass;
+ private ObjectState objectState;
private final Map<InvokeMethodWithReceiver, InliningInfo> methodCallsOnInstance =
new IdentityHashMap<>();
@@ -100,8 +102,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 +191,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 +387,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 +402,6 @@
throws IllegalClassInlinerStateException {
// Verify that `eligibleInstance` is not aliased.
assert eligibleInstance == eligibleInstance.getAliasedValue();
- replaceUsagesAsUnusedArgument(code);
boolean anyInlinedMethods = forceInlineExtraMethodInvocations(code, inliningIRProvider);
if (anyInlinedMethods) {
@@ -412,8 +415,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 +432,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()) {
@@ -733,11 +717,24 @@
// Replace field reads with appropriate values, insert phis when needed.
private void removeFieldReads(IRCode code) {
+ Set<Value> affectedValues = Sets.newIdentityHashSet();
+ if (root.isNewInstance()) {
+ removeFieldReadsFromNewInstance(code, affectedValues);
+ } else {
+ assert root.isStaticGet();
+ removeFieldReadsFromStaticGet(code, affectedValues);
+ }
+ if (!affectedValues.isEmpty()) {
+ new TypeAnalysis(appView).narrowing(affectedValues);
+ }
+ }
+
+ private void removeFieldReadsFromNewInstance(IRCode code, Set<Value> affectedValues) {
TreeSet<InstanceGet> uniqueInstanceGetUsersWithDeterministicOrder =
new TreeSet<>(Comparator.comparingInt(x -> x.outValue().getNumber()));
for (Instruction user : eligibleInstance.uniqueUsers()) {
if (user.isInstanceGet()) {
- if (user.outValue().hasAnyUsers()) {
+ if (user.hasUsedOutValue()) {
uniqueInstanceGetUsersWithDeterministicOrder.add(user.asInstanceGet());
} else {
removeInstruction(user);
@@ -748,6 +745,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;
}
@@ -761,12 +759,15 @@
Map<DexField, FieldValueHelper> fieldHelpers = new IdentityHashMap<>();
for (InstanceGet user : uniqueInstanceGetUsersWithDeterministicOrder) {
// Replace a field read with appropriate value.
- replaceFieldRead(code, user, fieldHelpers);
+ removeFieldReadFromNewInstance(code, user, affectedValues, fieldHelpers);
}
}
- private void replaceFieldRead(
- IRCode code, InstanceGet fieldRead, Map<DexField, FieldValueHelper> fieldHelpers) {
+ private void removeFieldReadFromNewInstance(
+ IRCode code,
+ InstanceGet fieldRead,
+ Set<Value> affectedValues,
+ Map<DexField, FieldValueHelper> fieldHelpers) {
Value value = fieldRead.outValue();
if (value != null) {
FieldValueHelper helper =
@@ -781,12 +782,89 @@
// `newValue` could be a phi introduced by FieldValueHelper. Its initial type is set as the
// type of read field, but it could be more precise than that due to (multiple) inlining.
// In addition to values affected by `newValue`, it's necessary to revisit `newValue` itself.
- new TypeAnalysis(appView).narrowing(
- Iterables.concat(ImmutableSet.of(newValue), newValue.affectedValues()));
+ affectedValues.add(newValue);
+ affectedValues.addAll(newValue.affectedValues());
}
removeInstruction(fieldRead);
}
+ private void removeFieldReadsFromStaticGet(IRCode code, Set<Value> affectedValues) {
+ Set<BasicBlock> seen = Sets.newIdentityHashSet();
+ Set<Instruction> users = eligibleInstance.uniqueUsers();
+ for (Instruction user : users) {
+ BasicBlock block = user.getBlock();
+ if (!seen.add(block)) {
+ continue;
+ }
+
+ InstructionListIterator instructionIterator = block.listIterator(code);
+ while (instructionIterator.hasNext()) {
+ Instruction instruction = instructionIterator.next();
+ if (!users.contains(instruction)) {
+ continue;
+ }
+
+ if (user.isInstanceGet()) {
+ if (user.hasUsedOutValue()) {
+ replaceFieldReadFromStaticGet(
+ code, instructionIterator, user.asInstanceGet(), affectedValues);
+ } else {
+ instructionIterator.removeOrReplaceByDebugLocalRead();
+ }
+ continue;
+ }
+
+ if (user.isInstancePut()) {
+ instructionIterator.removeOrReplaceByDebugLocalRead();
+ continue;
+ }
+
+ throw new Unreachable(
+ "Unexpected usage left in method `"
+ + method.toSourceString()
+ + "` after inlining: "
+ + user);
+ }
+ }
+ }
+
+ private void replaceFieldReadFromStaticGet(
+ IRCode code,
+ InstructionListIterator instructionIterator,
+ InstanceGet fieldRead,
+ Set<Value> affectedValues) {
+ DexField fieldReference = fieldRead.getField();
+ DexClass holder = appView.definitionFor(fieldReference.getHolderType(), method);
+ DexEncodedField field = fieldReference.lookupOnClass(holder);
+ if (field == null) {
+ throw reportUnknownFieldReadFromSingleton(fieldRead);
+ }
+
+ AbstractValue abstractValue = objectState.getAbstractFieldValue(field);
+ if (!abstractValue.isSingleConstValue()) {
+ throw reportUnknownFieldReadFromSingleton(fieldRead);
+ }
+
+ SingleConstValue singleConstValue = abstractValue.asSingleConstValue();
+ if (!singleConstValue.isMaterializableInContext(appView, method)) {
+ throw reportUnknownFieldReadFromSingleton(fieldRead);
+ }
+
+ Instruction replacement =
+ singleConstValue.createMaterializingInstruction(appView, code, fieldRead);
+ instructionIterator.replaceCurrentInstruction(replacement, affectedValues);
+ }
+
+ private RuntimeException reportUnknownFieldReadFromSingleton(InstanceGet fieldRead) {
+ throw appView
+ .reporter()
+ .fatalError(
+ "Unexpected usage left in method `"
+ + method.toSourceString()
+ + "` after inlining: "
+ + fieldRead.toString());
+ }
+
private void removeFieldWrites() {
for (Instruction user : eligibleInstance.uniqueUsers()) {
if (!user.isInstancePut()) {
@@ -796,6 +874,9 @@
+ "` after field reads removed: "
+ user);
}
+
+ assert root.isNewInstance();
+
InstancePut instancePut = user.asInstancePut();
DexEncodedField field =
appView
@@ -1111,7 +1192,7 @@
assert root.isStaticGet();
return classInlinerMethodConstraint.isEligibleForStaticGetClassInlining(
- singleTarget, parameter);
+ appView, parameter, objectState, method);
}
private boolean isExtraMethodCall(InvokeMethod invoke) {
@@ -1136,10 +1217,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..6ddae8d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java
@@ -4,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,11 @@
}
@Override
- public boolean isEligibleForStaticGetClassInlining(ProgramMethod method, int parameter) {
+ public boolean isEligibleForStaticGetClassInlining(
+ AppView<AppInfoWithLiveness> appView,
+ int parameter,
+ ObjectState objectState,
+ ProgramMethod context) {
return false;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java
index 9b04087..22079fe 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java
@@ -4,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,11 @@
boolean isEligibleForNewInstanceClassInlining(ProgramMethod method, int parameter);
- boolean isEligibleForStaticGetClassInlining(ProgramMethod method, int parameter);
+ boolean isEligibleForStaticGetClassInlining(
+ AppView<AppInfoWithLiveness> appView,
+ int parameter,
+ ObjectState objectState,
+ ProgramMethod context);
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 be9d99a..eb743cb 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,13 +4,21 @@
package com.android.tools.r8.ir.optimize.classinliner.constraint;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.analysis.value.SingleConstValue;
import com.android.tools.r8.ir.optimize.classinliner.analysis.AnalysisContext;
import com.android.tools.r8.ir.optimize.classinliner.analysis.NonEmptyParameterUsage;
import com.android.tools.r8.ir.optimize.classinliner.analysis.NonEmptyParameterUsages;
import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsage;
import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsagePerContext;
import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsages;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@@ -54,7 +62,11 @@
}
@Override
- public boolean isEligibleForStaticGetClassInlining(ProgramMethod method, int parameter) {
+ public boolean isEligibleForStaticGetClassInlining(
+ AppView<AppInfoWithLiveness> appView,
+ int parameter,
+ ObjectState objectState,
+ ProgramMethod context) {
AnalysisContext defaultContext = AnalysisContext.getDefaultContext();
ParameterUsage usage = usages.get(parameter).get(defaultContext);
if (usage.isBottom()) {
@@ -75,9 +87,20 @@
// 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) {
+ return false;
+ }
+ AbstractValue abstractValue = objectState.getAbstractFieldValue(definition);
+ if (!abstractValue.isSingleConstValue()) {
+ return false;
+ }
+ SingleConstValue singleConstValue = abstractValue.asSingleConstValue();
+ if (!singleConstValue.isMaterializableInContext(appView, context)) {
+ return false;
+ }
}
return true;
}