blob: e0f991d909686a4503d134b807359b47f31571bd [file] [log] [blame]
// Copyright (c) 2019, 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.ir.optimize.classinliner;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.utils.BooleanLatticeElement;
import com.android.tools.r8.utils.OptionalBool;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
/**
* This analysis determines whether a method returns the receiver. The analysis is specific to the
* class inliner, and the result is therefore not sound in general.
*
* <p>The analysis makes the following assumptions.
*
* <ul>
* <li>None of the given method's arguments is an alias of the receiver (except for the receiver
* itself).
* <li>The receiver is not stored in the heap. Thus, it is guaranteed that (i) all field-get
* instructions do not return an alias of the receiver, and (ii) invoke instructions can only
* return an alias of the receiver if the receiver is given as an argument.
* </ul>
*/
public class ClassInlinerReceiverAnalysis {
private final AppView<?> appView;
private final DexEncodedMethod method;
private final IRCode code;
private final Value receiver;
private final Map<Value, OptionalBool> isReceiverAliasCache = new IdentityHashMap<>();
public ClassInlinerReceiverAnalysis(AppView<?> appView, DexEncodedMethod method, IRCode code) {
this.appView = appView;
this.method = method;
this.code = code;
this.receiver = code.getThis();
assert !receiver.hasAliasedValue();
assert receiver.getType().isClassType();
}
public OptionalBool computeReturnsReceiver() {
if (method.method.proto.returnType.isVoidType()) {
return OptionalBool.FALSE;
}
List<BasicBlock> normalExitBlocks = code.computeNormalExitBlocks();
if (normalExitBlocks.isEmpty()) {
return OptionalBool.FALSE;
}
BooleanLatticeElement result = OptionalBool.BOTTOM;
for (BasicBlock block : normalExitBlocks) {
Value returnValue = block.exit().asReturn().returnValue();
result = result.join(getOrComputeIsReceiverAlias(returnValue));
// Stop as soon as we reach unknown.
if (result.isUnknown()) {
return OptionalBool.UNKNOWN;
}
}
assert !result.isBottom();
return result.asOptionalBool();
}
private OptionalBool getOrComputeIsReceiverAlias(Value value) {
Value root = value.getAliasedValue();
return isReceiverAliasCache.computeIfAbsent(root, this::computeIsReceiverAlias);
}
private OptionalBool computeIsReceiverAlias(Value value) {
assert !value.hasAliasedValue();
if (value == receiver) {
// Guaranteed to return the receiver.
return OptionalBool.TRUE;
}
ClassTypeElement valueType = value.getType().asClassType();
if (valueType == null) {
return OptionalBool.FALSE;
}
ClassTypeElement receiverType = receiver.getType().asClassType();
if (!valueType.isRelatedTo(receiverType, appView)) {
// Guaranteed not to return the receiver.
return OptionalBool.FALSE;
}
if (value.isPhi()) {
// Not sure what is returned.
return OptionalBool.UNKNOWN;
}
Instruction definition = value.definition;
if (definition.isArrayGet() || definition.isFieldGet()) {
// Guaranteed not to return the receiver, since the class inliner does not allow the
// receiver to flow into any arrays or fields.
return OptionalBool.FALSE;
}
if (definition.isConstInstruction() || definition.isCreatingInstanceOrArray()) {
// Guaranteed not to return the receiver.
return OptionalBool.FALSE;
}
if (definition.isInvokeMethod()) {
// Since the class inliner does not allow the receiver to flow into the heap, the only way for
// the invoked method to return the receiver is if one of the given arguments is an alias of
// the receiver.
InvokeMethod invoke = definition.asInvokeMethod();
for (Value argument : invoke.arguments()) {
if (getOrComputeIsReceiverAlias(argument).isPossiblyTrue()) {
return OptionalBool.UNKNOWN;
}
}
return OptionalBool.FALSE;
}
// Not sure what is returned.
return OptionalBool.UNKNOWN;
}
}