kilim.analysis.BasicBlock.java Source code

Java tutorial

Introduction

Here is the source code for kilim.analysis.BasicBlock.java

Source

/* Copyright (c) 2006, Sriram Srinivasan
 *
 * You may distribute this software under the terms of the license 
 * specified in the file "License"
 */
package kilim.analysis;

import static kilim.Constants.D_ARRAY_BOOLEAN;
import static kilim.Constants.D_ARRAY_BYTE;
import static kilim.Constants.D_ARRAY_CHAR;
import static kilim.Constants.D_ARRAY_DOUBLE;
import static kilim.Constants.D_ARRAY_FLOAT;
import static kilim.Constants.D_ARRAY_INT;
import static kilim.Constants.D_ARRAY_LONG;
import static kilim.Constants.D_ARRAY_SHORT;
import static kilim.Constants.D_BOOLEAN;
import static kilim.Constants.D_BYTE;
import static kilim.Constants.D_CHAR;
import static kilim.Constants.D_DOUBLE;
import static kilim.Constants.D_FLOAT;
import static kilim.Constants.D_INT;
import static kilim.Constants.D_LONG;
import static kilim.Constants.D_NULL;
import static kilim.Constants.D_RETURN_ADDRESS;
import static kilim.Constants.D_SHORT;
import static kilim.Constants.D_VOID;
import static kilim.Constants.TASK_CLASS;
import static kilim.Constants.THROWABLE_CLASS;
import static org.objectweb.asm.Opcodes.*;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;

import kilim.KilimException;
import kilim.mirrors.Detector;
import me.jor.util.Log4jUtil;

import org.apache.commons.logging.Log;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.IincInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.LookupSwitchInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MultiANewArrayInsnNode;
import org.objectweb.asm.tree.TableSwitchInsnNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;

/**
 * A basic block is a contiguous set of instructions that has one label at the
 * first instruction and a transfer-of-control instruction at the very end. A
 * transfer-of-control instruction includes all branching instructions that have
 * labelled targets (IF_x, GOTO, and JSR) and the rest (ATHROW, xRETURN, RET).
 * There can be no target labels in the middle of a basic block; in other words,
 * you can't jump into the middle of a basic block. This is the standard
 * definition; we make a few changes.
 * 
 * <dl>
 * <li>
 * We create BasicBlocks whenever we encounter a label (in a linear
 * scanning of a method's instructions. Some labels are meant for catch
 * handlers and debug (line number) information only; they are not the
 * target of a branching instruction, but we don't know that in the
 * first pass. We coalesce those BasicBlocks that merely follow
 * another, provided the preceding BB is the only preceder. Note that
 * blocks connected with a GOTO can't coalesce because they are not
 * likely to be contiguous, even if they obey the constraint of a
 * single edge. We also don't coalesce blocks starting with a pausable method
 * invocation with their predecessor, because we need these blocks to
 * tell us about downstream usage of local vars to help us generate
 * optimal continuations. </li>
    
 * 
 * <li> All catch handlers that intersect a basic block are treated as
 * successors to the block, for the purposes of liveness analysis.
 * 
 * <li> Subroutines (targets of JSR) are treated specially. We inline all JSR
 * calls, including nested JSRs, to simplify liveness analysis. In this phase, a
 * JSR/RET is treated the same as a GOTO sub followed by a GOTO to the caller.
 * During the weaving phase, we ignore the inlining information if the
 * subroutine doesn't have any pausable methods. If it does, then we spit out
 * duplicate code, complete with GOTOs as described above. This allows us to
 * jump in the middle of a "finally" block during rewinding.
 * 
 * Note: The JVM reference doesn't specify the boundaries of a JSR instruction;
 * in other words, there is no definitive way of saying which blocks belong to a
 * subroutine. This code treats the set of all nodes reachable via branching
 * instructions from the subroutine's entry point. (exception catch blocks don't
 * count) </li>
 * </dl>
 */
public class BasicBlock implements Comparable<BasicBlock> {
    private static final Log log = Log4jUtil.getLog(BasicBlock.class);
    /**
     * A number handed out in increasing order of starting position, to ease
     * sorting as well as for debug information
     */
    public int id;

    /*
     * One of the bit flags above.
     */
    int flags;

    /*
     * Used by the flow analysis algorithm to mark this BB as enqueued for
     * processing
     */
    static final int ENQUEUED = 1;

    /*
     * Used by the JSR inlining process to signify that a subroutine (a JSR
     * target) has been claimed by a corresponding call. All other JSR calls
     * pointing to this subroutine have to make their own duplicates.
     */
    static final int SUBROUTINE_CLAIMED = 1 << 1;
    /*
     * Flag used by the consolidation process to avoid processing this block
     * again.
     */
    static final int COALESCED = 1 << 2;
    /*
     * Set if this BB contains a call to a pausable method
     */
    static final int PAUSABLE = 1 << 4;
    /*
     * Set if this block is the entry point to a subroutine and the target of
     * one or more JSR instructions
     */
    static final int IS_SUBROUTINE = 1 << 5;
    /*
     * Set if this block belongs to a subroutine
     */
    static final int SUB_BLOCK = 1 << 6;
    /*
     * Set by the subroutine inlining phase to avoid rechecking this BB.
     */
    static final int INLINE_CHECKED = 1 << 7;

    /*
     * Set for the entry point to a subroutine that contains a pausable
     * method. The entry point is the target of a JSR instruction.
     */
    static final int PAUSABLE_SUB = 1 << 8;
    /**
     * The flow to which this BB belongs.
     */
    public MethodFlow flow;

    /**
     * The label that starts this BB. In some cases we create a label where it
     * didn't exist originally (after a jmp instruction, for example). This
     * allows us a unique indexing scheme.
     */
    public LabelNode startLabel;

