Home | History | Annotate | Download | only in conversion
      1 // Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
      2 // for details. All rights reserved. Use of this source code is governed by a
      3 // BSD-style license that can be found in the LICENSE file.
      4 
      5 package com.android.tools.r8.ir.conversion;
      6 
      7 import com.android.tools.r8.code.FillArrayData;
      8 import com.android.tools.r8.code.FillArrayDataPayload;
      9 import com.android.tools.r8.code.FilledNewArray;
     10 import com.android.tools.r8.code.FilledNewArrayRange;
     11 import com.android.tools.r8.code.Instruction;
     12 import com.android.tools.r8.code.InvokeDirect;
     13 import com.android.tools.r8.code.InvokeDirectRange;
     14 import com.android.tools.r8.code.InvokeInterface;
     15 import com.android.tools.r8.code.InvokeInterfaceRange;
     16 import com.android.tools.r8.code.InvokePolymorphic;
     17 import com.android.tools.r8.code.InvokePolymorphicRange;
     18 import com.android.tools.r8.code.InvokeStatic;
     19 import com.android.tools.r8.code.InvokeStaticRange;
     20 import com.android.tools.r8.code.InvokeSuper;
     21 import com.android.tools.r8.code.InvokeSuperRange;
     22 import com.android.tools.r8.code.InvokeVirtual;
     23 import com.android.tools.r8.code.InvokeVirtualRange;
     24 import com.android.tools.r8.code.MoveResult;
     25 import com.android.tools.r8.code.MoveResultObject;
     26 import com.android.tools.r8.code.MoveResultWide;
     27 import com.android.tools.r8.code.SwitchPayload;
     28 import com.android.tools.r8.code.Throw;
     29 import com.android.tools.r8.graph.DebugLocalInfo;
     30 import com.android.tools.r8.graph.DexAccessFlags;
     31 import com.android.tools.r8.graph.DexCode;
     32 import com.android.tools.r8.graph.DexCode.Try;
     33 import com.android.tools.r8.graph.DexCode.TryHandler;
     34 import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
     35 import com.android.tools.r8.graph.DexDebugEntry;
     36 import com.android.tools.r8.graph.DexDebugInfo;
     37 import com.android.tools.r8.graph.DexEncodedMethod;
     38 import com.android.tools.r8.graph.DexItemFactory;
     39 import com.android.tools.r8.graph.DexProto;
     40 import com.android.tools.r8.graph.DexType;
     41 import com.android.tools.r8.ir.code.CatchHandlers;
     42 import com.android.tools.r8.ir.code.MoveType;
     43 import java.util.ArrayList;
     44 import java.util.HashMap;
     45 import java.util.HashSet;
     46 import java.util.List;
     47 import java.util.Map;
     48 import java.util.Set;
     49 
     50 public class DexSourceCode implements SourceCode {
     51 
     52   private final DexCode code;
     53   private final DexAccessFlags accessFlags;
     54   private final DexProto proto;
     55 
     56   // Mapping from instruction offset to instruction index in the DexCode instruction array.
     57   private final Map<Integer, Integer> offsetToInstructionIndex = new HashMap<>();
     58 
     59   private final SwitchPayloadResolver switchPayloadResolver = new SwitchPayloadResolver();
     60   private final ArrayFilledDataPayloadResolver arrayFilledDataPayloadResolver =
     61       new ArrayFilledDataPayloadResolver();
     62 
     63   private Try currentTryRange = null;
     64   private CatchHandlers<Integer> currentCatchHandlers = null;
     65   private Instruction currentDexInstruction = null;
     66 
     67   private final List<MoveType> argumentTypes;
     68 
     69   private List<DexDebugEntry> debugEntries = null;
     70 
     71   public DexSourceCode(DexCode code, DexEncodedMethod method) {
     72     this.code = code;
     73     this.proto = method.method.proto;
     74     this.accessFlags = method.accessFlags;
     75     argumentTypes = computeArgumentTypes();
     76     DexDebugInfo info = code.getDebugInfo();
     77     if (info != null) {
     78       debugEntries = info.computeEntries();
     79     }
     80   }
     81 
     82   @Override
     83   public boolean verifyRegister(int register) {
     84     return register < code.registerSize;
     85   }
     86 
     87   @Override
     88   public boolean needsPrelude() {
     89     return !accessFlags.isStatic() || !argumentTypes.isEmpty();
     90   }
     91 
     92   @Override
     93   public int instructionCount() {
     94     return code.instructions.length;
     95   }
     96 
     97   @Override
     98   public DebugLocalInfo getCurrentLocal(int register) {
     99     // TODO(zerny): Support locals in the dex front-end. b/36378142
    100     return null;
    101   }
    102 
    103   @Override
    104   public void setUp() {
    105     // Collect all payloads in the instruction stream.
    106     for (int index = 0; index < code.instructions.length; index++) {
    107       Instruction insn = code.instructions[index];
    108       offsetToInstructionIndex.put(insn.getOffset(), index);
    109       if (insn.isPayload()) {
    110         if (insn.isSwitchPayload()) {
    111           switchPayloadResolver.resolve((SwitchPayload) insn);
    112         } else {
    113           arrayFilledDataPayloadResolver.resolve((FillArrayDataPayload) insn);
    114         }
    115       }
    116     }
    117   }
    118 
    119   @Override
    120   public void buildPrelude(IRBuilder builder) {
    121     if (code.incomingRegisterSize == 0) {
    122       return;
    123     }
    124     // Fill in the Argument instructions (incomingRegisterSize last registers) in the argument
    125     // block.
    126     int register = code.registerSize - code.incomingRegisterSize;
    127     if (!accessFlags.isStatic()) {
    128       builder.addThisArgument(register);
    129       ++register;
    130     }
    131     for (MoveType type : argumentTypes) {
    132       builder.addNonThisArgument(register, type);
    133       register += type.requiredRegisters();
    134     }
    135   }
    136 
    137   @Override
    138   public void buildPostlude(IRBuilder builder) {
    139     // Intentionally left empty. (Needed in the Java bytecode frontend for synchronization support.)
    140   }
    141 
    142   @Override
    143   public void closedCurrentBlockWithFallthrough(int fallthroughInstructionIndex) {
    144   }
    145 
    146   @Override
    147   public void closedCurrentBlock() {
    148   }
    149 
    150   @Override
    151   public void buildInstruction(IRBuilder builder, int instructionIndex) {
    152     updateCurrentCatchHandlers(instructionIndex);
    153     emitDebugPosition(instructionIndex, builder);
    154     currentDexInstruction = code.instructions[instructionIndex];
    155     currentDexInstruction.buildIR(builder);
    156   }
    157 
    158   @Override
    159   public CatchHandlers<Integer> getCurrentCatchHandlers() {
    160     return currentCatchHandlers;
    161   }
    162 
    163   @Override
    164   public boolean verifyCurrentInstructionCanThrow() {
    165     return currentDexInstruction.canThrow();
    166   }
    167 
    168   @Override
    169   public boolean verifyLocalInScope(DebugLocalInfo local) {
    170     return true;
    171   }
    172 
    173   private void updateCurrentCatchHandlers(int instructionIndex) {
    174     Try tryRange = getTryForOffset(instructionOffset(instructionIndex));
    175     if (tryRange == currentTryRange) {
    176       return;
    177     }
    178     currentTryRange = tryRange;
    179     if (tryRange == null) {
    180       currentCatchHandlers = null;
    181     } else {
    182       currentCatchHandlers = new CatchHandlers<>(
    183           getTryHandlerGuards(tryRange),
    184           getTryHandlerOffsets(tryRange));
    185     }
    186   }
    187 
    188   private void emitDebugPosition(int instructionIndex, IRBuilder builder) {
    189     if (debugEntries == null || debugEntries.isEmpty()) {
    190       return;
    191     }
    192     int offset = instructionOffset(instructionIndex);
    193     for (DexDebugEntry entry : debugEntries) {
    194       if (entry.address == offset) {
    195         builder.updateCurrentDebugPosition(entry.line, entry.sourceFile);
    196         return;
    197       }
    198       if (entry.address > offset) {
    199         return;
    200       }
    201     }
    202   }
    203 
    204   @Override
    205   public void clear() {
    206     switchPayloadResolver.clear();
    207     arrayFilledDataPayloadResolver.clear();
    208   }
    209 
    210   @Override
    211   public int instructionIndex(int instructionOffset) {
    212     return offsetToInstructionIndex.get(instructionOffset);
    213   }
    214 
    215   @Override
    216   public int instructionOffset(int instructionIndex) {
    217     return code.instructions[instructionIndex].getOffset();
    218   }
    219 
    220   @Override
    221   public void resolveAndBuildSwitch(int value, int fallthroughOffset, int payloadOffset,
    222       IRBuilder builder) {
    223     builder.addSwitch(value, switchPayloadResolver.getKeys(payloadOffset), fallthroughOffset,
    224         switchPayloadResolver.absoluteTargets(payloadOffset));
    225   }
    226 
    227   @Override
    228   public void resolveAndBuildNewArrayFilledData(int arrayRef, int payloadOffset,
    229       IRBuilder builder) {
    230     builder.addNewArrayFilledData(arrayRef,
    231         arrayFilledDataPayloadResolver.getElementWidth(payloadOffset),
    232         arrayFilledDataPayloadResolver.getSize(payloadOffset),
    233         arrayFilledDataPayloadResolver.getData(payloadOffset));
    234   }
    235 
    236   private List<MoveType> computeArgumentTypes() {
    237     List<MoveType> types = new ArrayList<>(proto.parameters.values.length);
    238     String shorty = proto.shorty.toString();
    239     for (int i = 1; i < proto.shorty.size; i++) {
    240       MoveType moveType = MoveType.fromTypeDescriptorChar(shorty.charAt(i));
    241       types.add(moveType);
    242     }
    243     return types;
    244   }
    245 
    246   private boolean isInvoke(Instruction dex) {
    247     return dex instanceof InvokeDirect
    248         || dex instanceof InvokeDirectRange
    249         || dex instanceof InvokeVirtual
    250         || dex instanceof InvokeVirtualRange
    251         || dex instanceof InvokeInterface
    252         || dex instanceof InvokeInterfaceRange
    253         || dex instanceof InvokeStatic
    254         || dex instanceof InvokeStaticRange
    255         || dex instanceof InvokeSuper
    256         || dex instanceof InvokeSuperRange
    257         || dex instanceof InvokePolymorphic
    258         || dex instanceof InvokePolymorphicRange
    259         || dex instanceof FilledNewArray
    260         || dex instanceof FilledNewArrayRange;
    261   }
    262 
    263   private boolean isMoveResult(Instruction dex) {
    264     return dex instanceof MoveResult
    265         || dex instanceof MoveResultObject
    266         || dex instanceof MoveResultWide;
    267   }
    268 
    269   @Override
    270   public int traceInstruction(int index, IRBuilder builder) {
    271     Instruction dex = code.instructions[index];
    272     int offset = dex.getOffset();
    273     assert !dex.isPayload();
    274     int[] targets = dex.getTargets();
    275     if (targets != Instruction.NO_TARGETS) {
    276       // Check that we don't ever have instructions that can throw and have targets.
    277       assert !dex.canThrow();
    278       for (int relativeOffset : targets) {
    279         builder.ensureNormalSuccessorBlock(offset, offset + relativeOffset);
    280       }
    281       return index;
    282     }
    283     if (dex.canThrow()) {
    284       // If the instruction can throw and is in a try block, add edges to its catch successors.
    285       Try tryRange = getTryForOffset(offset);
    286       if (tryRange != null) {
    287         // Ensure the block starts at the start of the try-range (don't enqueue, not a target).
    288         int tryRangeStartAddress = tryRange.startAddress;
    289         if (isMoveResult(code.instructions[offsetToInstructionIndex.get(tryRangeStartAddress)])) {
    290           // If a handler range starts at a move result instruction it is safe to start it at
    291           // the following instruction since the move-result cannot throw an exception. Doing so
    292           // makes sure that we do not split an invoke and its move result instruction across
    293           // two blocks.
    294           ++tryRangeStartAddress;
    295         }
    296         builder.ensureBlockWithoutEnqueuing(tryRangeStartAddress);
    297         // Edge to exceptional successors.
    298         for (Integer handlerOffset : getUniqueTryHandlerOffsets(tryRange)) {
    299           builder.ensureExceptionalSuccessorBlock(offset, handlerOffset);
    300         }
    301         // If the following instruction is a move-result include it in this (the invokes) block.
    302         if (index + 1 < code.instructions.length && isMoveResult(code.instructions[index + 1])) {
    303           assert isInvoke(dex);
    304           ++index;
    305           dex = code.instructions[index];
    306         }
    307         // Edge to normal successor if any (fallthrough).
    308         if (!(dex instanceof Throw)) {
    309           builder.ensureNormalSuccessorBlock(offset, dex.getOffset() + dex.getSize());
    310         }
    311         return index;
    312       }
    313       // Close the block if the instruction is a throw, otherwise the block remains open.
    314       return dex instanceof Throw ? index : -1;
    315     }
    316     if (dex.isSwitch()) {
    317       // TODO(zerny): Remove this from block computation.
    318       switchPayloadResolver.addPayloadUser(dex);
    319 
    320       for (int target : switchPayloadResolver.absoluteTargets(dex)) {
    321         builder.ensureNormalSuccessorBlock(offset, target);
    322       }
    323       builder.ensureNormalSuccessorBlock(offset, offset + dex.getSize());
    324       return index;
    325     }
    326     // TODO(zerny): Remove this from block computation.
    327     if (dex.hasPayload()) {
    328       arrayFilledDataPayloadResolver.addPayloadUser((FillArrayData) dex);
    329     }
    330     // This instruction does not close the block.
    331     return -1;
    332   }
    333 
    334   private boolean inTryRange(Try tryItem, int offset) {
    335     return tryItem.startAddress <= offset
    336         && offset < tryItem.startAddress + tryItem.instructionCount;
    337   }
    338 
    339   private Try getTryForOffset(int offset) {
    340     for (Try tryRange : code.tries) {
    341       if (inTryRange(tryRange, offset)) {
    342         return tryRange;
    343       }
    344     }
    345     return null;
    346   }
    347 
    348   private Set<Integer> getUniqueTryHandlerOffsets(Try tryRange) {
    349     return new HashSet<>(getTryHandlerOffsets(tryRange));
    350   }
    351 
    352   private List<Integer> getTryHandlerOffsets(Try tryRange) {
    353     List<Integer> handlerOffsets = new ArrayList<>();
    354     TryHandler handler = code.handlers[tryRange.handlerIndex];
    355     for (TypeAddrPair pair : handler.pairs) {
    356       handlerOffsets.add(pair.addr);
    357     }
    358     if (handler.catchAllAddr != TryHandler.NO_HANDLER) {
    359       handlerOffsets.add(handler.catchAllAddr);
    360     }
    361     return handlerOffsets;
    362   }
    363 
    364   private List<DexType> getTryHandlerGuards(Try tryRange) {
    365     List<DexType> handlerGuards = new ArrayList<>();
    366     TryHandler handler = code.handlers[tryRange.handlerIndex];
    367     for (TypeAddrPair pair : handler.pairs) {
    368       handlerGuards.add(pair.type);
    369     }
    370     if (handler.catchAllAddr != TryHandler.NO_HANDLER) {
    371       handlerGuards.add(DexItemFactory.catchAllType);
    372     }
    373     return handlerGuards;
    374   }
    375 }
    376