blob: 3ba9289cbc3feeba0bdbab10d652050492255ac4 [file] [log] [blame]
// Copyright (c) 2018, 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;
import static com.android.tools.r8.ir.code.DominatorTree.Assumption.MAY_HAVE_UNREACHABLE_BLOCKS;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.CatchHandlers;
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.DominatorTree;
import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.IRCode;
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.Position;
import com.android.tools.r8.ir.code.Throw;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.Sets;
import java.util.BitSet;
import java.util.ListIterator;
import java.util.Set;
public class UninstantiatedTypeOptimization {
private final AppView<? extends AppInfoWithLiveness> appView;
private final InternalOptions options;
private int numberOfInstanceGetOrInstancePutWithNullReceiver = 0;
private int numberOfInvokesWithNullArgument = 0;
private int numberOfInvokesWithNullReceiver = 0;
public UninstantiatedTypeOptimization(
AppView<? extends AppInfoWithLiveness> appView, InternalOptions options) {
this.appView = appView;
this.options = options;
}
public void rewrite(DexEncodedMethod method, IRCode code) {
Set<BasicBlock> blocksToBeRemoved = Sets.newIdentityHashSet();
ListIterator<BasicBlock> blockIterator = code.listIterator();
while (blockIterator.hasNext()) {
BasicBlock block = blockIterator.next();
if (blocksToBeRemoved.contains(block)) {
continue;
}
InstructionListIterator instructionIterator = block.listIterator();
while (instructionIterator.hasNext()) {
Instruction instruction = instructionIterator.next();
if (instruction.isFieldInstruction()) {
if (instruction.isInstanceGet() || instruction.isInstancePut()) {
rewriteInstanceFieldInstruction(
instruction.asFieldInstruction(),
blockIterator,
instructionIterator,
code,
blocksToBeRemoved);
} else {
rewriteStaticFieldInstruction(
instruction.asFieldInstruction(),
blockIterator,
instructionIterator,
code,
blocksToBeRemoved);
}
} else if (instruction.isInvokeMethod()) {
rewriteInvoke(
instruction.asInvokeMethod(),
blockIterator,
instructionIterator,
code,
blocksToBeRemoved);
}
}
}
code.removeBlocks(blocksToBeRemoved);
code.removeAllTrivialPhis();
code.removeUnreachableBlocks();
assert code.isConsistentSSA();
}
public void logResults() {
assert Log.ENABLED;
Log.info(
getClass(),
"Number of instance-get/instance-put with null receiver: %s",
numberOfInstanceGetOrInstancePutWithNullReceiver);
Log.info(
getClass(), "Number of invokes with null argument: %s", numberOfInvokesWithNullArgument);
Log.info(
getClass(), "Number of invokes with null receiver: %s", numberOfInvokesWithNullReceiver);
}
private void rewriteInstanceFieldInstruction(
FieldInstruction instruction,
ListIterator<BasicBlock> blockIterator,
InstructionListIterator instructionIterator,
IRCode code,
Set<BasicBlock> blocksToBeRemoved) {
assert instruction.isInstanceGet() || instruction.isInstancePut();
boolean replacedByThrowNull = false;
Value receiver = instruction.inValues().get(0);
if (isAlwaysNull(receiver)) {
// Unable to rewrite instruction if the receiver is defined from "const-number 0", since this
// would lead to an IncompatibleClassChangeError (see MemberResolutionTest#lookupStaticField-
// WithFieldGetFromNullReferenceDirectly).
if (!receiver.getTypeLattice().isDefinitelyNull()) {
replaceCurrentInstructionWithThrowNull(
instruction, blockIterator, instructionIterator, code, blocksToBeRemoved);
++numberOfInstanceGetOrInstancePutWithNullReceiver;
replacedByThrowNull = true;
}
}
if (!replacedByThrowNull) {
rewriteFieldInstruction(
instruction, blockIterator, instructionIterator, code, blocksToBeRemoved);
}
}
private void rewriteStaticFieldInstruction(
FieldInstruction instruction,
ListIterator<BasicBlock> blockIterator,
InstructionListIterator instructionIterator,
IRCode code,
Set<BasicBlock> blocksToBeRemoved) {
assert instruction.isStaticGet() || instruction.isStaticPut();
rewriteFieldInstruction(
instruction, blockIterator, instructionIterator, code, blocksToBeRemoved);
}
private void rewriteFieldInstruction(
FieldInstruction instruction,
ListIterator<BasicBlock> blockIterator,
InstructionListIterator instructionIterator,
IRCode code,
Set<BasicBlock> blocksToBeRemoved) {
DexType fieldType = instruction.getField().type;
if (isAlwaysNull(fieldType)) {
// Before trying to remove this instruction, we need to be sure that the field actually
// exists. Otherwise this instruction would throw a NoSuchFieldError exception.
DexEncodedField field = appView.appInfo().definitionFor(instruction.getField());
if (field == null) {
return;
}
// We also need to be sure that this field instruction cannot trigger static class
// initialization.
if (field.field.clazz != code.method.method.holder) {
DexClass enclosingClass = appView.appInfo().definitionFor(code.method.method.holder);
if (enclosingClass == null
|| enclosingClass.classInitializationMayHaveSideEffects(appView.appInfo())) {
return;
}
}
BasicBlock block = instruction.getBlock();
if (instruction.isFieldPut()) {
Value value =
instruction.isInstancePut()
? instruction.asInstancePut().value()
: instruction.asStaticPut().inValue();
TypeLatticeElement fieldLatticeType =
TypeLatticeElement.fromDexType(fieldType, true, appView.appInfo());
if (!value.getTypeLattice().lessThanOrEqual(fieldLatticeType, appView.appInfo())) {
// Broken type hierarchy. See FieldTypeTest#test_brokenTypeHierarchy.
assert options.testing.allowTypeErrors;
return;
}
// We know that the right-hand side must be null, so this is a no-op.
instructionIterator.removeOrReplaceByDebugLocalRead();
} else {
// Replace the field read by the constant null.
instructionIterator.replaceCurrentInstruction(code.createConstNull());
}
if (block.hasCatchHandlers()) {
// This block can no longer throw.
block.getCatchHandlers().getUniqueTargets().forEach(BasicBlock::unlinkCatchHandler);
}
}
}
private void rewriteInvoke(
InvokeMethod invoke,
ListIterator<BasicBlock> blockIterator,
InstructionListIterator instructionIterator,
IRCode code,
Set<BasicBlock> blocksToBeRemoved) {
if (invoke.isInvokeMethodWithReceiver()) {
Value receiver = invoke.asInvokeMethodWithReceiver().getReceiver();
if (isAlwaysNull(receiver)) {
replaceCurrentInstructionWithThrowNull(
invoke, blockIterator, instructionIterator, code, blocksToBeRemoved);
++numberOfInvokesWithNullReceiver;
return;
}
}
DexEncodedMethod target =
invoke.lookupSingleTarget(appView.appInfo(), code.method.method.holder);
if (target == null) {
return;
}
BitSet nonNullParamHints = target.getOptimizationInfo().getNonNullParamHints();
if (nonNullParamHints != null) {
int argumentIndex = target.isStatic() ? 0 : 1;
int nonNullParamHintIndex = 0;
while (argumentIndex < invoke.arguments().size()) {
Value argument = invoke.arguments().get(argumentIndex);
if (isAlwaysNull(argument) && nonNullParamHints.get(nonNullParamHintIndex)) {
replaceCurrentInstructionWithThrowNull(
invoke, blockIterator, instructionIterator, code, blocksToBeRemoved);
++numberOfInvokesWithNullArgument;
return;
}
argumentIndex++;
nonNullParamHintIndex++;
}
assert argumentIndex == nonNullParamHintIndex + (target.isStatic() ? 0 : 1);
}
}
private void replaceCurrentInstructionWithThrowNull(
Instruction instruction,
ListIterator<BasicBlock> blockIterator,
InstructionListIterator instructionIterator,
IRCode code,
Set<BasicBlock> blocksToBeRemoved) {
BasicBlock block = instruction.getBlock();
assert !blocksToBeRemoved.contains(block);
BasicBlock normalSuccessorBlock = instructionIterator.split(code, blockIterator);
instructionIterator.previous();
// Unlink all blocks that are dominated by successor.
{
DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
blocksToBeRemoved.addAll(block.unlink(normalSuccessorBlock, dominatorTree));
}
// Insert constant null before the instruction.
instructionIterator.previous();
Value nullValue = new Value(code.valueNumberGenerator.next(), TypeLatticeElement.NULL, null);
ConstNumber constNumberInstruction = new ConstNumber(nullValue, 0);
// Note that we only keep position info for throwing instructions in release mode.
constNumberInstruction.setPosition(options.debug ? instruction.getPosition() : Position.none());
instructionIterator.add(constNumberInstruction);
instructionIterator.next();
// Replace the instruction by throw.
Throw throwInstruction = new Throw(nullValue);
for (Value inValue : instruction.inValues()) {
if (inValue.hasLocalInfo()) {
// Add this value as a debug value to avoid changing its live range.
throwInstruction.addDebugValue(inValue);
}
}
instructionIterator.replaceCurrentInstruction(throwInstruction);
instructionIterator.next();
instructionIterator.remove();
// Remove all catch handlers where the guard does not include NullPointerException.
if (block.hasCatchHandlers()) {
CatchHandlers<BasicBlock> catchHandlers = block.getCatchHandlers();
catchHandlers.forEach(
(guard, target) -> {
if (blocksToBeRemoved.contains(target)) {
// Already removed previously. This may happen if two catch handlers have the same
// target.
return;
}
if (!appView.dexItemFactory().npeType.isSubtypeOf(guard, appView.appInfo())) {
// TODO(christofferqa): Consider updating previous dominator tree instead of
// rebuilding it from scratch.
DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
blocksToBeRemoved.addAll(block.unlink(target, dominatorTree));
}
});
}
}
private boolean isAlwaysNull(Value value) {
if (value.hasLocalInfo()) {
// Not always null as the value can be changed via the debugger.
return false;
}
TypeLatticeElement typeLatticeElement = value.getTypeLattice();
if (typeLatticeElement.isDefinitelyNull()) {
return true;
}
if (typeLatticeElement.isClassType()) {
return isAlwaysNull(typeLatticeElement.asClassTypeLatticeElement().getClassType());
}
return false;
}
private boolean isAlwaysNull(DexType type) {
if (type.isClassType()) {
DexClass clazz = appView.appInfo().definitionFor(type);
return clazz != null
&& clazz.isProgramClass()
&& !appView.appInfo().isInstantiatedDirectlyOrIndirectly(type);
}
return false;
}
}