    /**
     * Start and end points (both inclusive) in the current method's list of
     * instructions (this.flow.instructions)
     */
    public int startPos = -1;

    public int endPos = -1;

    /**
     * List of successors (follower and all branch targets). Should be null 
     */
    public ArrayList<BasicBlock> successors = new ArrayList<BasicBlock>(3);

    public ArrayList<Handler> handlers = new ArrayList<Handler>(2);

    int numPredecessors;

    /**
     * usage initially contains the usage of local variables in this block
     * (without reference to any other block). After flow analysis it contains
     * the combined effect of this and all downstream blocks
     */
    public Usage usage;

    /**
     * A cached version of all sucessors' usage, successors being catch handlers
     * and real successors.
     */
    ArrayList<Usage> succUsage;

    /**
     * The frame at the BB's entry point. It changes when propagating changes
     * from its predeccessors, until there's a fixed point.
     */
    public Frame startFrame;

    /*
     * If this BB is a catch block (the entry point to a series of catch handler
     * blocks, it contains the type of the exception
     */

    String caughtExceptionType;

    /*
     * The BB that follows this BB. Is null if the last instruction is a GOTO or
     * THROW or RETURN or RET. The follower is also part of the successors list.
     */
    BasicBlock follower;

    /*
     * sa subroutine, subBlocks contains the list of BBs that belong to it.
     */
    ArrayList<BasicBlock> subBlocks;

    public BasicBlock(MethodFlow aflow, LabelNode aStartLabel) {
        flow = aflow;
        startLabel = aStartLabel;
        usage = new Usage(aflow.maxLocals);
        successors = new ArrayList<BasicBlock>(2);
    }

    Detector detector() {
        return flow.detector();
    }

    /**
     * Absorb as many instructions until the next label or the next transfer of
     * control instruction. In the first pass we may end up creating many many
     * BBs because there may be a lot of non-target labels (esp. when debug
     * information is available). The constraints are as follows:
     *   1. A transfer of control instruction must be the last instruction. It 
     *      may also be the first (and only) instruction
     *   2. A labeled instruction must be the first instruction in a BB. It
     *      may optionally be the last (and only) instruction
     *   3. A pausable method is treated like a labeled instruction, and is 
     *      given a label if there isn't one already. Constraint 2 applies.
     */
    @SuppressWarnings("unchecked")
    int initialize(int pos) {
        AbstractInsnNode ain;
        startPos = pos;

        BasicBlock bb;
        boolean endOfBB = false;
        boolean hasFollower = true;
        int size = flow.instructions.size();
        for (; pos < size; pos++) {
            if (pos > startPos && flow.getLabelAt(pos) != null) {
                pos--;
                hasFollower = true;
                endOfBB = true;
                break;
            }
            ain = getInstruction(pos);
            int opcode = ain.getOpcode();
            switch (opcode) {
            case ALOAD:
            case ILOAD:
            case LLOAD:
            case FLOAD:
            case DLOAD:
                usage.read(((VarInsnNode) ain).var);
                break;

            case ISTORE:
            case LSTORE:
            case FSTORE:
            case DSTORE:
            case ASTORE:
                usage.write(((VarInsnNode) ain).var);
                break;

            case IINC:
                int v = ((IincInsnNode) ain).var;
                usage.read(v);
                usage.write(v);
                break;

            case IFEQ:
            case IFNE:
            case IFLT:
            case IFGE:
            case IFGT:
            case IFLE:
            case IFNULL:
            case IFNONNULL:
            case IF_ICMPEQ:
            case IF_ICMPNE:
            case IF_ICMPLT:
            case IF_ICMPGE:
            case IF_ICMPGT:
            case IF_ICMPLE:
            case IF_ACMPEQ:
            case IF_ACMPNE:
            case JSR:
            case GOTO:
                LabelNode l = ((JumpInsnNode) ain).label;
                bb = flow.getOrCreateBasicBlock(l);
                if (opcode == JSR) {
                    bb.setFlag(IS_SUBROUTINE);
                    hasFollower = false;
                }
                addSuccessor(bb);
                if (opcode == GOTO) {
                    hasFollower = false;
                }
                endOfBB = true;
                break;

            case RET:
            case IRETURN:
            case LRETURN:
            case FRETURN:
            case DRETURN:
            case ARETURN:
            case RETURN:
            case ATHROW:
                hasFollower = false;
                endOfBB = true;
                break;

            case TABLESWITCH:
            case LOOKUPSWITCH:
                LabelNode defaultLabel;
                List<LabelNode> otherLabels;
                if (opcode == TABLESWITCH) {
                    defaultLabel = ((TableSwitchInsnNode) ain).dflt;
                    otherLabels = ((TableSwitchInsnNode) ain).labels;
                } else {
                    defaultLabel = ((LookupSwitchInsnNode) ain).dflt;
                    otherLabels = ((LookupSwitchInsnNode) ain).labels;
                }
                for (Iterator<LabelNode> it = otherLabels.iterator(); it.hasNext();) {
                    l = it.next();
                    addSuccessor(flow.getOrCreateBasicBlock(l));
                }
                addSuccessor(flow.getOrCreateBasicBlock(defaultLabel));
                endOfBB = true;
                hasFollower = false;
                break;

            case INVOKEVIRTUAL:
            case INVOKESTATIC:
            case INVOKEINTERFACE:
            case INVOKESPECIAL:
                if (flow.isPausableMethodInsn((MethodInsnNode) ain)) {
                    LabelNode il = flow.getOrCreateLabelAtPos(pos);
                    if (pos == startPos) {
                        setFlag(PAUSABLE);
                    } else {
                        bb = flow.getOrCreateBasicBlock(il);
                        bb.setFlag(PAUSABLE);
                        addSuccessor(bb);
                        pos--; // don't consume this instruction
                        hasFollower = true;
                        endOfBB = true;
                    }
                }
                break;

            default:
                if (opcode >= 26 && opcode <= 45)
                    throw new IllegalStateException("instruction variants not expected here");

                break;
            }

            if (endOfBB)
                break;
        }
        endPos = pos;
        if (hasFollower && (pos + 1) < flow.instructions.size()) {
            // add the following basic block as a successor
            LabelNode l = flow.getOrCreateLabelAtPos(pos + 1);
            bb = flow.getOrCreateBasicBlock(l);
            addFollower(bb);
        }

        return pos;
    }

