|  | // 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.ReturnVoid; | 
|  | 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.DexItemFactory; | 
|  | 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.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.Switch; | 
|  | 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.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, | 
|  | InternalOptions options) { | 
|  | assert ir != null; | 
|  | assert registerAllocator != null; | 
|  | this.ir = ir; | 
|  | this.registerAllocator = registerAllocator; | 
|  | this.options = options; | 
|  | } | 
|  |  | 
|  | 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(); | 
|  |  | 
|  | // Reset the state of the builder to start from scratch. | 
|  | reset(); | 
|  |  | 
|  | // Remove redundant debug position instructions. They would otherwise materialize as | 
|  | // unnecessary nops. | 
|  | removeRedundantDebugPositions(ir); | 
|  |  | 
|  | // 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; | 
|  | InstructionIterator it = ir.instructionIterator(); | 
|  | while (it.hasNext()) { | 
|  | Info info = getInfo(it.next()); | 
|  | 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; | 
|  | InstructionIterator instructionIterator = ir.instructionIterator(); | 
|  | while (instructionIterator.hasNext()) { | 
|  | com.android.tools.r8.ir.code.Instruction ir = instructionIterator.next(); | 
|  | Info info = getInfo(ir); | 
|  | int previousInstructionCount = dexInstructions.size(); | 
|  | info.addInstructions(this, dexInstructions); | 
|  | int instructionStartOffset = instructionOffset; | 
|  | if (previousInstructionCount < dexInstructions.size()) { | 
|  | while (previousInstructionCount < dexInstructions.size()) { | 
|  | Instruction instruction = dexInstructions.get(previousInstructionCount++); | 
|  | instruction.setOffset(instructionOffset); | 
|  | instructionOffset += instruction.getSize(); | 
|  | } | 
|  | } | 
|  | debugEventBuilder.add(instructionStartOffset, instructionOffset, ir); | 
|  | } | 
|  |  | 
|  | // 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. | 
|  | // 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) { | 
|  | // Generating a throw with the right type makes some Art constant propagation | 
|  | // implementations crash. Therefore, since this is dead code anyway, we do not | 
|  | // generate a return of the right type. Instead we just generate an unreachable | 
|  | // return-void. See b/121355317. | 
|  | Instruction returnVoid = new ReturnVoid(); | 
|  | returnVoid.setOffset(offset); | 
|  | offset += returnVoid.getSize(); | 
|  | dexInstructions.add(returnVoid); | 
|  | } | 
|  |  | 
|  | // 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(new Instruction[dexInstructions.size()]), | 
|  | 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.hasDebugPositions) { | 
|  | 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; | 
|  |  | 
|  | InstructionIterator iterator = currentBlock.iterator(); | 
|  | while (iterator.hasNext()) { | 
|  | com.android.tools.r8.ir.code.Instruction instruction = iterator.next(); | 
|  | 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 if (instruction.getPosition().isSome()) { | 
|  | 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(); | 
|  | } | 
|  | } else { | 
|  | // Only local-change instructions don't have positions in debug mode but fail gracefully. | 
|  | assert instruction.isDebugLocalsChange(); | 
|  | if (instruction.isDebugLocalsChange()) { | 
|  | instruction.asDebugLocalsChange().apply(locals); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | // Remove all unneeded positions. | 
|  | if (!toRemove.isEmpty()) { | 
|  | InstructionIterator it = code.instructionIterator(); | 
|  | 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.blocks.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(), 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(); | 
|  | assert !instr.isDexItemBasedConstString() | 
|  | || ir.method.getOptimizationInfo().useIdentifierNameString(); | 
|  | 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(Switch 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) { | 
|  | 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) { | 
|  | return instructionToInfo[instructionNumberToIndex(instruction.getNumber())]; | 
|  | } | 
|  |  | 
|  | private void setInfo(com.android.tools.r8.ir.code.Instruction instruction, Info info) { | 
|  | if (!(info instanceof FallThroughInfo)) { | 
|  | previousNonFallthroughInfo = info; | 
|  | } | 
|  | instructionToInfo[instructionNumberToIndex(instruction.getNumber())] = 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) { | 
|  | Switch 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 == DexItemFactory.catchAllType) { | 
|  | assert i == handlerGroup.getGuards().size() - 1; | 
|  | catchAllOffset = targetOffset; | 
|  | } else { | 
|  | pairs.add(new TypeAddrPair(type, targetOffset)); | 
|  | } | 
|  | } | 
|  | TypeAddrPair[] pairsArray = pairs.toArray(new TypeAddrPair[pairs.size()]); | 
|  | 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; | 
|  | } | 
|  | } | 
|  |  | 
|  | 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); | 
|  | } | 
|  | } | 
|  |  | 
|  | 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; | 
|  | } | 
|  | } | 
|  |  | 
|  | 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; | 
|  | } | 
|  | } | 
|  |  | 
|  | 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); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | public static class IfInfo extends Info { | 
|  |  | 
|  | private int size = -1; | 
|  |  | 
|  | public IfInfo(If branch) { | 
|  | super(branch); | 
|  | } | 
|  |  | 
|  | 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 = builder.allocatedRegister(branch.inValues().get(0), branch.getNumber()); | 
|  |  | 
|  | 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 = builder.allocatedRegister(branch.inValues().get(1), branch.getNumber()); | 
|  | 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; | 
|  | } | 
|  | } | 
|  |  | 
|  | 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 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().getTypeLattice(); | 
|  | int src = srcRegister(builder); | 
|  | int dest = destRegister(builder); | 
|  | Instruction instruction; | 
|  | switch (size) { | 
|  | case 1: | 
|  | if (src == dest) { | 
|  | instruction = new Nop(); | 
|  | break; | 
|  | } | 
|  | if (moveType.isSingle()) { | 
|  | instruction = new com.android.tools.r8.code.Move(dest, src); | 
|  | } else if (moveType.isWide()) { | 
|  | instruction = new MoveWide(dest, src); | 
|  | } else if (moveType.isReference()) { | 
|  | instruction = new MoveObject(dest, src); | 
|  | } else { | 
|  | throw new Unreachable("Unexpected type: " + move.outType()); | 
|  | } | 
|  | break; | 
|  | case 2: | 
|  | if (moveType.isSingle()) { | 
|  | instruction = new MoveFrom16(dest, src); | 
|  | } else if (moveType.isWide()) { | 
|  | instruction = new MoveWideFrom16(dest, src); | 
|  | } else if (moveType.isReference()) { | 
|  | instruction = new MoveObjectFrom16(dest, src); | 
|  | } else { | 
|  | throw new Unreachable("Unexpected type: " + move.outType()); | 
|  | } | 
|  | break; | 
|  | case 3: | 
|  | if (moveType.isSingle()) { | 
|  | instruction = new Move16(dest, src); | 
|  | } else if (moveType.isWide()) { | 
|  | instruction = new MoveWide16(dest, src); | 
|  | } else if (moveType.isReference()) { | 
|  | 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 Switch ir; | 
|  | public final Format31t dex; | 
|  |  | 
|  | public SwitchPayloadInfo(Switch 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; | 
|  | } | 
|  | } | 
|  | } |