Strengthen inlining of side effect free methods
Bug: 174285670
Change-Id: Ibffc2f78be5a149803c86e5d7a772019ff4d58a0
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 1dc0e89..2591960 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -680,14 +680,18 @@
// Library methods listed here are based on their original implementations. That is, we assume
// these cannot be overridden.
public final Set<DexMethod> libraryMethodsReturningNonNull =
- ImmutableSet.of(
- classMethods.getName,
- classMethods.getSimpleName,
- classMethods.forName,
- objectsMethods.requireNonNull,
- objectsMethods.requireNonNullWithMessage,
- objectsMethods.requireNonNullWithMessageSupplier,
- stringMembers.valueOf);
+ ImmutableSet.<DexMethod>builder()
+ .add(
+ classMethods.getName,
+ classMethods.getSimpleName,
+ classMethods.forName,
+ objectsMethods.requireNonNull,
+ objectsMethods.requireNonNullWithMessage,
+ objectsMethods.requireNonNullWithMessageSupplier,
+ stringMembers.valueOf)
+ .addAll(stringBufferMethods.appendMethods)
+ .addAll(stringBuilderMethods.appendMethods)
+ .build();
// TODO(b/119596718): More idempotent methods? Any singleton accessors? E.g.,
// java.util.Calendar#getInstance(...) // 4 variants
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index e00c1fe..d35b1a0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -261,6 +261,10 @@
return factory.isConstructor(this);
}
+ public boolean mustBeInlinedIntoInstanceInitializer(DexItemFactory dexItemFactory) {
+ return getName().startsWith(dexItemFactory.temporaryConstructorMethodPrefix);
+ }
+
public DexMethod withExtraArgumentPrepended(DexType type, DexItemFactory dexItemFactory) {
return dexItemFactory.createMethod(
holder, dexItemFactory.prependTypeToProto(type, proto), name);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index 495be54..dd0486a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -155,7 +155,7 @@
new AllInstantiatedOrUninstantiated(appView),
new SameParentClass(),
new SameNestHost(),
- new PreserveMethodCharacteristics(),
+ new PreserveMethodCharacteristics(appView),
new SameFeatureSplit(appView),
new RespectPackageBoundaries(appView),
new DontMergeSynchronizedClasses(appView),
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
index 431fdb9..3adb35c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
@@ -4,12 +4,14 @@
package com.android.tools.r8.horizontalclassmerging.policies;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethodSignature;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.horizontalclassmerging.MergeGroup;
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.OptionalBool;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
@@ -28,9 +30,10 @@
static class MethodCharacteristics {
private final MethodAccessFlags accessFlags;
+ private final boolean isAssumeNoSideEffectsMethod;
private final OptionalBool isLibraryMethodOverride;
- private MethodCharacteristics(DexEncodedMethod method) {
+ private MethodCharacteristics(boolean isAssumeNoSideEffectsMethod, DexEncodedMethod method) {
this.accessFlags =
MethodAccessFlags.builder()
.setPrivate(method.getAccessFlags().isPrivate())
@@ -39,9 +42,16 @@
.setStrict(method.getAccessFlags().isStrict())
.setSynchronized(method.getAccessFlags().isSynchronized())
.build();
+ this.isAssumeNoSideEffectsMethod = isAssumeNoSideEffectsMethod;
this.isLibraryMethodOverride = method.isLibraryMethodOverride();
}
+ static MethodCharacteristics create(
+ AppView<AppInfoWithLiveness> appView, DexEncodedMethod method) {
+ return new MethodCharacteristics(
+ appView.appInfo().isAssumeNoSideEffectsMethod(method.getReference()), method);
+ }
+
@Override
public int hashCode() {
return (accessFlags.hashCode() << 2) | isLibraryMethodOverride.ordinal();
@@ -56,12 +66,17 @@
return false;
}
MethodCharacteristics characteristics = (MethodCharacteristics) obj;
- return isLibraryMethodOverride == characteristics.isLibraryMethodOverride
- && accessFlags.equals(characteristics.accessFlags);
+ return accessFlags.equals(characteristics.accessFlags)
+ && isAssumeNoSideEffectsMethod == characteristics.isAssumeNoSideEffectsMethod
+ && isLibraryMethodOverride == characteristics.isLibraryMethodOverride;
}
}
- public PreserveMethodCharacteristics() {}
+ private final AppView<AppInfoWithLiveness> appView;
+
+ public PreserveMethodCharacteristics(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ }
public static class TargetGroup {
@@ -72,12 +87,12 @@
return group;
}
- public boolean tryAdd(DexProgramClass clazz) {
+ public boolean tryAdd(AppView<AppInfoWithLiveness> appView, DexProgramClass clazz) {
Map<DexMethodSignature, MethodCharacteristics> newMethods = new HashMap<>();
for (DexEncodedMethod method : clazz.methods()) {
DexMethodSignature signature = method.getSignature();
MethodCharacteristics existingCharacteristics = methodMap.get(signature);
- MethodCharacteristics methodCharacteristics = new MethodCharacteristics(method);
+ MethodCharacteristics methodCharacteristics = MethodCharacteristics.create(appView, method);
if (existingCharacteristics == null) {
newMethods.put(signature, methodCharacteristics);
continue;
@@ -97,10 +112,10 @@
List<TargetGroup> groups = new ArrayList<>();
for (DexProgramClass clazz : group) {
- boolean added = Iterables.any(groups, targetGroup -> targetGroup.tryAdd(clazz));
+ boolean added = Iterables.any(groups, targetGroup -> targetGroup.tryAdd(appView, clazz));
if (!added) {
TargetGroup newGroup = new TargetGroup();
- added = newGroup.tryAdd(clazz);
+ added = newGroup.tryAdd(appView, clazz);
assert added;
groups.add(newGroup);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index ee770e8..a1c4e7b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.code;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
import static com.android.tools.r8.ir.code.DominatorTree.Assumption.MAY_HAVE_UNREACHABLE_BLOCKS;
@@ -12,6 +13,9 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
@@ -19,6 +23,7 @@
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.Phi.RegisterReadType;
import com.android.tools.r8.ir.optimize.NestUtils;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.IteratorUtils;
import com.google.common.collect.ImmutableList;
@@ -269,6 +274,76 @@
}
@Override
+ public boolean replaceCurrentInstructionByNullCheckIfPossible(
+ AppView<?> appView, ProgramMethod context) {
+ Instruction toBeReplaced = current;
+ assert toBeReplaced != null;
+ assert toBeReplaced.isInstanceFieldInstruction() || toBeReplaced.isInvokeMethodWithReceiver();
+ if (toBeReplaced.hasUsedOutValue()) {
+ return false;
+ }
+ if (toBeReplaced.isInvokeDirect()) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ DexMethod invokedMethod = toBeReplaced.asInvokeDirect().getInvokedMethod();
+ if (invokedMethod.isInstanceInitializer(dexItemFactory)
+ || invokedMethod.mustBeInlinedIntoInstanceInitializer(dexItemFactory)) {
+ return false;
+ }
+ }
+ if (toBeReplaced.instructionMayHaveSideEffects(
+ appView, context, Instruction.SideEffectAssumption.RECEIVER_NOT_NULL)) {
+ return false;
+ }
+ Value receiver =
+ toBeReplaced.isInstanceFieldInstruction()
+ ? toBeReplaced.asInstanceFieldInstruction().object()
+ : toBeReplaced.asInvokeMethodWithReceiver().getReceiver();
+ if (receiver.isNeverNull()) {
+ removeOrReplaceByDebugLocalRead();
+ return true;
+ }
+ InvokeMethod replacement;
+ if (appView.options().canUseRequireNonNull()) {
+ DexMethod requireNonNullMethod = appView.dexItemFactory().objectsMethods.requireNonNull;
+ replacement = new InvokeStatic(requireNonNullMethod, null, ImmutableList.of(receiver));
+ } else {
+ DexMethod getClassMethod = appView.dexItemFactory().objectMembers.getClass;
+ replacement = new InvokeVirtual(getClassMethod, null, ImmutableList.of(receiver));
+ }
+ replaceCurrentInstruction(replacement);
+ return true;
+ }
+
+ @Override
+ public boolean replaceCurrentInstructionByInitClassIfPossible(
+ AppView<AppInfoWithLiveness> appView, IRCode code, DexType type) {
+ Instruction toBeReplaced = current;
+ assert toBeReplaced != null;
+ assert toBeReplaced.isStaticFieldInstruction() || toBeReplaced.isInvokeStatic();
+ if (toBeReplaced.hasUsedOutValue()) {
+ return false;
+ }
+ ProgramMethod context = code.context();
+ if (toBeReplaced.instructionMayHaveSideEffects(
+ appView, context, Instruction.SideEffectAssumption.CLASS_ALREADY_INITIALIZED)) {
+ return false;
+ }
+ if (!type.classInitializationMayHaveSideEffectsInContext(appView, context)) {
+ removeOrReplaceByDebugLocalRead();
+ return true;
+ }
+ if (!appView.canUseInitClass()) {
+ return false;
+ }
+ DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
+ if (clazz != null) {
+ Value dest = code.createValue(TypeElement.getInt());
+ replaceCurrentInstruction(new InitClass(dest, clazz.type));
+ }
+ return true;
+ }
+
+ @Override
public void replaceCurrentInstructionWithConstClass(
AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo) {
if (current == null) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index a96760f..757a581 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -11,7 +11,9 @@
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import java.util.ListIterator;
import java.util.NoSuchElementException;
@@ -42,6 +44,18 @@
}
@Override
+ public boolean replaceCurrentInstructionByNullCheckIfPossible(
+ AppView<?> appView, ProgramMethod context) {
+ return instructionIterator.replaceCurrentInstructionByNullCheckIfPossible(appView, context);
+ }
+
+ @Override
+ public boolean replaceCurrentInstructionByInitClassIfPossible(
+ AppView<AppInfoWithLiveness> appView, IRCode code, DexType type) {
+ return instructionIterator.replaceCurrentInstructionByInitClassIfPossible(appView, code, type);
+ }
+
+ @Override
public void replaceCurrentInstructionWithConstClass(
AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo) {
instructionIterator.replaceCurrentInstructionWithConstClass(appView, code, type, localInfo);
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 e8ba6bd..689514f 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
@@ -130,7 +130,11 @@
}
public boolean hasUnusedOutValue() {
- return !hasOutValue() || !outValue().hasAnyUsers();
+ return !hasUsedOutValue();
+ }
+
+ public boolean hasUsedOutValue() {
+ return hasOutValue() && outValue().hasAnyUsers();
}
public Value outValue() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index c4cceea..6b17a04 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -11,7 +11,9 @@
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.Sets;
import java.util.ListIterator;
@@ -97,6 +99,11 @@
Value insertConstStringInstruction(AppView<?> appView, IRCode code, DexString value);
+ boolean replaceCurrentInstructionByNullCheckIfPossible(AppView<?> appView, ProgramMethod context);
+
+ boolean replaceCurrentInstructionByInitClassIfPossible(
+ AppView<AppInfoWithLiveness> appView, IRCode code, DexType type);
+
void replaceCurrentInstructionWithConstClass(
AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Invoke.java b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
index 31fd8f4..5793ec1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Invoke.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
@@ -144,6 +144,10 @@
abstract public DexType getReturnType();
+ public boolean hasArguments() {
+ return !arguments().isEmpty();
+ }
+
public boolean hasReturnTypeVoid(DexItemFactory factory) {
return getReturnType() == factory.voidType;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index 0fc2768..bbb6d27 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -10,7 +10,9 @@
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.Sets;
import java.util.ListIterator;
@@ -62,6 +64,18 @@
}
@Override
+ public boolean replaceCurrentInstructionByNullCheckIfPossible(
+ AppView<?> appView, ProgramMethod context) {
+ return currentBlockIterator.replaceCurrentInstructionByNullCheckIfPossible(appView, context);
+ }
+
+ @Override
+ public boolean replaceCurrentInstructionByInitClassIfPossible(
+ AppView<AppInfoWithLiveness> appView, IRCode code, DexType type) {
+ return currentBlockIterator.replaceCurrentInstructionByInitClassIfPossible(appView, code, type);
+ }
+
+ @Override
public void replaceCurrentInstructionWithConstClass(
AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo) {
currentBlockIterator.replaceCurrentInstructionWithConstClass(appView, code, type, localInfo);
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 7e75ee2..38c4e6f 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
@@ -982,6 +982,16 @@
continue;
}
+ if (invoke.isInvokeMethodWithReceiver()) {
+ if (iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, context)) {
+ continue;
+ }
+ } else if (invoke.isInvokeStatic()
+ && iterator.replaceCurrentInstructionByInitClassIfPossible(
+ appView, code, resolutionResult.getResolvedHolder().getType())) {
+ continue;
+ }
+
// TODO(b/156853206): Should not duplicate resolution.
ProgramMethod singleTarget = oracle.lookupSingleTarget(invoke, context);
if (singleTarget == null) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 39ce729..2950d4c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize;
-import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
import static com.google.common.base.Predicates.alwaysTrue;
@@ -14,7 +13,6 @@
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.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
@@ -26,13 +24,10 @@
import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.IRMetadata;
-import com.android.tools.r8.ir.code.InitClass;
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.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.StaticPut;
@@ -44,7 +39,6 @@
import com.android.tools.r8.shaking.ProguardMemberRuleReturnValue;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
-import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import java.util.ListIterator;
import java.util.Set;
@@ -216,8 +210,8 @@
}
if (current.isStaticGet()) {
StaticGet staticGet = current.asStaticGet();
- replaceInstructionByInitClassIfPossible(
- staticGet, staticGet.getField().holder, code, iterator, code.context());
+ iterator.replaceCurrentInstructionByInitClassIfPossible(
+ appView, code, staticGet.getField().holder);
}
replacement.setPosition(position);
if (block.hasCatchHandlers()) {
@@ -236,16 +230,12 @@
ListIterator<BasicBlock> blocks,
InstructionListIterator iterator,
InvokeMethod invoke) {
+ if (invoke.hasUnusedOutValue()) {
+ return;
+ }
+
DexMethod invokedMethod = invoke.getInvokedMethod();
- if (invokedMethod.proto.returnType.isVoidType()) {
- return;
- }
-
- if (!invoke.hasOutValue() || !invoke.outValue().hasNonDebugUsers()) {
- return;
- }
-
- DexType invokedHolder = invokedMethod.holder;
+ DexType invokedHolder = invokedMethod.getHolderType();
if (!invokedHolder.isClassType()) {
return;
}
@@ -302,10 +292,10 @@
invoke.setOutValue(null);
if (invoke.isInvokeMethodWithReceiver()) {
- replaceInstructionByNullCheckIfPossible(invoke, iterator, context);
+ iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, context);
} else if (invoke.isInvokeStatic()) {
- replaceInstructionByInitClassIfPossible(
- invoke, singleTarget.getHolderType(), code, iterator, context);
+ iterator.replaceCurrentInstructionByInitClassIfPossible(
+ appView, code, singleTarget.getHolderType());
}
// Insert the definition of the replacement.
@@ -404,11 +394,11 @@
// To preserve side effects, original field-get is replaced by an explicit null-check, if
// the field-get instruction may only fail with an NPE, or the field-get remains as-is.
if (current.isInstanceGet()) {
- replaceInstructionByNullCheckIfPossible(current, iterator, context);
+ iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, context);
} else {
assert current.isStaticGet();
- replaceInstructionByInitClassIfPossible(
- current, target.getHolderType(), code, iterator, context);
+ iterator.replaceCurrentInstructionByInitClassIfPossible(
+ appView, code, target.getHolderType());
}
// Insert the definition of the replacement.
@@ -426,58 +416,6 @@
}
}
- private void replaceInstructionByNullCheckIfPossible(
- Instruction instruction, InstructionListIterator iterator, ProgramMethod context) {
- assert instruction.isInstanceFieldInstruction() || instruction.isInvokeMethodWithReceiver();
- assert !instruction.hasOutValue() || !instruction.outValue().hasAnyUsers();
- if (instruction.instructionMayHaveSideEffects(
- appView, context, Instruction.SideEffectAssumption.RECEIVER_NOT_NULL)) {
- return;
- }
- Value receiver =
- instruction.isInstanceFieldInstruction()
- ? instruction.asInstanceFieldInstruction().object()
- : instruction.asInvokeMethodWithReceiver().getReceiver();
- if (receiver.isNeverNull()) {
- iterator.removeOrReplaceByDebugLocalRead();
- return;
- }
- InvokeMethod replacement;
- if (appView.options().canUseRequireNonNull()) {
- DexMethod requireNonNullMethod = appView.dexItemFactory().objectsMethods.requireNonNull;
- replacement = new InvokeStatic(requireNonNullMethod, null, ImmutableList.of(receiver));
- } else {
- DexMethod getClassMethod = appView.dexItemFactory().objectMembers.getClass;
- replacement = new InvokeVirtual(getClassMethod, null, ImmutableList.of(receiver));
- }
- iterator.replaceCurrentInstruction(replacement);
- }
-
- private void replaceInstructionByInitClassIfPossible(
- Instruction instruction,
- DexType holder,
- IRCode code,
- InstructionListIterator iterator,
- ProgramMethod context) {
- assert instruction.isStaticFieldInstruction() || instruction.isInvokeStatic();
- if (instruction.instructionMayHaveSideEffects(
- appView, context, Instruction.SideEffectAssumption.CLASS_ALREADY_INITIALIZED)) {
- return;
- }
- if (!holder.classInitializationMayHaveSideEffectsInContext(appView, context)) {
- iterator.removeOrReplaceByDebugLocalRead();
- return;
- }
- if (!appView.canUseInitClass()) {
- return;
- }
- DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(holder));
- if (clazz != null) {
- Value dest = code.createValue(TypeElement.getInt());
- iterator.replaceCurrentInstruction(new InitClass(dest, clazz.type));
- }
- }
-
private void replaceInstancePutByNullCheckIfNeverRead(
IRCode code, InstructionListIterator iterator, InstancePut current) {
DexEncodedField field = appView.appInfo().resolveField(current.getField()).getResolvedField();
@@ -492,7 +430,7 @@
return;
}
- replaceInstructionByNullCheckIfPossible(current, iterator, code.context());
+ iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, code.context());
}
private void replaceStaticPutByInitClassIfNeverRead(
@@ -509,8 +447,7 @@
return;
}
- replaceInstructionByInitClassIfPossible(
- current, field.getHolderType(), code, iterator, code.context());
+ iterator.replaceCurrentInstructionByInitClassIfPossible(appView, code, field.getHolderType());
}
/**
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 508e0f4..f673395 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -37,6 +37,7 @@
import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.Add;
+import com.android.tools.r8.ir.code.Assume;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
import com.android.tools.r8.ir.code.Binop;
@@ -782,7 +783,7 @@
// Process instruction. Returns true if an outline candidate was found.
private void processInstruction(Instruction instruction) {
// Figure out whether to include the instruction.
- boolean include = false;
+ boolean include;
int instructionIncrement = 1;
if (instruction.isConstInstruction()) {
if (index == start) {
@@ -811,13 +812,13 @@
includeInstruction(instruction);
// Check if this instruction ends the outline.
if (actualInstructions >= appView.options().outline.maxSize) {
- candidate(start, index + 1, actualInstructions);
+ candidate(start, index + 1);
} else {
index++;
}
} else if (index > start) {
// Do not add this instruction, candidate ends with previous instruction.
- candidate(start, index, actualInstructions);
+ candidate(start, index);
} else {
// Restart search from next instruction.
reset(index + 1);
@@ -830,7 +831,7 @@
int returnValueUsersLeftIfIncluded = returnValueUsersLeft;
if (returnValue != null) {
for (Value value : instruction.inValues()) {
- if (value == returnValue) {
+ if (value.getAliasedValue() == returnValue) {
returnValueUsersLeftIfIncluded--;
}
}
@@ -870,10 +871,9 @@
}
// Find the number of in-going arguments, if adding this instruction.
int newArgumentRegisters = argumentRegisters;
- if (instruction.inValues().size() > 0) {
- List<Value> inValues = orderedInValues(instruction, returnValue);
- for (int i = 0; i < inValues.size(); i++) {
- Value value = inValues.get(i);
+ if (invoke.hasArguments()) {
+ for (int i = 0; i < invoke.arguments().size(); i++) {
+ Value value = invoke.getArgument(i).getAliasedValue();
if (value == returnValue) {
continue;
}
@@ -884,7 +884,8 @@
newArgumentRegisters += value.requiredRegisters();
} else {
// For virtual calls only re-use the receiver argument.
- if (i > 0 || !arguments.contains(value)) {
+ Value receiver = invoke.asInvokeMethodWithReceiver().getReceiver().getAliasedValue();
+ if (value != receiver || !arguments.contains(value)) {
newArgumentRegisters += value.requiredRegisters();
}
}
@@ -907,7 +908,11 @@
offset++;
previous = instructions.get(index - offset);
} while (previous.isConstInstruction());
- if (!previous.isNewInstance() || previous.outValue() != returnValue) {
+ if (!previous.isNewInstance()) {
+ return false;
+ }
+ if (returnValue == null || returnValue != previous.outValue()) {
+ assert false;
return false;
}
// Clear pending new instance flag as the last thing, now the matching constructor is known
@@ -996,6 +1001,10 @@
// Add the current instruction to the outline.
private void includeInstruction(Instruction instruction) {
if (instruction.isAssume()) {
+ Assume assume = instruction.asAssume();
+ if (returnValue != null && assume.src().getAliasedValue() == returnValue) {
+ adjustReturnValueUsersLeft(assume.outValue().numberOfAllUsers() - 1);
+ }
return;
}
@@ -1005,12 +1014,7 @@
if (returnValue != null) {
for (Value value : inValues) {
if (value.getAliasedValue() == returnValue) {
- assert returnValueUsersLeft > 0;
- returnValueUsersLeft--;
- }
- if (returnValueUsersLeft == 0) {
- returnValue = null;
- returnType = appView.dexItemFactory().voidType;
+ adjustReturnValueUsersLeft(-1);
}
}
}
@@ -1080,9 +1084,18 @@
}
}
+ private void adjustReturnValueUsersLeft(int change) {
+ returnValueUsersLeft += change;
+ assert returnValueUsersLeft >= 0;
+ if (returnValueUsersLeft == 0) {
+ returnValue = null;
+ returnType = appView.dexItemFactory().voidType;
+ }
+ }
+
protected abstract void handle(int start, int end, Outline outline);
- private void candidate(int start, int index, int actualInstructions) {
+ private void candidate(int start, int index) {
List<Instruction> instructions = getInstructionArray();
assert !instructions.get(start).isConstInstruction();
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 143d776..0ce08a5 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
@@ -584,10 +584,13 @@
currentUsers = indirectOutValueUsers;
}
- assert !methodCallsOnInstance.isEmpty();
-
- inliner.performForcedInlining(
- method, code, methodCallsOnInstance, inliningIRProvider, Timing.empty());
+ if (!methodCallsOnInstance.isEmpty()) {
+ inliner.performForcedInlining(
+ method, code, methodCallsOnInstance, inliningIRProvider, Timing.empty());
+ } else {
+ assert indirectMethodCallsOnInstance.stream()
+ .noneMatch(method -> method.getDefinition().getOptimizationInfo().mayHaveSideEffects());
+ }
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
index b1890af..ae91ac8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
@@ -38,7 +38,10 @@
ImmutableMap.<DexMethod, BiPredicate<DexMethod, List<Value>>>builder()
.put(
dexItemFactory.stringMembers.constructor,
- (method, arguments) -> arguments.get(1).isNeverNull());
+ (method, arguments) -> arguments.get(1).isNeverNull())
+ .put(
+ dexItemFactory.stringMembers.valueOf,
+ (method, arguments) -> arguments.get(0).isNeverNull());
putAll(
builder,
dexItemFactory.stringBufferMethods.constructorMethods,
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 97b2504..5619d97 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -575,6 +575,10 @@
return neverInlineDueToSingleCaller.contains(method.getReference());
}
+ public boolean isAssumeNoSideEffectsMethod(DexMethod method) {
+ return noSideEffects.containsKey(method);
+ }
+
public boolean isWhyAreYouNotInliningMethod(DexMethod method) {
return whyAreYouNotInlining.contains(method);
}
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/StringSwitchConversionFromIfTest.java b/src/test/java/com/android/tools/r8/ir/conversion/StringSwitchConversionFromIfTest.java
index 294efee..bcdd93f 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/StringSwitchConversionFromIfTest.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/StringSwitchConversionFromIfTest.java
@@ -7,7 +7,7 @@
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.assertNotEquals;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
@@ -87,7 +87,16 @@
// instruction may throw. This holds even if the string-switch instruction is
// compiled to a sequence of `if (x.equals("..."))` instructions that do not even
// use the hash code.
- assertNotEquals(0, hashCodeValues.size());
+ assertTrue(
+ code.collectArguments().get(0).uniqueUsers().stream()
+ .filter(Instruction::isInvokeMethod)
+ .map(Instruction::asInvokeMethod)
+ .map(invoke -> invoke.getInvokedMethod().getName().toSourceString())
+ .anyMatch(
+ name ->
+ name.equals("getClass")
+ || name.equals("hashCode")
+ || name.equals("requireNonNull")));
}
})
.run(parameters.getRuntime(), TestClass.class)
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 af047b9..7049089 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
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -333,7 +335,7 @@
assertCounters(INLINABLE, NEVER_INLINABLE, countInvokes(inspector, m));
m = clazz.method("int", "notInlinableDueToMissingNpe", ImmutableList.of("inlining.A"));
- assertCounters(INLINABLE, NEVER_INLINABLE, countInvokes(inspector, m));
+ assertCounters(INLINABLE, ALWAYS_INLINABLE, countInvokes(inspector, m));
m = clazz.method("int", "notInlinableDueToSideEffect", ImmutableList.of("inlining.A"));
assertCounters(INLINABLE, NEVER_INLINABLE, countInvokes(inspector, m));
@@ -354,7 +356,12 @@
CodeInspector inspector =
new CodeInspector(getGeneratedFiles(), getGeneratedProguardMap(), null);
ClassSubject clazz = inspector.clazz(nullabilityClass);
- MethodSubject m = clazz.method("int", "conditionalOperator", ImmutableList.of("inlining.A"));
+ assertThat(clazz.uniqueMethodWithName("conditionalOperator"), isAbsent());
+
+ // The enum parameter may get unboxed.
+ MethodSubject m =
+ clazz.uniqueMethodWithName(
+ parameters.isCfRuntime() ? "moreControlFlows" : "moreControlFlows$enumunboxing$");
assertTrue(m.isPresent());
// Verify that a.b() is resolved to an inline instance-get.
@@ -365,27 +372,6 @@
InstructionSubject instruction = iterator.next();
if (instruction.isInstanceGet()) {
++instanceGetCount;
- } else if (instruction.isInvoke()) {
- ++invokeCount;
- }
- }
- assertEquals(1, instanceGetCount);
- assertEquals(0, invokeCount);
-
- // The enum parameter may get unboxed.
- m =
- clazz.uniqueMethodWithName(
- parameters.isCfRuntime() ? "moreControlFlows" : "moreControlFlows$enumunboxing$");
- assertTrue(m.isPresent());
-
- // Verify that a.b() is resolved to an inline instance-get.
- iterator = m.iterateInstructions();
- instanceGetCount = 0;
- invokeCount = 0;
- while (iterator.hasNext()) {
- InstructionSubject instruction = iterator.next();
- if (instruction.isInstanceGet()) {
- ++instanceGetCount;
} else if (instruction.isInvoke() && !isEnumInvoke(instruction)) {
++invokeCount;
}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index c253c45..8d01a11 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.BasicBlock;
@@ -23,6 +24,7 @@
import com.android.tools.r8.ir.code.Move;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import java.util.LinkedList;
import java.util.List;
@@ -62,6 +64,18 @@
}
@Override
+ public boolean replaceCurrentInstructionByNullCheckIfPossible(
+ AppView<?> appView, ProgramMethod context) {
+ throw new Unimplemented();
+ }
+
+ @Override
+ public boolean replaceCurrentInstructionByInitClassIfPossible(
+ AppView<AppInfoWithLiveness> appView, IRCode code, DexType type) {
+ throw new Unimplemented();
+ }
+
+ @Override
public void replaceCurrentInstructionWithConstClass(
AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo) {
throw new Unimplemented();
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForJavaLangClassTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForJavaLangClassTest.java
index eb29f88..fed4e6d 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForJavaLangClassTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForJavaLangClassTest.java
@@ -7,6 +7,7 @@
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.onlyIf;
+import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.AssumeMayHaveSideEffects;
@@ -71,7 +72,11 @@
assertThat(
methodSubject,
onlyIf(maybeNullReceiver || maybeSubtype, invokesMethodWithName("hashCode")));
- assertThat(methodSubject, onlyIf(maybeNullReceiver, invokesMethodWithName("getClass")));
+ assertThat(
+ methodSubject,
+ onlyIf(
+ maybeNullReceiver,
+ anyOf(invokesMethodWithName("getClass"), invokesMethodWithName("requireNonNull"))));
assertThat(
methodSubject,
onlyIf(maybeNullReceiver || maybeSubtype, invokesMethodWithName("toString")));
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java
index 1c7f6ac..8130a6f 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java
@@ -111,7 +111,7 @@
private final TestConfig config;
private final boolean enableHorizontalClassMerging;
- @Parameterized.Parameters(name = "{0} {1}")
+ @Parameterized.Parameters(name = "{0}, config: {1}, horizontal: {2}")
public static Collection<Object[]> data() {
return buildParameters(
getTestParameters().withAllRuntimesAndApiLevels().build(),
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index e85f2d8..04ac085 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -252,7 +252,7 @@
" move-result-object v0",
" invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" move-result-object v0",
- " invoke-virtual { v0 }, " + stringBuilderToStringSignature,
+ " invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" move-result-object v0",
" const v0, 0",
" const v1, 1",
@@ -818,7 +818,7 @@
" move-result-object v0",
" invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" move-result-object v0",
- " invoke-virtual { v0 }, " + stringBuilderToStringSignature,
+ " invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" return-void");
String returnType2 = "java.lang.String";
@@ -866,14 +866,14 @@
ClassSubject clazz = inspector.clazz(OutlineOptions.CLASS_NAME);
assertTrue(clazz.isPresent());
assertEquals(3, clazz.getDexProgramClass().getMethodCollection().numberOfDirectMethods());
- // Collect the return types of the putlines for the body of method1 and method2.
+ // Collect the return types of the outlines for the body of method1 and method2.
List<DexType> r = new ArrayList<>();
for (DexEncodedMethod directMethod : clazz.getDexProgramClass().directMethods()) {
if (directMethod.getCode().asDexCode().instructions[0] instanceof InvokeVirtual) {
r.add(directMethod.method.proto.returnType);
}
}
- assert r.size() == 2;
+ assertEquals(2, r.size());
DexType r1 = r.get(0);
DexType r2 = r.get(1);
DexItemFactory factory = inspector.getFactory();
@@ -882,7 +882,7 @@
// Run the code.
String result = runArt(processedApplication);
- assertEquals("TestTestTestTest", result);
+ assertEquals("TestTestTestTestTest", result);
}
@Test