    void addFollower(BasicBlock bb) {
        this.follower = bb;
        addSuccessor(bb);
    }

    void addSuccessor(BasicBlock bb) {
        if (!successors.contains(bb)) {
            this.successors.add(bb);
            bb.numPredecessors++;
        }
    }

    public Usage getVarUsage() {
        return usage;
    }

    int lastInstruction() {
        AbstractInsnNode ainode = getInstruction(endPos);
        return ainode.getOpcode();
    }

    /*
     * Blocks connected by an edge are candidates for coalescing if: <dl> <li>
     * There is a single edge between the two and neither has any other edges.
     * </li>
     * 
     * <li> The edge connecting the two is not because of a GOTO. We only want
     * those where one block falls into the other. The reason is that each block
     * marks out a *contiguous* range of instructions. Most compilers would have
     * gotten rid of this unnecessary jump anyway. </li>
     * 
     * <li> The successor block doesn't begin with a method call that we are
     * interested in (like pausable methods). This is a boundary we are
     * interested in maintaining in subsequent processing. </li>
     * 
     * </dl>
     */
    void coalesceTrivialFollowers() {
        while (true) {
            if (successors.size() == 1) {
                BasicBlock succ = successors.get(0);
                if (succ.numPredecessors == 1 && lastInstruction() != GOTO && lastInstruction() != JSR
                        && !succ.isPausable()) {
                    // successor can be merged
                    // absorb succesors and usage mask
                    this.successors = succ.successors;
                    this.follower = succ.follower;
                    this.usage.absorb(succ.usage);
                    this.endPos = succ.endPos;
                    succ.setFlag(COALESCED);
                    // mark succ so it doesn't get visited. This block's merk remains 0. We'll let the outer driver
                    // loop to
                    // revisit this block and its new successors
                    continue;
                }
            }
            break;
        }

    }

    // Made public for testing purposes
    public void setFlag(int bitFlag) {
        flags |= bitFlag;
    }

    public void unsetFlag(int bitFlag) {
        flags &= ~bitFlag;
    }

    public boolean hasFlag(int bitFlag) {
        return (flags & bitFlag) != 0;
    }

    public int compareTo(BasicBlock o) {
        if (this.id == o.id) {
            assert this == o; // Just in case we have mistakenly assigned the
            // same id to different BBs
            return 0;
        }
        return this.id < o.id ? -1 : +1;
    }

