Optimize unused return values
Change-Id: Ib3a5c3bb109df60cf530df2e05aa838c06110878
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 14b0ded..24c47b4 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -515,7 +515,6 @@
}
public void setRootSet(RootSet rootSet) {
- assert this.rootSet == null : "Root set should never be recomputed";
this.rootSet = rootSet;
}
@@ -806,9 +805,12 @@
appView.setProguardCompatibilityActions(
appView.getProguardCompatibilityActions().rewrittenWithLens(lens));
}
- if (appView.getMainDexRootSet() != null) {
+ if (appView.hasMainDexRootSet()) {
appView.setMainDexRootSet(appView.getMainDexRootSet().rewrittenWithLens(lens));
}
+ if (appView.hasRootSet()) {
+ appView.setRootSet(appView.rootSet().rewrittenWithLens(lens));
+ }
});
}
diff --git a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
index e6a6cba..a382689 100644
--- a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
+++ b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
@@ -240,7 +240,6 @@
this.oldType = oldType;
this.newType = newType;
this.singleValue = singleValue;
- assert !newType.isVoidType() || singleValue != null;
}
public RewrittenTypeInfo combine(RewrittenPrototypeDescription other) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 8bcdea8..cd0e433 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -386,38 +386,42 @@
}
Instruction constantReturnMaterializingInstruction = null;
- if (prototypeChanges.hasBeenChangedToReturnVoid() && invoke.hasOutValue()) {
- TypeAndLocalInfoSupplier typeAndLocalInfo =
- new TypeAndLocalInfoSupplier() {
- @Override
- public DebugLocalInfo getLocalInfo() {
- return invoke.getLocalInfo();
- }
+ if (invoke.hasOutValue()) {
+ if (invoke.hasUnusedOutValue()) {
+ invoke.clearOutValue();
+ } else if (prototypeChanges.hasBeenChangedToReturnVoid()) {
+ TypeAndLocalInfoSupplier typeAndLocalInfo =
+ new TypeAndLocalInfoSupplier() {
+ @Override
+ public DebugLocalInfo getLocalInfo() {
+ return invoke.getLocalInfo();
+ }
- @Override
- public TypeElement getOutType() {
- return graphLens
- .lookupType(invokedMethod.getReturnType(), codeLens)
- .toTypeElement(appView);
- }
- };
- prototypeChanges.verifyConstantReturnAccessibleInContext(
- appView.withLiveness(), method, graphLens);
- constantReturnMaterializingInstruction =
- prototypeChanges.getConstantReturn(
- appView.withLiveness(),
- code,
- invoke.getPosition(),
- typeAndLocalInfo);
- if (invoke.outValue().hasLocalInfo()) {
- constantReturnMaterializingInstruction
+ @Override
+ public TypeElement getOutType() {
+ return graphLens
+ .lookupType(invokedMethod.getReturnType(), codeLens)
+ .toTypeElement(appView);
+ }
+ };
+ assert prototypeChanges.verifyConstantReturnAccessibleInContext(
+ appView.withLiveness(), method, graphLens);
+ constantReturnMaterializingInstruction =
+ prototypeChanges.getConstantReturn(
+ appView.withLiveness(), code, invoke.getPosition(), typeAndLocalInfo);
+ if (invoke.outValue().hasLocalInfo()) {
+ constantReturnMaterializingInstruction
+ .outValue()
+ .setLocalInfo(invoke.outValue().getLocalInfo());
+ }
+ invoke
.outValue()
- .setLocalInfo(invoke.outValue().getLocalInfo());
- }
- invoke.outValue().replaceUsers(constantReturnMaterializingInstruction.outValue());
- if (invoke.getOutType() != constantReturnMaterializingInstruction.getOutType()) {
- affectedPhis.addAll(
- constantReturnMaterializingInstruction.outValue().uniquePhiUsers());
+ .replaceUsers(constantReturnMaterializingInstruction.outValue());
+ if (invoke.getOutType()
+ != constantReturnMaterializingInstruction.getOutType()) {
+ affectedPhis.addAll(
+ constantReturnMaterializingInstruction.outValue().uniquePhiUsers());
+ }
}
}
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 95483ce..f6aa6ad 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
@@ -18,6 +18,7 @@
import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OptionalBool;
import com.google.common.collect.ImmutableSet;
import java.util.BitSet;
import java.util.Set;
@@ -161,6 +162,11 @@
}
@Override
+ public OptionalBool isReturnValueUsed() {
+ return OptionalBool.unknown();
+ }
+
+ @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 17cffe5..473f718 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
@@ -15,6 +15,7 @@
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OptionalBool;
import java.util.BitSet;
import java.util.Set;
@@ -86,6 +87,8 @@
public abstract boolean isMultiCallerMethod();
+ public abstract OptionalBool isReturnValueUsed();
+
public abstract boolean forceInline();
public abstract boolean triggersClassInitBeforeAnySideEffect();
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 69f0497..2cffcc0 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
@@ -28,6 +28,7 @@
import com.android.tools.r8.utils.BitSetUtils;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OptionalBool;
import java.util.BitSet;
import java.util.Collections;
import java.util.Set;
@@ -47,6 +48,7 @@
EnumUnboxerMethodClassification.unknown();
private DynamicType dynamicType = DynamicType.unknown();
private InlinePreference inlining = InlinePreference.Default;
+ private OptionalBool isReturnValueUsed = OptionalBool.unknown();
// Stores information about instance methods and constructors for
// class inliner, null value indicates that the method is not eligible.
private BridgeInfo bridgeInfo = null;
@@ -481,6 +483,15 @@
}
@Override
+ public OptionalBool isReturnValueUsed() {
+ return isReturnValueUsed;
+ }
+
+ void setIsReturnValueUsed(OptionalBool isReturnValueUsed) {
+ this.isReturnValueUsed = isReturnValueUsed;
+ }
+
+ @Override
public boolean forceInline() {
return inlining == InlinePreference.ForceInline;
}
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 5be58f0..1b54d1f 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
@@ -18,6 +18,7 @@
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.OptionalBool;
import java.util.BitSet;
import java.util.Set;
import java.util.function.Consumer;
@@ -179,6 +180,10 @@
method.getMutableOptimizationInfo().setInitializerEnablingJavaAssertions();
}
+ public void setIsReturnValueUsed(OptionalBool isReturnValueUsed, ProgramMethod method) {
+ method.getDefinition().getMutableOptimizationInfo().setIsReturnValueUsed(isReturnValueUsed);
+ }
+
@Override
public void setNonNullParamOrThrow(DexEncodedMethod method, BitSet facts) {
method.getMutableOptimizationInfo().setNonNullParamOrThrow(facts);
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
index 2839aeb..ba4b05a 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
@@ -47,7 +47,6 @@
import com.android.tools.r8.optimize.argumentpropagation.utils.WideningUtils;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.IdentityHashMap;
@@ -152,12 +151,6 @@
}
private void scan(InvokeMethod invoke, ProgramMethod context, Timing timing) {
- List<Value> arguments = invoke.arguments();
- if (arguments.isEmpty()) {
- // Nothing to propagate.
- return;
- }
-
DexMethod invokedMethod = invoke.getInvokedMethod();
if (invokedMethod.getHolderType().isArrayType()) {
// Nothing to propagate; the targeted method is not a program method.
@@ -191,7 +184,7 @@
return;
}
- if (arguments.size() != resolvedMethod.getDefinition().getNumberOfArguments()
+ if (invoke.arguments().size() != resolvedMethod.getDefinition().getNumberOfArguments()
|| invoke.isInvokeStatic() != resolvedMethod.getAccessFlags().isStatic()) {
// Nothing to propagate; the invoke instruction fails.
return;
@@ -410,13 +403,10 @@
methodReprocessingCriteria.getParameterReprocessingCriteria(argumentIndex)));
}
- // If all parameter states are unknown, then return a canonicalized unknown method state that
- // has this property.
- if (Iterables.all(parameterStates, ParameterState::isUnknown)) {
- return MethodState.unknown();
- }
-
- return new ConcreteMonomorphicMethodState(parameterStates);
+ // We simulate that the return value is used for methods with void return type. This ensures
+ // that we will widen the method state to unknown if/when all parameter states become unknown.
+ boolean isReturnValueUsed = invoke.getReturnType().isVoidType() || invoke.hasUsedOutValue();
+ return ConcreteMonomorphicMethodState.create(isReturnValueUsed, parameterStates);
}
// For receivers there is not much point in trying to track an abstract value. Therefore we only
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
index 470e442..32a3036 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
@@ -34,6 +34,7 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import java.util.List;
@@ -197,11 +198,17 @@
.map(ParameterState::asConcrete)
.noneMatch(ConcreteParameterState::hasInParameters);
- getSimpleFeedback()
- .setArgumentInfos(
- method,
- ConcreteCallSiteOptimizationInfo.fromMethodState(
- appView, method, monomorphicMethodState));
+ if (monomorphicMethodState.size() > 0) {
+ getSimpleFeedback()
+ .setArgumentInfos(
+ method,
+ ConcreteCallSiteOptimizationInfo.fromMethodState(
+ appView, method, monomorphicMethodState));
+ }
+
+ if (!monomorphicMethodState.isReturnValueUsed()) {
+ getSimpleFeedback().setIsReturnValueUsed(OptionalBool.FALSE, method);
+ }
// Strengthen the return value of the method if the method is known to return one of the
// arguments.
@@ -223,10 +230,18 @@
}
int numberOfArguments = method.getDefinition().getNumberOfArguments();
- List<ParameterState> parameterStates =
- methodState.isMonomorphic()
- ? methodState.asMonomorphic().getParameterStates()
- : ListUtils.newInitializedArrayList(numberOfArguments, ParameterState.unknown());
+ boolean isReturnValueUsed;
+ List<ParameterState> parameterStates;
+ if (methodState.isMonomorphic()) {
+ ConcreteMonomorphicMethodState monomorphicMethodState = methodState.asMonomorphic();
+ isReturnValueUsed = monomorphicMethodState.isReturnValueUsed();
+ parameterStates = monomorphicMethodState.getParameterStates();
+ } else {
+ assert methodState.isUnknown();
+ isReturnValueUsed = true;
+ parameterStates =
+ ListUtils.newInitializedArrayList(numberOfArguments, ParameterState.unknown());
+ }
List<ParameterState> narrowedParameterStates =
ListUtils.mapOrElse(
parameterStates,
@@ -243,7 +258,7 @@
},
null);
return narrowedParameterStates != null
- ? new ConcreteMonomorphicMethodState(narrowedParameterStates)
+ ? new ConcreteMonomorphicMethodState(isReturnValueUsed, narrowedParameterStates)
: methodState;
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
index c2912c0..202b249 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
@@ -44,6 +44,7 @@
import com.android.tools.r8.utils.IntBox;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.CallSiteOptimizationOptions;
+import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
@@ -389,14 +390,10 @@
}
// Also record the found return value for abstract virtual methods.
- if (newReturnType == dexItemFactory.voidType) {
+ if (newReturnType == dexItemFactory.voidType && returnValueForVirtualMethods != null) {
for (ProgramMethod method : methods) {
if (method.getAccessFlags().isAbstract()) {
returnValuesForVirtualMethods.put(method, returnValueForVirtualMethods);
- } else {
- AbstractValue returnValueForVirtualMethod =
- method.getOptimizationInfo().getAbstractReturnValue();
- assert returnValueForVirtualMethod.equals(returnValueForVirtualMethods);
}
}
}
@@ -441,7 +438,10 @@
if (!appView.appInfo().mayPropagateValueFor(method)) {
return null;
}
- AbstractValue returnValueForMethod = method.getOptimizationInfo().getAbstractReturnValue();
+ AbstractValue returnValueForMethod =
+ method.getReturnType().isAlwaysNull(appView)
+ ? appView.abstractValueFactory().createNullValue()
+ : method.getOptimizationInfo().getAbstractReturnValue();
if (!returnValueForMethod.isSingleValue()
|| !returnValueForMethod.asSingleValue().isMaterializableInAllContexts(appView)
|| (returnValue != null && !returnValueForMethod.equals(returnValue))) {
@@ -510,16 +510,17 @@
private DexType getNewReturnTypeForVirtualMethods(
ProgramMethodSet methods, SingleValue returnValue) {
- if (returnValue != null) {
+ if (returnValue != null || isReturnValueUnusedForVirtualMethods(methods)) {
return dexItemFactory.voidType;
}
+
DexType newReturnType = null;
for (ProgramMethod method : methods) {
if (method.getDefinition().isAbstract()) {
// OK, this method can have any return type.
continue;
}
- DexType newReturnTypeForMethod = getNewReturnType(method, null);
+ DexType newReturnTypeForMethod = getNewReturnType(method, OptionalBool.UNKNOWN, null);
if (newReturnTypeForMethod == null
|| (newReturnType != null && newReturnType != newReturnTypeForMethod)) {
return null;
@@ -530,6 +531,16 @@
return newReturnType;
}
+ private boolean isReturnValueUnusedForVirtualMethods(ProgramMethodSet methods) {
+ ProgramMethod representative = methods.getFirst();
+ return !representative.getReturnType().isVoidType()
+ && Iterables.all(
+ methods,
+ method ->
+ appView.getKeepInfo(method).isUnusedReturnValueOptimizationAllowed(options)
+ && method.getOptimizationInfo().isReturnValueUsed().isFalse());
+ }
+
private DexType getNewParameterTypeForVirtualMethods(
ProgramMethodSet methods, int parameterIndex) {
if (parameterIndex == 0) {
@@ -885,18 +896,26 @@
}
private DexType getNewReturnType(ProgramMethod method) {
- return getNewReturnType(method, getReturnValue(method));
+ return getNewReturnType(
+ method, method.getOptimizationInfo().isReturnValueUsed(), getReturnValue(method));
}
- private DexType getNewReturnType(ProgramMethod method, SingleValue returnValue) {
+ private DexType getNewReturnType(
+ ProgramMethod method, OptionalBool isReturnValueUsed, SingleValue returnValue) {
DexType staticType = method.getReturnType();
- if (staticType.isVoidType()
- || !appView.getKeepInfo(method).isReturnTypeStrengtheningAllowed(options)) {
+ if (staticType.isVoidType()) {
return null;
}
if (returnValue != null) {
return dexItemFactory.voidType;
}
+ KeepMethodInfo keepInfo = appView.getKeepInfo(method);
+ if (keepInfo.isUnusedReturnValueOptimizationAllowed(options) && isReturnValueUsed.isFalse()) {
+ return dexItemFactory.voidType;
+ }
+ if (!keepInfo.isReturnTypeStrengtheningAllowed(options)) {
+ return null;
+ }
TypeElement newReturnTypeElement =
method
.getOptimizationInfo()
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorUnoptimizableMethods.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorUnoptimizableMethods.java
index 6319e90..c277bb7 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorUnoptimizableMethods.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorUnoptimizableMethods.java
@@ -12,15 +12,12 @@
import com.android.tools.r8.optimize.argumentpropagation.codescanner.UnknownMethodState;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.android.tools.r8.utils.classhierarchy.MethodOverridesCollector;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import java.util.Collection;
public class ArgumentPropagatorUnoptimizableMethods {
- private static final MethodSignatureEquivalence equivalence = MethodSignatureEquivalence.get();
-
private final AppView<AppInfoWithLiveness> appView;
private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
private final MethodStateCollectionByReference methodStates;
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodState.java
index d32df00..e730d74 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodState.java
@@ -16,15 +16,25 @@
public class ConcreteMonomorphicMethodState extends ConcreteMethodState
implements ConcreteMonomorphicMethodStateOrBottom, ConcreteMonomorphicMethodStateOrUnknown {
+ boolean isReturnValueUsed;
List<ParameterState> parameterStates;
- public ConcreteMonomorphicMethodState(List<ParameterState> parameterStates) {
+ public ConcreteMonomorphicMethodState(
+ boolean isReturnValueUsed, List<ParameterState> parameterStates) {
assert Streams.stream(Iterables.skip(parameterStates, 1))
.noneMatch(x -> x.isConcrete() && x.asConcrete().isReceiverParameter());
+ this.isReturnValueUsed = isReturnValueUsed;
this.parameterStates = parameterStates;
assert !isEffectivelyUnknown() : "Must use UnknownMethodState instead";
}
+ public static ConcreteMonomorphicMethodStateOrUnknown create(
+ boolean isReturnValueUsed, List<ParameterState> parameterStates) {
+ return isEffectivelyUnknown(isReturnValueUsed, parameterStates)
+ ? unknown()
+ : new ConcreteMonomorphicMethodState(isReturnValueUsed, parameterStates);
+ }
+
public ParameterState getParameterState(int index) {
return parameterStates.get(index);
}
@@ -33,8 +43,21 @@
return parameterStates;
}
+ public boolean isReturnValueUsed() {
+ return isReturnValueUsed;
+ }
+
+ public boolean isEffectivelyBottom() {
+ return Iterables.any(parameterStates, ParameterState::isBottom);
+ }
+
public boolean isEffectivelyUnknown() {
- return Iterables.all(parameterStates, ParameterState::isUnknown);
+ return isEffectivelyUnknown(isReturnValueUsed, parameterStates);
+ }
+
+ private static boolean isEffectivelyUnknown(
+ boolean isReturnValueUsed, List<ParameterState> parameterStates) {
+ return isReturnValueUsed && Iterables.all(parameterStates, ParameterState::isUnknown);
}
@Override
@@ -43,7 +66,7 @@
for (ParameterState parameterState : getParameterStates()) {
copiedParametersStates.add(parameterState.mutableCopy());
}
- return new ConcreteMonomorphicMethodState(copiedParametersStates);
+ return new ConcreteMonomorphicMethodState(isReturnValueUsed, copiedParametersStates);
}
public ConcreteMonomorphicMethodStateOrUnknown mutableJoin(
@@ -56,6 +79,10 @@
return unknown();
}
+ if (methodState.isReturnValueUsed()) {
+ isReturnValueUsed = true;
+ }
+
int argumentIndex = 0;
if (size() > methodSignature.getArity()) {
assert size() == methodSignature.getArity() + 1;
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InParameterFlowPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InParameterFlowPropagator.java
index 9b10015..fae616e 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InParameterFlowPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InParameterFlowPropagator.java
@@ -133,18 +133,10 @@
return;
}
assert methodState.isMonomorphic();
- boolean allUnknown = true;
- for (ParameterState parameterState : methodState.asMonomorphic().getParameterStates()) {
- if (parameterState.isBottom()) {
- methodStates.set(method, MethodState.bottom());
- return;
- }
- if (!parameterState.isUnknown()) {
- assert parameterState.isConcrete();
- allUnknown = false;
- }
- }
- if (allUnknown) {
+ ConcreteMonomorphicMethodState monomorphicMethodState = methodState.asMonomorphic();
+ if (monomorphicMethodState.isEffectivelyBottom()) {
+ methodStates.set(method, MethodState.bottom());
+ } else if (monomorphicMethodState.isEffectivelyUnknown()) {
methodStates.set(method, MethodState.unknown());
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/DependentMinimumKeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/DependentMinimumKeepInfoCollection.java
index 8e48dfe..4c36623 100644
--- a/src/main/java/com/android/tools/r8/shaking/DependentMinimumKeepInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/shaking/DependentMinimumKeepInfoCollection.java
@@ -152,10 +152,14 @@
DependentMinimumKeepInfoCollection rewrittenDependentMinimumKeepInfo =
new DependentMinimumKeepInfoCollection();
forEach(
- (preconditionEvent, minimumKeepInfo) ->
+ (preconditionEvent, minimumKeepInfo) -> {
+ EnqueuerEvent rewrittenPreconditionEvent = preconditionEvent.rewrittenWithLens(graphLens);
+ if (!rewrittenPreconditionEvent.isNoSuchEvent()) {
rewrittenDependentMinimumKeepInfo
- .getOrCreateMinimumKeepInfoFor(preconditionEvent.rewrittenWithLens(graphLens))
- .merge(minimumKeepInfo.rewrittenWithLens(graphLens)));
+ .getOrCreateMinimumKeepInfoFor(rewrittenPreconditionEvent)
+ .merge(minimumKeepInfo.rewrittenWithLens(graphLens));
+ }
+ });
return rewrittenDependentMinimumKeepInfo;
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerEvent.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerEvent.java
index ee2b7d0..8a3439d 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerEvent.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerEvent.java
@@ -16,6 +16,10 @@
return null;
}
+ public boolean isNoSuchEvent() {
+ return false;
+ }
+
public boolean isClassEvent() {
return false;
}
@@ -46,6 +50,27 @@
public abstract EnqueuerEvent rewrittenWithLens(GraphLens lens);
+ public static class NoSuchEnqueuerEvent extends EnqueuerEvent {
+
+ private static final NoSuchEnqueuerEvent INSTANCE = new NoSuchEnqueuerEvent();
+
+ private NoSuchEnqueuerEvent() {}
+
+ public static NoSuchEnqueuerEvent get() {
+ return INSTANCE;
+ }
+
+ @Override
+ public boolean isNoSuchEvent() {
+ return true;
+ }
+
+ @Override
+ public EnqueuerEvent rewrittenWithLens(GraphLens lens) {
+ return this;
+ }
+ }
+
public abstract static class ClassEnqueuerEvent extends EnqueuerEvent {
private final DexType clazz;
@@ -96,7 +121,11 @@
@Override
public EnqueuerEvent rewrittenWithLens(GraphLens lens) {
- return new LiveClassEnqueuerEvent(lens.lookupType(getType()));
+ DexType rewrittenType = lens.lookupType(getType());
+ if (rewrittenType.isIntType()) {
+ return NoSuchEnqueuerEvent.get();
+ }
+ return new LiveClassEnqueuerEvent(rewrittenType);
}
@Override
@@ -139,7 +168,11 @@
@Override
public EnqueuerEvent rewrittenWithLens(GraphLens lens) {
- return new InstantiatedClassEnqueuerEvent(lens.lookupType(getType()));
+ DexType rewrittenType = lens.lookupType(getType());
+ if (rewrittenType.isIntType()) {
+ return NoSuchEnqueuerEvent.get();
+ }
+ return new InstantiatedClassEnqueuerEvent(rewrittenType);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index 2832262..4555f23 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -1823,6 +1823,35 @@
}
}
+ public RootSet rewrittenWithLens(GraphLens graphLens) {
+ if (graphLens.isIdentityLens()) {
+ return this;
+ }
+ return new RootSet(
+ getDependentMinimumKeepInfo().rewrittenWithLens(graphLens),
+ reasonAsked,
+ alwaysInline,
+ neverInlineDueToSingleCaller,
+ bypassClinitForInlining,
+ whyAreYouNotInlining,
+ reprocess,
+ neverReprocess,
+ alwaysClassInline,
+ neverClassInline,
+ noUnusedInterfaceRemoval,
+ noVerticalClassMerging,
+ noHorizontalClassMerging,
+ neverPropagateValue,
+ mayHaveSideEffects,
+ noSideEffects,
+ assumedValues,
+ dependentKeepClassCompatRule,
+ identifierNameStrings,
+ ifRules,
+ delayedRootSetActionItems,
+ pendingMethodMoveInverse);
+ }
+
void shouldNotBeMinified(ProgramDefinition definition) {
getDependentMinimumKeepInfo()
.getOrCreateUnconditionalMinimumKeepInfoFor(definition.getReference())
@@ -2133,6 +2162,7 @@
// Do nothing.
}
+ @Override
public MainDexRootSet rewrittenWithLens(GraphLens graphLens) {
if (graphLens.isIdentityLens()) {
return this;
diff --git a/src/test/java/com/android/tools/r8/cf/InlineCmpDoubleTest.java b/src/test/java/com/android/tools/r8/cf/InlineCmpDoubleTest.java
index 1db3ee4..36e6189 100644
--- a/src/test/java/com/android/tools/r8/cf/InlineCmpDoubleTest.java
+++ b/src/test/java/com/android/tools/r8/cf/InlineCmpDoubleTest.java
@@ -5,6 +5,7 @@
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.KeepUnusedReturnValue;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -42,6 +43,7 @@
.addProgramClasses(TestClass.class)
.addKeepMainRule(TestClass.class)
.addOptionsModification(options -> options.inlinerOptions().enableInlining = enableInlining)
+ .enableKeepUnusedReturnValueAnnotations()
.setMinApi(parameters.getApiLevel())
.compile()
.inspect(this::inspect)
@@ -66,6 +68,7 @@
inlineMe(x + 41);
}
+ @KeepUnusedReturnValue
public static int inlineMe(int x) {
// Side effect to ensure that the invocation is not removed simply because the method does not
// have any side effects.
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
index b056931..bfd9f97 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
@@ -15,6 +15,7 @@
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.KeepUnusedReturnValue;
import com.android.tools.r8.ProgramResourceProvider;
import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.R8TestCompileResult;
@@ -184,9 +185,12 @@
"return");
// Add two methods with the same name that have return types A[] and B[], respectively.
+ classBuilder.addRuntimeInvisibleAnnotation(KeepUnusedReturnValue.class.getTypeName());
classBuilder.addStaticMethod(
"method", ImmutableList.of(), "[Lclassmerging/A;",
".limit stack 1", ".limit locals 1", "iconst_0", "anewarray classmerging/A", "areturn");
+
+ classBuilder.addRuntimeInvisibleAnnotation(KeepUnusedReturnValue.class.getTypeName());
classBuilder.addStaticMethod(
"method", ImmutableList.of(), "[Lclassmerging/B;",
".limit stack 1", ".limit locals 1", "iconst_0", "anewarray classmerging/B", "areturn");
@@ -209,7 +213,8 @@
"-neverinline class " + main + " {",
" static classmerging.A[] method(...);",
" static classmerging.B[] method(...);",
- "}"),
+ "}")
+ .enableKeepUnusedReturnValueAnnotations(),
main,
jasminBuilder.build(),
preservedClassNames::contains);
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 bded17f..b7a3474 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
@@ -328,7 +328,7 @@
m =
clazz.method(
- "int",
+ allowAccessModification ? "void" : "int",
"notInlinable",
ImmutableList.of("inlining." + (allowAccessModification ? "B" : "A")));
assertCounters(INLINABLE, NEVER_INLINABLE, countInvokes(inspector, m));
@@ -344,12 +344,12 @@
m =
clazz.method(
- "int", "notInlinableOnThrow", ImmutableList.of("java.lang.IllegalArgumentException"));
+ "void", "notInlinableOnThrow", ImmutableList.of("java.lang.IllegalArgumentException"));
assertCounters(ALWAYS_INLINABLE, NEVER_INLINABLE, countInvokes(inspector, m));
m =
clazz.method(
- "int",
+ "void",
"notInlinableDueToMissingNpeBeforeThrow",
ImmutableList.of("java.lang.IllegalArgumentException"));
assertCounters(ALWAYS_INLINABLE, NEVER_INLINABLE * 2, countInvokes(inspector, m));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/InvokeStaticWithNullOutvalueTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/InvokeStaticWithNullOutvalueTest.java
index 6750562..9dfcb42 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/InvokeStaticWithNullOutvalueTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/InvokeStaticWithNullOutvalueTest.java
@@ -8,6 +8,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.KeepUnusedReturnValue;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NeverPropagateValue;
@@ -46,6 +47,7 @@
.addInnerClasses(InvokeStaticWithNullOutvalueTest.class)
.addKeepMainRule(MAIN)
.enableInliningAnnotations()
+ .enableKeepUnusedReturnValueAnnotations()
.enableMemberValuePropagationAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
@@ -85,6 +87,7 @@
@NoHorizontalClassMerging
static class Companion {
+ @KeepUnusedReturnValue
@NeverInline
@NeverPropagateValue
private static Object boo() {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java
index f8dd8d9..16c8f61 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java
@@ -45,7 +45,12 @@
"SubFactory.createVirtual() -> null",
"SubSubFactory.createVirtual() -> null");
- testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expected);
+ if (parameters.isCfRuntime()) {
+ testForJvm()
+ .addTestClasspath()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(expected);
+ }
CodeInspector inspector =
testForR8(parameters.getBackend())
@@ -55,8 +60,8 @@
.enableNoMethodStaticizingAnnotations()
.enableNoVerticalClassMergingAnnotations()
.enableNoHorizontalClassMergingAnnotations()
- .addKeepRules("-dontobfuscate")
.addOptionsModification(options -> options.enableClassInlining = false)
+ .noMinification()
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(expected)
@@ -66,16 +71,16 @@
MethodSubject createStaticMethodSubject =
factoryClassSubject.uniqueMethodWithName("createStatic");
assertThat(createStaticMethodSubject, isPresent());
- assertTrue(createStaticMethodSubject.getMethod().getReference().proto.returnType.isVoidType());
+ assertTrue(createStaticMethodSubject.getMethod().getReturnType().isVoidType());
MethodSubject createVirtualMethodSubject =
factoryClassSubject.uniqueMethodWithName("createVirtual");
assertThat(createVirtualMethodSubject, isPresent());
- assertTrue(createVirtualMethodSubject.getMethod().getReference().proto.returnType.isVoidType());
+ assertTrue(createVirtualMethodSubject.getMethod().getReturnType().isVoidType());
createVirtualMethodSubject =
inspector.clazz(SubFactory.class).uniqueMethodWithName("createVirtual");
assertThat(createVirtualMethodSubject, isPresent());
- assertTrue(createVirtualMethodSubject.getMethod().getReference().proto.returnType.isVoidType());
+ assertTrue(createVirtualMethodSubject.getMethod().getReturnType().isVoidType());
ClassSubject subSubFactoryClassSubject = inspector.clazz(SubSubFactory.class);
assertThat(subSubFactoryClassSubject.method("void", "createVirtual"), isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentRemovalWithOverridingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentRemovalWithOverridingTest.java
index 58b052c..44c15e6 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentRemovalWithOverridingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentRemovalWithOverridingTest.java
@@ -83,7 +83,7 @@
@NeverInline
public String greeting(String used) {
- return used;
+ return System.currentTimeMillis() >= 0 ? used : null;
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNoSuchMethodErrorTest.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNoSuchMethodErrorTest.java
index 1c2f06d..f3718cd 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNoSuchMethodErrorTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNoSuchMethodErrorTest.java
@@ -7,6 +7,7 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
+import com.android.tools.r8.KeepUnusedReturnValue;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.R8TestRunResult;
@@ -78,6 +79,7 @@
.addKeepAttributeSourceFile()
.setMinApi(parameters.getApiLevel())
.enableInliningAnnotations()
+ .enableKeepUnusedReturnValueAnnotations()
.enableExperimentalMapFileVersion();
R8TestRunResult runResult;
if (throwReceiverNpe) {
@@ -112,6 +114,7 @@
throw new RuntimeException("Will be removed");
}
+ @KeepUnusedReturnValue
Object inlinable() {
return foo();
}
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java
index bb37a8a..5403673 100644
--- a/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java
@@ -25,8 +25,6 @@
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -34,7 +32,6 @@
@RunWith(Parameterized.class)
public class VerticalClassMergingRetraceTest extends RetraceTestBase {
- private Set<StackTraceLine> haveSeenLines = new HashSet<>();
@Parameters(name = "{0}, mode: {1}, compat: {2}")
public static Collection<Object[]> data() {
@@ -136,7 +133,6 @@
public void testNoLineNumberTable() throws Exception {
assumeTrue(compat);
assumeTrue(parameters.isDexRuntime());
- haveSeenLines.clear();
Box<MethodSubject> syntheticMethod = new Box<>();
runTest(
ImmutableList.of(),
@@ -171,8 +167,11 @@
// Will be merged down, and represented as:
// java.lang.String ...ResourceWrapper.foo() -> a
@NeverInline
- String foo() {
- throw null;
+ String foo(boolean doThrow) {
+ if (doThrow) {
+ throw null;
+ }
+ return System.currentTimeMillis() > 0 ? "arg" : null;
}
}
@@ -181,6 +180,7 @@
class MainApp {
public static void main(String[] args) {
TintResources t = new TintResources();
- System.out.println(t.foo());
+ boolean doThrow = System.currentTimeMillis() > 0;
+ System.out.println(t.foo(doThrow));
}
}
diff --git a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
index aedd6a4..173990f 100644
--- a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
+++ b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
@@ -13,7 +13,6 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.BooleanUtils;
@@ -201,11 +200,7 @@
insn instanceof DexInstructionSubject && ((DexInstructionSubject) insn).isConst4());
} else {
assertTrue(insn instanceof CfInstructionSubject);
- assertTrue(((CfInstructionSubject) insn).isStackInstruction(Opcode.Pop));
- assertTrue(instructions.hasNext());
- insn = instructions.next();
- assertTrue(insn instanceof CfInstructionSubject);
- assertTrue(((CfInstructionSubject) insn).isConstNull());
+ assertTrue(insn.isConstNull());
}
assertTrue(nextInstruction(instructions).isThrow());
assertFalse(instructions.hasNext());
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/MonomorphicMethodWithUnusedReturnValueTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/MonomorphicMethodWithUnusedReturnValueTest.java
new file mode 100644
index 0000000..d7130ef
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/MonomorphicMethodWithUnusedReturnValueTest.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2022, 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.optimize.argumentpropagation;
+
+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.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+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 MonomorphicMethodWithUnusedReturnValueTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ // TODO(b/173398086): uniqueMethodWithName() does not work with proto changes.
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ // The test() method has been changed to have return type void.
+ MethodSubject testMethodSubject =
+ inspector.clazz(Main.class).uniqueMethodWithName("test");
+ assertThat(testMethodSubject, isPresent());
+ assertTrue(testMethodSubject.getProgramMethod().getReturnType().isVoidType());
+
+ // The class ReturnType has been removed by tree shaking.
+ assertThat(inspector.clazz(ReturnType.class), isAbsent());
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("create()");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ test();
+ }
+
+ @NeverInline
+ static ReturnType test() {
+ System.out.println("create()");
+ return new ReturnType();
+ }
+ }
+
+ static class ReturnType {}
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/PolymorphicMethodWithUnusedReturnValueTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/PolymorphicMethodWithUnusedReturnValueTest.java
new file mode 100644
index 0000000..e8007c3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/PolymorphicMethodWithUnusedReturnValueTest.java
@@ -0,0 +1,91 @@
+// Copyright (c) 2022, 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.optimize.argumentpropagation;
+
+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.Assert.assertTrue;
+
+import com.android.tools.r8.NoHorizontalClassMerging;
+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.codeinspector.MethodSubject;
+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 PolymorphicMethodWithUnusedReturnValueTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableNoHorizontalClassMergingAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ // TODO(b/173398086): uniqueMethodWithName() does not work with proto changes.
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ // The test() methods have been changed to have return type void.
+ for (Class<?> clazz : new Class<?>[] {A.class, B.class}) {
+ MethodSubject testMethodSubject = inspector.clazz(clazz).uniqueMethodWithName("m");
+ assertThat(testMethodSubject, isPresent());
+ assertTrue(testMethodSubject.getProgramMethod().getReturnType().isVoidType());
+ }
+
+ // The class ReturnType has been removed by tree shaking.
+ assertThat(inspector.clazz(ReturnType.class), isAbsent());
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A.m()");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ A a = System.currentTimeMillis() >= 0 ? new A() : new B();
+ a.m();
+ }
+ }
+
+ @NoHorizontalClassMerging
+ @NoVerticalClassMerging
+ static class A {
+
+ ReturnType m() {
+ System.out.println("A.m()");
+ return new ReturnType();
+ }
+ }
+
+ static class B extends A {
+
+ @Override
+ ReturnType m() {
+ System.out.println("B.m()");
+ return new ReturnType();
+ }
+ }
+
+ @NoHorizontalClassMerging
+ static class ReturnType {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/KeepDisallowAnnotationRemovalAllowOptimizationTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/KeepDisallowAnnotationRemovalAllowOptimizationTest.java
index 8fa7bc1..62bcf76 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/KeepDisallowAnnotationRemovalAllowOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/KeepDisallowAnnotationRemovalAllowOptimizationTest.java
@@ -11,6 +11,7 @@
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
+import com.android.tools.r8.KeepUnusedReturnValue;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -53,6 +54,7 @@
// In compatibility mode the rule above is a no-op.
.allowUnusedProguardConfigurationRules(enableCompatibilityMode)
.enableInliningAnnotations()
+ .enableKeepUnusedReturnValueAnnotations()
.setMinApi(parameters.getApiLevel())
.compile()
.inspect(
@@ -83,6 +85,7 @@
}
}
+ @KeepUnusedReturnValue
@NeverInline
static Object getNonNull() {
System.out.println("getNonNull()");