BasicBlocker.java revision de75089fb7216d19e9c22cce4dc62a49513477d3
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
17package com.android.dx.cf.code;
18
19import com.android.dx.rop.cst.Constant;
20import com.android.dx.rop.cst.CstMemberRef;
21import com.android.dx.rop.cst.CstString;
22import com.android.dx.rop.cst.CstType;
23import com.android.dx.rop.type.Type;
24import com.android.dx.util.Bits;
25import com.android.dx.util.IntList;
26import java.util.ArrayList;
27
28/**
29 * Utility that identifies basic blocks in bytecode.
30 */
31public 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