    /*
     * This is the main workhorse of the flow analysis phase, translating each
     * instruction's effects on the stack and local variables. Unlike the 
     * verifier which tracks the flow  of types, this method tracks values,
     * which allows us to track types as well as the flow of constant values
     * and set the stage for SSA-style optimizations.
     */
    void interpret() {
        Value v, v1, v2, v3, v4;
        Frame frame = startFrame.dup();
        if (isCatchHandler()) {
            // When an exception is thrown, the stack is cleared
            // and the thrown exception is pushed into the stack
            frame.clearStack();
            frame.push(Value.make(startPos, caughtExceptionType));
        } else if (hasFlag(IS_SUBROUTINE)) {
            // The target of a JSR instruction has a JVM-internal
            // return address which we model with a type of its
            // own
            frame.push(Value.make(startPos, D_RETURN_ADDRESS));
        }
        String componentType = null;
        @SuppressWarnings("unused")
        boolean canThrowException = false;
        boolean propagateFrame = true;
        int i = 0;
        try {
            for (i = startPos; i <= endPos; i++) {
                AbstractInsnNode ain = getInstruction(i);
                int opcode = ain.getOpcode();
                int val, var;
                switch (opcode) {
                case -1: // linenumbernode, framenode, etc.
                    continue;
                case NOP:
                    break;
                case ACONST_NULL:
                    frame.push(Value.make(i, D_NULL));
                    break;

                case ICONST_M1:
                case ICONST_0:
                case ICONST_1:
                case ICONST_2:
                case ICONST_3:
                case ICONST_4:
                case ICONST_5:
                    frame.push(Value.make(i, D_INT, new Integer(opcode - ICONST_0)));
                    break;

                case LCONST_0:
                case LCONST_1:
                    frame.push(Value.make(i, D_LONG, new Long(opcode - LCONST_0)));
                    break;

                case ILOAD:
                case LLOAD:
                case FLOAD:
                case DLOAD:
                case ALOAD:
                    var = ((VarInsnNode) ain).var;
                    v = frame.getLocal(var, opcode);
                    frame.push(v);
                    break;

                case FCONST_0:
                case FCONST_1:
                case FCONST_2:
                    frame.push(Value.make(i, D_FLOAT, new Float(opcode - FCONST_0)));
                    break;

                case DCONST_0:
                case DCONST_1:
                    frame.push(Value.make(i, D_DOUBLE, new Double(opcode - DCONST_0)));
                    break;

                case BIPUSH:
                    val = ((IntInsnNode) ain).operand;
                    frame.push(Value.make(i, D_BYTE, new Integer(val)));
                    break;

                case SIPUSH:
                    val = ((IntInsnNode) ain).operand;
                    frame.push(Value.make(i, D_SHORT, new Integer(val)));
                    break;

                case LDC:
                    Object cval = ((LdcInsnNode) ain).cst;
                    frame.push(Value.make(i, TypeDesc.getTypeDesc(cval), cval));
                    break;

                case IALOAD:
                case LALOAD:
                case FALOAD:
                case DALOAD:
                case AALOAD:
                case BALOAD:
                case CALOAD:
                case SALOAD:
                    canThrowException = true;
                    frame.popWord(); // pop index
                    v = frame.popWord(); // array ref
                    frame.push(Value.make(i, TypeDesc.getComponentType(v.getTypeDesc()))); // push
                    // component
                    // of
                    // array
                    break;

                case ISTORE:
                case LSTORE:
                case FSTORE:
                case DSTORE:
                case ASTORE:
                    v1 = frame.pop();
                    var = ((VarInsnNode) ain).var;
                    frame.setLocal(var, v1);
                    break;

                case IASTORE:
                case LASTORE:
                case FASTORE:
                case DASTORE:
                case AASTORE:
                case BASTORE:
                case CASTORE:
                case SASTORE:
                    canThrowException = true;
                    frame.popn(3);
                    break;

                case POP:
                    frame.popWord();
                    break;

                case POP2:
                    if (frame.pop().isCategory1()) {
                        frame.popWord();
                    }
                    break;

                case DUP:
                    // ... w => ... w w
                    v = frame.popWord();
                    frame.push(v);
                    frame.push(v);
                    break;

                case DUP_X1:
                    // Insert top word beneath the next word
                    // .. w2 w1 => .. w1 w2 w1
                    v1 = frame.popWord();
                    v2 = frame.popWord();
                    frame.push(v1);
                    frame.push(v2);
                    frame.push(v1);
                    break;

                case DUP_X2:
                    // Insert top word beneath the next two words (or dword)
                    v1 = frame.popWord();
                    v2 = frame.pop();
                    if (v2.isCategory1()) {
                        v3 = frame.pop();
                        if (v3.isCategory1()) {
                            // w3,w2,w1 => w1,w3,w2,w1
                            frame.push(v1);
                            frame.push(v3);
                            frame.push(v2);
                            frame.push(v1);
                            break;
                        }
                    } else {
                        // dw2,w1 => w1,dw2,w1
                        frame.push(v1);
                        frame.push(v2);
                        frame.push(v1);
                        break;
                    }
                    throw new InternalError("Illegal use of DUP_X2");

                case DUP2:
                    // duplicate top two words (or dword)
                    v1 = frame.pop();
                    if (v1.isCategory1()) {
                        v2 = frame.pop();
                        if (v2.isCategory1()) {
                            // w2,w1 => w2,w1,w2,w1
                            frame.push(v2);
                            frame.push(v1);
                            frame.push(v2);
                            frame.push(v1);
                            break;
                        }
                    } else {
                        // dw1 => dw1,dw1
                        frame.push(v1);
                        frame.push(v1);
                        break;
                    }
                    throw new InternalError("Illegal use of DUP2");

                case DUP2_X1:
                    // insert two words (or dword) beneath next word
                    v1 = frame.pop();
                    if (v1.isCategory1()) {
                        v2 = frame.pop();
                        if (v2.isCategory1()) {
                            v3 = frame.popWord();
                            // w3,w2,w1 => w2,w1,w3,w2,w1
                            frame.push(v2);
                            frame.push(v1);
                            frame.push(v3);
                            frame.push(v2);
                            frame.push(v1);
                            break;
                        }
                    } else { // TypeDesc.isDoubleWord(t1)
                        // w2,dw1 => dw1,w2,dw1
                        v2 = frame.popWord();
                        frame.push(v1);
                        frame.push(v2);
                        frame.push(v1);
                        break;
                    }
                    throw new InternalError("Illegal use of DUP2_X1");
                case DUP2_X2:
                    // insert two words (or dword) beneath next two words (or
                    // dword)
                    v1 = frame.pop();
                    if (v1.isCategory1()) {
                        v2 = frame.pop();
                        if (v2.isCategory1()) {
                            v3 = frame.pop();
                            if (v3.isCategory1()) {
                                v4 = frame.pop();
                                if (v4.isCategory1()) {
                                    // w4,w3,w2,w1 => w2,w1,w4,w3,w2,w1
                                    frame.push(v2);
                                    frame.push(v1);
                                    frame.push(v4);
                                    frame.push(v3);
                                    frame.push(v2);
                                    frame.push(v1);
                                    break;
                                }
                            } else { // TypeDesc.isDoubleWord(t3)
                                // dw3,w2,w1 => w2,w1,dw3,w2,w1
                                frame.push(v2);
                                frame.push(v1);
                                frame.push(v3);
                                frame.push(v2);
                                frame.push(v1);
                                break;
                            }
                        }
                    } else { // TypeDesc.isDoubleWord(t1)
                        v2 = frame.pop();
                        if (v2.isCategory1()) {
                            v3 = frame.pop();
                            if (v3.isCategory1()) {
                                // w3,w2,dw1 => dw1,w3,w2,dw1
                                frame.push(v1);
                                frame.push(v3);
                                frame.push(v2);
                                frame.push(v1);
                                break;
                            }
                        } else {
                            // dw2,dw1 => dw1,dw2,dw1
                            frame.push(v1);
                            frame.push(v2);
                            frame.push(v1);
                            break;
                        }
                    }
                    throw new InternalError("Illegal use of DUP2_X2");

                case SWAP:
                    // w2, w1 => w1, w2
                    v1 = frame.popWord();
                    v2 = frame.popWord();
                    frame.push(v1);
                    frame.push(v2);
                    break;

                case IDIV:
                case IREM:
                case LDIV:
                case LREM:
                    frame.pop(); // See next case
                    canThrowException = true;
                    break;

                case IADD:
                case LADD:
                case FADD:
                case DADD:
                case ISUB:
                case LSUB:
                case FSUB:
                case DSUB:
                case IMUL:
                case LMUL:
                case FMUL:
                case DMUL:
                case FDIV:
                case DDIV:
                case FREM:
                case DREM:
                case ISHL:
                case LSHL:
                case ISHR:
                case LSHR:
                case IUSHR:
                case LUSHR:
                case IAND:
                case LAND:
                case IOR:
                case LOR:
                case IXOR:
                case LXOR:
                    // Binary op.
                    frame.pop();
                    v = frame.pop();
                    // The result is always the same type as the first arg
                    frame.push(Value.make(i, v.getTypeDesc()));
                    break;

                case LCMP:
                case FCMPL:
                case FCMPG:
                case DCMPL:
                case DCMPG:
                    frame.popn(2);
                    frame.push(Value.make(i, D_INT));
                    break;

                case INEG:
                case LNEG:
                case FNEG:
                case DNEG:
                    v = frame.pop();
                    frame.push(Value.make(i, v.getTypeDesc()));
                    break;

                case IINC:
                    var = ((IincInsnNode) ain).var;
                    frame.setLocal(var, Value.make(i, D_INT));
                    break;

                case I2L:
                case F2L:
                case D2L:
                    frame.pop();
                    frame.push(Value.make(i, D_LONG));
                    break;

                case I2D:
                case L2D:
                case F2D:
                    frame.pop();
                    frame.push(Value.make(i, D_DOUBLE));
                    break;

                case I2F:
                case L2F:
                case D2F:
                    frame.pop();
                    frame.push(Value.make(i, D_FLOAT));
                    break;

                case L2I:
                case F2I:
                case D2I:
                    frame.pop();
                    frame.push(Value.make(i, D_INT));
                    break;

                case I2B:
                    frame.popWord();
                    frame.push(Value.make(i, D_BOOLEAN));
                    break;

                case I2C:
                    frame.popWord();
                    frame.push(Value.make(i, D_CHAR));
                    break;

                case I2S:
                    frame.popWord();
                    frame.push(Value.make(i, D_SHORT));
                    break;

                case IFEQ:
                case IFNE:
                case IFLT:
                case IFGE:
                case IFGT:
                case IFLE:
                case IFNULL:
                case IFNONNULL:
                    frame.popWord();
                    break;

                case IF_ICMPEQ:
                case IF_ICMPNE:
                case IF_ICMPLT:
                case IF_ICMPGE:
                case IF_ICMPGT:
                case IF_ICMPLE:
                case IF_ACMPEQ:
                case IF_ACMPNE:
                    frame.popn(2);
                    break;

                case GOTO:
                case JSR: // note: the targetBB pushes the return address
                    // itself
                    // because it is marked with isSubroutine
                case RET:
                    break;

                case TABLESWITCH:
                case LOOKUPSWITCH:
                    frame.pop();
                    break;

                case IRETURN:
                case LRETURN:
                case FRETURN:
                case DRETURN:
                case ARETURN:
                case RETURN:
                    canThrowException = true;
                    if (opcode != RETURN) {
                        frame.pop();
                    }
                    if (frame.stacklen != 0) {
                        throw new InternalError("stack non null at method return");
                    }
                    break;

                case GETSTATIC:
                    canThrowException = true;
                    v = Value.make(i, TypeDesc.getInterned(((FieldInsnNode) ain).desc));
                    frame.push(v);
                    break;

                case PUTSTATIC:
                    canThrowException = true;
                    frame.pop();
                    break;

                case GETFIELD:
                    canThrowException = true;
                    v1 = frame.pop();
                    v = Value.make(i, TypeDesc.getInterned(((FieldInsnNode) ain).desc));
                    //if (TypeDesc.isRefType(v.getTypeDesc())) {
                    //    System.out.println("GETFIELD " + ((FieldInsnNode)ain).name  + ": " + v + "---->" + v1);
                    //}
                    frame.push(v);
                    break;

                case PUTFIELD:
                    canThrowException = true;
                    v1 = frame.pop();
                    v = frame.pop();
                    //if (TypeDesc.isRefType(v.getTypeDesc())) {
                    //    System.out.println("PUTFIELD " + ((FieldInsnNode)ain).name  + ": " + v + " ----> " + v1);
                    //}
                    break;

                case INVOKEVIRTUAL:
                case INVOKESPECIAL:
                case INVOKESTATIC:
                case INVOKEINTERFACE:
                    // pop args, push return value
                    MethodInsnNode min = ((MethodInsnNode) ain);
                    String desc = min.desc;
                    if (flow.isPausableMethodInsn(min) && frame.numMonitorsActive > 0) {
                        throw new KilimException(
                                "Error: Can not call pausable nethods from within a synchronized block\n"
                                        + "Caller: " + this.flow.classFlow.name.replace('/', '.') + "."
                                        + this.flow.name + this.flow.desc + "\nCallee: "
                                        + ((MethodInsnNode) ain).name);
                    }
                    canThrowException = true;
                    frame.popn(TypeDesc.getNumArgumentTypes(desc));
                    if (opcode != INVOKESTATIC) {
                        v = frame.pop(); // "this" ref
                        //assert checkReceiverType(v, min) : "Method " + flow.name + " calls " + min.name + " on a receiver with incompatible type " + v.getTypeDesc() ;
                    }
                    desc = TypeDesc.getReturnTypeDesc(desc);
                    if (desc != D_VOID) {
                        frame.push(Value.make(i, desc));
                    }
                    break;

                case NEW:
                    canThrowException = true;
                    v = Value.make(i, TypeDesc.getInterned(((TypeInsnNode) ain).desc));
                    frame.push(v);
                    break;

                case NEWARRAY:
                    canThrowException = true;
                    frame.popWord();
                    int atype = ((IntInsnNode) ain).operand;
                    String t;
                    switch (atype) {
                    case T_BOOLEAN:
                        t = D_ARRAY_BOOLEAN;
                        break;
                    case T_CHAR:
                        t = D_ARRAY_CHAR;
                        break;
                    case T_FLOAT:
                        t = D_ARRAY_FLOAT;
                        break;
                    case T_DOUBLE:
                        t = D_ARRAY_DOUBLE;
                        break;
                    case T_BYTE:
                        t = D_ARRAY_BYTE;
                        break;
                    case T_SHORT:
                        t = D_ARRAY_SHORT;
                        break;
                    case T_INT:
                        t = D_ARRAY_INT;
                        break;
                    case T_LONG:
                        t = D_ARRAY_LONG;
                        break;
                    default:
                        throw new InternalError("Illegal argument to NEWARRAY: " + atype);
                    }
                    frame.push(Value.make(i, t));
                    break;
                case ANEWARRAY:
                    canThrowException = true;
                    frame.popWord();
                    componentType = TypeDesc.getInterned(((TypeInsnNode) ain).desc);
                    v = Value.make(i, TypeDesc.getInterned("[" + componentType));
                    frame.push(v);
                    break;

                case ARRAYLENGTH:
                    canThrowException = true;
                    frame.popWord();
                    frame.push(Value.make(i, D_INT));
                    break;

                case ATHROW:
                    canThrowException = true;
                    frame.pop();
                    propagateFrame = false;
                    break;

                case CHECKCAST:
                    canThrowException = true;
                    frame.pop();
                    v = Value.make(i, TypeDesc.getInterned(((TypeInsnNode) ain).desc));
                    frame.push(v);
                    break;

                case INSTANCEOF:
                    canThrowException = true;
                    frame.pop();
                    frame.push(Value.make(i, D_INT));
                    break;

                case MONITORENTER:
                case MONITOREXIT:
                    if (opcode == MONITORENTER) {
                        frame.numMonitorsActive++;
                    } else {
                        frame.numMonitorsActive--;
                    }
                    canThrowException = true;
                    frame.pop();
                    canThrowException = true;
                    break;

                case MULTIANEWARRAY:
                    MultiANewArrayInsnNode minode = (MultiANewArrayInsnNode) ain;
                    int dims = minode.dims;
                    frame.popn(dims);
                    componentType = TypeDesc.getInterned(minode.desc);
                    StringBuffer sb = new StringBuffer(componentType.length() + dims);
                    for (int j = 0; j < dims; j++)
                        sb.append('[');
                    sb.append(componentType);
                    v = Value.make(i, TypeDesc.getInterned(sb.toString()));
                    frame.push(v);
                    break;
                default:
                    assert false : "Unexpected opcode: " + ain.getOpcode();
                }
            }
            i = -1; // reset for assertion catch block below
            if (propagateFrame) {
                mergeSuccessors(frame);
            }
            if (handlers != null) {
                for (Handler handler : handlers) {
                    handler.catchBB.merge(frame, /* localsOnly= */true); // merge
                    // only
                    // locals
                }
                canThrowException = false;
            }
        } catch (AssertionError ae) {
            log.error("**** Assertion Error analyzing " + flow.classFlow.name + "." + flow.name);
            log.error("Basic block " + this);
            log.error("i = " + i);
            log.error("Frame: " + frame);
            throw ae;
        }

    }

