Home | History | Annotate | Download | only in code
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.dx.cf.code;
     18 
     19 import com.android.dx.rop.cst.Constant;
     20 import com.android.dx.rop.cst.CstMemberRef;
     21 import com.android.dx.rop.cst.CstString;
     22 import com.android.dx.rop.cst.CstType;
     23 import com.android.dx.rop.type.Type;
     24 import com.android.dx.util.Bits;
     25 import com.android.dx.util.IntList;
     26 import java.util.ArrayList;
     27 
     28 /**
     29  * Utility that identifies basic blocks in bytecode.
     30  */
     31 public final class BasicBlocker implements BytecodeArray.Visitor {
     32     /** {@code non-null;} method being converted */
     33     private final ConcreteMethod method;
     34 
     35     /**
     36      * {@code non-null;} work set; bits indicate offsets in need of
     37      * examination
     38      */
     39     private final int[] workSet;
     40 
     41     /**
     42      * {@code non-null;} live set; bits indicate potentially-live
     43      * opcodes; contrawise, a bit that isn't on is either in the
     44      * middle of an instruction or is a definitely-dead opcode
     45      */
     46     private final int[] liveSet;
     47 
     48     /**
     49      * {@code non-null;} block start set; bits indicate the starts of
     50      * basic blocks, including the opcodes that start blocks of
     51      * definitely-dead code
     52      */
     53     private final int[] blockSet;
     54 
     55     /**
     56      * {@code non-null, sparse;} for each instruction offset to a branch of
     57      * some sort, the list of targets for that instruction
     58      */
     59     private final IntList[] targetLists;
     60 
     61     /**
     62      * {@code non-null, sparse;} for each instruction offset to a throwing
     63      * instruction, the list of exception handlers for that instruction
     64      */
     65     private final ByteCatchList[] catchLists;
     66 
     67     /** offset of the previously parsed bytecode */
     68     private int previousOffset;
     69 
     70     /**
     71      * Identifies and enumerates the basic blocks in the given method,
     72      * returning a list of them. The returned list notably omits any
     73      * definitely-dead code that is identified in the process.
     74      *
     75      * @param method {@code non-null;} method to convert
     76      * @return {@code non-null;} list of basic blocks
     77      */
     78     public static ByteBlockList identifyBlocks(ConcreteMethod method) {
     79         BasicBlocker bb = new BasicBlocker(method);
     80 
     81         bb.doit();
     82         return bb.getBlockList();
     83     }
     84 
     85     /**
     86      * Constructs an instance. This class is not publicly instantiable; use
     87      * {@link #identifyBlocks}.
     88      *
     89      * @param method {@code non-null;} method to convert
     90      */
     91     private BasicBlocker(ConcreteMethod method) {
     92         if (method == null) {
     93             throw new NullPointerException("method == null");
     94         }
     95 
     96         this.method = method;
     97 
     98         /*
     99          * The "+1" below is so the idx-past-end is also valid,
    100          * avoiding a special case, but without preventing
    101          * flow-of-control falling past the end of the method from
    102          * getting properly reported.
    103          */
    104         int sz = method.getCode().size() + 1;
    105 
    106         workSet = Bits.makeBitSet(sz);
    107         liveSet = Bits.makeBitSet(sz);
    108         blockSet = Bits.makeBitSet(sz);
    109         targetLists = new IntList[sz];
    110         catchLists = new ByteCatchList[sz];
    111         previousOffset = -1;
    112     }
    113 
    114     /*
    115      * Note: These methods are defined implementation of the interface
    116      * BytecodeArray.Visitor; since the class isn't publicly
    117      * instantiable, no external code ever gets a chance to actually
    118      * call these methods.
    119      */
    120 
    121     /** {@inheritDoc} */
    122     public void visitInvalid(int opcode, int offset, int length) {
    123         visitCommon(offset, length, true);
    124     }
    125 
    126     /** {@inheritDoc} */
    127     public void visitNoArgs(int opcode, int offset, int length, Type type) {
    128         switch (opcode) {
    129             case ByteOps.IRETURN:
    130             case ByteOps.RETURN: {
    131                 visitCommon(offset, length, false);
    132                 targetLists[offset] = IntList.EMPTY;
    133                 break;
    134             }
    135             case ByteOps.ATHROW: {
    136                 visitCommon(offset, length, false);
    137                 visitThrowing(offset, length, false);
    138                 break;
    139             }
    140             case ByteOps.IALOAD:
    141             case ByteOps.LALOAD:
    142             case ByteOps.FALOAD:
    143             case ByteOps.DALOAD:
    144             case ByteOps.AALOAD:
    145             case ByteOps.BALOAD:
    146             case ByteOps.CALOAD:
    147             case ByteOps.SALOAD:
    148             case ByteOps.IASTORE:
    149             case ByteOps.LASTORE:
    150             case ByteOps.FASTORE:
    151             case ByteOps.DASTORE:
    152             case ByteOps.AASTORE:
    153             case ByteOps.BASTORE:
    154             case ByteOps.CASTORE:
    155             case ByteOps.SASTORE:
    156             case ByteOps.ARRAYLENGTH:
    157             case ByteOps.MONITORENTER:
    158             case ByteOps.MONITOREXIT: {
    159                 /*
    160                  * These instructions can all throw, so they have to end
    161                  * the block they appear in (since throws are branches).
    162                  */
    163                 visitCommon(offset, length, true);
    164                 visitThrowing(offset, length, true);
    165                 break;
    166             }
    167             case ByteOps.IDIV:
    168             case ByteOps.IREM: {
    169                 /*
    170                  * The int and long versions of division and remainder may
    171                  * throw, but not the other types.
    172                  */
    173                 visitCommon(offset, length, true);
    174                 if ((type == Type.INT) || (type == Type.LONG)) {
    175                     visitThrowing(offset, length, true);
    176                 }
    177                 break;
    178             }
    179             default: {
    180                 visitCommon(offset, length, true);
    181                 break;
    182             }
    183         }
    184     }
    185 
    186     /** {@inheritDoc} */
    187     public void visitLocal(int opcode, int offset, int length,
    188             int idx, Type type, int value) {
    189         if (opcode == ByteOps.RET) {
    190             visitCommon(offset, length, false);
    191             targetLists[offset] = IntList.EMPTY;
    192         } else {
    193             visitCommon(offset, length, true);
    194         }
    195     }
    196 
    197     /** {@inheritDoc} */
    198     public void visitConstant(int opcode, int offset, int length,
    199             Constant cst, int value) {
    200         visitCommon(offset, length, true);
    201 
    202         if ((cst instanceof CstMemberRef) || (cst instanceof CstType) ||
    203             (cst instanceof CstString)) {
    204             /*
    205              * Instructions with these sorts of constants have the
    206              * possibility of throwing, so this instruction needs to
    207              * end its block (since it can throw, and possible-throws
    208              * are branch points).
    209              */
    210             visitThrowing(offset, length, true);
    211         }
    212     }
    213 
    214     /** {@inheritDoc} */
    215     public void visitBranch(int opcode, int offset, int length,
    216             int target) {
    217         switch (opcode) {
    218             case ByteOps.GOTO: {
    219                 visitCommon(offset, length, false);
    220                 targetLists[offset] = IntList.makeImmutable(target);
    221                 break;
    222             }
    223             case ByteOps.JSR: {
    224                 /*
    225                  * Each jsr is quarantined into a separate block (containing
    226                  * only the jsr instruction) but is otherwise treated
    227                  * as a conditional branch. (That is to say, both its
    228                  * target and next instruction begin new blocks.)
    229                  */
    230                 addWorkIfNecessary(offset, true);
    231                 // Fall through to next case...
    232             }
    233             default: {
    234                 int next = offset + length;
    235                 visitCommon(offset, length, true);
    236                 addWorkIfNecessary(next, true);
    237                 targetLists[offset] = IntList.makeImmutable(next, target);
    238                 break;
    239             }
    240         }
    241 
    242         addWorkIfNecessary(target, true);
    243     }
    244 
    245     /** {@inheritDoc} */
    246     public void visitSwitch(int opcode, int offset, int length,
    247             SwitchList cases, int padding) {
    248         visitCommon(offset, length, false);
    249         addWorkIfNecessary(cases.getDefaultTarget(), true);
    250 
    251         int sz = cases.size();
    252         for (int i = 0; i < sz; i++) {
    253             addWorkIfNecessary(cases.getTarget(i), true);
    254         }
    255 
    256         targetLists[offset] = cases.getTargets();
    257     }
    258 
    259     /** {@inheritDoc} */
    260     public void visitNewarray(int offset, int length, CstType type,
    261             ArrayList<Constant> intVals) {
    262         visitCommon(offset, length, true);
    263         visitThrowing(offset, length, true);
    264     }
    265 
    266     /**
    267      * Extracts the list of basic blocks from the bit sets.
    268      *
    269      * @return {@code non-null;} the list of basic blocks
    270      */
    271     private ByteBlockList getBlockList() {
    272         BytecodeArray bytes = method.getCode();
    273         ByteBlock[] bbs = new ByteBlock[bytes.size()];
    274         int count = 0;
    275 
    276         for (int at = 0, next; /*at*/; at = next) {
    277             next = Bits.findFirst(blockSet, at + 1);
    278             if (next < 0) {
    279                 break;
    280             }
    281 
    282             if (Bits.get(liveSet, at)) {
    283                 /*
    284                  * Search backward for the branch or throwing
    285                  * instruction at the end of this block, if any. If
    286                  * there isn't any, then "next" is the sole target.
    287                  */
    288                 IntList targets = null;
    289                 int targetsAt = -1;
    290                 ByteCatchList blockCatches;
    291 
    292                 for (int i = next - 1; i >= at; i--) {
    293                     targets = targetLists[i];
    294                     if (targets != null) {
    295                         targetsAt = i;
    296                         break;
    297                     }
    298                 }
    299 
    300                 if (targets == null) {
    301                     targets = IntList.makeImmutable(next);
    302                     blockCatches = ByteCatchList.EMPTY;
    303                 } else {
    304                     blockCatches = catchLists[targetsAt];
    305                     if (blockCatches == null) {
    306                         blockCatches = ByteCatchList.EMPTY;
    307                     }
    308                 }
    309 
    310                 bbs[count] =
    311                     new ByteBlock(at, at, next, targets, blockCatches);
    312                 count++;
    313             }
    314         }
    315 
    316         ByteBlockList result = new ByteBlockList(count);
    317         for (int i = 0; i < count; i++) {
    318             result.set(i, bbs[i]);
    319         }
    320 
    321         return result;
    322     }
    323 
    324     /**
    325      * Does basic block identification.
    326      */
    327     private void doit() {
    328         BytecodeArray bytes = method.getCode();
    329         ByteCatchList catches = method.getCatches();
    330         int catchSz = catches.size();
    331 
    332         /*
    333          * Start by setting offset 0 as the start of a block and in need
    334          * of work...
    335          */
    336         Bits.set(workSet, 0);
    337         Bits.set(blockSet, 0);
    338 
    339         /*
    340          * And then process the work set, add new work based on
    341          * exception ranges that are active, and iterate until there's
    342          * nothing left to work on.
    343          */
    344         while (!Bits.isEmpty(workSet)) {
    345             try {
    346                 bytes.processWorkSet(workSet, this);
    347             } catch (IllegalArgumentException ex) {
    348                 // Translate the exception.
    349                 throw new SimException("flow of control falls off " +
    350                                        "end of method",
    351                                        ex);
    352             }
    353 
    354             for (int i = 0; i < catchSz; i++) {
    355                 ByteCatchList.Item item = catches.get(i);
    356                 int start = item.getStartPc();
    357                 int end = item.getEndPc();
    358                 if (Bits.anyInRange(liveSet, start, end)) {
    359                     Bits.set(blockSet, start);
    360                     Bits.set(blockSet, end);
    361                     addWorkIfNecessary(item.getHandlerPc(), true);
    362                 }
    363             }
    364         }
    365     }
    366 
    367     /**
    368      * Sets a bit in the work set, but only if the instruction in question
    369      * isn't yet known to be possibly-live.
    370      *
    371      * @param offset offset to the instruction in question
    372      * @param blockStart {@code true} iff this instruction starts a
    373      * basic block
    374      */
    375     private void addWorkIfNecessary(int offset, boolean blockStart) {
    376         if (!Bits.get(liveSet, offset)) {
    377             Bits.set(workSet, offset);
    378         }
    379 
    380         if (blockStart) {
    381             Bits.set(blockSet, offset);
    382         }
    383     }
    384 
    385     /**
    386      * Helper method used by all the visitor methods.
    387      *
    388      * @param offset offset to the instruction
    389      * @param length length of the instruction, in bytes
    390      * @param nextIsLive {@code true} iff the instruction after
    391      * the indicated one is possibly-live (because this one isn't an
    392      * unconditional branch, a return, or a switch)
    393      */
    394     private void visitCommon(int offset, int length, boolean nextIsLive) {
    395         Bits.set(liveSet, offset);
    396 
    397         if (nextIsLive) {
    398             /*
    399              * If the next instruction is flowed to by this one, just
    400              * add it to the work set, and then a subsequent visit*()
    401              * will deal with it as appropriate.
    402              */
    403             addWorkIfNecessary(offset + length, false);
    404         } else {
    405             /*
    406              * If the next instruction isn't flowed to by this one,
    407              * then mark it as a start of a block but *don't* add it
    408              * to the work set, so that in the final phase we can know
    409              * dead code blocks as those marked as blocks but not also marked
    410              * live.
    411              */
    412             Bits.set(blockSet, offset + length);
    413         }
    414     }
    415 
    416     /**
    417      * Helper method used by all the visitor methods that deal with
    418      * opcodes that possibly throw. This method should be called after calling
    419      * {@link #visitCommon}.
    420      *
    421      * @param offset offset to the instruction
    422      * @param length length of the instruction, in bytes
    423      * @param nextIsLive {@code true} iff the instruction after
    424      * the indicated one is possibly-live (because this one isn't an
    425      * unconditional throw)
    426      */
    427     private void visitThrowing(int offset, int length, boolean nextIsLive) {
    428         int next = offset + length;
    429 
    430         if (nextIsLive) {
    431             addWorkIfNecessary(next, true);
    432         }
    433 
    434         ByteCatchList catches = method.getCatches().listFor(offset);
    435         catchLists[offset] = catches;
    436         targetLists[offset] = catches.toTargetList(nextIsLive ? next : -1);
    437     }
    438 
    439     /**
    440      * {@inheritDoc}
    441      */
    442     public void setPreviousOffset(int offset) {
    443         previousOffset = offset;
    444     }
    445 
    446     /**
    447      * {@inheritDoc}
    448      */
    449     public int getPreviousOffset() {
    450         return previousOffset;
    451     }
    452 }
    453