blob: 631ed81aa472f18ae849c5ca78a6e3ebb1f5c805 [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.conversion;
import com.android.tools.r8.code.FillArrayData;
import com.android.tools.r8.code.FillArrayDataPayload;
import com.android.tools.r8.code.Format31t;
import com.android.tools.r8.code.Goto;
import com.android.tools.r8.code.Goto16;
import com.android.tools.r8.code.Goto32;
import com.android.tools.r8.code.IfEq;
import com.android.tools.r8.code.IfEqz;
import com.android.tools.r8.code.IfGe;
import com.android.tools.r8.code.IfGez;
import com.android.tools.r8.code.IfGt;
import com.android.tools.r8.code.IfGtz;
import com.android.tools.r8.code.IfLe;
import com.android.tools.r8.code.IfLez;
import com.android.tools.r8.code.IfLt;
import com.android.tools.r8.code.IfLtz;
import com.android.tools.r8.code.IfNe;
import com.android.tools.r8.code.IfNez;
import com.android.tools.r8.code.InstanceOf;
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.code.Move16;
import com.android.tools.r8.code.MoveFrom16;
import com.android.tools.r8.code.MoveObject;
import com.android.tools.r8.code.MoveObject16;
import com.android.tools.r8.code.MoveObjectFrom16;
import com.android.tools.r8.code.MoveWide;
import com.android.tools.r8.code.MoveWide16;
import com.android.tools.r8.code.MoveWideFrom16;
import com.android.tools.r8.code.Nop;
import com.android.tools.r8.code.Throw;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexCode.Try;
import com.android.tools.r8.graph.DexCode.TryHandler;
import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
import com.android.tools.r8.graph.DexDebugEventBuilder;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.Argument;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.CatchHandlers;
import com.android.tools.r8.ir.code.DebugPosition;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.IntSwitch;
import com.android.tools.r8.ir.code.JumpInstruction;
import com.android.tools.r8.ir.code.Move;
import com.android.tools.r8.ir.code.NewArrayFilledData;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Return;
import com.android.tools.r8.ir.code.StackValue;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.CodeRewriter;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOutputMode;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
/**
* Builder object for constructing dex bytecode from the high-level IR.
*/
public class DexBuilder {
// The IR representation of the code to build.
private final IRCode ir;
// The register allocator providing register assignments for the code to build.
private final RegisterAllocator registerAllocator;
private final InternalOptions options;
// List of information about switch payloads that have to be created at the end of the
// dex code.
private final List<SwitchPayloadInfo> switchPayloadInfos = new ArrayList<>();
// List of generated FillArrayData dex instructions.
private final List<FillArrayDataInfo> fillArrayDataInfos = new ArrayList<>();
// Set of if instructions that have offsets that are so large that they cannot be encoded in
// the if instruction format.
private final Set<BasicBlock> ifsNeedingRewrite = Sets.newIdentityHashSet();
// Running bounds on offsets.
private int maxOffset = 0;
private int minOffset = 0;
// Mapping from IR instructions to info for computing the dex translation. Use the
// getInfo/setInfo methods to access the mapping.
private Info[] instructionToInfo;
// Keeps track of the previous non-fallthrough info added to the dex builder.
private Info previousNonFallthroughInfo;
// The number of ingoing and outgoing argument registers for the code.
private int inRegisterCount = 0;
private int outRegisterCount = 0;
// Whether or not the generated code has a backwards branch.
private boolean hasBackwardsBranch = false;
BasicBlock nextBlock;
public DexBuilder(IRCode ir, RegisterAllocator registerAllocator) {
this(ir, registerAllocator, registerAllocator.options());
assert ir != null;
}
private DexBuilder(IRCode ir, RegisterAllocator registerAllocator, InternalOptions options) {
this.ir = ir;
this.registerAllocator = registerAllocator;
this.options = options;
if (isBuildingForComparison()) {
instructionToInfo = new Info[1];
}
}
public static boolean identicalInstructionsAfterBuildingDexCode(
com.android.tools.r8.ir.code.Instruction a,
com.android.tools.r8.ir.code.Instruction b,
RegisterAllocator allocator) {
DexBuilder builder = new DexBuilder(null, allocator, allocator.options());
Info infoA = buildInfoForComparison(a, builder);
Info infoB = buildInfoForComparison(b, builder);
return infoA.identicalInstructions(infoB, builder);
}
private static Info buildInfoForComparison(
com.android.tools.r8.ir.code.Instruction instruction, DexBuilder builder) {
instruction.buildDex(builder);
assert builder.instructionToInfo.length == 1;
return builder.instructionToInfo[0];
}
private boolean isBuildingForComparison() {
return ir == null;
}
private void reset() {
switchPayloadInfos.clear();
fillArrayDataInfos.clear();
ifsNeedingRewrite.clear();
maxOffset = 0;
minOffset = 0;
instructionToInfo = new Info[instructionNumberToIndex(ir.numberRemainingInstructions())];
inRegisterCount = 0;
outRegisterCount = 0;
nextBlock = null;
}
/**
* Build the dex instructions added to this builder.
*
* This is a two pass construction that will first compute concrete offsets and then construct
* the concrete instructions.
*/
public DexCode build() {
int numberOfInstructions;
int offset;
do {
// Rewrite ifs that are know from the previous iteration to have offsets that are too
// large for the if encoding.
rewriteIfs();
// Remove redundant debug position instructions. They would otherwise materialize as
// unnecessary nops.
removeRedundantDebugPositions(ir);
// Reset the state of the builder to start from scratch.
reset();
// Populate the builder info objects.
numberOfInstructions = 0;
ListIterator<BasicBlock> iterator = ir.listIterator();
assert iterator.hasNext();
BasicBlock block = iterator.next();
do {
nextBlock = iterator.hasNext() ? iterator.next() : null;
block.buildDex(this);
block = nextBlock;
} while (block != null);
// Compute offsets.
offset = 0;
for (com.android.tools.r8.ir.code.Instruction instruction : ir.instructions()) {
Info info = getInfo(instruction);
info.setOffset(offset);
offset += info.computeSize(this);
++numberOfInstructions;
}
} while (!ifsNeedingRewrite.isEmpty());
// Build instructions.
DexDebugEventBuilder debugEventBuilder = new DexDebugEventBuilder(ir, options);
List<Instruction> dexInstructions = new ArrayList<>(numberOfInstructions);
int instructionOffset = 0;
for (com.android.tools.r8.ir.code.Instruction irInstruction : ir.instructions()) {
Info info = getInfo(irInstruction);
int previousInstructionCount = dexInstructions.size();
info.addInstructions(this, dexInstructions);
int instructionStartOffset = instructionOffset;
while (previousInstructionCount < dexInstructions.size()) {
Instruction dexInstruction = dexInstructions.get(previousInstructionCount++);
dexInstruction.setOffset(instructionOffset);
instructionOffset += dexInstruction.getSize();
}
debugEventBuilder.add(instructionStartOffset, instructionOffset, irInstruction);
}
// Workaround dalvik tracing bug, where the dalvik tracing JIT can end up tracing
// past the end of the instruction stream if the instruction streams ends with a throw.
// See: b/117907456
// We could have also changed the block order, however, moving the throwing block higher
// led to larger code in all experiments (multiple gmscore version and R8 run on itself).
if (options.canHaveTracingPastInstructionsStreamBug()
&& dexInstructions.get(dexInstructions.size() - 1) instanceof Throw
&& hasBackwardsBranch) {
// This is the last in a series of different workarounds tried out.
// Having an empty non reachable loop make some mediatek vms crash: b/119895393
// Having a (unreachable) return null/return-void (type correct) trips up the constant
// propagation in some vms: b/121355317
// Having always a (unreachable) return-void causes Mediatek 4.4.2/4.4.4 to crash trying
// to get a dominator for the unreachable code: b/128926846
// Current implementation generates code that jumps over the throw, and then back to the throw
// again.
// throw v10
// becomes:
// goto +2
// throw v10
// goto -1
// That way we have no unreachable code, and we never end in a throw. The tracer will still
// trace to the throw, but after moving to the second goto it will trace back again and see
// an instruction it has already seen.
Instruction throwInstruction = dexInstructions.get(dexInstructions.size() -1);
offset = throwInstruction.getOffset();
// Generate the new forward and backward gotos, update offsets.
Instruction forward = new Goto(throwInstruction.getSize() + Goto.SIZE);
Instruction backward = new Goto(-throwInstruction.getSize());
forward.setOffset(offset);
offset += forward.getSize();
throwInstruction.setOffset(offset);
offset += throwInstruction.getSize();
backward.setOffset(offset);
offset += backward.getSize();
// Replace the throw in the instruction stream with goto(forward), throw, goto(backwards)
dexInstructions.remove(dexInstructions.size()-1);
dexInstructions.add(forward);
dexInstructions.add(throwInstruction);
dexInstructions.add(backward);
}
// Compute switch payloads.
for (SwitchPayloadInfo switchPayloadInfo : switchPayloadInfos) {
// Align payloads at even addresses.
if (offset % 2 != 0) {
Nop nop = new Nop();
nop.setOffset(offset++);
dexInstructions.add(nop);
}
// Create payload and add it to the instruction stream.
Nop payload = createSwitchPayload(switchPayloadInfo, offset);
payload.setOffset(offset);
offset += payload.getSize();
dexInstructions.add(payload);
}
// Compute fill array data payloads.
for (FillArrayDataInfo info : fillArrayDataInfos) {
// Align payloads at even addresses.
if (offset % 2 != 0) {
Nop nop = new Nop();
nop.setOffset(offset++);
dexInstructions.add(nop);
}
// Create payload and add it to the instruction stream.
FillArrayDataPayload payload = info.ir.createPayload();
payload.setOffset(offset);
info.dex.setPayloadOffset(offset - info.dex.getOffset());
offset += payload.getSize();
dexInstructions.add(payload);
}
// Construct try-catch info.
TryInfo tryInfo = computeTryInfo();
// Return the dex code.
DexCode code =
new DexCode(
registerAllocator.registersUsed(),
inRegisterCount,
outRegisterCount,
dexInstructions.toArray(Instruction.EMPTY_ARRAY),
tryInfo.tries,
tryInfo.handlers,
debugEventBuilder.build());
return code;
}
private static boolean isTrivialFallthroughTarget(
BasicBlock previousBlock, BasicBlock currentBlock) {
return previousBlock.exit().isGoto()
&& currentBlock.getPredecessors().size() == 1
&& currentBlock.getPredecessors().get(0) == previousBlock;
}
// Eliminates unneeded debug positions.
//
// After this pass all remaining debug positions mark places where we must ensure a materializing
// instruction, eg, for two successive lines without intermediate instructions.
public static void removeRedundantDebugPositions(IRCode code) {
if (!code.metadata().mayHaveDebugPosition()) {
return;
}
// Current position known to have a materializing instruction associated with it.
Position currentMaterializedPosition = Position.none();
// Current debug-position marker that is not yet known to have another instruction materializing
// to the same position.
DebugPosition unresolvedPosition = null;
// Locals live at the debug-position marker. These must also be the same at a possible
// materializing instruction with the same position for it to be sound to remove the marker.
Int2ReferenceMap<DebugLocalInfo> localsAtUnresolvedPosition = null;
// Compute the set of all positions that can be removed.
// (Delaying removal to avoid ConcurrentModificationException).
List<DebugPosition> toRemove = new ArrayList<>();
Set<BasicBlock> trivialBlocks = new HashSet<>();
for (int blockIndex = 0; blockIndex < code.blocks.size(); blockIndex++) {
BasicBlock currentBlock = code.blocks.get(blockIndex);
// Current materialized position must be updated to the position we can guarantee is emitted
// in all predecessors. The position of a fallthrough predecessor is defined by
// currentMaterializedPosition and unresolvedPosition (and not by the position of its exit!)
// If this is the entry block or a trivial fall-through with no other predecessors the
// materialized and unresolved positions remain unchanged.
if (blockIndex != 0) {
BasicBlock previousBlock = code.blocks.get(blockIndex - 1);
if (!isTrivialFallthroughTarget(previousBlock, currentBlock)) {
Position positionAtAllPredecessors = null;
for (BasicBlock pred : currentBlock.getPredecessors()) {
Position predExit;
if (pred == previousBlock) {
predExit =
unresolvedPosition != null
? unresolvedPosition.getPosition()
: currentMaterializedPosition;
} else {
predExit = pred.exit().getPosition();
}
if (positionAtAllPredecessors == null) {
positionAtAllPredecessors = predExit;
} else if (!positionAtAllPredecessors.equals(predExit)) {
positionAtAllPredecessors = Position.none();
break;
}
}
unresolvedPosition = null;
currentMaterializedPosition = positionAtAllPredecessors;
}
}
// Current locals.
Int2ReferenceMap<DebugLocalInfo> locals =
currentBlock.getLocalsAtEntry() != null
? new Int2ReferenceOpenHashMap<>(currentBlock.getLocalsAtEntry())
: new Int2ReferenceOpenHashMap<>();
// Next block to decide which gotos are fall-throughs.
BasicBlock nextBlock =
blockIndex + 1 < code.blocks.size() ? code.blocks.get(blockIndex + 1) : null;
for (com.android.tools.r8.ir.code.Instruction instruction : currentBlock.getInstructions()) {
if (instruction.isDebugPosition()) {
if (unresolvedPosition == null
&& currentMaterializedPosition == instruction.getPosition()) {
// Here we don't need to check locals state as the line is already active.
toRemove.add(instruction.asDebugPosition());
if (currentBlock.getInstructions().size() == 2) {
JumpInstruction exit = currentBlock.exit();
if (exit.isGoto() && exit.getPosition() == currentMaterializedPosition) {
trivialBlocks.add(currentBlock);
}
}
} else if (unresolvedPosition != null
&& unresolvedPosition.getPosition() == instruction.getPosition()
&& locals.equals(localsAtUnresolvedPosition)) {
// toRemove needs to be in instruction iteration order since the removal assumes that.
// Therefore, we have to remove unresolvedPosition here and record the current
// instruction as unresolved. Otherwise, if both of these instructions end up in
// toRemove they will be out of order.
toRemove.add(unresolvedPosition);
unresolvedPosition = instruction.asDebugPosition();
} else {
unresolvedPosition = instruction.asDebugPosition();
localsAtUnresolvedPosition = new Int2ReferenceOpenHashMap<>(locals);
}
} else {
assert instruction.getPosition().isSome();
if (instruction.isDebugLocalsChange()) {
instruction.asDebugLocalsChange().apply(locals);
} else if (!isNopInstruction(instruction, nextBlock)) {
if (unresolvedPosition != null) {
if (unresolvedPosition.getPosition() == instruction.getPosition()
&& locals.equals(localsAtUnresolvedPosition)) {
toRemove.add(unresolvedPosition);
}
unresolvedPosition = null;
localsAtUnresolvedPosition = null;
}
currentMaterializedPosition = instruction.getPosition();
}
}
}
}
// Remove all unneeded positions.
if (!toRemove.isEmpty()) {
InstructionListIterator it = code.instructionListIterator();
int i = 0;
while (it.hasNext() && i < toRemove.size()) {
if (it.next() == toRemove.get(i)) {
it.remove();
++i;
}
}
assert i == toRemove.size();
}
// Remove all trivial goto blocks that have a position known to be emitted in their predecessor.
if (!trivialBlocks.isEmpty()) {
List<BasicBlock> blocksToRemove = new ArrayList<>();
ListIterator<BasicBlock> iterator = code.listIterator();
// Skip the entry block.
assert code.blocks.size() > 1;
iterator.next();
BasicBlock nextBlock = iterator.next();
do {
BasicBlock block = nextBlock;
nextBlock = iterator.hasNext() ? iterator.next() : null;
if (block.isTrivialGoto()
&& trivialBlocks.contains(block)
&& block.exit().asGoto().getTarget() != block
&& !CodeRewriter.isFallthroughBlock(block)) {
BasicBlock target = block.exit().asGoto().getTarget();
blocksToRemove.add(block);
CodeRewriter.unlinkTrivialGotoBlock(block, target);
}
} while (iterator.hasNext());
code.removeBlocks(blocksToRemove);
}
}
// Rewrite ifs with offsets that are too large for the if encoding. The rewriting transforms:
//
//
// BB0: if condition goto BB_FAR_AWAY
// BB1: ...
//
// to:
//
// BB0: if !condition goto BB1
// BB2: goto BB_FAR_AWAY
// BB1: ...
private void rewriteIfs() {
if (ifsNeedingRewrite.isEmpty()) {
return;
}
ListIterator<BasicBlock> it = ir.listIterator();
while (it.hasNext()) {
BasicBlock block = it.next();
if (ifsNeedingRewrite.contains(block)) {
If theIf = block.exit().asIf();
BasicBlock trueTarget = theIf.getTrueTarget();
BasicBlock newBlock =
BasicBlock.createGotoBlock(
ir.blocks.size(), theIf.getPosition(), ir.metadata(), trueTarget);
theIf.setTrueTarget(newBlock);
theIf.invert();
it.add(newBlock);
}
}
}
private void needsIfRewriting(BasicBlock block) {
ifsNeedingRewrite.add(block);
}
public void requestOutgoingRegisters(int requiredRegisterCount) {
if (requiredRegisterCount > outRegisterCount) {
outRegisterCount = requiredRegisterCount;
}
}
public int allocatedRegister(Value value, int instructionNumber) {
return registerAllocator.getRegisterForValue(value, instructionNumber);
}
// Get the argument register for a value if it is an argument, otherwise returns the
// allocated register at the instruction number.
public int argumentOrAllocateRegister(Value value, int instructionNumber) {
return registerAllocator.getArgumentOrAllocateRegisterForValue(value, instructionNumber);
}
public void addGoto(com.android.tools.r8.ir.code.Goto jump) {
if (jump.getTarget() != nextBlock) {
add(jump, new GotoInfo(jump));
} else {
addNothing(jump);
}
}
private boolean needsNopBetweenMoveAndInstanceOf(InstanceOf instanceOf) {
if (!options.canHaveArtInstanceOfVerifierBug()) {
return false;
}
if (previousNonFallthroughInfo instanceof MoveInfo) {
MoveInfo moveInfo = (MoveInfo) previousNonFallthroughInfo;
int moveSrcRegister = moveInfo.srcRegister(this);
int moveDestRegister = moveInfo.destRegister(this);
// If the previous move materializes as a move and we have the pattern:
//
// move vA, vB
// instance-of vB, vA, Type
//
// we insert a nop between the move and the instance-of instruction to make sure
// that we do not trigger a verifier bug in Art. See b/120985556.
if (moveSrcRegister != moveDestRegister
&& moveSrcRegister == instanceOf.A
&& moveDestRegister == instanceOf.B) {
return true;
}
}
return false;
}
public void addInstanceOf(com.android.tools.r8.ir.code.InstanceOf ir, InstanceOf instanceOf) {
if (needsNopBetweenMoveAndInstanceOf(instanceOf)) {
add(ir, new Nop(), instanceOf);
} else {
add(ir, instanceOf);
}
}
public void addIf(If branch) {
assert nextBlock == branch.fallthroughBlock();
add(branch, new IfInfo(branch));
}
public void addMove(Move move) {
add(move, new MoveInfo(move));
}
public void addNothing(com.android.tools.r8.ir.code.Instruction instruction) {
add(instruction, new FallThroughInfo(instruction));
}
private static boolean isNopInstruction(
com.android.tools.r8.ir.code.Instruction instruction, BasicBlock nextBlock) {
return instruction.isArgument()
|| instruction.isDebugLocalsChange()
|| isNonMaterializingConstNumber(instruction)
|| (instruction.isGoto() && instruction.asGoto().getTarget() == nextBlock);
}
private static boolean isNonMaterializingConstNumber(
com.android.tools.r8.ir.code.Instruction instruction) {
return instruction.isConstNumber()
&& !(instruction.outValue() instanceof StackValue)
&& !(instruction.outValue().needsRegister());
}
public void addNop(com.android.tools.r8.ir.code.Instruction ir) {
add(ir, new FixedSizeInfo(ir, new Nop()));
}
public void addDebugPosition(DebugPosition position) {
// Remaining debug positions always require we emit an actual nop instruction.
// See removeRedundantDebugPositions.
addNop(position);
}
public void add(com.android.tools.r8.ir.code.Instruction instr, Instruction dex) {
assert !instr.isGoto();
add(instr, new FixedSizeInfo(instr, dex));
}
public void add(com.android.tools.r8.ir.code.Instruction ir, Instruction... dex) {
assert !ir.isGoto();
add(ir, new MultiFixedSizeInfo(ir, dex));
}
public void addSwitch(IntSwitch s, Format31t dex) {
assert nextBlock == s.fallthroughBlock();
switchPayloadInfos.add(new SwitchPayloadInfo(s, dex));
add(s, dex);
}
public void addFillArrayData(NewArrayFilledData nafd, FillArrayData dex) {
fillArrayDataInfos.add(new FillArrayDataInfo(nafd, dex));
add(nafd, dex);
}
public void addArgument(Argument argument) {
inRegisterCount += argument.outValue().requiredRegisters();
add(argument, new FallThroughInfo(argument));
}
public void addReturn(Return ret, Instruction dex) {
if (nextBlock != null
&& ret.identicalAfterRegisterAllocation(nextBlock.entry(), registerAllocator)) {
addNothing(ret);
} else {
add(ret, dex);
}
}
private void add(com.android.tools.r8.ir.code.Instruction ir, Info info) {
if (isBuildingForComparison()) {
// We are building for instruction comparison, so just set the info.
setSingleInfo(info);
return;
}
assert ir != null;
assert info != null;
assert getInfo(ir) == null;
info.setMinOffset(minOffset);
info.setMaxOffset(maxOffset);
minOffset += info.minSize();
maxOffset += info.maxSize();
setInfo(ir, info);
}
public static int instructionNumberToIndex(int instructionNumber) {
return instructionNumber / IRCode.INSTRUCTION_NUMBER_DELTA;
}
// Helper used by the info objects.
private Info getInfo(com.android.tools.r8.ir.code.Instruction instruction) {
assert instruction.getNumber() >= 0;
return instructionToInfo[instructionNumberToIndex(instruction.getNumber())];
}
private void setInfo(com.android.tools.r8.ir.code.Instruction instruction, Info info) {
assert instruction.getNumber() >= 0;
if (!(info instanceof FallThroughInfo)) {
previousNonFallthroughInfo = info;
}
instructionToInfo[instructionNumberToIndex(instruction.getNumber())] = info;
}
private void setSingleInfo(Info info) {
assert instructionToInfo.length == 1;
instructionToInfo[0] = info;
}
private Info getTargetInfo(BasicBlock block) {
InstructionIterator iterator = block.iterator();
com.android.tools.r8.ir.code.Instruction instruction = null;
while (iterator.hasNext()) {
instruction = iterator.next();
Info info = getInfo(instruction);
if (!(info instanceof FallThroughInfo)) {
return info;
}
}
assert instruction != null;
if (instruction.isReturn()) {
assert getInfo(instruction) instanceof FallThroughInfo;
return getTargetInfo(computeNextBlock(block));
}
assert instruction.isGoto();
return getTargetInfo(instruction.asGoto().getTarget());
}
private BasicBlock computeNextBlock(BasicBlock block) {
ListIterator<BasicBlock> it = ir.listIterator();
BasicBlock current = it.next();
while (current != block) {
current = it.next();
}
return it.next();
}
// Helper for computing switch payloads.
private Nop createSwitchPayload(SwitchPayloadInfo info, int offset) {
IntSwitch ir = info.ir;
// Patch the payload offset in the generated switch instruction now
// that the location is known.
info.dex.setPayloadOffset(offset - getInfo(ir).getOffset());
// Compute target offset for each of the keys based on the offset of the
// first instruction in the block that the switch goes to for that key.
int[] targetBlockIndices = ir.targetBlockIndices();
int[] targets = new int[targetBlockIndices.length];
for (int i = 0; i < targetBlockIndices.length; i++) {
BasicBlock targetBlock = ir.targetBlock(i);
com.android.tools.r8.ir.code.Instruction targetInstruction = targetBlock.entry();
targets[i] = getInfo(targetInstruction).getOffset() - getInfo(ir).getOffset();
}
BasicBlock fallthroughBlock = ir.fallthroughBlock();
com.android.tools.r8.ir.code.Instruction fallthroughTargetInstruction =
fallthroughBlock.entry();
int fallthroughTarget =
getInfo(fallthroughTargetInstruction).getOffset() - getInfo(ir).getOffset();
return ir.buildPayload(targets, fallthroughTarget, InternalOutputMode.DexIndexed);
}
// Helpers for computing the try items and handlers.
private TryInfo computeTryInfo() {
// Canonical map of handlers.
BiMap<CatchHandlers<BasicBlock>, Integer> canonicalHandlers = HashBiMap.create();
// Compute the list of try items and their handlers.
List<TryItem> tryItems = computeTryItems(canonicalHandlers);
// Compute handler sets before dex items which depend on the handler index.
Try[] tries = getDexTryItems(tryItems, canonicalHandlers);
TryHandler[] handlers = getDexTryHandlers(canonicalHandlers.inverse());
return new TryInfo(tries, handlers);
}
private List<TryItem> computeTryItems(
BiMap<CatchHandlers<BasicBlock>, Integer> handlerToIndex) {
BiMap<Integer, CatchHandlers<BasicBlock>> indexToHandler = handlerToIndex.inverse();
List<TryItem> tryItems = new ArrayList<>();
List<BasicBlock> blocksWithHandlers = new ArrayList<>();
TryItem currentTryItem = null;
// Create try items with maximal ranges to get as much coalescing as possible. After coalescing
// the try ranges are trimmed.
for (BasicBlock block : ir.blocks) {
CatchHandlers<BasicBlock> handlers = block.getCatchHandlers();
// If this assert is hit, then the block contains no instruction that can throw. This is most
// likely due to dead-code elimination or other optimizations that might now work on a refined
// notion of what can throw. If so, the trivial blocks should either be removed or their catch
// handlers deleted to reflect the simpler graph prior to building the dex code.
assert handlers.isEmpty() || block.canThrow();
if (!handlers.isEmpty()) {
if (handlerToIndex.containsKey(handlers)) {
handlers = indexToHandler.get(handlerToIndex.get(handlers));
} else {
handlerToIndex.put(handlers, handlerToIndex.size());
}
Info startInfo = getInfo(block.entry());
Info endInfo = getInfo(block.exit());
int start = startInfo.getOffset();
int end = endInfo.getOffset() + endInfo.getSize();
currentTryItem = new TryItem(handlers, start, end);
tryItems.add(currentTryItem);
blocksWithHandlers.add(block);
} else if (currentTryItem != null && !block.canThrow()) {
Info endInfo = getInfo(block.exit());
// If the block only contains a goto there might not be an info for the exit instruction.
if (endInfo != null) {
currentTryItem.end = endInfo.getOffset() + endInfo.getSize();
}
} else {
currentTryItem = null;
}
}
// If there are no try items it is trivially coalesced.
if (tryItems.isEmpty()) {
return tryItems;
}
// Coalesce try blocks.
tryItems.sort(TryItem::compareTo);
List<TryItem> coalescedTryItems = new ArrayList<>(tryItems.size());
TryItem item = null;
for (int i = 0; i < tryItems.size(); ) {
if (item != null) {
item.end = trimEnd(blocksWithHandlers.get(i - 1));
}
item = tryItems.get(i);
coalescedTryItems.add(item);
// Trim the range start for non-throwing instructions when starting a new range.
List<com.android.tools.r8.ir.code.Instruction> instructions = blocksWithHandlers.get(i)
.getInstructions();
for (com.android.tools.r8.ir.code.Instruction insn : instructions) {
if (insn.instructionTypeCanThrow()) {
item.start = getInfo(insn).getOffset();
break;
}
}
// Append all consecutive ranges that define the same handlers.
++i;
while (i < tryItems.size()) {
TryItem next = tryItems.get(i);
if (item.end != next.start || !item.handlers.equals(next.handlers)) {
break;
}
item.end = next.end;
++i;
}
}
// Trim the last try range.
int lastIndex = tryItems.size() - 1;
item.end = trimEnd(blocksWithHandlers.get(lastIndex));
return coalescedTryItems;
}
private int trimEnd(BasicBlock block) {
// Trim the range end for non-throwing instructions when end has been computed.
List<com.android.tools.r8.ir.code.Instruction> instructions = block.getInstructions();
for (com.android.tools.r8.ir.code.Instruction insn : Lists.reverse(instructions)) {
if (insn.instructionTypeCanThrow()) {
Info info = getInfo(insn);
return info.getOffset() + info.getSize();
}
}
throw new Unreachable("Expected to find a possibly throwing instruction");
}
private static Try[] getDexTryItems(List<TryItem> tryItems,
Map<CatchHandlers<BasicBlock>, Integer> catchHandlers) {
Try[] tries = new Try[tryItems.size()];
for (int i = 0; i < tries.length; ++i) {
TryItem item = tryItems.get(i);
Try dexTry = new Try(item.start, item.end - item.start, -1);
dexTry.handlerIndex = catchHandlers.get(item.handlers);
tries[i] = dexTry;
}
return tries;
}
private TryHandler[] getDexTryHandlers(Map<Integer, CatchHandlers<BasicBlock>> catchHandlers) {
TryHandler[] handlers = new TryHandler[catchHandlers.size()];
for (int j = 0; j < catchHandlers.size(); j++) {
CatchHandlers<BasicBlock> handlerGroup = catchHandlers.get(j);
int catchAllOffset = TryHandler.NO_HANDLER;
List<TypeAddrPair> pairs = new ArrayList<>();
for (int i = 0; i < handlerGroup.getGuards().size(); i++) {
DexType type = handlerGroup.getGuards().get(i);
BasicBlock target = handlerGroup.getAllTargets().get(i);
int targetOffset = getInfo(target.entry()).getOffset();
if (type == options.itemFactory.throwableType) {
assert i == handlerGroup.getGuards().size() - 1;
catchAllOffset = targetOffset;
} else {
pairs.add(new TypeAddrPair(type, targetOffset));
}
}
TypeAddrPair[] pairsArray = pairs.toArray(new TypeAddrPair[]{});
handlers[j] = new TryHandler(pairsArray, catchAllOffset);
}
return handlers;
}
public InternalOptions getOptions() {
return options;
}
public RegisterAllocator getRegisterAllocator() {
return registerAllocator;
}
// Dex instruction wrapper with information to compute instruction sizes and offsets for jumps.
private static abstract class Info {
private final com.android.tools.r8.ir.code.Instruction ir;
// Concrete final offset of the instruction.
private int offset = -1;
// Lower and upper bound of the final offset.
private int minOffset = -1;
private int maxOffset = -1;
public Info(com.android.tools.r8.ir.code.Instruction ir) {
assert ir != null;
this.ir = ir;
}
// Computes the final size of the instruction.
// All instruction offsets up-to and including this instruction will be defined at this point.
public abstract int computeSize(DexBuilder builder);
// Materialize the actual construction.
// All instruction offsets are known at this point.
public abstract void addInstructions(DexBuilder builder, List<Instruction> instructions);
// Lower bound on the size of the instruction.
public abstract int minSize();
// Upper bound on the size of the instruction.
public abstract int maxSize();
public abstract int getSize();
public int getOffset() {
assert offset >= 0 : this;
return offset;
}
public void setOffset(int offset) {
assert offset >= 0;
this.offset = offset;
}
public int getMinOffset() {
assert minOffset >= 0;
return minOffset;
}
public void setMinOffset(int minOffset) {
assert minOffset >= 0;
this.minOffset = minOffset;
}
public int getMaxOffset() {
assert maxOffset >= 0;
return maxOffset;
}
public void setMaxOffset(int maxOffset) {
assert maxOffset >= 0;
this.maxOffset = maxOffset;
}
public com.android.tools.r8.ir.code.Instruction getIR() {
return ir;
}
public abstract boolean identicalInstructions(Info other, DexBuilder builder);
}
private static class FixedSizeInfo extends Info {
private final Instruction instruction;
public FixedSizeInfo(com.android.tools.r8.ir.code.Instruction ir, Instruction instruction) {
super(ir);
this.instruction = instruction;
}
@Override
public int getSize() {
return instruction.getSize();
}
@Override
public int minSize() {
return instruction.getSize();
}
@Override
public int maxSize() {
return instruction.getSize();
}
@Override
public int computeSize(DexBuilder builder) {
instruction.setOffset(getOffset()); // for better printing of the dex code.
return instruction.getSize();
}
@Override
public void addInstructions(DexBuilder builder, List<Instruction> instructions) {
instructions.add(instruction);
}
@Override
public boolean identicalInstructions(Info other, DexBuilder builder) {
return other instanceof FixedSizeInfo
&& instruction.equals(((FixedSizeInfo) other).instruction);
}
}
private static class MultiFixedSizeInfo extends Info {
private final Instruction[] instructions;
private final int size;
public MultiFixedSizeInfo(com.android.tools.r8.ir.code.Instruction ir,
Instruction[] instructions) {
super(ir);
this.instructions = instructions;
int size = 0;
for (Instruction instruction : instructions) {
size += instruction.getSize();
}
this.size = size;
}
@Override
public int computeSize(DexBuilder builder) {
return size;
}
@Override
public void addInstructions(DexBuilder builder, List<Instruction> instructions) {
int offset = getOffset();
for (Instruction instruction : this.instructions) {
instructions.add(instruction);
instruction.setOffset(offset);
offset += instruction.getSize();
}
}
@Override
public int minSize() {
return size;
}
@Override
public int maxSize() {
return size;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean identicalInstructions(Info other, DexBuilder builder) {
return other instanceof MultiFixedSizeInfo
&& Arrays.equals(instructions, ((MultiFixedSizeInfo) other).instructions);
}
}
private static class FallThroughInfo extends Info {
public FallThroughInfo(com.android.tools.r8.ir.code.Instruction ir) {
super(ir);
}
@Override
public int getSize() {
return 0;
}
@Override
public int computeSize(DexBuilder builder) {
return 0;
}
@Override
public void addInstructions(DexBuilder builder, List<Instruction> instructions) {
}
@Override
public int minSize() {
return 0;
}
@Override
public int maxSize() {
return 0;
}
@Override
public boolean identicalInstructions(Info other, DexBuilder builder) {
return other instanceof FallThroughInfo;
}
}
private static class GotoInfo extends Info {
private int size = -1;
public GotoInfo(com.android.tools.r8.ir.code.Goto jump) {
super(jump);
}
private com.android.tools.r8.ir.code.Goto getJump() {
return (com.android.tools.r8.ir.code.Goto) getIR();
}
@Override
public int getSize() {
assert size > 0;
return size;
}
@Override
public int minSize() {
assert new Goto(42).getSize() == 1;
return 1;
}
@Override
public int maxSize() {
assert new Goto32(0).getSize() == 3;
return 3;
}
@Override
public int computeSize(DexBuilder builder) {
assert size < 0;
com.android.tools.r8.ir.code.Goto jump = getJump();
Info targetInfo = builder.getTargetInfo(jump.getTarget());
// Trivial loop will be emitted as: nop & goto -1
if (jump == targetInfo.getIR()) {
size = 2;
return size;
}
int maxOffset = getMaxOffset();
int maxTargetOffset = targetInfo.getMaxOffset();
int delta;
if (maxTargetOffset < maxOffset) {
// Backward branch: compute exact size (the target offset is set).
delta = getOffset() - targetInfo.getOffset();
} else {
// Forward branch: over estimate the distance, but take into account the sizes
// of instructions generated so far. That way the over estimation is only for the
// instructions between this one and the target.
int maxOverEstimation = maxOffset - getOffset();
delta = (maxTargetOffset - maxOverEstimation) - getOffset();
}
if (delta <= Byte.MAX_VALUE) {
size = 1;
} else if (delta <= Short.MAX_VALUE) {
size = 2;
} else {
size = 3;
}
if (targetInfo.getIR().isReturn() && targetInfo.getIR().getPosition().isNone()) {
// Set the size to the min of the size of the return and the size of the goto. When
// adding instructions, we use the return if the computed size matches the size of the
// return.
assert !(targetInfo instanceof FallThroughInfo);
size = Math.min(targetInfo.getSize(), size);
}
assert size != 0;
return size;
}
@Override
public void addInstructions(DexBuilder builder, List<Instruction> instructions) {
com.android.tools.r8.ir.code.Goto jump = getJump();
int source = builder.getInfo(jump).getOffset();
Info targetInfo = builder.getTargetInfo(jump.getTarget());
int relativeOffset = targetInfo.getOffset() - source;
if (relativeOffset < 0) {
builder.hasBackwardsBranch = true;
}
// Emit a return if the target is a return and the size of the return is the computed
// size of this instruction.
Return ret = targetInfo.getIR().asReturn();
if (ret != null && size == targetInfo.getSize() && ret.getPosition().isNone()) {
Instruction dex = ret.createDexInstruction(builder);
dex.setOffset(getOffset()); // for better printing of the dex code.
instructions.add(dex);
} else if (size == relativeOffset) {
// We should never generate a goto targeting the next instruction. However, if we do
// we replace it with nops. This works around a dalvik bug where the dalvik tracing
// jit crashes on 'goto next instruction' on Android 4.1.1.
// TODO(b/34726595): We currently do hit this case and we should see if we can avoid that.
for (int i = 0; i < size; i++) {
Instruction dex = new Nop();
assert dex.getSize() == 1;
dex.setOffset(getOffset() + i); // for better printing of the dex code.
instructions.add(dex);
}
} else {
Instruction dex;
switch (size) {
case 1:
assert relativeOffset != 0;
dex = new Goto(relativeOffset);
break;
case 2:
if (relativeOffset == 0) {
Nop nop = new Nop();
instructions.add(nop);
dex = new Goto(-nop.getSize());
} else {
dex = new Goto16(relativeOffset);
}
break;
case 3:
dex = new Goto32(relativeOffset);
break;
default:
throw new Unreachable("Unexpected size for goto instruction: " + size);
}
dex.setOffset(getOffset()); // for better printing of the dex code.
instructions.add(dex);
}
}
@Override
public boolean identicalInstructions(Info other, DexBuilder builder) {
return other instanceof GotoInfo;
}
}
public static class IfInfo extends Info {
private int size = -1;
public IfInfo(If branch) {
super(branch);
}
private int getRegister(int operandIndex, DexBuilder builder) {
If branch = getBranch();
return builder.allocatedRegister(branch.inValues().get(operandIndex), branch.getNumber());
}
private int[] getRegisters(DexBuilder builder) {
if (getBranch().isZeroTest()) {
return new int[] {getRegister(0, builder)};
}
return new int[] {getRegister(0, builder), getRegister(1, builder)};
}
private If getBranch() {
return (If) getIR();
}
private boolean branchesToSelf(DexBuilder builder) {
If branch = getBranch();
Info trueTargetInfo = builder.getTargetInfo(branch.getTrueTarget());
return branch == trueTargetInfo.getIR();
}
private boolean offsetOutOfRange(DexBuilder builder) {
Info targetInfo = builder.getTargetInfo(getBranch().getTrueTarget());
int maxOffset = getMaxOffset();
int maxTargetOffset = targetInfo.getMaxOffset();
if (maxTargetOffset < maxOffset) {
return getOffset() - targetInfo.getOffset() < Short.MIN_VALUE;
}
// Forward branch: over estimate the distance, but take into account the sizes
// of instructions generated so far. That way the over estimation is only for the
// instructions between this one and the target.
int maxOverEstimation = maxOffset - getOffset();
return (maxTargetOffset - maxOverEstimation) - getOffset() > Short.MAX_VALUE;
}
@Override
public void addInstructions(DexBuilder builder, List<Instruction> instructions) {
If branch = getBranch();
int source = builder.getInfo(branch).getOffset();
int target = builder.getInfo(branch.getTrueTarget().entry()).getOffset();
int relativeOffset = target - source;
int register1 = getRegister(0, builder);
if (relativeOffset < 0) {
builder.hasBackwardsBranch = true;
}
if (size == 3) {
assert branchesToSelf(builder);
Nop nop = new Nop();
relativeOffset -= nop.getSize();
instructions.add(nop);
}
assert relativeOffset != 0;
Instruction instruction = null;
if (branch.isZeroTest()) {
switch (getBranch().getType()) {
case EQ:
instruction = new IfEqz(register1, relativeOffset);
break;
case GE:
instruction = new IfGez(register1, relativeOffset);
break;
case GT:
instruction = new IfGtz(register1, relativeOffset);
break;
case LE:
instruction = new IfLez(register1, relativeOffset);
break;
case LT:
instruction = new IfLtz(register1, relativeOffset);
break;
case NE:
instruction = new IfNez(register1, relativeOffset);
break;
}
} else {
int register2 = getRegister(1, builder);
switch (getBranch().getType()) {
case EQ:
instruction = new IfEq(register1, register2, relativeOffset);
break;
case GE:
instruction = new IfGe(register1, register2, relativeOffset);
break;
case GT:
instruction = new IfGt(register1, register2, relativeOffset);
break;
case LE:
instruction = new IfLe(register1, register2, relativeOffset);
break;
case LT:
instruction = new IfLt(register1, register2, relativeOffset);
break;
case NE:
instruction = new IfNe(register1, register2, relativeOffset);
break;
}
}
instruction.setOffset(getOffset());
instructions.add(instruction);
}
@Override
public int computeSize(DexBuilder builder) {
if (offsetOutOfRange(builder)) {
builder.needsIfRewriting(getBranch().getBlock());
}
size = branchesToSelf(builder) ? 3 : 2;
return size;
}
@Override
public int minSize() {
return 2;
}
@Override
public int maxSize() {
return 3;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean identicalInstructions(Info other, DexBuilder builder) {
if (!(other instanceof IfInfo)) {
return false;
}
IfInfo otherInfo = (IfInfo) other;
return getBranch().getType() == otherInfo.getBranch().getType()
&& Arrays.equals(getRegisters(builder), otherInfo.getRegisters(builder));
}
}
public static class MoveInfo extends Info {
private int size = -1;
public MoveInfo(Move move) {
super(move);
}
private Move getMove() {
return (Move) getIR();
}
public int srcRegister(DexBuilder builder) {
return builder.argumentOrAllocateRegister(getMove().src(), getMove().getNumber());
}
public int destRegister(DexBuilder builder) {
return builder.allocatedRegister(getMove().dest(), getMove().getNumber());
}
@Override
public boolean identicalInstructions(Info other, DexBuilder builder) {
if (!(other instanceof MoveInfo)) {
return false;
}
MoveInfo moveInfo = (MoveInfo) other;
return srcRegister(builder) == moveInfo.srcRegister(builder)
&& destRegister(builder) == moveInfo.destRegister(builder);
}
@Override
public int computeSize(DexBuilder builder) {
int srcRegister = srcRegister(builder);
int destRegister = destRegister(builder);
if (srcRegister == destRegister) {
size = 1;
} else if (srcRegister <= Constants.U4BIT_MAX && destRegister <= Constants.U4BIT_MAX) {
size = 1;
} else if (destRegister <= Constants.U8BIT_MAX) {
size = 2;
} else {
size = 3;
}
return size;
}
@Override
public void addInstructions(DexBuilder builder, List<Instruction> instructions) {
Move move = getMove();
TypeLatticeElement moveType = move.outValue().getType();
int src = srcRegister(builder);
int dest = destRegister(builder);
Instruction instruction;
switch (size) {
case 1:
if (src == dest) {
instruction = new Nop();
break;
}
if (moveType.isSinglePrimitive()) {
instruction = new com.android.tools.r8.code.Move(dest, src);
} else if (moveType.isWidePrimitive()) {
instruction = new MoveWide(dest, src);
} else if (moveType.isReferenceType()) {
instruction = new MoveObject(dest, src);
} else {
throw new Unreachable("Unexpected type: " + move.outType());
}
break;
case 2:
if (moveType.isSinglePrimitive()) {
instruction = new MoveFrom16(dest, src);
} else if (moveType.isWidePrimitive()) {
instruction = new MoveWideFrom16(dest, src);
} else if (moveType.isReferenceType()) {
instruction = new MoveObjectFrom16(dest, src);
} else {
throw new Unreachable("Unexpected type: " + move.outType());
}
break;
case 3:
if (moveType.isSinglePrimitive()) {
instruction = new Move16(dest, src);
} else if (moveType.isWidePrimitive()) {
instruction = new MoveWide16(dest, src);
} else if (moveType.isReferenceType()) {
instruction = new MoveObject16(dest, src);
} else {
throw new Unreachable("Unexpected type: " + move.outType());
}
break;
default:
throw new Unreachable("Unexpected size: " + size);
}
instruction.setOffset(getOffset());
instructions.add(instruction);
}
@Override
public int minSize() {
assert new Nop().getSize() == 1 && new com.android.tools.r8.code.Move(0, 0).getSize() == 1;
return 1;
}
@Override
public int maxSize() {
assert new Move16(0, 0).getSize() == 3;
return 3;
}
@Override
public int getSize() {
assert size > 0;
return size;
}
}
// Return-type wrapper for try-related data.
private static class TryInfo {
public final Try[] tries;
public final TryHandler[] handlers;
public TryInfo(Try[] tries, TryHandler[] handlers) {
this.tries = tries;
this.handlers = handlers;
}
}
// Helper class for coalescing ranges for try blocks.
private static class TryItem implements Comparable<TryItem> {
public final CatchHandlers<BasicBlock> handlers;
public int start;
public int end;
public TryItem(CatchHandlers<BasicBlock> handlers, int start, int end) {
this.handlers = handlers;
this.start = start;
this.end = end;
}
@Override
public int compareTo(TryItem other) {
return Integer.compare(start, other.start);
}
}
private static class SwitchPayloadInfo {
public final IntSwitch ir;
public final Format31t dex;
public SwitchPayloadInfo(IntSwitch ir, Format31t dex) {
this.ir = ir;
this.dex = dex;
}
}
private static class FillArrayDataInfo {
public final NewArrayFilledData ir;
public final FillArrayData dex;
public FillArrayDataInfo(NewArrayFilledData ir, FillArrayData dex) {
this.ir = ir;
this.dex = dex;
}
}
}