    /*
       private boolean checkReceiverType(Value v, MethodInsnNode min) {
    String t = v.getTypeDesc();
    if (t == D_NULL) {
        return true;
    }
    t = TypeDesc.getInternalName(t);
    return detector().getPausableStatus(t, min.name, min.desc)  != Detector.METHOD_NOT_FOUND;
       }
    */
    public boolean isCatchHandler() {
        return caughtExceptionType != null;
    }

    void mergeSuccessors(Frame frame) {
        for (BasicBlock s : successors) {
            s.merge(frame, false);
        }
    }

    /**
     * @param inframe
     * @param localsOnly
     */
    void merge(Frame inframe, boolean localsOnly) {
        boolean enqueue = true;
        if (startFrame == null) {
            startFrame = inframe.dup();
        } else {
            Frame ret;
            // Absorb only those local vars dictacted by usage.in.
            ret = startFrame.merge(inframe, localsOnly, usage);
            if (ret == startFrame) { // no change
                enqueue = false;
            } else {
                startFrame = ret;
            }
        }
        if (enqueue) {
            flow.enqueue(this);
        }
    }

    public void chooseCatchHandlers(ArrayList<Handler> handlerList) {
        for (Handler h : handlerList) {
            if (this == h.catchBB) {
                // This bb is one of the catch handlers
                caughtExceptionType = TypeDesc.getInterned((h.type == null ? THROWABLE_CLASS : h.type));
            } else {
                Range ri = Range.intersect(startPos, endPos, h.from, h.to);
                if (ri != null) {
                    handlers.add(new Handler(ri.from, ri.to, h.type, h.catchBB));
                }
            }
        }
    }

