blob: 4ad6534ed04407884204e0e24bfba60afcc97b56 [file] [log] [blame]
// Copyright (c) 2016, 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.analysis.type.Nullability.definitelyNotNull;
import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
import static com.android.tools.r8.ir.code.Opcodes.CONST_CLASS;
import static com.android.tools.r8.ir.code.Opcodes.CONST_NUMBER;
import static com.android.tools.r8.ir.code.Opcodes.CONST_STRING;
import static com.android.tools.r8.ir.code.Opcodes.DEX_ITEM_BASED_CONST_STRING;
import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET;
import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProto;
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.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.code.ArrayLength;
import com.android.tools.r8.ir.code.Assume;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.BasicBlockIterator;
import com.android.tools.r8.ir.code.Binop;
import com.android.tools.r8.ir.code.CatchHandlers;
import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
import com.android.tools.r8.ir.code.ConstClass;
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.ConstString;
import com.android.tools.r8.ir.code.DebugLocalWrite;
import com.android.tools.r8.ir.code.DebugLocalsChange;
import com.android.tools.r8.ir.code.DexItemBasedConstString;
import com.android.tools.r8.ir.code.DominatorTree;
import com.android.tools.r8.ir.code.Goto;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.IfType;
import com.android.tools.r8.ir.code.InstanceFieldInstruction;
import com.android.tools.r8.ir.code.InstanceGet;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.Instruction.SideEffectAssumption;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InstructionOrPhi;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeInterface;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.Move;
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Position.SyntheticPosition;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Throw;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.LazyBox;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.longs.Long2ReferenceMap;
import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
public class CodeRewriter {
// This constant was determined by experimentation.
private static final int STOP_SHARED_CONSTANT_THRESHOLD = 50;
private final AppView<?> appView;
private final DexItemFactory dexItemFactory;
private final InternalOptions options;
public CodeRewriter(AppView<?> appView) {
this.appView = appView;
this.options = appView.options();
this.dexItemFactory = appView.dexItemFactory();
}
public static void removeAssumeInstructions(AppView<?> appView, IRCode code) {
// We need to update the types of all values whose definitions depend on a non-null value.
// This is needed to preserve soundness of the types after the Assume instructions have been
// removed.
//
// As an example, consider a check-cast instruction on the form "z = (T) y". If y used to be
// defined by a NonNull instruction, then the type analysis could have used this information
// to mark z as non-null. However, cleanupNonNull() have now replaced y by a nullable value x.
// Since z is defined as "z = (T) x", and x is nullable, it is no longer sound to have that z
// is not nullable. This is fixed by rerunning the type analysis for the affected values.
Set<Value> valuesThatRequireWidening = Sets.newIdentityHashSet();
InstructionListIterator it = code.instructionListIterator();
boolean needToCheckTrivialPhis = false;
while (it.hasNext()) {
Instruction instruction = it.next();
// The following deletes Assume instructions and replaces any specialized value by its
// original value:
// y <- Assume(x)
// ...
// y.foo()
//
// becomes:
//
// x.foo()
if (instruction.isAssume()) {
Assume assumeInstruction = instruction.asAssume();
Value src = assumeInstruction.src();
Value dest = assumeInstruction.outValue();
valuesThatRequireWidening.addAll(dest.affectedValues());
// Replace `dest` by `src`.
needToCheckTrivialPhis |= dest.numberOfPhiUsers() > 0;
dest.replaceUsers(src);
it.remove();
}
}
// Assume insertion may introduce phis, e.g.,
// y <- Assume(x)
// ...
// z <- phi(x, y)
//
// Therefore, Assume elimination may result in a trivial phi:
// z <- phi(x, x)
if (needToCheckTrivialPhis) {
code.removeAllDeadAndTrivialPhis(valuesThatRequireWidening);
}
if (!valuesThatRequireWidening.isEmpty()) {
new TypeAnalysis(appView).widening(valuesThatRequireWidening);
}
assert Streams.stream(code.instructions()).noneMatch(Instruction::isAssume);
}
// Rewrite 'throw new NullPointerException()' to 'throw null'.
public void rewriteThrowNullPointerException(IRCode code) {
boolean shouldRemoveUnreachableBlocks = false;
for (BasicBlock block : code.blocks) {
InstructionListIterator it = block.listIterator(code);
while (it.hasNext()) {
Instruction instruction = it.next();
// Check for the patterns 'if (x == null) throw null' and
// 'if (x == null) throw new NullPointerException()'.
if (instruction.isIf()) {
if (appView
.dexItemFactory()
.objectsMethods
.isRequireNonNullMethod(code.method().getReference())) {
continue;
}
If ifInstruction = instruction.asIf();
if (!ifInstruction.isZeroTest()) {
continue;
}
Value value = ifInstruction.lhs();
if (!value.getType().isReferenceType()) {
assert value.getType().isPrimitiveType();
continue;
}
BasicBlock valueIsNullTarget = ifInstruction.targetFromCondition(0);
if (valueIsNullTarget.getPredecessors().size() != 1
|| !valueIsNullTarget.exit().isThrow()) {
continue;
}
Throw throwInstruction = valueIsNullTarget.exit().asThrow();
Value exceptionValue = throwInstruction.exception().getAliasedValue();
if (!exceptionValue.isConstZero()
&& exceptionValue.isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
NewInstance newInstance = exceptionValue.definition.asNewInstance();
if (newInstance.clazz != dexItemFactory.npeType) {
continue;
}
if (newInstance.outValue().numberOfAllUsers() != 2) {
continue; // Could be mutated before it is thrown.
}
InvokeDirect constructorCall = newInstance.getUniqueConstructorInvoke(dexItemFactory);
if (constructorCall == null) {
continue;
}
if (constructorCall.getInvokedMethod() != dexItemFactory.npeMethods.init) {
continue;
}
} else if (!exceptionValue.isConstZero()) {
continue;
}
boolean canDetachValueIsNullTarget = true;
for (Instruction i : valueIsNullTarget.instructionsBefore(throwInstruction)) {
if (!i.isBlockLocalInstructionWithoutSideEffects(appView, code.context())) {
canDetachValueIsNullTarget = false;
break;
}
}
if (!canDetachValueIsNullTarget) {
continue;
}
insertNotNullCheck(
block,
it,
ifInstruction,
ifInstruction.targetFromCondition(1),
valueIsNullTarget,
throwInstruction.getPosition());
shouldRemoveUnreachableBlocks = true;
}
// Check for 'new-instance NullPointerException' with 2 users, not declaring a local and
// not ending the scope of any locals.
if (instruction.isNewInstance()
&& instruction.asNewInstance().clazz == dexItemFactory.npeType
&& instruction.outValue().numberOfAllUsers() == 2
&& !instruction.outValue().hasLocalInfo()
&& instruction.getDebugValues().isEmpty()) {
if (it.hasNext()) {
Instruction instruction2 = it.next();
// Check for 'invoke NullPointerException.init() not ending the scope of any locals
// and with the result of the first instruction as the argument. Also check that
// the two first instructions have the same position.
if (instruction2.isInvokeDirect()
&& instruction2.getDebugValues().isEmpty()) {
InvokeDirect invokeDirect = instruction2.asInvokeDirect();
if (invokeDirect.getInvokedMethod() == dexItemFactory.npeMethods.init
&& invokeDirect.getReceiver() == instruction.outValue()
&& invokeDirect.arguments().size() == 1
&& invokeDirect.getPosition() == instruction.getPosition()) {
if (it.hasNext()) {
Instruction instruction3 = it.next();
// Finally check that the last instruction is a throw of the initialized
// exception object and replace with 'throw null' if so.
if (instruction3.isThrow()
&& instruction3.asThrow().exception() == instruction.outValue()) {
// Create const 0 with null type and a throw using that value.
Instruction nullPointer = code.createConstNull();
Instruction throwInstruction = new Throw(nullPointer.outValue());
// Preserve positions: we have checked that the first two original instructions
// have the same position.
assert instruction.getPosition() == instruction2.getPosition();
nullPointer.setPosition(instruction.getPosition());
throwInstruction.setPosition(instruction3.getPosition());
// Copy debug values from original throw to new throw to correctly end scope
// of locals.
instruction3.moveDebugValues(throwInstruction);
// Remove the three original instructions.
it.remove();
it.previous();
it.remove();
it.previous();
it.remove();
// Replace them with 'const 0' and 'throw'.
it.add(nullPointer);
it.add(throwInstruction);
}
}
}
}
}
}
}
}
if (shouldRemoveUnreachableBlocks) {
Set<Value> affectedValues = code.removeUnreachableBlocks();
if (!affectedValues.isEmpty()) {
new TypeAnalysis(appView).narrowing(affectedValues);
}
}
assert code.isConsistentSSA(appView);
}
private boolean checkArgumentType(InvokeMethod invoke, int argumentIndex) {
// TODO(sgjesse): Insert cast if required.
TypeElement returnType =
TypeElement.fromDexType(invoke.getInvokedMethod().proto.returnType, maybeNull(), appView);
TypeElement argumentType =
TypeElement.fromDexType(getArgumentType(invoke, argumentIndex), maybeNull(), appView);
return appView.enableWholeProgramOptimizations()
? argumentType.lessThanOrEqual(returnType, appView)
: argumentType.equals(returnType);
}
private DexType getArgumentType(InvokeMethod invoke, int argumentIndex) {
if (invoke.isInvokeStatic()) {
return invoke.getInvokedMethod().proto.parameters.values[argumentIndex];
}
if (argumentIndex == 0) {
return invoke.getInvokedMethod().holder;
}
return invoke.getInvokedMethod().proto.parameters.values[argumentIndex - 1];
}
// Replace result uses for methods where something is known about what is returned.
public boolean rewriteMoveResult(IRCode code) {
if (options.isGeneratingClassFiles() || !code.metadata().mayHaveInvokeMethod()) {
return false;
}
AssumeRemover assumeRemover = new AssumeRemover(appView, code);
boolean changed = false;
boolean mayHaveRemovedTrivialPhi = false;
Set<BasicBlock> blocksToBeRemoved = Sets.newIdentityHashSet();
ListIterator<BasicBlock> blockIterator = code.listIterator();
while (blockIterator.hasNext()) {
BasicBlock block = blockIterator.next();
if (blocksToBeRemoved.contains(block)) {
continue;
}
InstructionListIterator iterator = block.listIterator(code);
while (iterator.hasNext()) {
InvokeMethod invoke = iterator.next().asInvokeMethod();
if (invoke == null || !invoke.hasOutValue() || invoke.outValue().hasLocalInfo()) {
continue;
}
// Check if the invoked method is known to return one of its arguments.
DexClassAndMethod target = invoke.lookupSingleTarget(appView, code.context());
if (target == null) {
continue;
}
MethodOptimizationInfo optimizationInfo = target.getDefinition().getOptimizationInfo();
if (!optimizationInfo.returnsArgument()) {
continue;
}
int argumentIndex = optimizationInfo.getReturnedArgument();
// Replace the out value of the invoke with the argument and ignore the out value.
if (argumentIndex < 0 || !checkArgumentType(invoke, argumentIndex)) {
continue;
}
Value argument = invoke.arguments().get(argumentIndex);
Value outValue = invoke.outValue();
assert outValue.verifyCompatible(argument.outType());
// Make sure that we are only narrowing information here. Note, in cases where we cannot
// find the definition of types, computing lessThanOrEqual will return false unless it is
// object.
if (!argument.getType().lessThanOrEqual(outValue.getType(), appView)) {
continue;
}
Set<Value> affectedValues =
argument.getType().equals(outValue.getType())
? Collections.emptySet()
: outValue.affectedValues();
assumeRemover.markAssumeDynamicTypeUsersForRemoval(outValue);
mayHaveRemovedTrivialPhi |= outValue.numberOfPhiUsers() > 0;
outValue.replaceUsers(argument);
invoke.setOutValue(null);
changed = true;
if (!affectedValues.isEmpty()) {
new TypeAnalysis(appView).narrowing(affectedValues);
}
}
}
assumeRemover.removeMarkedInstructions(blocksToBeRemoved).finish();
Set<Value> affectedValues = Sets.newIdentityHashSet();
if (!blocksToBeRemoved.isEmpty()) {
code.removeBlocks(blocksToBeRemoved);
code.removeAllDeadAndTrivialPhis(affectedValues);
assert code.getUnreachableBlocks().isEmpty();
} else if (mayHaveRemovedTrivialPhi || assumeRemover.mayHaveIntroducedTrivialPhi()) {
code.removeAllDeadAndTrivialPhis(affectedValues);
}
if (!affectedValues.isEmpty()) {
new TypeAnalysis(appView).narrowing(affectedValues);
}
assert code.isConsistentSSA(appView);
return changed;
}
public static void removeOrReplaceByDebugLocalWrite(
Instruction currentInstruction, InstructionListIterator it, Value inValue, Value outValue) {
if (outValue.hasLocalInfo() && outValue.getLocalInfo() != inValue.getLocalInfo()) {
DebugLocalWrite debugLocalWrite = new DebugLocalWrite(outValue, inValue);
it.replaceCurrentInstruction(debugLocalWrite);
} else {
if (outValue.hasLocalInfo()) {
assert outValue.getLocalInfo() == inValue.getLocalInfo();
// Should remove the end-marker before replacing the current instruction.
currentInstruction.removeDebugValue(outValue.getLocalInfo());
}
outValue.replaceUsers(inValue);
it.removeOrReplaceByDebugLocalRead();
}
}
// Split constants that flow into ranged invokes. This gives the register allocator more
// freedom in assigning register to ranged invokes which can greatly reduce the number
// of register needed (and thereby code size as well).
public void splitRangeInvokeConstants(IRCode code) {
for (BasicBlock block : code.blocks) {
InstructionListIterator it = block.listIterator(code);
while (it.hasNext()) {
Instruction current = it.next();
if (current.isInvoke() && current.asInvoke().requiredArgumentRegisters() > 5) {
Invoke invoke = current.asInvoke();
it.previous();
Map<ConstNumber, ConstNumber> oldToNew = new IdentityHashMap<>();
for (int i = 0; i < invoke.inValues().size(); i++) {
Value value = invoke.inValues().get(i);
if (value.isConstNumber() && value.numberOfUsers() > 1) {
ConstNumber definition = value.getConstInstruction().asConstNumber();
Value originalValue = definition.outValue();
ConstNumber newNumber = oldToNew.get(definition);
if (newNumber == null) {
newNumber = ConstNumber.copyOf(code, definition);
it.add(newNumber);
newNumber.setPosition(current.getPosition());
oldToNew.put(definition, newNumber);
}
invoke.inValues().set(i, newNumber.outValue());
originalValue.removeUser(invoke);
newNumber.outValue().addUser(invoke);
}
}
it.next();
}
}
}
assert code.isConsistentSSA(appView);
}
public void rewriteKnownArrayLengthCalls(IRCode code) {
InstructionListIterator iterator = code.instructionListIterator();
while (iterator.hasNext()) {
Instruction current = iterator.next();
if (!current.isArrayLength()) {
continue;
}
ArrayLength arrayLength = current.asArrayLength();
if (arrayLength.hasOutValue() && arrayLength.outValue().hasLocalInfo()) {
continue;
}
Value array = arrayLength.array().getAliasedValue();
if (array.isPhi() || !arrayLength.array().isNeverNull() || array.hasLocalInfo()) {
continue;
}
AbstractValue abstractValue = array.getAbstractValue(appView, code.context());
if (!abstractValue.hasKnownArrayLength() && !array.isNeverNull()) {
continue;
}
Instruction arrayDefinition = array.getDefinition();
assert arrayDefinition != null;
Set<Phi> phiUsers = arrayLength.outValue().uniquePhiUsers();
if (arrayDefinition.isNewArrayEmpty()) {
Value size = arrayDefinition.asNewArrayEmpty().size();
arrayLength.outValue().replaceUsers(size);
iterator.removeOrReplaceByDebugLocalRead();
} else if (arrayDefinition.isNewArrayFilledData()) {
long size = arrayDefinition.asNewArrayFilledData().size;
if (size > Integer.MAX_VALUE) {
continue;
}
iterator.replaceCurrentInstructionWithConstInt(code, (int) size);
} else if (abstractValue.hasKnownArrayLength()) {
iterator.replaceCurrentInstructionWithConstInt(code, abstractValue.getKnownArrayLength());
} else {
continue;
}
phiUsers.forEach(Phi::removeTrivialPhi);
}
assert code.isConsistentSSA(appView);
}
/**
* If an instruction is known to be a binop/lit8 or binop//lit16 instruction, update the
* instruction to use its own constant that will be defined just before the instruction. This
* transformation allows to decrease pressure on register allocation by defining the shortest
* range of constant used by this kind of instruction. D8 knowns at build time that constant will
* be encoded directly into the final Dex instruction.
*/
public void useDedicatedConstantForLitInstruction(IRCode code) {
if (!code.metadata().mayHaveArithmeticOrLogicalBinop()) {
return;
}
for (BasicBlock block : code.blocks) {
InstructionListIterator instructionIterator = block.listIterator(code);
// Collect all the non constant in values for binop/lit8 or binop/lit16 instructions.
Set<Value> binopsWithLit8OrLit16NonConstantValues = Sets.newIdentityHashSet();
while (instructionIterator.hasNext()) {
Instruction currentInstruction = instructionIterator.next();
if (!isBinopWithLit8OrLit16(currentInstruction)) {
continue;
}
Value value = binopWithLit8OrLit16NonConstant(currentInstruction.asBinop());
assert value != null;
binopsWithLit8OrLit16NonConstantValues.add(value);
}
if (binopsWithLit8OrLit16NonConstantValues.isEmpty()) {
continue;
}
// Find last use in block of all the non constant in values for binop/lit8 or binop/lit16
// instructions.
Reference2IntMap<Value> lastUseOfBinopsWithLit8OrLit16NonConstantValues =
new Reference2IntOpenHashMap<>();
lastUseOfBinopsWithLit8OrLit16NonConstantValues.defaultReturnValue(-1);
int currentInstructionNumber = block.getInstructions().size();
while (instructionIterator.hasPrevious()) {
Instruction currentInstruction = instructionIterator.previous();
currentInstructionNumber--;
for (Value value :
Iterables.concat(currentInstruction.inValues(), currentInstruction.getDebugValues())) {
if (!binopsWithLit8OrLit16NonConstantValues.contains(value)) {
continue;
}
if (!lastUseOfBinopsWithLit8OrLit16NonConstantValues.containsKey(value)) {
lastUseOfBinopsWithLit8OrLit16NonConstantValues.put(value, currentInstructionNumber);
}
}
}
// Do the transformation except if the binop can use the binop/2addr format.
currentInstructionNumber--;
assert currentInstructionNumber == -1;
while (instructionIterator.hasNext()) {
Instruction currentInstruction = instructionIterator.next();
currentInstructionNumber++;
if (!isBinopWithLit8OrLit16(currentInstruction)) {
continue;
}
Binop binop = currentInstruction.asBinop();
if (!canBe2AddrInstruction(
binop, currentInstructionNumber, lastUseOfBinopsWithLit8OrLit16NonConstantValues)) {
Value constValue = binopWithLit8OrLit16Constant(currentInstruction);
if (constValue.numberOfAllUsers() > 1) {
// No need to do the transformation if the const value is already used only one time.
ConstNumber newConstant = ConstNumber
.copyOf(code, constValue.definition.asConstNumber());
newConstant.setPosition(currentInstruction.getPosition());
newConstant.setBlock(currentInstruction.getBlock());
currentInstruction.replaceValue(constValue, newConstant.outValue());
constValue.removeUser(currentInstruction);
instructionIterator.previous();
instructionIterator.add(newConstant);
instructionIterator.next();
}
}
}
}
assert code.isConsistentSSA(appView);
}
// Check if a binop can be represented in the binop/lit8 or binop/lit16 form.
private static boolean isBinopWithLit8OrLit16(Instruction instruction) {
if (!instruction.isArithmeticBinop() && !instruction.isLogicalBinop()) {
return false;
}
Binop binop = instruction.asBinop();
// If one of the values does not need a register it is implicitly a binop/lit8 or binop/lit16.
boolean result =
!binop.needsValueInRegister(binop.leftValue())
|| !binop.needsValueInRegister(binop.rightValue());
assert !result || binop.leftValue().isConstNumber() || binop.rightValue().isConstNumber();
return result;
}
// Return the constant in-value of a binop/lit8 or binop/lit16 instruction.
private static Value binopWithLit8OrLit16Constant(Instruction instruction) {
assert isBinopWithLit8OrLit16(instruction);
Binop binop = instruction.asBinop();
if (binop.leftValue().isConstNumber()) {
return binop.leftValue();
} else if (binop.rightValue().isConstNumber()) {
return binop.rightValue();
} else {
throw new Unreachable();
}
}
// Return the non-constant in-value of a binop/lit8 or binop/lit16 instruction.
private static Value binopWithLit8OrLit16NonConstant(Binop binop) {
if (binop.leftValue().isConstNumber()) {
return binop.rightValue();
} else if (binop.rightValue().isConstNumber()) {
return binop.leftValue();
} else {
throw new Unreachable();
}
}
/**
* Estimate if a binary operation can be a binop/2addr form or not. It can be a 2addr form when an
* argument is no longer needed after the binary operation and can be overwritten. That is
* definitely the case if there is no path between the binary operation and all other usages.
*/
private static boolean canBe2AddrInstruction(
Binop binop, int binopInstructionNumber, Reference2IntMap<Value> lastUseOfRelevantValue) {
Value value = binopWithLit8OrLit16NonConstant(binop);
assert value != null;
int lastUseInstructionNumber = lastUseOfRelevantValue.getInt(value);
// The binop instruction is a user, so there is always a last use in the block.
assert lastUseInstructionNumber != -1;
if (lastUseInstructionNumber > binopInstructionNumber) {
return false;
}
Set<BasicBlock> noPathTo = Sets.newIdentityHashSet();
BasicBlock binopBlock = binop.getBlock();
Iterable<InstructionOrPhi> users =
value.debugUsers() != null
? Iterables.concat(value.uniqueUsers(), value.debugUsers(), value.uniquePhiUsers())
: Iterables.concat(value.uniqueUsers(), value.uniquePhiUsers());
for (InstructionOrPhi user : users) {
BasicBlock userBlock = user.getBlock();
if (userBlock == binopBlock) {
// All users in the current block are either before the binop instruction or the
// binop instruction itself.
continue;
}
if (noPathTo.contains(userBlock)) {
continue;
}
if (binopBlock.hasPathTo(userBlock)) {
return false;
}
noPathTo.add(userBlock);
}
return true;
}
public void shortenLiveRanges(IRCode code, ConstantCanonicalizer canonicalizer) {
if (options.testing.disableShortenLiveRanges) {
return;
}
LazyBox<DominatorTree> dominatorTreeMemoization = new LazyBox<>(() -> new DominatorTree(code));
Map<BasicBlock, LinkedHashMap<Value, Instruction>> addConstantInBlock = new IdentityHashMap<>();
LinkedList<BasicBlock> blocks = code.blocks;
for (BasicBlock block : blocks) {
shortenLiveRangesInsideBlock(
code,
block,
dominatorTreeMemoization,
addConstantInBlock,
canonicalizer::isConstantCanonicalizationCandidate);
}
// Heuristic to decide if constant instructions are shared in dominator block
// of usages or moved to the usages.
// Process all blocks in stable order to avoid non-determinism of hash map iterator.
BasicBlockIterator blockIterator = code.listIterator();
while (blockIterator.hasNext()) {
BasicBlock block = blockIterator.next();
Map<Value, Instruction> movedInstructions = addConstantInBlock.get(block);
if (movedInstructions == null) {
continue;
}
assert !movedInstructions.isEmpty();
if (!block.isEntry() && movedInstructions.size() > STOP_SHARED_CONSTANT_THRESHOLD) {
// If there are too many constant numbers in the same block they are copied rather than
// shared unless used by a phi.
movedInstructions
.values()
.removeIf(
movedInstruction -> {
if (movedInstruction.outValue().hasPhiUsers()
|| !movedInstruction.isConstNumber()) {
return false;
}
ConstNumber constNumber = movedInstruction.asConstNumber();
Value constantValue = movedInstruction.outValue();
for (Instruction user : constantValue.uniqueUsers()) {
ConstNumber newCstNum = ConstNumber.copyOf(code, constNumber);
newCstNum.setPosition(user.getPosition());
InstructionListIterator iterator = user.getBlock().listIterator(code, user);
iterator.previous();
iterator.add(newCstNum);
user.replaceValue(constantValue, newCstNum.outValue());
}
constantValue.clearUsers();
return true;
});
}
// Add constant into the dominator block of usages.
boolean hasCatchHandlers = block.hasCatchHandlers();
InstructionListIterator instructionIterator = block.listIterator(code);
while (instructionIterator.hasNext()) {
Instruction insertionPoint = instructionIterator.next();
if (insertionPoint.isJumpInstruction()
|| (hasCatchHandlers && insertionPoint.instructionTypeCanThrow())
|| (options.canHaveCmpIfFloatBug() && insertionPoint.isCmp())) {
break;
}
for (Value use :
Iterables.concat(insertionPoint.inValues(), insertionPoint.getDebugValues())) {
Instruction movedInstruction = movedInstructions.remove(use);
if (movedInstruction != null) {
instructionIterator =
insertInstructionWithShortenedLiveRange(
code, blockIterator, instructionIterator, movedInstruction, insertionPoint);
}
}
}
// Insert remaining constant instructions prior to the "exit".
Instruction insertionPoint = instructionIterator.peekPrevious();
for (Instruction movedInstruction : movedInstructions.values()) {
instructionIterator =
insertInstructionWithShortenedLiveRange(
code, blockIterator, instructionIterator, movedInstruction, insertionPoint);
}
}
assert code.isConsistentSSA(appView);
}
private InstructionListIterator insertInstructionWithShortenedLiveRange(
IRCode code,
BasicBlockIterator blockIterator,
InstructionListIterator instructionIterator,
Instruction movedInstruction,
Instruction insertionPoint) {
Instruction previous = instructionIterator.previous();
assert previous == insertionPoint;
movedInstruction.setPosition(
getPositionForMovedNonThrowingInstruction(movedInstruction, insertionPoint));
if (movedInstruction.instructionTypeCanThrow()
&& insertionPoint.getBlock().hasCatchHandlers()) {
// Split the block and reset the block iterator.
BasicBlock splitBlock =
instructionIterator.splitCopyCatchHandlers(code, blockIterator, appView.options());
BasicBlock previousBlock = blockIterator.previousUntil(b -> b == splitBlock);
assert previousBlock == splitBlock;
blockIterator.next();
// Add the constant instruction before the exit instruction.
assert !instructionIterator.hasNext();
instructionIterator.previous();
instructionIterator.add(movedInstruction);
// Continue insertion at the entry of the split block.
instructionIterator = splitBlock.listIterator(code);
} else {
instructionIterator.add(movedInstruction);
}
Instruction next = instructionIterator.next();
assert next == insertionPoint;
return instructionIterator;
}
private Position getPositionForMovedNonThrowingInstruction(
Instruction movedInstruction, Instruction insertionPoint) {
// If the type of the moved instruction is throwing and we don't have a position at the
// insertion point, we use the special synthetic-none position, which is OK as the moved
// instruction instance is known not to throw (or we would not be allowed the move it).
if (movedInstruction.instructionTypeCanThrow() && !insertionPoint.getPosition().isSome()) {
return Position.syntheticNone();
}
return insertionPoint.getPosition();
}
private void shortenLiveRangesInsideBlock(
IRCode code,
BasicBlock block,
LazyBox<DominatorTree> dominatorTreeMemoization,
Map<BasicBlock, LinkedHashMap<Value, Instruction>> addConstantInBlock,
Predicate<Instruction> selector) {
InstructionListIterator iterator = block.listIterator(code);
boolean seenCompareExit = false;
while (iterator.hasNext()) {
Instruction instruction = iterator.next();
if (options.canHaveCmpIfFloatBug() && instruction.isCmp()) {
seenCompareExit = true;
}
if (instruction.hasUnusedOutValue() || instruction.outValue().hasLocalInfo()) {
continue;
}
if (!selector.test(instruction)) {
continue;
}
// Here we try to stop wasting time in the common case where constants are used immediately
// after their definition.
//
// This is especially important for the creation of large arrays, which has the following code
// pattern repeated many times, where the two loaded constants are only used by the ArrayPut
// instruction.
//
// Const number (the array index)
// Const (the array entry value)
// ArrayPut
//
// The heuristic is therefore to check for constants used only once if the use is within the
// next two instructions, and only swap them if that is the case (cannot shorten the live
// range anyway).
if (instruction.outValue().hasSingleUniqueUser() && !instruction.outValue().hasPhiUsers()) {
Instruction uniqueUse = instruction.outValue().singleUniqueUser();
Instruction next = iterator.next();
if (uniqueUse == next) {
iterator.previous();
continue;
}
if (next.hasOutValue()
&& next.outValue().hasSingleUniqueUser()
&& !next.outValue().hasPhiUsers()
&& iterator.hasNext()) {
Instruction nextNext = iterator.peekNext();
Instruction uniqueUseNext = next.outValue().singleUniqueUser();
if (uniqueUse == nextNext && uniqueUseNext == nextNext) {
iterator.previous();
continue;
}
}
iterator.previous();
// The call to removeOrReplaceByDebugLocalRead() at the end of this method will remove the
// last returned element of this iterator. Therefore, we re-read this element from the
// iterator.
iterator.previous();
iterator.next();
}
// Collect the blocks for all users of the constant.
Set<BasicBlock> userBlocks = new LinkedHashSet<>();
for (Instruction user : instruction.outValue().uniqueUsers()) {
userBlocks.add(user.getBlock());
}
for (Phi phi : instruction.outValue().uniquePhiUsers()) {
int predecessorIndex = 0;
for (Value operand : phi.getOperands()) {
if (operand == instruction.outValue()) {
userBlocks.add(phi.getBlock().getPredecessors().get(predecessorIndex));
}
predecessorIndex++;
}
}
// Locate the closest dominator block for all user blocks.
DominatorTree dominatorTree = dominatorTreeMemoization.computeIfAbsent();
BasicBlock dominator = dominatorTree.closestDominator(userBlocks);
if (instruction.instructionTypeCanThrow()) {
if (block.hasCatchHandlers() || dominator.hasCatchHandlers()) {
// Do not move the constant if the constant instruction can throw
// and the dominator or the original block has catch handlers.
continue;
}
}
// If the dominator block has a potential compare exit we will chose that as the insertion
// point. Uniquely for instructions having invalues this can be before the definition of them.
// Bail-out when this is the case. See b/251015885 for more information.
if (seenCompareExit
&& Iterables.any(instruction.inValues(), x -> x.getBlock() == dominator)) {
continue;
}
Instruction copy;
switch (instruction.opcode()) {
case CONST_CLASS:
copy = ConstClass.copyOf(code, instruction.asConstClass());
break;
case CONST_NUMBER:
copy = ConstNumber.copyOf(code, instruction.asConstNumber());
break;
case CONST_STRING:
copy = ConstString.copyOf(code, instruction.asConstString());
break;
case DEX_ITEM_BASED_CONST_STRING:
copy = DexItemBasedConstString.copyOf(code, instruction.asDexItemBasedConstString());
break;
case INSTANCE_GET:
copy = InstanceGet.copyOf(code, instruction.asInstanceGet());
break;
case STATIC_GET:
copy = StaticGet.copyOf(code, instruction.asStaticGet());
break;
default:
throw new Unreachable();
}
instruction.outValue().replaceUsers(copy.outValue());
addConstantInBlock
.computeIfAbsent(dominator, k -> new LinkedHashMap<>())
.put(copy.outValue(), copy);
assert iterator.peekPrevious() == instruction;
iterator.removeOrReplaceByDebugLocalRead();
}
}
// TODO(mikaelpeltier) Manage that from and to instruction do not belong to the same block.
private static boolean hasLocalOrLineChangeBetween(
Instruction from, Instruction to, DexString localVar) {
if (from.getBlock() != to.getBlock()) {
return true;
}
if (from.getPosition().isSome()
&& to.getPosition().isSome()
&& !from.getPosition().equals(to.getPosition())) {
return true;
}
Position position = null;
for (Instruction instruction : from.getBlock().instructionsAfter(from)) {
if (position == null) {
if (instruction.getPosition().isSome()) {
position = instruction.getPosition();
}
} else if (instruction.getPosition().isSome()
&& !position.equals(instruction.getPosition())) {
return true;
}
if (instruction == to) {
return false;
}
if (instruction.outValue() != null && instruction.outValue().hasLocalInfo()) {
if (instruction.outValue().getLocalInfo().name == localVar) {
return true;
}
}
}
throw new Unreachable();
}
public void simplifyDebugLocals(IRCode code) {
for (BasicBlock block : code.blocks) {
InstructionListIterator iterator = block.listIterator(code);
while (iterator.hasNext()) {
Instruction prevInstruction = iterator.peekPrevious();
Instruction instruction = iterator.next();
if (instruction.isDebugLocalWrite()) {
assert instruction.inValues().size() == 1;
Value inValue = instruction.inValues().get(0);
DebugLocalInfo localInfo = instruction.outValue().getLocalInfo();
DexString localName = localInfo.name;
if (!inValue.hasLocalInfo() &&
inValue.numberOfAllUsers() == 1 &&
inValue.definition != null &&
!hasLocalOrLineChangeBetween(inValue.definition, instruction, localName)) {
inValue.setLocalInfo(localInfo);
instruction.outValue().replaceUsers(inValue);
Value overwrittenLocal = instruction.removeDebugValue(localInfo);
if (overwrittenLocal != null) {
overwrittenLocal.addDebugLocalEnd(inValue.definition);
}
if (prevInstruction != null &&
(prevInstruction.outValue() == null
|| !prevInstruction.outValue().hasLocalInfo()
|| !instruction.getDebugValues().contains(prevInstruction.outValue()))) {
instruction.moveDebugValues(prevInstruction);
}
iterator.removeOrReplaceByDebugLocalRead();
}
}
}
}
}
/**
* This optimization exploits that we can sometimes learn the constant value of an SSA value that
* flows into an if-eq of if-neq instruction.
*
* <p>Consider the following example:
*
* <pre>
* 1. if (obj != null) {
* 2. return doStuff();
* 3. }
* 4. return null;
* </pre>
*
* <p>Since we know that `obj` is null in all blocks that are dominated by the false-target of the
* if-instruction in line 1, we can safely replace the null-constant in line 4 by `obj`, and
* thereby save a const-number instruction.
*/
public void redundantConstNumberRemoval(IRCode code) {
if (appView.options().canHaveDalvikIntUsedAsNonIntPrimitiveTypeBug()
&& !appView.options().testing.forceRedundantConstNumberRemoval) {
// See also b/124152497.
return;
}
if (!code.metadata().mayHaveConstNumber()) {
return;
}
LazyBox<Long2ReferenceMap<List<ConstNumber>>> constantsByValue =
new LazyBox<>(() -> getConstantsByValue(code));
LazyBox<DominatorTree> dominatorTree = new LazyBox<>(() -> new DominatorTree(code));
boolean changed = false;
for (BasicBlock block : code.blocks) {
Instruction lastInstruction = block.getInstructions().getLast();
if (!lastInstruction.isIf()) {
continue;
}
If ifInstruction = lastInstruction.asIf();
IfType type = ifInstruction.getType();
Value lhs = ifInstruction.inValues().get(0);
Value rhs = !ifInstruction.isZeroTest() ? ifInstruction.inValues().get(1) : null;
if (!ifInstruction.isZeroTest() && !lhs.isConstNumber() && !rhs.isConstNumber()) {
// We can only conclude anything from an if-instruction if it is a zero-test or if one of
// the two operands is a constant.
continue;
}
// If the type is neither EQ nor NE, we cannot conclude anything about any of the in-values
// of the if-instruction from the outcome of the if-instruction.
if (type != IfType.EQ && type != IfType.NE) {
continue;
}
BasicBlock trueTarget, falseTarget;
if (type == IfType.EQ) {
trueTarget = ifInstruction.getTrueTarget();
falseTarget = ifInstruction.fallthroughBlock();
} else {
falseTarget = ifInstruction.getTrueTarget();
trueTarget = ifInstruction.fallthroughBlock();
}
if (ifInstruction.isZeroTest()) {
changed |=
replaceDominatedConstNumbers(0, lhs, trueTarget, constantsByValue, code, dominatorTree);
if (lhs.knownToBeBoolean()) {
changed |=
replaceDominatedConstNumbers(
1, lhs, falseTarget, constantsByValue, code, dominatorTree);
}
} else {
assert rhs != null;
if (lhs.isConstNumber()) {
ConstNumber lhsAsNumber = lhs.getConstInstruction().asConstNumber();
changed |=
replaceDominatedConstNumbers(
lhsAsNumber.getRawValue(),
rhs,
trueTarget,
constantsByValue,
code,
dominatorTree);
if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) {
changed |=
replaceDominatedConstNumbers(
negateBoolean(lhsAsNumber),
rhs,
falseTarget,
constantsByValue,
code,
dominatorTree);
}
} else {
assert rhs.isConstNumber();
ConstNumber rhsAsNumber = rhs.getConstInstruction().asConstNumber();
changed |=
replaceDominatedConstNumbers(
rhsAsNumber.getRawValue(),
lhs,
trueTarget,
constantsByValue,
code,
dominatorTree);
if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) {
changed |=
replaceDominatedConstNumbers(
negateBoolean(rhsAsNumber),
lhs,
falseTarget,
constantsByValue,
code,
dominatorTree);
}
}
}
if (constantsByValue.computeIfAbsent().isEmpty()) {
break;
}
}
if (changed) {
code.removeAllDeadAndTrivialPhis();
}
assert code.isConsistentSSA(appView);
}
private static Long2ReferenceMap<List<ConstNumber>> getConstantsByValue(IRCode code) {
// A map from the raw value of constants in `code` to the const number instructions that define
// the given raw value (irrespective of the type of the raw value).
Long2ReferenceMap<List<ConstNumber>> constantsByValue = new Long2ReferenceOpenHashMap<>();
// Initialize `constantsByValue`.
for (Instruction instruction : code.instructions()) {
if (instruction.isConstNumber()) {
ConstNumber constNumber = instruction.asConstNumber();
if (constNumber.outValue().hasLocalInfo()) {
// Not necessarily constant, because it could be changed in the debugger.
continue;
}
long rawValue = constNumber.getRawValue();
if (constantsByValue.containsKey(rawValue)) {
constantsByValue.get(rawValue).add(constNumber);
} else {
List<ConstNumber> list = new ArrayList<>();
list.add(constNumber);
constantsByValue.put(rawValue, list);
}
}
}
return constantsByValue;
}
private static int negateBoolean(ConstNumber number) {
assert number.outValue().knownToBeBoolean();
return number.getRawValue() == 0 ? 1 : 0;
}
private boolean replaceDominatedConstNumbers(
long withValue,
Value newValue,
BasicBlock dominator,
LazyBox<Long2ReferenceMap<List<ConstNumber>>> constantsByValueSupplier,
IRCode code,
LazyBox<DominatorTree> dominatorTree) {
if (newValue.hasLocalInfo()) {
// We cannot replace a constant with a value that has local info, because that could change
// debugging behavior.
return false;
}
Long2ReferenceMap<List<ConstNumber>> constantsByValue =
constantsByValueSupplier.computeIfAbsent();
List<ConstNumber> constantsWithValue = constantsByValue.get(withValue);
if (constantsWithValue == null || constantsWithValue.isEmpty()) {
return false;
}
boolean changed = false;
ListIterator<ConstNumber> constantWithValueIterator = constantsWithValue.listIterator();
while (constantWithValueIterator.hasNext()) {
ConstNumber constNumber = constantWithValueIterator.next();
Value value = constNumber.outValue();
assert !value.hasLocalInfo();
assert constNumber.getRawValue() == withValue;
BasicBlock block = constNumber.getBlock();
// If the following condition does not hold, then the if-instruction does not dominate the
// block containing the constant, although the true or false target does.
if (block == dominator && block.getPredecessors().size() != 1) {
// This should generally not happen, but it is possible to write bytecode where it does.
assert false;
continue;
}
if (value.knownToBeBoolean() && !newValue.knownToBeBoolean()) {
// We cannot replace a boolean by a none-boolean since that can lead to verification
// errors. For example, the following code fails with "register v1 has type Imprecise
// Constant: 127 but expected Boolean return-1nr".
//
// public boolean convertIntToBoolean(int v1) {
// const/4 v0, 0x1
// if-eq v1, v0, :eq_true
// const/4 v1, 0x0
// :eq_true
// return v1
// }
continue;
}
if (dominatorTree.computeIfAbsent().dominatedBy(block, dominator)) {
if (newValue.getType().lessThanOrEqual(value.getType(), appView)) {
value.replaceUsers(newValue);
block.listIterator(code, constNumber).removeOrReplaceByDebugLocalRead();
constantWithValueIterator.remove();
changed = true;
} else if (value.getType().isNullType()) {
// TODO(b/120257211): Need a mechanism to determine if `newValue` can be used at all of
// the use sites of `value` without introducing a type error.
}
}
}
if (constantsWithValue.isEmpty()) {
constantsByValue.remove(withValue);
}
return changed;
}
private boolean isPotentialTrivialRethrowValue(Value exceptionValue) {
if (exceptionValue.hasDebugUsers()) {
return false;
}
if (exceptionValue.hasUsers()) {
if (exceptionValue.hasPhiUsers()
|| !exceptionValue.hasSingleUniqueUser()
|| !exceptionValue.singleUniqueUser().isThrow()) {
return false;
}
} else if (exceptionValue.numberOfPhiUsers() != 1) {
return false;
}
return true;
}
private boolean isSingleHandlerTrivial(BasicBlock firstBlock, IRCode code) {
InstructionListIterator instructionIterator = firstBlock.listIterator(code);
Instruction instruction = instructionIterator.next();
if (!instruction.isMoveException()) {
// A catch handler which doesn't use its exception is not going to be a trivial rethrow.
return false;
}
Value exceptionValue = instruction.outValue();
if (!isPotentialTrivialRethrowValue(exceptionValue)) {
return false;
}
while (instructionIterator.hasNext()) {
instruction = instructionIterator.next();
BasicBlock currentBlock = instruction.getBlock();
if (instruction.isGoto()) {
BasicBlock nextBlock = instruction.asGoto().getTarget();
int predecessorIndex = nextBlock.getPredecessors().indexOf(currentBlock);
Value phiAliasOfExceptionValue = null;
for (Phi phi : nextBlock.getPhis()) {
Value operand = phi.getOperand(predecessorIndex);
if (exceptionValue == operand) {
phiAliasOfExceptionValue = phi;
break;
}
}
if (phiAliasOfExceptionValue != null) {
if (!isPotentialTrivialRethrowValue(phiAliasOfExceptionValue)) {
return false;
}
exceptionValue = phiAliasOfExceptionValue;
}
instructionIterator = nextBlock.listIterator(code);
} else if (instruction.isThrow()) {
List<Value> throwValues = instruction.inValues();
assert throwValues.size() == 1;
if (throwValues.get(0) != exceptionValue) {
return false;
}
CatchHandlers<BasicBlock> currentHandlers = currentBlock.getCatchHandlers();
if (!currentHandlers.isEmpty()) {
// This is the case where our trivial catch handler has catch handler(s). For now, we
// will only treat our block as trivial if all its catch handlers are also trivial.
// Note: it is possible that we could "bridge" a trivial handler, where we take the
// handlers of the handler and bring them up to replace the trivial handler. Example:
// catch (Throwable t) {
// try { throw t; } catch(Throwable abc) { foo(abc); }
// }
// could turn into:
// catch (Throwable abc) {
// foo(abc);
// }
// However this gets significantly harder when you have to consider non-matching guard
// types.
for (CatchHandler<BasicBlock> handler : currentHandlers) {
if (!isSingleHandlerTrivial(handler.getTarget(), code)) {
return false;
}
}
}
return true;
} else {
// Any other instructions in the catch handler means it's not trivial, and thus we can't
// elide.
return false;
}
}
throw new Unreachable("Triviality check should always return before the loop terminates");
}
// Find any case where we have a catch followed immediately and only by a rethrow. This is extra
// code doing what the JVM does automatically and can be safely elided.
public void optimizeRedundantCatchRethrowInstructions(IRCode code) {
ListIterator<BasicBlock> blockIterator = code.listIterator();
boolean hasUnlinkedCatchHandlers = false;
while (blockIterator.hasNext()) {
BasicBlock blockWithHandlers = blockIterator.next();
if (blockWithHandlers.hasCatchHandlers()) {
boolean allHandlersAreTrivial = true;
for (CatchHandler<BasicBlock> handler : blockWithHandlers.getCatchHandlers()) {
if (!isSingleHandlerTrivial(handler.target, code)) {
allHandlersAreTrivial = false;
break;
}
}
// We need to ensure all handlers are trivial to unlink, since if one is non-trivial, and
// its guard is a parent type to a trivial one, removing the trivial catch will result in
// us hitting the non-trivial catch. This could be avoided by more sophisticated type
// analysis.
if (allHandlersAreTrivial) {
hasUnlinkedCatchHandlers = true;
for (CatchHandler<BasicBlock> handler : blockWithHandlers.getCatchHandlers()) {
handler.getTarget().unlinkCatchHandler();
}
}
}
}
if (hasUnlinkedCatchHandlers) {
code.removeUnreachableBlocks();
}
}
// Find all instructions that always throw, split the block after each such instruction and follow
// it with a block throwing a null value (which should result in NPE). Note that this throw is not
// expected to be ever reached, but is intended to satisfy verifier.
public void optimizeAlwaysThrowingInstructions(IRCode code) {
Set<Value> affectedValues = Sets.newIdentityHashSet();
Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet();
ListIterator<BasicBlock> blockIterator = code.listIterator();
ProgramMethod context = code.context();
boolean hasUnlinkedCatchHandlers = false;
// For cyclic phis we sometimes do not propagate the dynamic upper type after rewritings.
// The inValue.isAlwaysNull(appView) check below will not recompute the dynamic type of phi's
// so we recompute all phis here if they are always null.
AppView<AppInfoWithClassHierarchy> appViewWithClassHierarchy =
appView.hasClassHierarchy() ? appView.withClassHierarchy() : null;
if (appViewWithClassHierarchy != null) {
code.blocks.forEach(
block ->
block
.getPhis()
.forEach(
phi -> {
if (!phi.getType().isDefinitelyNull()) {
TypeElement dynamicUpperBoundType =
phi.getDynamicUpperBoundType(appViewWithClassHierarchy);
if (dynamicUpperBoundType.isDefinitelyNull()) {
affectedValues.add(phi);
phi.setType(dynamicUpperBoundType);
}
}
}));
}
while (blockIterator.hasNext()) {
BasicBlock block = blockIterator.next();
if (block.getNumber() != 0 && block.getPredecessors().isEmpty()) {
continue;
}
if (blocksToRemove.contains(block)) {
continue;
}
InstructionListIterator instructionIterator = block.listIterator(code);
while (instructionIterator.hasNext()) {
Instruction instruction = instructionIterator.next();
if (instruction.throwsOnNullInput()) {
Value inValue = instruction.getNonNullInput();
if (inValue.isAlwaysNull(appView)) {
// Insert `throw null` after the instruction if it is not guaranteed to throw an NPE.
if (instruction.isAssume()) {
// If this assume is in a block with catch handlers, then the out-value can have
// usages in the catch handler if the block's throwing instruction comes after the
// assume instruction. In this case, the catch handler is also guaranteed to be dead,
// so we detach it from the current block.
if (block.hasCatchHandlers()
&& block.isInstructionBeforeThrowingInstruction(instruction)) {
for (CatchHandler<BasicBlock> catchHandler : block.getCatchHandlers()) {
catchHandler.getTarget().unlinkCatchHandler();
}
hasUnlinkedCatchHandlers = true;
}
} else if (instruction.isInstanceFieldInstruction()) {
InstanceFieldInstruction instanceFieldInstruction =
instruction.asInstanceFieldInstruction();
if (instanceFieldInstruction.instructionInstanceCanThrow(
appView, context, SideEffectAssumption.RECEIVER_NOT_NULL)) {
instructionIterator.next();
}
} else if (instruction.isInvokeMethodWithReceiver()) {
InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver();
SideEffectAssumption assumption =
SideEffectAssumption.RECEIVER_NOT_NULL.join(
SideEffectAssumption.INVOKED_METHOD_DOES_NOT_HAVE_SIDE_EFFECTS);
if (invoke.instructionMayHaveSideEffects(appView, context, assumption)) {
instructionIterator.next();
}
}
instructionIterator.replaceCurrentInstructionWithThrowNull(
appView, code, blockIterator, blocksToRemove, affectedValues);
continue;
}
}
if (!instruction.isInvokeMethod()) {
continue;
}
InvokeMethod invoke = instruction.asInvokeMethod();
DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, code.context());
if (singleTarget == null) {
continue;
}
MethodOptimizationInfo optimizationInfo =
singleTarget.getDefinition().getOptimizationInfo();
// If the invoke instruction is a null check, we can remove it.
boolean isNullCheck = false;
if (optimizationInfo.hasNonNullParamOrThrow()) {
BitSet nonNullParamOrThrow = optimizationInfo.getNonNullParamOrThrow();
for (int i = 0; i < invoke.arguments().size(); i++) {
Value argument = invoke.arguments().get(i);
if (argument.isAlwaysNull(appView) && nonNullParamOrThrow.get(i)) {
isNullCheck = true;
break;
}
}
}
// If the invoke instruction never returns normally, we can insert a throw null instruction
// after the invoke.
if (isNullCheck || optimizationInfo.neverReturnsNormally()) {
instructionIterator.setInsertionPosition(invoke.getPosition());
instructionIterator.next();
instructionIterator.replaceCurrentInstructionWithThrowNull(
appView, code, blockIterator, blocksToRemove, affectedValues);
instructionIterator.unsetInsertionPosition();
}
}
}
code.removeBlocks(blocksToRemove);
if (hasUnlinkedCatchHandlers) {
affectedValues.addAll(code.removeUnreachableBlocks());
}
assert code.getUnreachableBlocks().isEmpty();
if (!affectedValues.isEmpty()) {
new TypeAnalysis(appView).narrowing(affectedValues);
}
assert code.isConsistentSSA(appView);
}
private void insertNotNullCheck(
BasicBlock block,
InstructionListIterator iterator,
If theIf,
BasicBlock target,
BasicBlock deadTarget,
Position position) {
deadTarget.unlinkSinglePredecessorSiblingsAllowed();
assert theIf == block.exit();
iterator.previous();
Instruction instruction;
DexMethod getClassMethod = appView.dexItemFactory().objectMembers.getClass;
instruction = new InvokeVirtual(getClassMethod, null, ImmutableList.of(theIf.lhs()));
instruction.setPosition(position);
iterator.add(instruction);
iterator.next();
iterator.replaceCurrentInstruction(new Goto());
assert block.exit().isGoto();
assert block.exit().asGoto().getTarget() == target;
}
/**
* Remove moves that are not actually used by instructions in exiting paths. These moves can arise
* due to debug local info needing a particular value and the live-interval for it then moves it
* back into the properly assigned register. If the register is only used for debug purposes, it
* is safe to just remove the move and update the local information accordingly.
*/
public static void removeUnneededMovesOnExitingPaths(
IRCode code, LinearScanRegisterAllocator allocator) {
if (!allocator.options().debug) {
return;
}
for (BasicBlock block : code.blocks) {
// Skip non-exit blocks.
if (!block.getSuccessors().isEmpty()) {
continue;
}
// Skip blocks with no locals at entry.
Int2ReferenceMap<DebugLocalInfo> localsAtEntry = block.getLocalsAtEntry();
if (localsAtEntry == null || localsAtEntry.isEmpty()) {
continue;
}
// Find the locals state after spill moves.
DebugLocalsChange postSpillLocalsChange = null;
for (Instruction instruction : block.getInstructions()) {
if (instruction.getNumber() != -1 || postSpillLocalsChange != null) {
break;
}
postSpillLocalsChange = instruction.asDebugLocalsChange();
}
// Skip if the locals state did not change.
if (postSpillLocalsChange == null
|| !postSpillLocalsChange.apply(new Int2ReferenceOpenHashMap<>(localsAtEntry))) {
continue;
}
// Collect the moves that can safely be removed.
Set<Move> unneededMoves = computeUnneededMoves(block, postSpillLocalsChange, allocator);
if (unneededMoves.isEmpty()) {
continue;
}
Int2IntMap previousMapping = new Int2IntOpenHashMap();
Int2IntMap mapping = new Int2IntOpenHashMap();
InstructionListIterator it = block.listIterator(code);
while (it.hasNext()) {
Instruction instruction = it.next();
if (instruction.isMove()) {
Move move = instruction.asMove();
if (unneededMoves.contains(move)) {
int dst = allocator.getRegisterForValue(move.dest(), move.getNumber());
int src = allocator.getRegisterForValue(move.src(), move.getNumber());
int mappedSrc = mapping.getOrDefault(src, src);
mapping.put(dst, mappedSrc);
it.removeInstructionIgnoreOutValue();
}
} else if (instruction.isDebugLocalsChange()) {
DebugLocalsChange change = instruction.asDebugLocalsChange();
updateDebugLocalsRegisterMap(previousMapping, change.getEnding());
updateDebugLocalsRegisterMap(mapping, change.getStarting());
previousMapping = mapping;
mapping = new Int2IntOpenHashMap(previousMapping);
}
}
}
}
private static Set<Move> computeUnneededMoves(
BasicBlock block,
DebugLocalsChange postSpillLocalsChange,
LinearScanRegisterAllocator allocator) {
Set<Move> unneededMoves = Sets.newIdentityHashSet();
IntSet usedRegisters = new IntOpenHashSet();
IntSet clobberedRegisters = new IntOpenHashSet();
// Backwards instruction scan collecting the registers used by actual instructions.
boolean inEntrySpillMoves = false;
InstructionIterator it = block.iterator(block.getInstructions().size());
while (it.hasPrevious()) {
Instruction instruction = it.previous();
if (instruction == postSpillLocalsChange) {
inEntrySpillMoves = true;
}
// If this is a move in the block-entry spill moves check if it is unneeded.
if (inEntrySpillMoves && instruction.isMove()) {
Move move = instruction.asMove();
int dst = allocator.getRegisterForValue(move.dest(), move.getNumber());
int src = allocator.getRegisterForValue(move.src(), move.getNumber());
if (!usedRegisters.contains(dst) && !clobberedRegisters.contains(src)) {
unneededMoves.add(move);
continue;
}
}
if (instruction.outValue() != null && instruction.outValue().needsRegister()) {
int register =
allocator.getRegisterForValue(instruction.outValue(), instruction.getNumber());
// The register is defined anew, so uses before this are on distinct values.
usedRegisters.remove(register);
// Mark it clobbered to avoid any uses in locals after this point to become invalid.
clobberedRegisters.add(register);
}
if (!instruction.inValues().isEmpty()) {
for (Value inValue : instruction.inValues()) {
if (inValue.needsRegister()) {
int register = allocator.getRegisterForValue(inValue, instruction.getNumber());
// Record the register as being used.
usedRegisters.add(register);
}
}
}
}
return unneededMoves;
}
private static void updateDebugLocalsRegisterMap(
Int2IntMap mapping, Int2ReferenceMap<DebugLocalInfo> locals) {
// If nothing is mapped nothing needs to be changed.
if (mapping.isEmpty()) {
return;
}
// Locals is final, so we copy and clear it during update.
Int2ReferenceMap<DebugLocalInfo> copy = new Int2ReferenceOpenHashMap<>(locals);
locals.clear();
for (Entry<DebugLocalInfo> entry : copy.int2ReferenceEntrySet()) {
int oldRegister = entry.getIntKey();
int newRegister = mapping.getOrDefault(oldRegister, oldRegister);
locals.put(newRegister, entry.getValue());
}
}
private Value addConstString(IRCode code, InstructionListIterator iterator, String s) {
TypeElement typeLattice = TypeElement.stringClassType(appView, definitelyNotNull());
Value value = code.createValue(typeLattice);
iterator.add(new ConstString(value, dexItemFactory.createString(s)));
return value;
}
/**
* Insert code into <code>method</code> to log the argument types to System.out.
*
* The type is determined by calling getClass() on the argument.
*/
public void logArgumentTypes(DexEncodedMethod method, IRCode code) {
List<Value> arguments = code.collectArguments();
BasicBlock block = code.entryBlock();
InstructionListIterator iterator = block.listIterator(code);
// Attach some synthetic position to all inserted code.
Position position =
SyntheticPosition.builder().setLine(1).setMethod(method.getReference()).build();
iterator.setInsertionPosition(position);
// Split arguments into their own block.
iterator.nextUntil(instruction -> !instruction.isArgument());
iterator.previous();
iterator.split(code);
iterator.previous();
// Now that the block is split there should not be any catch handlers in the block.
assert !block.hasCatchHandlers();
DexType javaLangSystemType = dexItemFactory.javaLangSystemType;
DexType javaIoPrintStreamType = dexItemFactory.javaIoPrintStreamType;
Value out =
code.createValue(
TypeElement.fromDexType(javaIoPrintStreamType, definitelyNotNull(), appView));
DexProto proto = dexItemFactory.createProto(dexItemFactory.voidType, dexItemFactory.objectType);
DexMethod print = dexItemFactory.createMethod(javaIoPrintStreamType, proto, "print");
DexMethod printLn = dexItemFactory.createMethod(javaIoPrintStreamType, proto, "println");
iterator.add(
new StaticGet(
out, dexItemFactory.createField(javaLangSystemType, javaIoPrintStreamType, "out")));
Value value = addConstString(code, iterator, "INVOKE ");
iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, value)));
value = addConstString(code, iterator, method.getReference().qualifiedName());
iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, value)));
Value openParenthesis = addConstString(code, iterator, "(");
Value comma = addConstString(code, iterator, ",");
Value closeParenthesis = addConstString(code, iterator, ")");
Value indent = addConstString(code, iterator, " ");
Value nul = addConstString(code, iterator, "(null)");
Value primitive = addConstString(code, iterator, "(primitive)");
Value empty = addConstString(code, iterator, "");
iterator.add(new InvokeVirtual(printLn, null, ImmutableList.of(out, openParenthesis)));
for (int i = 0; i < arguments.size(); i++) {
iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, indent)));
// Add a block for end-of-line printing.
BasicBlock eol =
BasicBlock.createGotoBlock(code.getNextBlockNumber(), position, code.metadata());
code.blocks.add(eol);
BasicBlock successor = block.unlinkSingleSuccessor();
block.link(eol);
eol.link(successor);
Value argument = arguments.get(i);
if (!argument.getType().isReferenceType()) {
iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, primitive)));
} else {
// Insert "if (argument != null) ...".
successor = block.unlinkSingleSuccessor();
If theIf = new If(IfType.NE, argument);
theIf.setPosition(position);
BasicBlock ifBlock =
BasicBlock.createIfBlock(code.getNextBlockNumber(), theIf, code.metadata());
code.blocks.add(ifBlock);
// Fallthrough block must be added right after the if.
BasicBlock isNullBlock =
BasicBlock.createGotoBlock(code.getNextBlockNumber(), position, code.metadata());
code.blocks.add(isNullBlock);
BasicBlock isNotNullBlock =
BasicBlock.createGotoBlock(code.getNextBlockNumber(), position, code.metadata());
code.blocks.add(isNotNullBlock);
// Link the added blocks together.
block.link(ifBlock);
ifBlock.link(isNotNullBlock);
ifBlock.link(isNullBlock);
isNotNullBlock.link(successor);
isNullBlock.link(successor);
// Fill code into the blocks.
iterator = isNullBlock.listIterator(code);
iterator.setInsertionPosition(position);
iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, nul)));
iterator = isNotNullBlock.listIterator(code);
iterator.setInsertionPosition(position);
value = code.createValue(TypeElement.classClassType(appView, definitelyNotNull()));
iterator.add(
new InvokeVirtual(
dexItemFactory.objectMembers.getClass, value, ImmutableList.of(arguments.get(i))));
iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, value)));
}
iterator = eol.listIterator(code);
iterator.setInsertionPosition(position);
if (i == arguments.size() - 1) {
iterator.add(new InvokeVirtual(printLn, null, ImmutableList.of(out, closeParenthesis)));
} else {
iterator.add(new InvokeVirtual(printLn, null, ImmutableList.of(out, comma)));
}
block = eol;
}
// When we fall out of the loop the iterator is in the last eol block.
iterator.add(new InvokeVirtual(printLn, null, ImmutableList.of(out, empty)));
}
// The javac fix for JDK-8272564 has to be rewritten back to invoke-virtual on Object if the
// method with an Object signature is not defined on the interface. See
// https://bugs.openjdk.java.net/browse/JDK-8272564
public static void rewriteJdk8272564Fix(IRCode code, ProgramMethod context, AppView<?> appView) {
DexItemFactory dexItemFactory = appView.dexItemFactory();
InstructionListIterator it = code.instructionListIterator();
while (it.hasNext()) {
Instruction instruction = it.next();
if (instruction.isInvokeInterface()) {
InvokeInterface invoke = instruction.asInvokeInterface();
DexMethod method = invoke.getInvokedMethod();
DexClass clazz = appView.definitionFor(method.getHolderType(), context);
if (clazz == null || clazz.isInterface()) {
DexMethod objectMember = dexItemFactory.objectMembers.matchingPublicObjectMember(method);
// javac before JDK-8272564 would still use invoke interface if the method is defined
// directly on the interface reference, so mimic that by not rewriting.
if (objectMember != null && appView.definitionFor(method) == null) {
it.replaceCurrentInstruction(
new InvokeVirtual(objectMember, invoke.outValue(), invoke.arguments()));
}
}
}
}
}
}