blob: 52aed30ff7106915f33fd5e3abea6df6d601d84a [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.code;
import static com.android.tools.r8.ir.code.IRCode.INSTRUCTION_NUMBER_DELTA;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DebugLocalInfo.PrintLevel;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.Phi.RegisterReadType;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.utils.CfgPrinter;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.StringUtils.BraceType;
import com.google.common.base.Equivalence;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* Basic block abstraction.
*/
public class BasicBlock {
public interface BasicBlockChangeListener {
void onSuccessorsMayChange(BasicBlock block);
void onPredecessorsMayChange(BasicBlock block);
}
private Int2ReferenceMap<DebugLocalInfo> localsAtEntry;
public boolean consistentBlockInstructions(boolean argumentsAllowed, boolean debug) {
for (Instruction instruction : getInstructions()) {
assert instruction.verifyValidPositionInfo(debug);
assert instruction.getBlock() == this;
assert !instruction.isArgument() || argumentsAllowed;
assert !instruction.isDebugLocalRead() || !instruction.getDebugValues().isEmpty();
if (instruction.isMoveException()) {
assert instruction == entry();
for (BasicBlock pred : getPredecessors()) {
assert pred.hasCatchSuccessor(this)
|| (pred.isTrivialGoto() && pred.endOfGotoChain() == this);
}
}
if (!instruction.isArgument()) {
argumentsAllowed = false;
}
}
return true;
}
public boolean verifyTypes(AppView<?> appView) {
assert instructions.stream().allMatch(instruction -> instruction.verifyTypes(appView));
return true;
}
public void setLocalsAtEntry(Int2ReferenceMap<DebugLocalInfo> localsAtEntry) {
this.localsAtEntry = localsAtEntry;
}
public Int2ReferenceMap<DebugLocalInfo> getLocalsAtEntry() {
return localsAtEntry;
}
public void replaceLastInstruction(Instruction instruction, IRCode code) {
InstructionListIterator iterator = listIterator(code, getInstructions().size());
iterator.previous();
iterator.replaceCurrentInstruction(instruction);
}
public enum ThrowingInfo {
NO_THROW,
CAN_THROW;
public static ThrowingInfo defaultForConstString(InternalOptions options) {
return options.isGeneratingClassFiles() ? NO_THROW : CAN_THROW;
}
}
public enum EdgeType {
NON_EDGE,
NORMAL,
EXCEPTIONAL
}
public static class Pair implements Comparable<Pair> {
public BasicBlock first;
public BasicBlock second;
public Pair(BasicBlock first, BasicBlock second) {
this.first = first;
this.second = second;
}
@Override
public int compareTo(Pair o) {
if (first != o.first) {
return first.getNumber() - o.first.getNumber();
}
if (second != o.second) {
return second.getNumber() - o.second.getNumber();
}
return 0;
}
@Override
public String toString() {
return "Edge: " + first.getNumber() + " -> " + second.getNumber();
}
}
private final List<BasicBlock> successors = new ArrayList<>();
private final List<BasicBlock> predecessors = new ArrayList<>();
private Set<BasicBlockChangeListener> onControlFlowEdgesMayChangeListeners = null;
// Catch handler information about which successors are catch handlers and what their guards are.
private CatchHandlers<Integer> catchHandlers = CatchHandlers.EMPTY_INDICES;
private LinkedList<Instruction> instructions = new LinkedList<>();
private int number = -1;
private List<Phi> phis = new ArrayList<>();
// State used during SSA construction. The SSA construction is based on the paper:
//
// "Simple and Efficient Construction of Static Single Assignment Form"
// http://compilers.cs.uni-saarland.de/papers/bbhlmz13cc.pdf
//
// A basic block is filled when local value numbering is complete for that block.
// A basic block is sealed when all predecessor blocks have been filled.
//
// Therefore, for a sealed block we can always search backwards to find reaching values
// in predecessor blocks.
private boolean filled = false;
private boolean sealed = false;
private final Map<Integer, Phi> incompletePhis = new HashMap<>();
private int estimatedPredecessorsCount = 0;
private int unfilledPredecessorsCount = 0;
// State used for basic block sorting and tracing.
private int color = 0;
// Map of registers to current SSA value. Used during SSA numbering and cleared once filled.
private Map<Integer, Value> currentDefinitions = new HashMap<>();
public void addControlFlowEdgesMayChangeListener(BasicBlockChangeListener listener) {
if (onControlFlowEdgesMayChangeListeners == null) {
// WeakSet to allow the listeners to be garbage collected.
onControlFlowEdgesMayChangeListeners = Collections.newSetFromMap(new WeakHashMap<>());
}
onControlFlowEdgesMayChangeListeners.add(listener);
}
public List<BasicBlock> getSuccessors() {
return Collections.unmodifiableList(successors);
}
public List<BasicBlock> getMutableSuccessors() {
assert notifySuccessorsMayChangeListeners();
return successors;
}
private boolean notifySuccessorsMayChangeListeners() {
if (onControlFlowEdgesMayChangeListeners != null) {
onControlFlowEdgesMayChangeListeners.forEach(l -> l.onSuccessorsMayChange(this));
}
return true;
}
public List<BasicBlock> getNormalSuccessors() {
if (!hasCatchHandlers()) {
return successors;
}
Set<Integer> handlers = catchHandlers.getUniqueTargets();
ImmutableList.Builder<BasicBlock> normals = ImmutableList.builder();
for (int i = 0; i < successors.size(); i++) {
if (!handlers.contains(i)) {
normals.add(successors.get(i));
}
}
return normals.build();
}
public int numberOfNormalSuccessors() {
if (hasCatchHandlers()) {
return successors.size() - catchHandlers.getUniqueTargets().size();
}
return successors.size();
}
public boolean hasUniquePredecessor() {
return predecessors.size() == 1;
}
public BasicBlock getUniquePredecessor() {
assert hasUniquePredecessor();
return predecessors.get(0);
}
public List<BasicBlock> getPredecessors() {
return Collections.unmodifiableList(predecessors);
}
public List<BasicBlock> getMutablePredecessors() {
assert notifyPredecessorsMayChangeListeners();
return predecessors;
}
private boolean notifyPredecessorsMayChangeListeners() {
if (onControlFlowEdgesMayChangeListeners != null) {
onControlFlowEdgesMayChangeListeners.forEach(l -> l.onPredecessorsMayChange(this));
}
return true;
}
public List<BasicBlock> getNormalPredecessors() {
ImmutableList.Builder<BasicBlock> normals = ImmutableList.builder();
for (BasicBlock predecessor : predecessors) {
if (!predecessor.hasCatchSuccessor(this)) {
normals.add(predecessor);
}
}
return normals.build();
}
public void removeSuccessor(BasicBlock block) {
int index = successors.indexOf(block);
assert index >= 0 : "removeSuccessor did not find the successor to remove";
removeSuccessorsByIndex(new IntArrayList(new int[] {index}));
}
public void removePredecessor(BasicBlock block, Set<Value> affectedValues) {
int index = predecessors.indexOf(block);
assert index >= 0 : "removePredecessor did not find the predecessor to remove";
getMutablePredecessors().remove(index);
if (phis != null) {
for (Phi phi : getPhis()) {
phi.removeOperand(index);
}
// Collect and remove trivial phis after block removal.
List<Phi> trivials = new ArrayList<>();
for (Phi phi : getPhis()) {
if (phi.isTrivialPhi()) {
trivials.add(phi);
if (affectedValues != null) {
affectedValues.addAll(phi.affectedValues());
}
}
}
for (Phi phi : trivials) {
phi.removeTrivialPhi(null, affectedValues);
}
}
}
public void removeAllNormalSuccessors() {
if (hasCatchHandlers()) {
IntList successorsToRemove = new IntArrayList();
Set<Integer> handlers = catchHandlers.getUniqueTargets();
for (int i = 0; i < successors.size(); i++) {
if (!handlers.contains(i)) {
successorsToRemove.add(i);
}
}
removeSuccessorsByIndex(successorsToRemove);
} else {
successors.clear();
}
}
public void swapSuccessors(BasicBlock a, BasicBlock b) {
assert a != b;
int aIndex = successors.indexOf(a);
int bIndex = successors.indexOf(b);
assert aIndex >= 0 && bIndex >= 0;
swapSuccessorsByIndex(aIndex, bIndex);
}
public void swapSuccessorsByIndex(int index1, int index2) {
assert index1 != index2;
if (hasCatchHandlers()) {
List<Integer> targets = new ArrayList<>(catchHandlers.getAllTargets());
assert targets.contains(index1) == targets.contains(index2)
: "Swapping normal successor and catch handler";
for (int i = 0; i < targets.size(); i++) {
if (targets.get(i) == index1) {
targets.set(i, index2);
} else if (targets.get(i) == index2) {
targets.set(i, index1);
}
}
catchHandlers = new CatchHandlers<>(catchHandlers.getGuards(), targets);
}
List<BasicBlock> successors = getMutableSuccessors();
BasicBlock tmp = successors.get(index1);
successors.set(index1, successors.get(index2));
successors.set(index2, tmp);
}
// TODO(b/116174212): Remove the predecessor pointer from the old successor block.
public void replaceSuccessor(BasicBlock block, BasicBlock newBlock) {
assert successors.contains(block) : "attempt to replace non-existent successor";
if (successors.contains(newBlock)) {
int indexOfOldBlock = successors.indexOf(block);
int indexOfNewBlock = successors.indexOf(newBlock);
// Always rewrite catch handlers.
if (hasCatchHandlers()) {
List<Integer> targets = new ArrayList<>(catchHandlers.getAllTargets());
for (int i = 0; i < targets.size(); i++) {
if (targets.get(i) == indexOfOldBlock) {
targets.set(i, indexOfNewBlock);
}
if (targets.get(i) > indexOfOldBlock) {
targets.set(i, targets.get(i) - 1);
}
}
catchHandlers = new CatchHandlers<>(catchHandlers.getGuards(), targets);
}
// Check if the replacement influences jump targets and rewrite as needed.
if (exit().isGoto()) {
if (indexOfOldBlock == successors.size() - 1 && indexOfNewBlock != successors.size() - 2) {
// Replacing the goto target and the new block will not become the goto target.
// We perform a swap to get the new block into the goto target position.
swapSuccessorsByIndex(indexOfOldBlock - 1, indexOfNewBlock);
}
} else if (exit().isIf()) {
if (indexOfNewBlock >= successors.size() - 2 && indexOfOldBlock >= successors.size() - 2) {
// New and old are true target and fallthrough, replace last instruction with a goto.
Instruction instruction = getInstructions().removeLast();
// Iterate in reverse order to ensure that POP instructions are inserted in correct order.
for (int i = instruction.inValues().size() - 1; i >= 0; i--) {
Value value = instruction.inValues().get(i);
if (value instanceof StackValue) {
if (value.definition.isLoad() && value.definition.getBlock() == this) {
assert hasLinearFlow(this, value.definition.getBlock());
value.definition.getBlock().removeInstruction(value.definition);
} else {
Pop pop = new Pop((StackValue) value);
pop.setBlock(this);
pop.setPosition(instruction.getPosition());
getInstructions().addLast(pop);
}
}
if (value.hasUsersInfo()) {
value.removeUser(instruction);
}
}
Instruction exit = new Goto();
exit.setBlock(this);
exit.setPosition(instruction.getPosition());
getInstructions().addLast(exit);
} else if (indexOfOldBlock >= successors.size() - 2) {
// Old is either true or fallthrough and we need to swap the new block into the right
// position to become that target.
swapSuccessorsByIndex(indexOfOldBlock - 1, indexOfNewBlock);
}
} else if (exit().isSwitch()) {
// Rewrite fallthrough and case target indices.
Switch exit = exit().asSwitch();
if (exit.getFallthroughBlockIndex() == indexOfOldBlock) {
exit.setFallthroughBlockIndex(indexOfNewBlock);
}
if (exit.getFallthroughBlockIndex() > indexOfOldBlock) {
exit.setFallthroughBlockIndex(exit.getFallthroughBlockIndex() - 1);
}
int[] indices = exit.targetBlockIndices();
for (int i = 0; i < indices.length; i++) {
if (indices[i] == indexOfOldBlock) {
indices[i] = indexOfNewBlock;
}
if (indices[i] > indexOfOldBlock) {
indices[i] = indices[i] - 1;
}
}
} else {
assert exit().isReturn() || exit().isThrow();
}
// Remove the replaced successor.
boolean removed = getMutableSuccessors().remove(block);
assert removed;
} else {
// If the new block is not a successor we don't have to rewrite indices or instructions
// and we can just replace the old successor with the new one.
for (int i = 0; i < successors.size(); i++) {
if (successors.get(i) == block) {
getMutableSuccessors().set(i, newBlock);
return;
}
}
}
}
private boolean hasLinearFlow(BasicBlock current, BasicBlock target) {
while (current != target) {
if (current.getPredecessors().size() != 1) {
return false;
}
BasicBlock candidate = current.getPredecessors().get(0);
if (!candidate.exit().isGoto() || candidate.exit().asGoto().getTarget() != current) {
return false;
}
current = candidate;
}
return true;
}
public void replacePredecessor(BasicBlock block, BasicBlock newBlock) {
for (int i = 0; i < predecessors.size(); i++) {
if (predecessors.get(i) == block) {
assert notifyPredecessorsMayChangeListeners();
getMutablePredecessors().set(i, newBlock);
return;
}
}
assert false : "replaceSuccessor did not find the predecessor to replace";
}
public void removeSuccessorsByIndex(IntList successorsToRemove) {
if (successorsToRemove.isEmpty()) {
return;
}
assert ListUtils.verifyListIsOrdered(successorsToRemove);
List<BasicBlock> successors = getMutableSuccessors();
List<BasicBlock> copy = new ArrayList<>(successors);
successors.clear();
int current = 0;
for (int i : successorsToRemove) {
successors.addAll(copy.subList(current, i));
current = i + 1;
}
successors.addAll(copy.subList(current, copy.size()));
if (hasCatchHandlers()) {
List<Integer> currentTargets = catchHandlers.getAllTargets();
List<DexType> currentGuards = catchHandlers.getGuards();
int size = catchHandlers.size();
List<DexType> newGuards = new ArrayList<>(size);
List<Integer> newTargets = new ArrayList<>(size);
// Since targets represent indices in the list of successors, we
// need to remove targets/indices included in successorsToRemove,
// and decrease the rest of targets/indices to reflect removed successors.
outer:
for (int i = 0; i < currentTargets.size(); i++) {
int index = currentTargets.get(i);
int decreaseBy = 0;
for (int removedIndex : successorsToRemove) {
if (index == removedIndex) {
continue outer; // target was removed
}
if (index < removedIndex) {
break;
}
decreaseBy++;
}
newTargets.add(index - decreaseBy);
newGuards.add(currentGuards.get(i));
}
if (newTargets.isEmpty()) {
catchHandlers = CatchHandlers.EMPTY_INDICES;
} else {
catchHandlers = new CatchHandlers<>(newGuards, newTargets);
}
}
}
public void removePredecessorsByIndex(List<Integer> predecessorsToRemove) {
if (predecessorsToRemove.isEmpty()) {
return;
}
List<BasicBlock> predecessors = getMutablePredecessors();
List<BasicBlock> copy = new ArrayList<>(predecessors);
predecessors.clear();
int current = 0;
for (int i : predecessorsToRemove) {
predecessors.addAll(copy.subList(current, i));
current = i + 1;
}
predecessors.addAll(copy.subList(current, copy.size()));
}
public void removePhisByIndex(List<Integer> predecessorsToRemove) {
for (Phi phi : phis) {
phi.removeOperandsByIndex(predecessorsToRemove);
}
}
public boolean hasPhis() {
return !phis.isEmpty();
}
public boolean hasDeadPhi(AppView<?> appView, IRCode code) {
for (Phi phi : phis) {
if (phi.isDead(appView, code)) {
return true;
}
}
return false;
}
public List<Phi> getPhis() {
return phis;
}
public boolean isEntry() {
return getPredecessors().isEmpty();
}
public boolean isFilled() {
return filled;
}
public void setFilledForTesting() {
filled = true;
}
public boolean hasCatchHandlers() {
assert catchHandlers != null;
return !catchHandlers.isEmpty();
}
public int getNumber() {
assert number >= 0;
return number;
}
public void setNumber(int number) {
assert number >= 0;
this.number = number;
}
public String getNumberAsString() {
return number >= 0 ? "" + number : "<unknown>";
}
public int numberInstructions(int nextInstructionNumber) {
for (Instruction instruction : instructions) {
instruction.setNumber(nextInstructionNumber);
nextInstructionNumber += INSTRUCTION_NUMBER_DELTA;
}
return nextInstructionNumber;
}
public LinkedList<Instruction> getInstructions() {
return instructions;
}
public Iterable<Instruction> instructionsAfter(Instruction instruction) {
return () -> iterator(instruction);
}
public Iterable<Instruction> instructionsBefore(Instruction instruction) {
return () ->
new Iterator<Instruction>() {
private InstructionIterator iterator = iterator();
private Instruction next = advance();
private Instruction advance() {
if (iterator.hasNext()) {
Instruction next = iterator.next();
if (next != instruction) {
return next;
}
}
return null;
}
@Override
public boolean hasNext() {
return next != null;
}
@Override
public Instruction next() {
Instruction result = next;
if (result == null) {
throw new NoSuchElementException();
}
next = advance();
return result;
}
};
}
public boolean isEmpty() {
return instructions.isEmpty();
}
public int size() {
return instructions.size();
}
public boolean isReturnBlock() {
return exit().isReturn();
}
public Instruction entry() {
return instructions.get(0);
}
public JumpInstruction exit() {
assert filled;
assert instructions.get(instructions.size() - 1).isJumpInstruction();
return instructions.get(instructions.size() - 1).asJumpInstruction();
}
public Instruction exceptionalExit() {
assert hasCatchHandlers();
InstructionIterator it = iterator(instructions.size());
while (it.hasPrevious()) {
Instruction instruction = it.previous();
if (instruction.instructionTypeCanThrow()) {
return instruction;
}
}
return null;
}
public void clearUserInfo() {
phis = null;
instructions.forEach(Instruction::clearUserInfo);
}
public void buildDex(DexBuilder builder) {
for (Instruction instruction : instructions) {
instruction.buildDex(builder);
}
}
public void mark(int color) {
assert color != 0;
assert !isMarked(color);
this.color |= color;
assert isMarked(color);
}
public void clearMark(int color) {
assert color != 0;
this.color &= ~color;
assert !isMarked(color);
}
public boolean isMarked(int color) {
assert color != 0;
return (this.color & color) != 0;
}
public void incrementUnfilledPredecessorCount() {
++unfilledPredecessorsCount;
++estimatedPredecessorsCount;
}
public void decrementUnfilledPredecessorCount(int n) {
unfilledPredecessorsCount -= n;
estimatedPredecessorsCount -= n;
}
public void decrementUnfilledPredecessorCount() {
--unfilledPredecessorsCount;
--estimatedPredecessorsCount;
}
public boolean verifyFilledPredecessors() {
assert estimatedPredecessorsCount == predecessors.size();
assert unfilledPredecessorsCount == 0;
return true;
}
public void addPhi(Phi phi) {
phis.add(phi);
}
public void removePhi(Phi phi) {
phis.remove(phi);
assert currentDefinitions == null || !currentDefinitions.containsValue(phi)
: "Attempt to remove Phi " + phi + " which is present in currentDefinitions";
}
public void add(Instruction next, IRCode code) {
add(next, code.metadata());
}
public void add(Instruction next, IRMetadata metadata) {
assert !isFilled();
instructions.add(next);
metadata.record(next);
next.setBlock(this);
}
public void close(IRBuilder builder) {
assert !isFilled();
assert !instructions.isEmpty();
filled = true;
sealed = unfilledPredecessorsCount == 0;
assert exit().isJumpInstruction();
assert verifyNoValuesAfterThrowingInstruction();
for (BasicBlock successor : successors) {
successor.filledPredecessor(builder);
}
}
public void link(BasicBlock successor) {
assert !successors.contains(successor);
assert !successor.predecessors.contains(this);
getMutableSuccessors().add(successor);
successor.getMutablePredecessors().add(this);
}
private static boolean blocksClean(List<BasicBlock> blocks) {
blocks.forEach(
b -> {
assert b.predecessors.size() == 0;
assert b.successors.size() == 0;
});
return true;
}
/**
* Unlinks this block from a single predecessor.
*
* @return returns the unlinked predecessor.
*/
public BasicBlock unlinkSinglePredecessor() {
assert predecessors.size() == 1;
assert predecessors.get(0).successors.size() == 1;
BasicBlock unlinkedBlock = predecessors.get(0);
unlinkedBlock.getMutableSuccessors().clear();
getMutablePredecessors().clear();
return unlinkedBlock;
}
/** Like unlinkSinglePredecessor but the predecessor may have multiple successors. */
public void unlinkSinglePredecessorSiblingsAllowed() {
assert predecessors.size() == 1; // There are no critical edges.
assert predecessors.get(0).successors.contains(this);
List<BasicBlock> predecessors = getMutablePredecessors();
predecessors.get(0).getMutableSuccessors().remove(this);
getMutablePredecessors().clear();
}
/**
* Unlinks this block from a single normal successor.
*
* @return Returns the unlinked successor.
*/
public BasicBlock unlinkSingleSuccessor() {
assert !hasCatchHandlers();
assert successors.size() == 1;
assert successors.get(0).predecessors.size() == 1;
BasicBlock unlinkedBlock = successors.get(0);
unlinkedBlock.getMutablePredecessors().clear();
getMutableSuccessors().clear();
return unlinkedBlock;
}
/**
* Unlinks the current block based on the assumption that it is a catch handler.
*
* Catch handlers always have only one predecessor and at most one successor.
* That is because we have edge-split form for all exceptional flow.
*/
public void unlinkCatchHandler() {
assert predecessors.size() == 1;
predecessors.get(0).removeSuccessor(this);
getMutablePredecessors().clear();
}
/**
* Removes this basic block as a catch handler for the given guard, based on the assumption that
* this block is a catch handler.
*
* <p>If this basic block is the target for a unique guard, then this catch handler will be
* unlinked. If this basic block is the target for multiple guards, then the catch handlers of the
* predecessor block will be updated such that this block is no longer a catch handler for the
* given guard.
*/
public void unlinkCatchHandlerForGuard(DexType guard) {
assert predecessors.size() == 1;
if (isCatchHandlerForSingleGuard()) {
// Unlink catch handler entirely.
unlinkCatchHandler();
} else {
// Update catch handlers of predecessor.
BasicBlock predecessor = predecessors.get(0);
predecessor.removeCatchHandlerWithGuard(guard);
}
}
private void removeCatchHandlerWithGuard(DexType guard) {
int guardIndex = catchHandlers.getGuards().indexOf(guard);
if (guardIndex >= 0) {
int successorIndex = catchHandlers.getAllTargets().get(guardIndex);
assert successorIndex >= 0;
catchHandlers = catchHandlers.removeGuard(guard);
if (getCatchHandlers().getAllTargets().stream()
.noneMatch(target -> target == successors.get(successorIndex))) {
getMutableSuccessors().remove(successorIndex);
}
assert consistentCatchHandlers();
}
}
private boolean isCatchHandlerForSingleGuard() {
assert predecessors.size() == 1;
BasicBlock predecessor = predecessors.get(0);
assert predecessor.getCatchHandlers().getAllTargets().contains(this);
int count = 0;
for (BasicBlock target : predecessor.getCatchHandlers().getAllTargets()) {
if (target == this && ++count > 1) {
return false;
}
}
return true;
}
public void detachAllSuccessors() {
for (BasicBlock successor : successors) {
successor.getMutablePredecessors().remove(this);
}
getMutableSuccessors().clear();
}
public List<BasicBlock> unlink(
BasicBlock successor, DominatorTree dominator, Set<Value> affectedValues) {
assert affectedValues != null;
assert successors.contains(successor);
assert successor.predecessors.size() == 1; // There are no critical edges.
assert successor.predecessors.get(0) == this;
List<BasicBlock> removedBlocks = new ArrayList<>();
for (BasicBlock dominated : dominator.dominatedBlocks(successor)) {
affectedValues.addAll(dominated.cleanForRemoval());
removedBlocks.add(dominated);
}
assert blocksClean(removedBlocks);
return removedBlocks;
}
public Set<Value> cleanForRemoval() {
Set<Value> affectedValues = Sets.newIdentityHashSet();
for (BasicBlock block : successors) {
affectedValues.addAll(block.getPhis());
block.removePredecessor(this, affectedValues);
}
getMutableSuccessors().clear();
for (BasicBlock block : predecessors) {
block.removeSuccessor(this);
}
getMutablePredecessors().clear();
for (Phi phi : getPhis()) {
affectedValues.addAll(phi.affectedValues());
for (Value operand : phi.getOperands()) {
operand.removePhiUser(phi);
}
}
getPhis().clear();
for (Instruction instruction : getInstructions()) {
if (instruction.outValue() != null) {
affectedValues.addAll(instruction.outValue().affectedValues());
instruction.outValue().clearUsers();
instruction.setOutValue(null);
}
for (Value value : instruction.inValues) {
value.removeUser(instruction);
}
for (Value value : instruction.getDebugValues()) {
value.removeDebugUser(instruction);
}
}
return affectedValues;
}
public void linkCatchSuccessors(List<DexType> guards, List<BasicBlock> targets) {
List<Integer> successorIndexes = new ArrayList<>(targets.size());
for (BasicBlock target : targets) {
int index = successors.indexOf(target);
if (index < 0) {
index = successors.size();
link(target);
}
successorIndexes.add(index);
}
catchHandlers = new CatchHandlers<>(guards, successorIndexes);
}
public void appendCatchHandler(BasicBlock target, DexType guard) {
if (!canThrow()) {
// Nothing to catch.
return;
}
if (hasCatchHandlers()) {
if (catchHandlers.getGuards().contains(guard)) {
// Subsumed by an existing catch handler.
return;
}
int targetIndex = successors.indexOf(target);
if (targetIndex < 0) {
List<BasicBlock> successors = getMutableSuccessors();
int numberOfSuccessors = successors.size();
int numberOfNormalSuccessors = numberOfNormalSuccessors();
if (numberOfNormalSuccessors > 0) {
// Increase the size of the successor list by 1, and increase the index of each normal
// successor by 1.
targetIndex = numberOfSuccessors - numberOfNormalSuccessors;
successors.add(targetIndex, target);
} else {
// If there are no normal successors we can simply add the new catch handler.
targetIndex = successors.size();
successors.add(target);
}
target.getMutablePredecessors().add(this);
}
catchHandlers = catchHandlers.appendGuard(guard, targetIndex);
} else {
assert instructions.stream().filter(Instruction::instructionTypeCanThrow).count() == 1;
getMutableSuccessors().add(0, target);
target.getMutablePredecessors().add(this);
catchHandlers = new CatchHandlers<>(ImmutableList.of(guard), ImmutableList.of(0));
}
}
/**
* Due to class merging, it is possible that two exception classes have been merged into one. This
* function renames the guards according to the given graph lense.
*
* @return true if any guards were renamed.
*/
public boolean renameGuardsInCatchHandlers(GraphLense graphLense) {
assert hasCatchHandlers();
boolean anyGuardsRenamed = false;
List<DexType> newGuards = new ArrayList<>(catchHandlers.getGuards().size());
for (DexType guard : catchHandlers.getGuards()) {
// The type may have changed due to class merging.
DexType renamed = graphLense.lookupType(guard);
newGuards.add(renamed);
anyGuardsRenamed |= renamed != guard;
}
if (anyGuardsRenamed) {
this.catchHandlers = new CatchHandlers<>(newGuards, catchHandlers.getAllTargets());
}
return anyGuardsRenamed;
}
public boolean consistentCatchHandlers() {
// Check that catch handlers are always the first successors of a block.
if (hasCatchHandlers()) {
assert exit().isGoto() || exit().isThrow();
CatchHandlers<Integer> catchHandlers = getCatchHandlersWithSuccessorIndexes();
// Check that guards are unique.
assert catchHandlers.getGuards().size()
== ImmutableSet.copyOf(catchHandlers.getGuards()).size();
// If there is a catch-all guard it must be the last.
List<DexType> guards = catchHandlers.getGuards();
int lastGuardIndex = guards.size() - 1;
for (int i = 0; i < guards.size(); i++) {
assert !guards.get(i).toDescriptorString().equals(DexItemFactory.throwableDescriptorString)
|| i == lastGuardIndex;
}
// Check that all successors except maybe the last are catch successors.
List<Integer> sortedHandlerIndices = new ArrayList<>(catchHandlers.getAllTargets());
sortedHandlerIndices.sort(Comparator.naturalOrder());
int firstIndex = sortedHandlerIndices.get(0);
int lastIndex = sortedHandlerIndices.get(sortedHandlerIndices.size() - 1);
assert firstIndex == 0;
assert lastIndex < sortedHandlerIndices.size();
int lastSuccessorIndex = getSuccessors().size() - 1;
assert lastIndex == lastSuccessorIndex // All successors are catch successors.
|| lastIndex == lastSuccessorIndex - 1; // All but one successors are catch successors.
assert lastIndex == lastSuccessorIndex || !exit().isThrow();
}
return true;
}
public void clearCurrentDefinitions() {
currentDefinitions = null;
for (Phi phi : getPhis()) {
phi.clearDefinitionsUsers();
}
}
// The proper incoming register for a catch successor (that is otherwise shadowed by the out-value
// of a throwing instruction) is stored at the negative register-index in the definitions map.
// (See readCurrentDefinition/writeCurrentDefinition/updateCurrentDefinition).
private static int onThrowValueRegister(int register) {
return -(register + 1);
}
private Value readOnThrowValue(int register, EdgeType readingEdge) {
if (readingEdge == EdgeType.EXCEPTIONAL) {
return currentDefinitions.get(onThrowValueRegister(register));
}
return null;
}
private boolean isOnThrowValue(int register, EdgeType readingEdge) {
return readOnThrowValue(register, readingEdge) != null;
}
public Value readCurrentDefinition(int register, EdgeType readingEdge) {
// If the block reading the current definition is a catch successor, then we must return the
// previous value of the throwing-instructions outgoing register if any.
Value result = readOnThrowValue(register, readingEdge);
if (result != null) {
return result == Value.UNDEFINED ? null : result;
}
return currentDefinitions.get(register);
}
public void replaceCurrentDefinitions(Value oldValue, Value newValue) {
assert oldValue.definition.getBlock() == this;
assert !oldValue.isUsed();
for (Entry<Integer, Value> entry : currentDefinitions.entrySet()) {
if (entry.getValue() == oldValue) {
if (oldValue.isPhi()) {
oldValue.asPhi().removeDefinitionsUser(currentDefinitions);
}
entry.setValue(newValue);
if (newValue.isPhi()) {
newValue.asPhi().addDefinitionsUser(currentDefinitions);
}
}
}
}
public void updateCurrentDefinition(int register, Value value, EdgeType readingEdge) {
// If the reading/writing block is a catch successor, possibly update the on-throw value.
if (isOnThrowValue(register, readingEdge)) {
register = onThrowValueRegister(register);
}
// We keep track of all users of phis so that we can update all users during
// trivial phi elimination. We only rewrite phi values during IR construction, so
// we only need to record definition users for phis.
Value previousValue = currentDefinitions.get(register);
if (value.isPhi()) {
value.asPhi().addDefinitionsUser(currentDefinitions);
}
assert verifyOnThrowWrite(register);
currentDefinitions.put(register, value);
// We have replaced one occurrence of value in currentDefinitions. There could be
// other occurrences. We only remove currentDefinitions from the set of users
// of the phi if we have removed all occurrences.
if (previousValue != null &&
previousValue.isPhi() &&
!currentDefinitions.values().contains(previousValue)) {
previousValue.asPhi().removeDefinitionsUser(currentDefinitions);
}
}
public void writeCurrentDefinition(int register, Value value, ThrowingInfo throwing) {
// If this write is dependent on not throwing, we move the existing value to its negative index
// so that it can be read by catch successors.
if (throwing == ThrowingInfo.CAN_THROW) {
Value previous = currentDefinitions.get(register);
assert verifyOnThrowWrite(register);
currentDefinitions.put(onThrowValueRegister(register),
previous == null ? Value.UNDEFINED : previous);
}
updateCurrentDefinition(register, value, EdgeType.NON_EDGE);
}
public void filledPredecessor(IRBuilder builder) {
assert unfilledPredecessorsCount > 0;
if (--unfilledPredecessorsCount == 0) {
assert estimatedPredecessorsCount == predecessors.size();
for (Entry<Integer, Phi> entry : incompletePhis.entrySet()) {
int register = entry.getKey();
if (register < 0) {
register = onThrowValueRegister(register);
}
entry.getValue().addOperands(builder, register);
}
sealed = true;
incompletePhis.clear();
}
}
public EdgeType getEdgeType(BasicBlock successor) {
assert successors.indexOf(successor) >= 0;
return hasCatchSuccessor(successor) ? EdgeType.EXCEPTIONAL : EdgeType.NORMAL;
}
public boolean hasCatchSuccessor(BasicBlock block) {
if (!hasCatchHandlers()) {
return false;
}
return catchHandlers.getUniqueTargets().contains(successors.indexOf(block));
}
public int guardsForCatchSuccessor(BasicBlock block) {
assert hasCatchSuccessor(block);
int index = successors.indexOf(block);
int count = 0;
for (int handler : catchHandlers.getAllTargets()) {
if (handler == index) {
count++;
}
}
assert count > 0;
return count;
}
public boolean isSealed() {
return sealed;
}
public void addIncompletePhi(int register, Phi phi, EdgeType readingEdge) {
if (isOnThrowValue(register, readingEdge)) {
register = onThrowValueRegister(register);
}
assert !incompletePhis.containsKey(register);
incompletePhis.put(register, phi);
}
public boolean hasIncompletePhis() {
return !incompletePhis.isEmpty();
}
public Collection<Integer> getIncompletePhiRegisters() {
return incompletePhis.keySet();
}
private static void appendBasicBlockList(
StringBuilder builder, List<BasicBlock> list, Function<BasicBlock, String> postfix) {
if (list.size() > 0) {
for (BasicBlock block : list) {
builder.append(block.getNumberAsString());
builder.append(postfix.apply(block));
builder.append(' ');
}
} else {
builder.append('-');
}
}
@Override
public String toString() {
return toDetailedString();
}
public String toSimpleString() {
return number < 0 ? super.toString() : ("block " + number);
}
private String predecessorPostfix(BasicBlock block) {
if (hasCatchSuccessor(block)) {
return new String(new char[guardsForCatchSuccessor(block)]).replace("\0", "*");
}
return "";
}
private static int digits(int number) {
return (int) Math.ceil(Math.log10(number + 1));
}
public String toDetailedString() {
StringBuilder builder = new StringBuilder();
builder.append("block ");
builder.append(number);
builder.append(", pred-counts: " + predecessors.size());
if (unfilledPredecessorsCount > 0) {
builder.append(" (" + unfilledPredecessorsCount + " unfilled)");
}
builder.append(", succ-count: " + successors.size());
builder.append(", filled: " + isFilled());
builder.append(", sealed: " + isSealed());
builder.append('\n');
builder.append("predecessors: ");
appendBasicBlockList(builder, predecessors, b -> "");
builder.append('\n');
builder.append("successors: ");
appendBasicBlockList(builder, successors, this::predecessorPostfix);
if (successors.size() > 0) {
builder.append(" (");
if (hasCatchHandlers()) {
builder.append(catchHandlers.size());
} else {
builder.append("no");
}
builder.append(" try/catch successors)");
}
builder.append('\n');
if (phis != null && phis.size() > 0) {
for (Phi phi : phis) {
builder.append(phi.printPhi());
if (incompletePhis.values().contains(phi)) {
builder.append(" (incomplete)");
}
builder.append('\n');
}
} else {
builder.append("no phis\n");
}
if (localsAtEntry != null) {
builder.append("locals: ");
StringUtils.append(builder, localsAtEntry.int2ReferenceEntrySet(), ", ", BraceType.NONE);
builder.append('\n');
}
int lineColumn = 0;
int numberColumn = 0;
for (Instruction instruction : instructions) {
lineColumn = Math.max(lineColumn, instruction.getPositionAsString().length());
numberColumn = Math.max(numberColumn, digits(instruction.getNumber()));
}
String currentPosition = null;
for (Instruction instruction : instructions) {
if (lineColumn > 0) {
String line = "";
if (!instruction.getPositionAsString().equals(currentPosition)) {
line = currentPosition = instruction.getPositionAsString();
}
StringUtils.appendLeftPadded(builder, line, lineColumn + 1);
builder.append(": ");
}
StringUtils.appendLeftPadded(builder, "" + instruction.getNumber(), numberColumn + 1);
builder.append(": ");
builder.append(instruction.toString());
if (DebugLocalInfo.PRINT_LEVEL != PrintLevel.NONE) {
List<Value> localEnds = new ArrayList<>(instruction.getDebugValues().size());
List<Value> localStarts = new ArrayList<>(instruction.getDebugValues().size());
List<Value> localLive = new ArrayList<>(instruction.getDebugValues().size());
for (Value value : instruction.getDebugValues()) {
if (value.getDebugLocalEnds().contains(instruction)) {
localEnds.add(value);
} else if (value.getDebugLocalStarts().contains(instruction)) {
localStarts.add(value);
} else {
assert value.debugUsers().contains(instruction);
localLive.add(value);
}
}
printDebugValueSet("live", localLive, builder);
printDebugValueSet("end", localEnds, builder);
printDebugValueSet("start", localStarts, builder);
}
builder.append("\n");
}
return builder.toString();
}
private void printDebugValueSet(String header, List<Value> locals, StringBuilder builder) {
if (!locals.isEmpty()) {
builder.append(" [").append(header).append(": ");
StringUtils.append(builder, locals, ", ", BraceType.NONE);
builder.append("]");
}
}
public void print(CfgPrinter printer) {
printer.begin("block");
printer.print("name \"B").append(number).append("\"\n");
printer.print("from_bci -1\n");
printer.print("to_bci -1\n");
printer.print("predecessors");
printBlockList(printer, predecessors);
printer.ln();
printer.print("successors");
printBlockList(printer, successors);
printer.ln();
printer.print("xhandlers\n");
printer.print("flags\n");
printer.print("first_lir_id ").print(instructions.get(0).getNumber()).ln();
printer.print("last_lir_id ").print(instructions.get(instructions.size() - 1).getNumber()).ln();
printer.begin("HIR");
if (phis != null) {
for (Phi phi : phis) {
phi.print(printer);
printer.append(" <|@\n");
}
}
for (Instruction instruction : instructions) {
instruction.print(printer);
printer.append(" <|@\n");
}
printer.end("HIR");
printer.begin("LIR");
for (Instruction instruction : instructions) {
instruction.printLIR(printer);
printer.append(" <|@\n");
}
printer.end("LIR");
printer.end("block");
}
private static void printBlockList(CfgPrinter printer, List<BasicBlock> blocks) {
for (BasicBlock block : blocks) {
printer.append(" \"B").append(block.number).append("\"");
}
}
public void addPhiMove(Move move) {
// TODO(ager): Consider this more, is it always the case that we should add it before the
// exit instruction?
Instruction branch = exit();
instructions.set(instructions.size() - 1, move);
instructions.add(branch);
}
public void setInstructions(LinkedList<Instruction> instructions) {
this.instructions = instructions;
}
/**
* Remove a number of instructions. The instructions to remove are given as indexes in the
* instruction stream.
*/
public void removeInstructions(List<Integer> toRemove) {
if (!toRemove.isEmpty()) {
LinkedList<Instruction> newInstructions = new LinkedList<>();
int nextIndex = 0;
for (Integer index : toRemove) {
assert index >= nextIndex; // Indexes in toRemove must be sorted ascending.
newInstructions.addAll(instructions.subList(nextIndex, index));
instructions.get(index).clearBlock();
nextIndex = index + 1;
}
if (nextIndex < instructions.size()) {
newInstructions.addAll(instructions.subList(nextIndex, instructions.size()));
}
assert instructions.size() == newInstructions.size() + toRemove.size();
setInstructions(newInstructions);
}
}
/**
* Remove an instruction.
*/
public void removeInstruction(Instruction toRemove) {
int index = instructions.indexOf(toRemove);
assert index >= 0;
removeInstructions(Collections.singletonList(index));
}
/**
* Create a new basic block with a single goto instruction.
*
* <p>The constructed basic block has no predecessors and has one successor which is the target
* block.
*
* @param blockNumber the block number of the goto block
* @param target the target of the goto block
*/
public static BasicBlock createGotoBlock(
int blockNumber, Position position, IRMetadata metadata, BasicBlock target) {
BasicBlock block = createGotoBlock(blockNumber, position, metadata);
block.getMutableSuccessors().add(target);
return block;
}
/**
* Create a new basic block with a single goto instruction.
*
* <p>The constructed basic block has no predecessors and no successors.
*
* @param blockNumber the block number of the goto block
*/
public static BasicBlock createGotoBlock(
int blockNumber, Position position, IRMetadata metadata) {
BasicBlock block = new BasicBlock();
block.add(new Goto(), metadata);
block.close(null);
block.setNumber(blockNumber);
block.entry().setPosition(position);
return block;
}
/**
* Create a new basic block with a single if instruction.
*
* <p>The constructed basic block has no predecessors and no successors.
*
* @param blockNumber the block number of the block
* @param theIf the if instruction
*/
public static BasicBlock createIfBlock(int blockNumber, If theIf, IRMetadata metadata) {
BasicBlock block = new BasicBlock();
block.add(theIf, metadata);
block.close(null);
block.setNumber(blockNumber);
return block;
}
/**
* Create a new basic block with an instruction followed by an if instruction.
*
* <p>The constructed basic block has no predecessors and no successors.
*
* @param blockNumber the block number of the block
* @param theIf the if instruction
* @param instructions the instructions to place before the if instruction
*/
public static BasicBlock createIfBlock(
int blockNumber, If theIf, IRMetadata metadata, Instruction... instructions) {
BasicBlock block = new BasicBlock();
for (Instruction instruction : instructions) {
block.add(instruction, metadata);
}
block.add(theIf, metadata);
block.close(null);
block.setNumber(blockNumber);
return block;
}
public static BasicBlock createSwitchBlock(
int blockNumber, IntSwitch theSwitch, IRMetadata metadata) {
BasicBlock block = new BasicBlock();
block.add(theSwitch, metadata);
block.close(null);
block.setNumber(blockNumber);
return block;
}
public static BasicBlock createRethrowBlock(
IRCode code, Position position, DexType guard, AppView<?> appView) {
TypeElement guardTypeLattice =
TypeElement.fromDexType(guard, Nullability.definitelyNotNull(), appView);
BasicBlock block = new BasicBlock();
MoveException moveException =
new MoveException(
new Value(code.valueNumberGenerator.next(), guardTypeLattice, null),
guard,
appView.options());
moveException.setPosition(position);
Throw throwInstruction = new Throw(moveException.outValue);
throwInstruction.setPosition(position);
block.add(moveException, code);
block.add(throwInstruction, code);
block.close(null);
block.setNumber(code.getHighestBlockNumber() + 1);
return block;
}
public boolean isTrivialGoto() {
return instructions.size() == 1 && exit().isGoto();
}
// Go backwards in the control flow graph until a block that is not a trivial goto block is found,
// or a block that does not have a unique predecessor is found. Returns null if the goto chain is
// cyclic.
public BasicBlock startOfGotoChain() {
// See Floyd's cycle-finding algorithm for reference.
BasicBlock hare = this;
BasicBlock tortuous = this;
boolean advance = false;
while (hare.isTrivialGoto() && hare.hasUniquePredecessor()) {
hare = hare.getUniquePredecessor();
tortuous = advance ? tortuous.getUniquePredecessor() : tortuous;
advance = !advance;
if (hare == tortuous) {
return null;
}
}
return hare;
}
// Find the final target from this goto block. Returns null if the goto chain is cyclic.
public BasicBlock endOfGotoChain() {
// See Floyd's cycle-finding algorithm for reference.
BasicBlock hare = this;
BasicBlock tortuous = this;
boolean advance = false;
while (hare.isTrivialGoto()) {
hare = hare.exit().asGoto().getTarget();
tortuous = advance ? tortuous.exit().asGoto().getTarget() : tortuous;
advance = !advance;
if (hare == tortuous) {
return null;
}
}
return hare;
}
public boolean isSimpleAlwaysThrowingPath() {
// See Floyd's cycle-finding algorithm for reference.
BasicBlock hare = this;
BasicBlock tortuous = this;
boolean advance = false;
while (true) {
List<BasicBlock> normalSuccessors = hare.getNormalSuccessors();
if (normalSuccessors.size() > 1) {
return false;
}
if (normalSuccessors.size() == 0) {
return hare.exit().isThrow();
}
hare = normalSuccessors.get(0);
tortuous = advance ? tortuous.getNormalSuccessors().get(0) : tortuous;
advance = !advance;
if (hare == tortuous) {
return false;
}
}
}
public Position getPosition() {
return entry().getPosition();
}
public boolean hasOneNormalExit() {
return successors.size() == 1 && exit().isGoto();
}
public CatchHandlers<BasicBlock> getCatchHandlers() {
if (!hasCatchHandlers()) {
return CatchHandlers.EMPTY_BASIC_BLOCK;
}
List<BasicBlock> targets = ListUtils.map(catchHandlers.getAllTargets(), successors::get);
return new CatchHandlers<>(catchHandlers.getGuards(), targets);
}
public CatchHandlers<Integer> getCatchHandlersWithSuccessorIndexes() {
return catchHandlers;
}
public void clearCatchHandlers() {
catchHandlers = CatchHandlers.EMPTY_INDICES;
}
public void transferCatchHandlers(BasicBlock other) {
catchHandlers = other.catchHandlers;
other.catchHandlers = CatchHandlers.EMPTY_INDICES;
}
public int numberOfCatchHandlers() {
return catchHandlers.size();
}
public int numberOfThrowingInstructions() {
int count = 0;
for (Instruction instruction : getInstructions()) {
if (instruction.instructionTypeCanThrow()) {
count++;
}
}
return count;
}
public boolean canThrow() {
for (Instruction instruction : instructions) {
if (instruction.instructionTypeCanThrow()) {
return true;
}
}
return false;
}
// A block can have at most one "on throw" value.
private boolean verifyOnThrowWrite(int register) {
if (register >= 0) {
return true;
}
for (Integer other : currentDefinitions.keySet()) {
assert other >= 0 || other == register;
}
return true;
}
// Verify that if this block has a throwing instruction none of the following instructions may
// introduce an SSA value. Such values can lead to invalid uses since the values are not actually
// visible to exceptional successors.
private boolean verifyNoValuesAfterThrowingInstruction() {
if (hasCatchHandlers()) {
InstructionIterator iterator = iterator(instructions.size());
while (iterator.hasPrevious()) {
Instruction instruction = iterator.previous();
if (instruction.instructionTypeCanThrow()) {
return true;
}
assert instruction.outValue() == null;
}
}
return true;
}
public InstructionIterator iterator() {
return new BasicBlockInstructionIterator(this);
}
public InstructionIterator iterator(int index) {
return new BasicBlockInstructionIterator(this, index);
}
public InstructionIterator iterator(Instruction instruction) {
return new BasicBlockInstructionIterator(this, instruction);
}
public InstructionListIterator listIterator(IRCode code) {
return listIterator(code.metadata());
}
public InstructionListIterator listIterator(IRMetadata metadata) {
return new BasicBlockInstructionListIterator(metadata, this);
}
public InstructionListIterator listIterator(IRCode code, int index) {
return new BasicBlockInstructionListIterator(code.metadata(), this, index);
}
/**
* Creates an instruction list iterator starting at <code>instruction</code>.
*
* <p>The cursor will be positioned after <code>instruction</code>. Calling <code>next</code> on
* the returned iterator will return the instruction after <code>instruction</code>. Calling
* <code>previous</code> will return <code>instruction</code>.
*/
public InstructionListIterator listIterator(IRCode code, Instruction instruction) {
return new BasicBlockInstructionListIterator(code.metadata(), this, instruction);
}
/**
* Creates a new empty block as a successor for this block.
*
* The new block will have all the normal successors of the original block.
*
* The catch successors are either on the original block or the new block depending on the
* value of <code>keepCatchHandlers</code>.
*
* The current block still has all the instructions, and the new block is empty instruction-wise.
*
* @param blockNumber block number for new block
* @param keepCatchHandlers keep catch successors on the original block
* @return the new block
*/
BasicBlock createSplitBlock(int blockNumber, boolean keepCatchHandlers) {
boolean hadCatchHandlers = hasCatchHandlers();
BasicBlock newBlock = new BasicBlock();
newBlock.setNumber(blockNumber);
// Copy all successors including catch handlers to the new block, and update predecessors.
newBlock.getMutableSuccessors().addAll(successors);
for (BasicBlock successor : newBlock.getSuccessors()) {
successor.replacePredecessor(this, newBlock);
}
getMutableSuccessors().clear();
newBlock.catchHandlers = catchHandlers;
catchHandlers = CatchHandlers.EMPTY_INDICES;
// If the catch handlers should be kept on the original block move them back.
if (keepCatchHandlers && hadCatchHandlers) {
moveCatchHandlers(newBlock);
}
// Link the two blocks
link(newBlock);
// Mark the new block filled and sealed.
newBlock.filled = true;
newBlock.sealed = true;
return newBlock;
}
/**
* Moves catch successors from `fromBlock` into this block.
*/
public void moveCatchHandlers(BasicBlock fromBlock) {
List<BasicBlock> catchSuccessors = appendCatchHandlers(fromBlock);
for (BasicBlock successor : catchSuccessors) {
fromBlock.getMutableSuccessors().remove(successor);
successor.removePredecessor(fromBlock, null);
}
fromBlock.catchHandlers = CatchHandlers.EMPTY_INDICES;
}
/**
* Clone catch successors from `fromBlock` into this block.
*/
public void copyCatchHandlers(
IRCode code,
ListIterator<BasicBlock> blockIterator,
BasicBlock fromBlock,
InternalOptions options) {
if (catchHandlers != null && catchHandlers.hasCatchAll(options.itemFactory)) {
return;
}
List<BasicBlock> catchSuccessors = appendCatchHandlers(fromBlock);
// After cloning is done all catch handler targets are referenced from both the
// original and the newly created catch handlers. Thus, since we keep both of
// them, we need to split appropriate edges to make sure every catch handler
// target block has only one predecessor.
//
// Note that for each catch handler block target block we actually create two new blocks:
// a copy of the original block and a new block to serve as a merging point for
// the original and its copy. This actually simplifies things since we only need
// one new phi to merge the two exception values, and all other phis don't need
// to be changed.
for (BasicBlock catchSuccessor : catchSuccessors) {
catchSuccessor.splitCriticalExceptionEdges(code, blockIterator::add, options);
}
}
/**
* Assumes that `this` block is a catch handler target (note that it does not have to start with
* MoveException instruction, since the instruction can be removed by optimizations like dead code
* remover.
*
* <p>Introduces new blocks on all incoming edges and clones MoveException instruction to these
* blocks if it exists. All exception values introduced in newly created blocks are combined in a
* phi added to `this` block.
*
* <p>Note that if there are any other phis defined on this block, they remain valid, since this
* method does not affect incoming edges in any way, and just adds new blocks with MoveException
* and Goto.
*/
public int splitCriticalExceptionEdges(
IRCode code, Consumer<BasicBlock> onNewBlock, InternalOptions options) {
int nextBlockNumber = code.getHighestBlockNumber() + 1;
List<BasicBlock> predecessors = getMutablePredecessors();
boolean hasMoveException = entry().isMoveException();
TypeElement exceptionTypeLattice = null;
DexType exceptionType = null;
MoveException move = null;
Position position = entry().getPosition();
if (hasMoveException) {
// Remove the move-exception instruction.
move = entry().asMoveException();
exceptionTypeLattice = move.getOutType();
exceptionType = move.getExceptionType();
assert move.getDebugValues().isEmpty();
getInstructions().remove(0);
}
// Create new predecessor blocks.
List<BasicBlock> newPredecessors = new ArrayList<>();
List<Value> values = new ArrayList<>(predecessors.size());
for (BasicBlock predecessor : predecessors) {
if (!predecessor.hasCatchSuccessor(this)) {
throw new CompilationError(
"Invalid block structure: catch block reachable via non-exceptional flow.");
}
BasicBlock newBlock = new BasicBlock();
newBlock.setNumber(nextBlockNumber++);
newPredecessors.add(newBlock);
if (hasMoveException) {
Value value =
new Value(code.valueNumberGenerator.next(), exceptionTypeLattice, move.getLocalInfo());
values.add(value);
MoveException newMove = new MoveException(value, exceptionType, options);
newBlock.add(newMove, code);
newMove.setPosition(position);
}
Goto next = new Goto();
next.setPosition(position);
newBlock.add(next, code);
newBlock.close(null);
newBlock.getMutableSuccessors().add(this);
newBlock.getMutablePredecessors().add(predecessor);
predecessor.replaceSuccessor(this, newBlock);
onNewBlock.accept(newBlock);
assert newBlock.getNumber() >= 0 : "Number must be assigned by `onNewBlock`";
}
// Replace the blocks predecessors with the new ones.
predecessors.clear();
predecessors.addAll(newPredecessors);
// Insert a phi for the move-exception value.
if (hasMoveException) {
Phi phi =
new Phi(
code.valueNumberGenerator.next(),
this,
exceptionTypeLattice,
move.getLocalInfo(),
RegisterReadType.NORMAL);
phi.addOperands(values);
move.outValue().replaceUsers(phi);
}
return nextBlockNumber;
}
/**
* Append catch handlers from another block <code>fromBlock</code> (which must have catch
* handlers) to the catch handlers of this block.
*
* Note that after appending catch handlers their targets are referenced by both
* <code>fromBlock</code> and <code>this</code> block, but no phis are inserted. For this reason
* this method should only be called from either {@link #moveCatchHandlers} or
* {@link #copyCatchHandlers} which know how to handle phis.
*
* @return the catch successors that are reused in both blocks after appending.
*/
private List<BasicBlock> appendCatchHandlers(BasicBlock fromBlock) {
assert fromBlock.hasCatchHandlers();
List<Integer> prevCatchTargets = fromBlock.catchHandlers.getAllTargets();
List<DexType> prevCatchGuards = fromBlock.catchHandlers.getGuards();
List<BasicBlock> catchSuccessors = new ArrayList<>();
List<DexType> newCatchGuards = new ArrayList<>();
List<Integer> newCatchTargets = new ArrayList<>();
// First add existing catch handlers to the catch handler list.
if (hasCatchHandlers()) {
newCatchGuards.addAll(catchHandlers.getGuards());
newCatchTargets.addAll(catchHandlers.getAllTargets());
for (int newCatchTarget : newCatchTargets) {
BasicBlock catchSuccessor = successors.get(newCatchTarget);
if (!catchSuccessors.contains(catchSuccessor)) {
catchSuccessors.add(catchSuccessor);
}
int index = catchSuccessors.indexOf(catchSuccessor);
assert index == newCatchTarget;
}
}
// This is the number of catch handlers which are already successors of this block.
int formerCatchHandlersCount = catchSuccessors.size();
// Then add catch handlers from the other block to the catch handler list.
for (int i = 0; i < prevCatchTargets.size(); i++) {
int prevCatchTarget = prevCatchTargets.get(i);
DexType prevCatchGuard = prevCatchGuards.get(i);
// TODO(sgjesse): Check sub-types of guards. Will require AppInfoWithSubtyping.
if (newCatchGuards.contains(prevCatchGuard)) {
continue;
}
BasicBlock catchSuccessor = fromBlock.successors.get(prevCatchTarget);
// We assume that all the catch handlers targets has only one predecessor and, thus, no phis.
assert catchSuccessor.getPredecessors().size() == 1;
assert catchSuccessor.getPhis().isEmpty();
int index = catchSuccessors.indexOf(catchSuccessor);
if (index == -1) {
catchSuccessors.add(catchSuccessor);
index = catchSuccessors.size() - 1;
}
newCatchGuards.add(prevCatchGuard);
newCatchTargets.add(index);
}
// Create the new successors list and link things up.
List<BasicBlock> successors = getMutableSuccessors();
List<BasicBlock> formerSuccessors = new ArrayList<>(successors);
successors.clear();
List<BasicBlock> sharedCatchSuccessors = new ArrayList<>();
for (int i = 0; i < catchSuccessors.size(); i++) {
if (i < formerCatchHandlersCount) {
// Former catch successors are just copied, as they are already linked.
assert catchSuccessors.get(i).getPredecessors().contains(this);
successors.add(catchSuccessors.get(i));
} else {
// New catch successors are linked properly.
assert !catchSuccessors.get(i).getPredecessors().contains(this);
link(catchSuccessors.get(i));
sharedCatchSuccessors.add(catchSuccessors.get(i));
}
}
catchHandlers = new CatchHandlers<>(newCatchGuards, newCatchTargets);
// Finally add the normal successor if any.
int catchSuccessorsCount = successors.size();
for (BasicBlock formerSuccessor : formerSuccessors) {
if (!successors.contains(formerSuccessor)) {
assert !exit().isThrow();
successors.add(formerSuccessor);
}
}
assert successors.size() == catchSuccessorsCount || !exit().isThrow();
return sharedCatchSuccessors;
}
/**
* Return true if there is a path from the current {@link BasicBlock} to {@code target} or if
* {@code target} is the same block than the current {@link BasicBlock}.
*/
public boolean hasPathTo(BasicBlock target) {
Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet();
ArrayDeque<BasicBlock> blocks = new ArrayDeque<>();
blocks.push(this);
while (!blocks.isEmpty()) {
BasicBlock block = blocks.pop();
if (block == target) {
return true;
}
visitedBlocks.add(block);
for (BasicBlock blockToVisit : block.getSuccessors()) {
if (!visitedBlocks.contains(blockToVisit)) {
blocks.push(blockToVisit);
}
}
}
return false;
}
private static class PhiEquivalence extends Equivalence<Phi> {
@Override
protected boolean doEquivalent(Phi a, Phi b) {
assert a.getBlock() == b.getBlock();
for (int i = 0; i < a.getOperands().size(); i++) {
if (a.getOperand(i) != b.getOperand(i)) {
return false;
}
}
return true;
}
@Override
protected int doHash(Phi phi) {
int hash = 0;
for (Value value : phi.getOperands()) {
hash = hash * 13 + value.hashCode();
}
return hash;
}
}
public void deduplicatePhis() {
PhiEquivalence equivalence = new PhiEquivalence();
HashMap<Wrapper<Phi>, Phi> wrapper2phi = new HashMap<>();
Iterator<Phi> phiIt = phis.iterator();
while (phiIt.hasNext()) {
Phi phi = phiIt.next();
Wrapper<Phi> key = equivalence.wrap(phi);
Phi replacement = wrapper2phi.get(key);
if (replacement != null) {
phi.replaceUsers(replacement);
for (Value operand : phi.getOperands()) {
operand.removePhiUser(phi);
}
phiIt.remove();
} else {
wrapper2phi.put(key, phi);
}
}
}
}