    public AbstractInsnNode getInstruction(int pos) {
        return (AbstractInsnNode) flow.instructions.get(pos);
    }

    public boolean flowVarUsage() {
        // for live var analysis, treat catch handlers as successors too.
        if (succUsage == null) {
            succUsage = new ArrayList<Usage>(successors.size() + handlers.size());
            for (BasicBlock succ : successors) {
                succUsage.add(succ.usage);
            }
            for (Handler h : handlers) {
                succUsage.add(h.catchBB.usage);
            }
        }
        return usage.evalLiveIn(succUsage);
    }

    /**
     * This basic block's last instruction is JSR. This method initiates a
     * subgraph traversal to identify the called subroutine's boundaries and to
     * make all encountered RET instructions point back to this BB's follower,
     * in essence turning it to a goto. The reason for not actually turning it
     * into a GOTO is that if we don't find any pausable methods in a
     * subroutine, then during code generation we'll simply use the original
     * code. The duplication is still required for flow analysis.
     * 
     * The VM spec is fuzzy on what constitutes the boundaries of a subroutine.
     * We consider the following situations invalid, even though the verifier is
     * ok with it: (a) looping back to itself (b) encountering xRETURN in a subroutine
     * 
     * inline() traverses the graph creating copies of BasicBlocks and labels
     * and keeps a mapping between the old and the new. In the second round, it
     * copies instructions translating any that have labels (branch and switch
     * instructions).
     * 
     * @return mapping of orig basic blocks to new.
     * 
     */
    ArrayList<BasicBlock> inline() throws KilimException {
        HashMap<BasicBlock, BasicBlock> bbCopyMap = null;
        HashMap<LabelNode, LabelNode> labelCopyMap = null;
        BasicBlock targetBB = successors.get(0);
        LabelNode returnToLabel = flow.getOrCreateLabelAtPos(endPos + 1);
        BasicBlock returnToBB = flow.getOrCreateBasicBlock(returnToLabel);
        boolean isPausableSub = targetBB.hasFlag(PAUSABLE_SUB);

        if (!targetBB.hasFlag(SUBROUTINE_CLAIMED)) {
            // This JSR call gets to claim the subroutine's blocks, so no
            // copying required. If another JSR wants to point to the same
            // subroutine, it'll copy BBs on demand)
            targetBB.setFlag(SUBROUTINE_CLAIMED);
            // Tell the RET blocks about the returnTo address and we are done.
            for (BasicBlock b : targetBB.getSubBlocks()) {
                if (b.lastInstruction() == RET) {
                    assert b.successors.size() == 0 : this.toString();
                    b.addSuccessor(returnToBB);
                }
            }
            return null;
        }
        bbCopyMap = new HashMap<BasicBlock, BasicBlock>(10);
        labelCopyMap = new HashMap<LabelNode, LabelNode>(10);
        successors.clear();
        // first pass
        targetBB.dupBBAndLabels(isPausableSub, bbCopyMap, labelCopyMap, returnToBB);
        addSuccessor(bbCopyMap.get(targetBB));
        // second pass
        return dupCopyContents(isPausableSub, targetBB, returnToBB, bbCopyMap, labelCopyMap);
    }

    void dupBBAndLabels(boolean deepCopy, HashMap<BasicBlock, BasicBlock> bbCopyMap,
            HashMap<LabelNode, LabelNode> labelCopyMap, BasicBlock returnToBB) throws KilimException {

        for (BasicBlock orig : getSubBlocks()) {
            BasicBlock dup = new BasicBlock(flow, orig.startLabel);
            bbCopyMap.put(orig, dup);
            if (deepCopy) {
                // copy labels for each instruction. This copy will be used
                // in dupCopyContents
                for (int i = orig.startPos; i <= orig.endPos; i++) {
                    LabelNode origLabel = flow.getLabelAt(i);
                    if (origLabel != null) {
                        LabelNode l = labelCopyMap.put(origLabel, new LabelNode());
                        assert l == null;
                    }
                }
                // dup.startLabel reset later in dupCopyContents
            }
        }
    }

    static ArrayList<BasicBlock> dupCopyContents(boolean deepCopy, BasicBlock targetBB, BasicBlock returnToBB,
            HashMap<BasicBlock, BasicBlock> bbCopyMap, HashMap<LabelNode, LabelNode> labelCopyMap)
            throws KilimException {

        ArrayList<BasicBlock> newBBs = new ArrayList<BasicBlock>(targetBB.getSubBlocks().size());
        for (BasicBlock orig : targetBB.getSubBlocks()) {
            BasicBlock dup = bbCopyMap.get(orig);
            dup.flags = orig.flags;
            dup.caughtExceptionType = orig.caughtExceptionType;
            dup.startPos = orig.startPos;
            dup.endPos = orig.endPos;
            dup.flow = orig.flow;
            dup.numPredecessors = orig.numPredecessors;
            dup.startFrame = null;
            dup.usage = orig.usage.copy();
            dup.handlers = orig.handlers;
            if (orig.follower != null) {
                dup.follower = bbCopyMap.get(orig.follower);
                if (dup.follower == null) {
                    assert dup.lastInstruction() == RET;
                }
            }
            dup.successors = new ArrayList<BasicBlock>(orig.successors.size());
            if (orig.lastInstruction() == RET) {
                dup.addSuccessor(returnToBB);
            } else {
                for (BasicBlock s : orig.successors) {
                    BasicBlock b = bbCopyMap.get(s);
                    dup.addSuccessor(b);
                }
            }

            if (deepCopy) {
                InsnList extraInsns = new InsnList();
                MethodFlow flow = targetBB.flow;
                InsnList instructions = flow.instructions;
                // copy instructions
                dup.startLabel = labelCopyMap.get(orig.startLabel);
                dup.startPos = instructions.size();
                dup.endPos = dup.startPos + (orig.endPos - orig.startPos);
                // Note: last instruction (@endPos) isn't copied in the loop.
                // If it has labels, a new instruction is generated; either
                // way the last instruction is appended separately.
                int i;
                int newPos = instructions.size();
                int end = orig.endPos;

                // create new labels and instructions
                for (i = orig.startPos; i <= end; i++, newPos++) {
                    LabelNode l = flow.getLabelAt(i);
                    if (l != null) {
                        l = labelCopyMap.get(l);
                        assert l != null;
                        flow.setLabel(newPos, l);
                    }
                    extraInsns.add(instructions.get(i).clone(labelCopyMap));
                }

                // new handlers
                dup.handlers = new ArrayList<Handler>(orig.handlers.size());
                if (orig.handlers.size() > 0) {
                    for (Handler oh : orig.handlers) {
                        Handler h = new Handler(dup.startPos + (oh.from - orig.startPos),
                                dup.endPos + (oh.to - orig.endPos), oh.type, oh.catchBB);
                        dup.handlers.add(h);
                    }
                }
                instructions.add(extraInsns);
            }
            newBBs.add(dup);
        }
        return newBBs;
    }

    public BasicBlock getJSRTarget() {
        return lastInstruction() == JSR ? successors.get(0) : null;
    }

    /*
     * Invoked on the subroutine entry point's BB. Returns all the BBs
     * linked to it.
     */
    public ArrayList<BasicBlock> getSubBlocks() throws KilimException {
        if (subBlocks == null) {
            if (!hasFlag(IS_SUBROUTINE))
                return null;
            subBlocks = new ArrayList<BasicBlock>(10);
            Stack<BasicBlock> stack = new Stack<BasicBlock>();
            this.setFlag(SUB_BLOCK);
            stack.add(this);
            while (!stack.isEmpty()) {
                BasicBlock b = stack.pop();
                subBlocks.add(b);
                if (b.lastInstruction() == JSR) {
                    // add the following block, but not its target
                    BasicBlock follower = b.getFollowingBlock();
                    if (!follower.hasFlag(SUB_BLOCK)) {
                        follower.setFlag(SUB_BLOCK);
                        stack.push(follower);
                    }
                    continue;
                }

                for (BasicBlock succ : b.successors) {
                    if (succ == this) {
                        throw new KilimException("JSRs looping back to themselves are not supported");
                    }
                    if (!succ.hasFlag(SUB_BLOCK)) {
                        succ.setFlag(SUB_BLOCK);
                        stack.push(succ);
                    }
                }
            }
            Collections.sort(subBlocks);
        }
        return subBlocks;
    }

    BasicBlock getFollowingBlock() {
        if (follower != null)
            return follower;
        // otherwise we'll return the next block anyway. This is used
        // to get the block following a JSR instruction, even though 
        // it is not a follower in the control flow sense.
        LabelNode l = flow.getLabelAt(endPos + 1);
        assert l != null : "No block follows this block: " + this;
        return flow.getBasicBlock(l);
    }

    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer(200);
        sb.append("\n========== BB #").append(id).append("[").append(System.identityHashCode(this)).append("]\n");
        sb.append("method: ").append(this.flow.name).append(this.flow.desc).append("\n");
        sb.append("start = ").append(startPos).append(",end = ").append(endPos).append('\n').append("Successors:");
        if (successors.isEmpty())
            sb.append(" None");
        else {
            for (int i = 0; i < successors.size(); i++) {
                BasicBlock succ = successors.get(i);
                sb.append(" ").append(succ.id).append("[").append(System.identityHashCode(succ)).append("]");
            }
        }
        sb.append("\nHandlers:");
        if (handlers.isEmpty())
            sb.append(" None");
        else {
            for (int i = 0; i < handlers.size(); i++) {
                sb.append(" ").append(handlers.get(i).catchBB.id);
            }
        }
        sb.append("\nStart frame:\n").append(startFrame);
        sb.append("\nUsage: ").append(usage);
        return sb.toString();
    }

    public boolean isPausable() {
        return hasFlag(PAUSABLE);
    }

    void setId(int aid) {
        id = aid;
    }

    /*
     * If any BB belonging to a subroutine makes a pausable
     * block, it taints all the blocks within the subroutine's
     * purview as PAUSABLE_SUB
     */
    void checkPausableJSR() throws KilimException {
        BasicBlock sub = getJSRTarget();
        boolean isPausableJSR = false;
        if (sub != null) {
            ArrayList<BasicBlock> subBlocks = sub.getSubBlocks();
            for (BasicBlock b : subBlocks) {
                if (b.hasFlag(PAUSABLE)) {
                    isPausableJSR = true;
                    break;
                }
            }
            if (isPausableJSR) {
                for (BasicBlock b : subBlocks) {
                    b.setFlag(PAUSABLE_SUB);
                }
            }
        }
    }

    void changeJSR_RET_toGOTOs() throws KilimException {
        int lastInsn = getInstruction(endPos).getOpcode();
        if (lastInsn == JSR) {
            BasicBlock targetBB = successors.get(0);
            if (!targetBB.hasFlag(PAUSABLE_SUB))
                return;
            changeLastInsnToGOTO(targetBB.startLabel);
            successors.clear();
            successors.add(targetBB);

            // change the first ASTORE instruction in targetBB to a NOP
            assert targetBB.getInstruction(targetBB.startPos).getOpcode() == ASTORE;
            targetBB.setInstruction(targetBB.startPos, new NopInsn());
            targetBB.unsetFlag(IS_SUBROUTINE);
        } else if (lastInsn == RET && hasFlag(PAUSABLE_SUB)) {
            changeLastInsnToGOTO(successors.get(0).startLabel);
        }
    }

    void setInstruction(int pos, AbstractInsnNode insn) {
        flow.instructions.set(getInstruction(pos), insn);
    }

    void changeLastInsnToGOTO(LabelNode label) {
        setInstruction(endPos, new JumpInsnNode(GOTO, label));
    }

    public boolean isGetCurrentTask() {
        AbstractInsnNode ain = getInstruction(startPos);
        if (ain.getOpcode() == INVOKESTATIC) {
            MethodInsnNode min = (MethodInsnNode) ain;
            return min.owner.equals(TASK_CLASS) && min.name.equals("getCurrentTask");
        }
        return false;
    }

    boolean isInitialized() {
        return startPos >= 0 && endPos >= 0;
    }
}

class BBComparator implements Comparator<BasicBlock> {
    public int compare(BasicBlock o1, BasicBlock o2) {
        if (o1.id == o2.id) {
            return 0;
        }
        return o1.id < o2.id ? -1 : +1;
    }
}