edu.mit.streamjit.util.bytecode.MethodResolver.java Source code

Java tutorial

Introduction

Here is the source code for edu.mit.streamjit.util.bytecode.MethodResolver.java

Source

/*
 * Copyright (c) 2013-2014 Massachusetts Institute of Technology
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package edu.mit.streamjit.util.bytecode;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import edu.mit.streamjit.util.bytecode.insts.ArrayLengthInst;
import edu.mit.streamjit.util.bytecode.insts.ArrayLoadInst;
import edu.mit.streamjit.util.bytecode.insts.ArrayStoreInst;
import edu.mit.streamjit.util.bytecode.insts.BinaryInst;
import edu.mit.streamjit.util.bytecode.insts.BranchInst;
import edu.mit.streamjit.util.bytecode.insts.CallInst;
import edu.mit.streamjit.util.bytecode.insts.CastInst;
import edu.mit.streamjit.util.bytecode.insts.InstanceofInst;
import edu.mit.streamjit.util.bytecode.insts.JumpInst;
import edu.mit.streamjit.util.bytecode.insts.LoadInst;
import edu.mit.streamjit.util.bytecode.insts.NewArrayInst;
import edu.mit.streamjit.util.bytecode.insts.PhiInst;
import edu.mit.streamjit.util.bytecode.insts.ReturnInst;
import edu.mit.streamjit.util.bytecode.insts.StoreInst;
import edu.mit.streamjit.util.bytecode.insts.SwitchInst;
import edu.mit.streamjit.util.bytecode.insts.ThrowInst;
import edu.mit.streamjit.util.bytecode.types.ArrayType;
import edu.mit.streamjit.util.bytecode.types.MethodType;
import edu.mit.streamjit.util.bytecode.types.ReferenceType;
import edu.mit.streamjit.util.bytecode.types.ReturnType;
import edu.mit.streamjit.util.bytecode.types.Type;
import edu.mit.streamjit.util.bytecode.types.TypeFactory;
import edu.mit.streamjit.util.bytecode.types.VoidType;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.TreeSet;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.FrameNode;
import org.objectweb.asm.tree.IincInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.LookupSwitchInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.MultiANewArrayInsnNode;
import org.objectweb.asm.tree.TableSwitchInsnNode;
import org.objectweb.asm.tree.TryCatchBlockNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;

/**
 * Resolves methods.
 *
 * This class assumes it's parsing valid bytecode, so it asserts rather than
 * throws on simple checks like "aload_0 is loading a reference type".
 * @author Jeffrey Bosboom <jbosboom@csail.mit.edu>
 * @since 4/12/2013
 */
public final class MethodResolver {
    public static void resolve(Method m) {
        try {
            new MethodResolver(m).resolve();
        } catch (RuntimeException ex) {
            throw new RuntimeException("while resolving " + m.toString() + " from " + m.getParent().toString(), ex);
        }
    }

    private final Method method;
    private final MethodNode methodNode;
    private final List<BBInfo> blocks = new ArrayList<>();
    private final Module module;
    private final TypeFactory typeFactory;
    /**
     * If we're resolving a constructor, this is the uninitializedThis value.
     */
    private final UninitializedValue uninitializedThis;
    /**
     * Maps 'new' instructions to the uninitialized values representing the
     * uninitialized object instances they create.
     */
    private final Map<AbstractInsnNode, UninitializedValue> newValues = new IdentityHashMap<>();
    /**
     * Used for generating sequential names (e.g., uninitialized object names).
     */
    private int counter = 1;

    private MethodResolver(Method m) {
        this.method = m;
        this.module = method.getParent().getParent();
        this.typeFactory = module.types();
        try {
            this.methodNode = MethodNodeBuilder.buildMethodNode(method);
        } catch (IOException | NoSuchMethodException ex) {
            throw new RuntimeException(ex);
        }
        if (m.isConstructor())
            this.uninitializedThis = new UninitializedValue(typeFactory.getType(m.getParent()),
                    "uninitializedThis");
        else
            this.uninitializedThis = null;
    }

    private void resolve() {
        findBlockBoundaries();
        nameArguments();

        //Process blocks such that at least one predecessor has already been
        //visited.  (We only process a block once; we add phi instructions when
        //frame merging and replace uses of the previous values in the block.)
        Set<BBInfo> visited = new HashSet<>();
        Queue<BBInfo> worklist = new ArrayDeque<>();
        worklist.add(blocks.get(0));
        visited.add(blocks.get(0));
        while (!worklist.isEmpty()) {
            BBInfo block = worklist.remove();
            buildInstructions(block);
            for (BasicBlock b : block.block.successors()) {
                for (BBInfo bi : blocks)
                    if (bi.block == b) {
                        if (!visited.contains(bi)) {
                            worklist.add(bi);
                            visited.add(bi);
                        }
                        break;
                    }
            }
        }

        //We don't parse exception handlers, but we record them as visited for
        //the purposes of the following assertion.
        for (TryCatchBlockNode handler : methodNode.tryCatchBlocks)
            visited.add(blockByInsn(handler.handler));

        //Assert there are no statically dead blocks.
        assert visited.size() == blocks.size();

        DeadCodeElimination.eliminateDeadCode(method);
    }

    private void nameArguments() {
        //Labels before and after all real instructions.
        List<LabelNode> beginLabels = new ArrayList<>(), endLabels = new ArrayList<>();
        for (AbstractInsnNode n = methodNode.instructions.getFirst(); n != null
                && n.getOpcode() == -1; n = n.getNext())
            if (n instanceof LabelNode)
                beginLabels.add((LabelNode) n);
        for (AbstractInsnNode n = methodNode.instructions.getLast(); n != null
                && n.getOpcode() == -1; n = n.getPrevious())
            if (n instanceof LabelNode)
                endLabels.add((LabelNode) n);

        //Get the initial frame state, which has the arguments in the proper
        //locals positions, and set names.
        Value[] locals = blocks.get(0).entryState.locals;
        for (LocalVariableNode lvn : methodNode.localVariables)
            if (beginLabels.contains(lvn.start) && endLabels.contains(lvn.end)
                    && (!method.isConstructor() || lvn.index != 0))
                locals[lvn.index].setName(lvn.name);
    }

    private void findBlockBoundaries() {
        InsnList insns = methodNode.instructions;
        //We find the indices of any block-ending instruction and of any jump
        //target, sort, remove duplicates, then use pairs to define blocks. Note
        //these are end-exclusive indices, thus one after the block-enders, but
        //right on the jump targets (they're one-past-the-end of the preceding
        //block).
        List<Integer> indices = new ArrayList<>();
        indices.add(0);
        for (int i = 0; i < insns.size(); ++i) {
            AbstractInsnNode insn = insns.get(i);
            int opcode = insn.getOpcode();
            //Terminator opcodes end blocks.
            if (insn instanceof JumpInsnNode || insn instanceof LookupSwitchInsnNode
                    || insn instanceof TableSwitchInsnNode || opcode == Opcodes.ATHROW || opcode == Opcodes.IRETURN
                    || opcode == Opcodes.LRETURN || opcode == Opcodes.FRETURN || opcode == Opcodes.DRETURN
                    || opcode == Opcodes.ARETURN || opcode == Opcodes.RETURN) {
                indices.add(i + 1);
            }
            //Jump targets of this instruction end blocks.
            if (insn instanceof JumpInsnNode)
                indices.add(insns.indexOf(((JumpInsnNode) insn).label));
            else if (insn instanceof LookupSwitchInsnNode) {
                indices.add(insns.indexOf(((LookupSwitchInsnNode) insn).dflt));
                for (Object label : ((LookupSwitchInsnNode) insn).labels)
                    indices.add(insns.indexOf((LabelNode) label));
            } else if (insn instanceof TableSwitchInsnNode) {
                indices.add(insns.indexOf(((TableSwitchInsnNode) insn).dflt));
                for (Object label : ((TableSwitchInsnNode) insn).labels)
                    indices.add(insns.indexOf((LabelNode) label));
            }

            //While we're scanning the instructions, make the UninitializedValue
            //values for 'new' opcodes.
            if (opcode == Opcodes.NEW) {
                Klass k = getKlassByInternalName(((TypeInsnNode) insn).desc);
                ReferenceType t = typeFactory.getReferenceType(k);
                newValues.put(insn, new UninitializedValue(t, "new" + (counter++)));
            }
        }

        //Remove duplicates and sort via TreeSet.
        indices = new ArrayList<>(new TreeSet<>(indices));
        for (int i = 1; i < indices.size(); ++i)
            blocks.add(new BBInfo(indices.get(i - 1), indices.get(i), i - 1));
    }

    private void buildInstructions(BBInfo block) {
        FrameState frame = block.entryState.copy();
        for (int i = block.start; i < block.end; ++i) {
            AbstractInsnNode insn = methodNode.instructions.get(i);
            if (insn.getOpcode() == -1)
                continue;//pseudo-instruction node
            if (insn instanceof FieldInsnNode)
                interpret((FieldInsnNode) insn, frame, block);
            else if (insn instanceof IincInsnNode)
                interpret((IincInsnNode) insn, frame, block);
            else if (insn instanceof InsnNode)
                interpret((InsnNode) insn, frame, block);
            else if (insn instanceof IntInsnNode)
                interpret((IntInsnNode) insn, frame, block);
            else if (insn instanceof InvokeDynamicInsnNode)
                interpret((InvokeDynamicInsnNode) insn, frame, block);
            else if (insn instanceof JumpInsnNode)
                interpret((JumpInsnNode) insn, frame, block);
            else if (insn instanceof LdcInsnNode)
                interpret((LdcInsnNode) insn, frame, block);
            else if (insn instanceof LookupSwitchInsnNode)
                interpret((LookupSwitchInsnNode) insn, frame, block);
            else if (insn instanceof MethodInsnNode)
                interpret((MethodInsnNode) insn, frame, block);
            else if (insn instanceof MultiANewArrayInsnNode)
                interpret((MultiANewArrayInsnNode) insn, frame, block);
            else if (insn instanceof TableSwitchInsnNode)
                interpret((TableSwitchInsnNode) insn, frame, block);
            else if (insn instanceof TypeInsnNode)
                interpret((TypeInsnNode) insn, frame, block);
            else if (insn instanceof VarInsnNode)
                interpret((VarInsnNode) insn, frame, block);
        }

        //If the block doesn't have a TerminatorInst, add a JumpInst to the
        //fallthrough block.  (This occurs when blocks begin due to being a
        //jump target rather than due to a terminator opcode.)
        if (block.block.getTerminator() == null)
            block.block.instructions().add(new JumpInst(blocks.get(blocks.indexOf(block) + 1).block));

        for (BasicBlock b : block.block.successors())
            for (BBInfo bi : blocks)
                if (bi.block == b) {
                    merge(block, frame, bi);
                    break;
                }
    }

    private void interpret(FieldInsnNode insn, FrameState frame, BBInfo block) {
        Klass k = getKlassByInternalName(insn.owner);
        Field f = k.getField(insn.name);
        switch (insn.getOpcode()) {
        case Opcodes.GETSTATIC:
            LoadInst li = new LoadInst(f);
            block.block.instructions().add(li);
            frame.stack.push(li);
            break;
        case Opcodes.GETFIELD:
            LoadInst li2 = new LoadInst(f, frame.stack.pop());
            block.block.instructions().add(li2);
            frame.stack.push(li2);
            break;
        case Opcodes.PUTSTATIC:
            StoreInst si = new StoreInst(f, frame.stack.pop());
            block.block.instructions().add(si);
            break;
        case Opcodes.PUTFIELD:
            StoreInst si2 = new StoreInst(f, frame.stack.pop(), frame.stack.pop());
            block.block.instructions().add(si2);
            break;
        default:
            throw new UnsupportedOperationException("" + insn.getOpcode());
        }
    }

    private void interpret(IincInsnNode insn, FrameState frame, BBInfo block) {
        switch (insn.getOpcode()) {
        case Opcodes.IINC:
            Constant<Integer> c = module.constants().getConstant(insn.incr);
            BinaryInst bi = new BinaryInst(frame.locals[insn.var], BinaryInst.Operation.ADD, c);
            block.block.instructions().add(bi);
            frame.locals[insn.var] = bi;
            break;
        default:
            throw new UnsupportedOperationException("" + insn.getOpcode());
        }
    }

    private void interpret(InsnNode insn, FrameState frame, BBInfo block) {
        ReturnType returnType = block.block.getParent().getType().getReturnType();
        switch (insn.getOpcode()) {
        case Opcodes.NOP:
            break;
        //<editor-fold defaultstate="collapsed" desc="Stack manipulation opcodes (pop, dup, swap)">
        case Opcodes.POP:
            assert frame.stack.peek().getType().getCategory() == 1;
            frame.stack.pop();
            break;
        case Opcodes.POP2:
            final int[][][] pop2Permutations = { { { 1, 1 }, {} }, { { 2 }, {} } };
            conditionallyPermute(frame, pop2Permutations);
            break;
        case Opcodes.DUP:
            final int[][][] dupPermutations = { { { 1 }, { 1, 1 } } };
            conditionallyPermute(frame, dupPermutations);
            break;
        case Opcodes.DUP_X1:
            final int[][][] dup_x1Permutations = { { { 1, 1 }, { 1, 2, 1 } } };
            conditionallyPermute(frame, dup_x1Permutations);
            break;
        case Opcodes.DUP_X2:
            final int[][][] dup_x2Permutations = { { { 1, 1, 1 }, { 1, 3, 2, 1 } }, { { 1, 2 }, { 1, 2, 1 } } };
            conditionallyPermute(frame, dup_x2Permutations);
            break;
        case Opcodes.DUP2:
            final int[][][] dup2Permutations = { { { 1, 1 }, { 2, 1, 2, 1 } }, { { 2 }, { 1, 1 } } };
            conditionallyPermute(frame, dup2Permutations);
            break;
        case Opcodes.DUP2_X1:
            final int[][][] dup2_x1Permutations = { { { 1, 1, 1 }, { 2, 1, 3, 2, 1 } }, { { 2, 1 }, { 1, 2, 1 } } };
            conditionallyPermute(frame, dup2_x1Permutations);
            break;
        case Opcodes.DUP2_X2:
            final int[][][] dup2_x2Permutations = { { { 1, 1, 1, 1 }, { 2, 1, 4, 3, 2, 1 } },
                    { { 2, 1, 1 }, { 1, 3, 2, 1 } }, { { 3, 2, 1 }, { 2, 1, 3, 2, 1 } },
                    { { 2, 2 }, { 1, 2, 1 } } };
            conditionallyPermute(frame, dup2_x2Permutations);
            break;
        case Opcodes.SWAP:
            final int[][][] swapPermutations = { { { 1, 1 }, { 1, 2 } } };
            conditionallyPermute(frame, swapPermutations);
            break;
        //</editor-fold>
        //<editor-fold defaultstate="collapsed" desc="Constant-stacking opcodes (iconst_0, etc.; see also bipush, sipush)">
        case Opcodes.ACONST_NULL:
            frame.stack.push(module.constants().getNullConstant());
            break;
        case Opcodes.ICONST_M1:
            frame.stack.push(module.constants().getSmallestIntConstant(-1));
            break;
        case Opcodes.ICONST_0:
            frame.stack.push(module.constants().getSmallestIntConstant(0));
            break;
        case Opcodes.ICONST_1:
            frame.stack.push(module.constants().getSmallestIntConstant(1));
            break;
        case Opcodes.ICONST_2:
            frame.stack.push(module.constants().getSmallestIntConstant(2));
            break;
        case Opcodes.ICONST_3:
            frame.stack.push(module.constants().getSmallestIntConstant(3));
            break;
        case Opcodes.ICONST_4:
            frame.stack.push(module.constants().getSmallestIntConstant(4));
            break;
        case Opcodes.ICONST_5:
            frame.stack.push(module.constants().getSmallestIntConstant(5));
            break;
        case Opcodes.LCONST_0:
            frame.stack.push(module.constants().getConstant(0L));
            break;
        case Opcodes.LCONST_1:
            frame.stack.push(module.constants().getConstant(1L));
            break;
        case Opcodes.FCONST_0:
            frame.stack.push(module.constants().getConstant(0f));
            break;
        case Opcodes.FCONST_1:
            frame.stack.push(module.constants().getConstant(1f));
            break;
        case Opcodes.FCONST_2:
            frame.stack.push(module.constants().getConstant(2f));
            break;
        case Opcodes.DCONST_0:
            frame.stack.push(module.constants().getConstant(0d));
            break;
        case Opcodes.DCONST_1:
            frame.stack.push(module.constants().getConstant(1d));
            break;
        //</editor-fold>
        //<editor-fold defaultstate="collapsed" desc="Return opcodes">
        case Opcodes.IRETURN:
            assert returnType.isSubtypeOf(typeFactory.getType(int.class));
            assert frame.stack.peek().getType().isSubtypeOf(returnType);
            block.block.instructions().add(new ReturnInst(returnType, frame.stack.pop()));
            break;
        case Opcodes.LRETURN:
            assert returnType.isSubtypeOf(typeFactory.getType(long.class));
            assert frame.stack.peek().getType().isSubtypeOf(returnType);
            block.block.instructions().add(new ReturnInst(returnType, frame.stack.pop()));
            break;
        case Opcodes.FRETURN:
            assert returnType.isSubtypeOf(typeFactory.getType(float.class));
            assert frame.stack.peek().getType().isSubtypeOf(returnType);
            block.block.instructions().add(new ReturnInst(returnType, frame.stack.pop()));
            break;
        case Opcodes.DRETURN:
            assert returnType.isSubtypeOf(typeFactory.getType(double.class));
            assert frame.stack.peek().getType().isSubtypeOf(returnType);
            block.block.instructions().add(new ReturnInst(returnType, frame.stack.pop()));
            break;
        case Opcodes.ARETURN:
            assert returnType.isSubtypeOf(typeFactory.getType(Object.class));
            assert frame.stack.peek().getType().isSubtypeOf(returnType);
            block.block.instructions().add(new ReturnInst(returnType, frame.stack.pop()));
            break;
        case Opcodes.RETURN:
            assert returnType instanceof VoidType || method.isConstructor();
            block.block.instructions().add(new ReturnInst(typeFactory.getVoidType()));
            break;
        //</editor-fold>
        //<editor-fold defaultstate="collapsed" desc="Unary math opcodes (negation)">
        //Unary minus is rendered as a multiplication by -1.  (The obvious
        //other choice, subtraction from 0, is not equivalent for floats and
        //doubles due to negative zero.)
        case Opcodes.INEG:
            frame.stack.push(module.constants().getSmallestIntConstant(-1));
            binary(BinaryInst.Operation.MUL, frame, block);
            break;
        case Opcodes.LNEG:
            frame.stack.push(module.constants().getConstant(-1L));
            binary(BinaryInst.Operation.MUL, frame, block);
            break;
        case Opcodes.FNEG:
            frame.stack.push(module.constants().getConstant(-1f));
            binary(BinaryInst.Operation.MUL, frame, block);
            break;
        case Opcodes.DNEG:
            frame.stack.push(module.constants().getConstant(-1d));
            binary(BinaryInst.Operation.MUL, frame, block);
            break;
        //</editor-fold>
        //<editor-fold defaultstate="collapsed" desc="Binary math opcodes">
        case Opcodes.IADD:
        case Opcodes.LADD:
        case Opcodes.FADD:
        case Opcodes.DADD:
            binary(BinaryInst.Operation.ADD, frame, block);
            break;
        case Opcodes.ISUB:
        case Opcodes.LSUB:
        case Opcodes.FSUB:
        case Opcodes.DSUB:
            binary(BinaryInst.Operation.SUB, frame, block);
            break;
        case Opcodes.IMUL:
        case Opcodes.LMUL:
        case Opcodes.FMUL:
        case Opcodes.DMUL:
            binary(BinaryInst.Operation.MUL, frame, block);
            break;
        case Opcodes.IDIV:
        case Opcodes.LDIV:
        case Opcodes.FDIV:
        case Opcodes.DDIV:
            binary(BinaryInst.Operation.DIV, frame, block);
            break;
        case Opcodes.IREM:
        case Opcodes.LREM:
        case Opcodes.FREM:
        case Opcodes.DREM:
            binary(BinaryInst.Operation.REM, frame, block);
            break;
        case Opcodes.ISHL:
        case Opcodes.LSHL:
            binary(BinaryInst.Operation.SHL, frame, block);
            break;
        case Opcodes.ISHR:
        case Opcodes.LSHR:
            binary(BinaryInst.Operation.SHR, frame, block);
            break;
        case Opcodes.IUSHR:
        case Opcodes.LUSHR:
            binary(BinaryInst.Operation.USHR, frame, block);
            break;
        case Opcodes.IAND:
        case Opcodes.LAND:
            binary(BinaryInst.Operation.AND, frame, block);
            break;
        case Opcodes.IOR:
        case Opcodes.LOR:
            binary(BinaryInst.Operation.OR, frame, block);
            break;
        case Opcodes.IXOR:
        case Opcodes.LXOR:
            binary(BinaryInst.Operation.XOR, frame, block);
            break;
        case Opcodes.LCMP:
        case Opcodes.FCMPL:
        case Opcodes.DCMPL:
            binary(BinaryInst.Operation.CMP, frame, block);
            break;
        case Opcodes.FCMPG:
        case Opcodes.DCMPG:
            binary(BinaryInst.Operation.CMPG, frame, block);
            break;
        //</editor-fold>
        //<editor-fold defaultstate="collapsed" desc="Primitive casts">
        case Opcodes.I2L:
            cast(int.class, long.class, frame, block);
            break;
        case Opcodes.I2F:
            cast(int.class, float.class, frame, block);
            break;
        case Opcodes.I2D:
            cast(int.class, double.class, frame, block);
            break;
        case Opcodes.L2I:
            cast(long.class, int.class, frame, block);
            break;
        case Opcodes.L2F:
            cast(long.class, float.class, frame, block);
            break;
        case Opcodes.L2D:
            cast(long.class, double.class, frame, block);
            break;
        case Opcodes.F2I:
            cast(float.class, int.class, frame, block);
            break;
        case Opcodes.F2L:
            cast(float.class, long.class, frame, block);
            break;
        case Opcodes.F2D:
            cast(float.class, double.class, frame, block);
            break;
        case Opcodes.D2I:
            cast(double.class, int.class, frame, block);
            break;
        case Opcodes.D2L:
            cast(double.class, long.class, frame, block);
            break;
        case Opcodes.D2F:
            cast(double.class, float.class, frame, block);
            break;
        case Opcodes.I2B:
            cast(int.class, byte.class, frame, block);
            break;
        case Opcodes.I2C:
            cast(int.class, char.class, frame, block);
            break;
        case Opcodes.I2S:
            cast(int.class, short.class, frame, block);
            break;
        //</editor-fold>
        //<editor-fold defaultstate="collapsed" desc="Array store opcodes">
        case Opcodes.IASTORE:
        case Opcodes.LASTORE:
        case Opcodes.FASTORE:
        case Opcodes.DASTORE:
        case Opcodes.AASTORE:
        case Opcodes.BASTORE:
        case Opcodes.CASTORE:
        case Opcodes.SASTORE:
            Value data = frame.stack.pop();
            Value index = frame.stack.pop();
            Value array = frame.stack.pop();
            ArrayStoreInst asi = new ArrayStoreInst(array, index, data);
            block.block.instructions().add(asi);
            break;
        //</editor-fold>
        //<editor-fold defaultstate="collapsed" desc="Array load opcodes">
        case Opcodes.IALOAD:
        case Opcodes.LALOAD:
        case Opcodes.FALOAD:
        case Opcodes.DALOAD:
        case Opcodes.AALOAD:
        case Opcodes.BALOAD:
        case Opcodes.CALOAD:
        case Opcodes.SALOAD:
            Value index2 = frame.stack.pop();
            Value array2 = frame.stack.pop();
            ArrayLoadInst ali = new ArrayLoadInst(array2, index2);
            block.block.instructions().add(ali);
            frame.stack.push(ali);
            break;
        //</editor-fold>
        case Opcodes.ARRAYLENGTH:
            ArrayLengthInst lengthInst = new ArrayLengthInst(frame.stack.pop());
            block.block.instructions().add(lengthInst);
            frame.stack.push(lengthInst);
            break;
        case Opcodes.ATHROW:
            block.block.instructions().add(new ThrowInst(frame.stack.pop()));
            break;
        default:
            throw new UnsupportedOperationException("" + insn.getOpcode());
        }
    }

    private void interpret(IntInsnNode insn, FrameState frame, BBInfo block) {
        int operand = insn.operand;
        switch (insn.getOpcode()) {
        case Opcodes.BIPUSH:
        case Opcodes.SIPUSH:
            frame.stack.push(module.constants().getSmallestIntConstant(insn.operand));
            break;
        case Opcodes.NEWARRAY:
            ArrayType t;
            switch (operand) {
            case Opcodes.T_BOOLEAN:
                t = module.types().getArrayType(boolean[].class);
                break;
            case Opcodes.T_BYTE:
                t = module.types().getArrayType(byte[].class);
                break;
            case Opcodes.T_CHAR:
                t = module.types().getArrayType(char[].class);
                break;
            case Opcodes.T_SHORT:
                t = module.types().getArrayType(short[].class);
                break;
            case Opcodes.T_INT:
                t = module.types().getArrayType(int[].class);
                break;
            case Opcodes.T_LONG:
                t = module.types().getArrayType(long[].class);
                break;
            case Opcodes.T_FLOAT:
                t = module.types().getArrayType(float[].class);
                break;
            case Opcodes.T_DOUBLE:
                t = module.types().getArrayType(double[].class);
                break;
            default:
                throw new AssertionError(operand);
            }
            NewArrayInst i = new NewArrayInst(t, frame.stack.pop());
            block.block.instructions().add(i);
            frame.stack.push(i);
            break;
        default:
            throw new UnsupportedOperationException("" + insn.getOpcode());
        }
    }

    private void interpret(InvokeDynamicInsnNode insn, FrameState frame, BBInfo block) {
        switch (insn.getOpcode()) {
        default:
            throw new UnsupportedOperationException("" + insn.getOpcode());
        }
    }

    //<editor-fold defaultstate="collapsed" desc="JumpInsnNode (goto and branches)">
    private static final ImmutableMap<Integer, BranchInst.Sense> OPCODE_TO_SENSE = ImmutableMap.<Integer, BranchInst.Sense>builder()
            .put(Opcodes.IFEQ, BranchInst.Sense.EQ).put(Opcodes.IFNE, BranchInst.Sense.NE)
            .put(Opcodes.IFLT, BranchInst.Sense.LT).put(Opcodes.IFGE, BranchInst.Sense.GE)
            .put(Opcodes.IFGT, BranchInst.Sense.GT).put(Opcodes.IFLE, BranchInst.Sense.LE)
            .put(Opcodes.IF_ICMPEQ, BranchInst.Sense.EQ).put(Opcodes.IF_ICMPNE, BranchInst.Sense.NE)
            .put(Opcodes.IF_ICMPLT, BranchInst.Sense.LT).put(Opcodes.IF_ICMPGE, BranchInst.Sense.GE)
            .put(Opcodes.IF_ICMPGT, BranchInst.Sense.GT).put(Opcodes.IF_ICMPLE, BranchInst.Sense.LE)
            .put(Opcodes.IF_ACMPEQ, BranchInst.Sense.EQ).put(Opcodes.IF_ACMPNE, BranchInst.Sense.NE)
            .put(Opcodes.IFNULL, BranchInst.Sense.EQ).put(Opcodes.IFNONNULL, BranchInst.Sense.NE).build();

    private void interpret(JumpInsnNode insn, FrameState frame, BBInfo block) {
        //All JumpInsnNodes have a target.  Find it.
        BBInfo target = blockByInsn(insn.label);
        assert target != null;

        if (insn.getOpcode() == Opcodes.GOTO) {
            block.block.instructions().add(new JumpInst(target.block));
            return;
        } else if (insn.getOpcode() == Opcodes.JSR)
            throw new UnsupportedOperationException("jsr not supported; upgrade to Java 6-era class files");

        //Remaining opcodes are branches.
        BBInfo fallthrough = blocks.get(blocks.indexOf(block) + 1);
        BranchInst.Sense sense = OPCODE_TO_SENSE.get(insn.getOpcode());
        //The second operand may come from the stack or may be a constant 0 or null.
        Value right;
        switch (insn.getOpcode()) {
        case Opcodes.IFEQ:
        case Opcodes.IFNE:
        case Opcodes.IFLT:
        case Opcodes.IFGE:
        case Opcodes.IFGT:
        case Opcodes.IFLE:
            right = module.constants().getConstant(0);
            break;
        case Opcodes.IFNULL:
        case Opcodes.IFNONNULL:
            right = module.constants().getNullConstant();
            break;
        case Opcodes.IF_ICMPEQ:
        case Opcodes.IF_ICMPNE:
        case Opcodes.IF_ICMPLT:
        case Opcodes.IF_ICMPGE:
        case Opcodes.IF_ICMPGT:
        case Opcodes.IF_ICMPLE:
        case Opcodes.IF_ACMPEQ:
        case Opcodes.IF_ACMPNE:
            right = frame.stack.pop();
            break;
        default:
            throw new AssertionError("Can't happen! Branch opcode missing? " + insn.getOpcode());
        }
        //First operand always comes from the stack.
        Value left = frame.stack.pop();
        block.block.instructions().add(new BranchInst(left, sense, right, target.block, fallthrough.block));
    }

    //</editor-fold>
    private void interpret(LdcInsnNode insn, FrameState frame, BBInfo block) {
        assert insn.getOpcode() == Opcodes.LDC;
        ConstantFactory cf = module.constants();
        Object c = insn.cst;
        if (c instanceof Integer)
            frame.stack.push(cf.getSmallestIntConstant((Integer) c));
        else if (c instanceof Long)
            frame.stack.push(cf.getConstant((Long) c));
        else if (c instanceof Float)
            frame.stack.push(cf.getConstant((Float) c));
        else if (c instanceof Double)
            frame.stack.push(cf.getConstant((Double) c));
        else if (c instanceof String)
            frame.stack.push(cf.getConstant((String) c));
        else if (c instanceof org.objectweb.asm.Type) {
            org.objectweb.asm.Type t = (org.objectweb.asm.Type) c;
            Constant<Class<?>> d = cf.getConstant(getKlassByInternalName(t.getInternalName()).getBackingClass());
            frame.stack.push(d);
        } else
            throw new AssertionError(c);
    }

    private void interpret(LookupSwitchInsnNode insn, FrameState frame, BBInfo block) {
        assert insn.getOpcode() == Opcodes.LOOKUPSWITCH;
        ConstantFactory cf = module.constants();
        SwitchInst inst = new SwitchInst(frame.stack.pop(), blockByInsn(insn.dflt).block);
        for (int i = 0; i < insn.keys.size(); ++i)
            inst.put(cf.getConstant((Integer) insn.keys.get(i)), blockByInsn((LabelNode) insn.labels.get(i)).block);
        block.block.instructions().add(inst);
    }

    private void interpret(MethodInsnNode insn, FrameState frame, BBInfo block) {
        Klass k = getKlassByInternalName(insn.owner);
        MethodType mt = typeFactory.getMethodType(insn.desc);
        Method m;
        if (insn.getOpcode() == Opcodes.INVOKESTATIC)
            m = k.getMethod(insn.name, mt);
        else if (insn.getOpcode() == Opcodes.INVOKESPECIAL && insn.name.equals("<init>")) {
            //TODO: invokespecial rules are more complex than this
            //We consider constructors to return their type.
            mt = mt.withReturnType(typeFactory.getType(k));
            m = k.getMethod(insn.name, mt);
        } else {
            //The receiver argument is not in the descriptor, but we represent it in
            //the IR type system.
            if (insn.getOpcode() != Opcodes.INVOKESTATIC)
                mt = mt.prependArgument(typeFactory.getRegularType(k));
            m = k.getMethodByVirtual(insn.name, mt);
        }
        CallInst inst = new CallInst(m);
        block.block.instructions().add(inst);

        //Args are pushed from left-to-right, popped from right-to-left.
        for (int i = mt.getParameterTypes().size() - 1; i >= 0; --i)
            inst.setArgument(i, frame.stack.pop());

        //If we called a ctor, we have an uninit object on the stack.  Replace
        //it with the constructed object, or with uninitializedThis if we're a
        //ctor ourselves and we called a superclass ctor.
        if (m.isConstructor()) {
            //TODO: always a direct superclass? seems likely but unsure.
            Value replacement = method.isConstructor() && method.getParent().getSuperclass().equals(k)
                    ? uninitializedThis
                    : inst;
            Value toBeReplaced = frame.stack.pop();
            //         assert toBeReplaced instanceof UninitializedValue;
            frame.replace(toBeReplaced, replacement);
        } else if (!(mt.getReturnType() instanceof VoidType))
            frame.stack.push(inst);
    }

    private void interpret(MultiANewArrayInsnNode insn, FrameState frame, BBInfo block) {
        assert insn.getOpcode() == Opcodes.MULTIANEWARRAY;
        Klass k = getKlassByInternalName(insn.desc);
        Value[] dims = new Value[insn.dims];
        for (int i = dims.length - 1; i >= 0; --i)
            dims[i] = frame.stack.pop();
        NewArrayInst nai = new NewArrayInst(typeFactory.getArrayType(k), dims);
        block.block.instructions().add(nai);
        frame.stack.push(nai);
    }

    private void interpret(TableSwitchInsnNode insn, FrameState frame, BBInfo block) {
        assert insn.getOpcode() == Opcodes.TABLESWITCH;
        ConstantFactory cf = module.constants();
        SwitchInst inst = new SwitchInst(frame.stack.pop(), blockByInsn(insn.dflt).block);
        for (int i = insn.min; i <= insn.max; ++i)
            inst.put(cf.getConstant(i), blockByInsn(insn.labels.get(i - insn.min)).block);
        block.block.instructions().add(inst);
    }

    private void interpret(TypeInsnNode insn, FrameState frame, BBInfo block) {
        Klass k = getKlassByInternalName(insn.desc);
        ReferenceType t = typeFactory.getReferenceType(k);
        switch (insn.getOpcode()) {
        case Opcodes.NEW:
            UninitializedValue newValue = newValues.get(insn);
            assert newValue != null;
            assert newValue.getType().equals(t);
            frame.stack.push(newValue);
            break;
        case Opcodes.CHECKCAST:
            CastInst c = new CastInst(t, frame.stack.pop());
            block.block.instructions().add(c);
            frame.stack.push(c);
            break;
        case Opcodes.INSTANCEOF:
            InstanceofInst ioi = new InstanceofInst(t, frame.stack.pop());
            block.block.instructions().add(ioi);
            frame.stack.push(ioi);
            break;
        case Opcodes.ANEWARRAY:
            ArrayType at = typeFactory.getArrayType(module.getArrayKlass(k, 1));
            NewArrayInst nai = new NewArrayInst(at, frame.stack.pop());
            block.block.instructions().add(nai);
            frame.stack.push(nai);
            break;
        default:
            throw new UnsupportedOperationException("" + insn.getOpcode());
        }
    }

    private void interpret(VarInsnNode insn, FrameState frame, BBInfo block) {
        int var = insn.var;
        switch (insn.getOpcode()) {
        case Opcodes.ILOAD:
            assert frame.locals[var].getType().isSubtypeOf(typeFactory.getType(int.class));
            frame.stack.push(frame.locals[var]);
            break;
        case Opcodes.LLOAD:
            assert frame.locals[var].getType().isSubtypeOf(typeFactory.getType(long.class));
            frame.stack.push(frame.locals[var]);
            break;
        case Opcodes.FLOAD:
            assert frame.locals[var].getType().isSubtypeOf(typeFactory.getType(float.class));
            frame.stack.push(frame.locals[var]);
            break;
        case Opcodes.DLOAD:
            assert frame.locals[var].getType().isSubtypeOf(typeFactory.getType(double.class));
            frame.stack.push(frame.locals[var]);
            break;
        case Opcodes.ALOAD:
            assert frame.locals[var].getType().isSubtypeOf(typeFactory.getType(Object.class));
            frame.stack.push(frame.locals[var]);
            break;
        case Opcodes.ISTORE:
            assert frame.stack.peek().getType().isSubtypeOf(typeFactory.getType(int.class));
            frame.locals[var] = frame.stack.pop();
            break;
        case Opcodes.LSTORE:
            assert frame.stack.peek().getType().isSubtypeOf(typeFactory.getType(long.class));
            frame.locals[var] = frame.stack.pop();
            break;
        case Opcodes.FSTORE:
            assert frame.stack.peek().getType().isSubtypeOf(typeFactory.getType(float.class));
            frame.locals[var] = frame.stack.pop();
            break;
        case Opcodes.DSTORE:
            assert frame.stack.peek().getType().isSubtypeOf(typeFactory.getType(double.class));
            frame.locals[var] = frame.stack.pop();
            break;
        case Opcodes.ASTORE:
            assert frame.stack.peek().getType().isSubtypeOf(typeFactory.getType(Object.class));
            frame.locals[var] = frame.stack.pop();
            break;
        default:
            throw new UnsupportedOperationException("" + insn.getOpcode());
        }
    }

    private Klass getKlassByInternalName(String internalName) {
        String binaryName = internalName.replace('/', '.');
        Klass k = module.getKlass(binaryName);
        if (k != null)
            return k;

        Class<?> c = null;
        try {
            c = Class.forName(binaryName);
        } catch (ClassNotFoundException ex) {
            throw new RuntimeException(ex);
        }
        return module.getKlass(c);
    }

    //<editor-fold defaultstate="collapsed" desc="Stack manipulation opcodes support">
    /**
     * Conditionally permutes the values on the operand stack in the given
     * frame. The permutations are given as an array of 2-element arrays, the
     * first element of which specifies the condition as a constraint on the
     * categories of the stacked operand types, with the top of the stack
     * beginning at index 0, and the second element of which specifies 1-based
     * indices giving the resulting permutation, with the element at index 0
     * being towards the bottom of the stack (pushed first). (This matches the
     * instruction descriptions in the JVMS.)
     *
     * Strictly speaking, the permutations need not be permutations; they may
     * contain duplicate or dropped indices.
     *
     * Only one permutation will be applied.  If no permutation matches, an
     * AssertionError is thrown.
     *
     * This is used for the implementation of the DUP instruction family.
     * @param frame the frame containing the stack to permute
     * @param permutations the conditional permutations to apply
     */
    private void conditionallyPermute(FrameState frame, int[][][] permutations) {
        for (int[][] permutation : permutations) {
            int[] categories = permutation[0];
            if (Arrays.equals(categories, categoriesOnStack(frame, categories.length))) {
                Value[] v = new Value[categories.length];
                for (int i = 0; i < v.length; ++i)
                    v[i] = frame.stack.pop();
                for (int i : permutation[1])
                    frame.stack.push(v[i - 1]);
                return;
            }
        }
        throw new AssertionError("no permutations matched");
    }

    /**
     * Returns an array containing the categories of the first n values on the
     * operand stack of the given frame.  If there are fewer values, the rest
     * are filled with -1.
     * @param frame the frame to check categories on stack of
     * @return an array of categories
     */
    private int[] categoriesOnStack(FrameState frame, int n) {
        int[] x = new int[n];
        Arrays.fill(x, -1);
        Value[] v = frame.stack.toArray(new Value[0]);
        for (int i = 0; i < v.length && i < x.length; ++i)
            x[i] = v[i].getType().getCategory();
        return x;
    }
    //</editor-fold>

    private void binary(BinaryInst.Operation operation, FrameState frame, BBInfo block) {
        Value right = frame.stack.pop();
        Value left = frame.stack.pop();
        BinaryInst inst = new BinaryInst(left, operation, right);
        block.block.instructions().add(inst);
        frame.stack.push(inst);
    }

    private void cast(Class<?> from, Class<?> to, FrameState frame, BBInfo block) {
        Type targetType = typeFactory.getType(to);
        CastInst c = new CastInst(targetType, frame.stack.pop());
        block.block.instructions().add(c);
        frame.stack.push(c);
    }

    private BBInfo blockByInsn(AbstractInsnNode insn) {
        int targetIdx = methodNode.instructions.indexOf(insn);
        for (BBInfo b : blocks)
            if (b.start <= targetIdx && targetIdx < b.end)
                return b;
        return null;
    }

    /**
     * Merge the given frame state with the entry state of the given block,
     * RAUW-ing as required if phi instructions are inserted.
     * @param p the final frame state of a predecessor
     * @param s the successor block
     */
    private void merge(BBInfo predecessor, FrameState p, BBInfo s) {
        if (s.entryState == null) {
            //If this state didn't have a frame, it has only one predecessor, so
            //just use our state.
            s.entryState = p.copy();
            return;
        }

        //This block has multiple predecessors, so it had a frame, so we gave it
        //phi instructions in its entry state.
        for (int i = 0; i < p.locals.length; ++i) {
            //If we're null, we don't have a definition.
            if (p.locals[i] == null)
                continue;
            //We might not unify with the other predecessors.
            if (!(s.entryState.locals[i] instanceof PhiInst))
                continue;
            //Otherwise, register our definition.
            ((PhiInst) s.entryState.locals[i]).put(predecessor.block, p.locals[i]);
        }
        Iterator<Value> us = p.stack.iterator(), them = s.entryState.stack.iterator();
        while (us.hasNext()) {
            Value theirVal = them.next(), ourVal = us.next();
            if (theirVal instanceof PhiInst)
                ((PhiInst) theirVal).put(predecessor.block, ourVal);
        }
    }

    private final class BBInfo {
        private final BasicBlock block;
        //The index of the first and one-past-the-last instructions.
        private final int start, end;
        private FrameState entryState;
        private final FrameNode frame;

        private BBInfo(int start, int end, int index) {
            this.block = new BasicBlock(method.getParent().getParent(),
                    String.format("%d@%d-%d", index, start, end));
            method.basicBlocks().add(this.block);
            this.start = start;
            this.end = end;
            if (start == 0) { //first block starts with args and empty stack
                this.entryState = new FrameState(methodNode.maxLocals);
                Value[] entryLocals = entryState.locals;
                int i = 0;
                //If the method is a constructor, it begins with an
                //UninitializedThis object in local variable 0.
                //            if (method.isConstructor())
                //               entryLocals[i++] = uninitializedThis;
                for (Argument a : method.arguments()) {
                    entryLocals[i] = a;
                    Type argType = a.getType();
                    i += argType.getCategory();
                }
            }
            this.frame = findOnlyFrameNode();
            if (this.frame != null)
                entryStateFromFrame();
        }

        private FrameNode findOnlyFrameNode() {
            FrameNode f = null;
            for (int i = start; i != end; ++i) {
                AbstractInsnNode insn = methodNode.instructions.get(i);
                if (insn instanceof FrameNode) {
                    assert f == null : f + " " + insn;
                    f = (FrameNode) insn;
                }
            }
            return f;
        }

        private void entryStateFromFrame() {
            this.entryState = new FrameState(methodNode.maxLocals);
            valueArrayFromFrameList(frame.local, entryState.locals, true);
            Value[] stack = new Value[frame.stack.size()];
            valueArrayFromFrameList(frame.stack, stack, false);
            for (Value v : stack)
                entryState.stack.push(v);

            //Attach those PhiInsts.
            for (Value v : this.entryState.locals)
                if (v instanceof PhiInst)
                    block.instructions().add((PhiInst) v);
            for (Value v : this.entryState.stack)
                if (v instanceof PhiInst)
                    block.instructions().add((PhiInst) v);
        }

        private void valueArrayFromFrameList(List<?> frameList, Value[] values, boolean expandCat2Types) {
            int i = 0;
            for (Object o : frameList) {
                if (o instanceof Integer) {
                    Integer t = (Integer) o;
                    if (t.equals(Opcodes.INTEGER))
                        values[i] = new PhiInst(typeFactory.getType(int.class));
                    else if (t.equals(Opcodes.FLOAT))
                        values[i] = new PhiInst(typeFactory.getType(float.class));
                    else if (t.equals(Opcodes.DOUBLE)) {
                        values[i] = new PhiInst(typeFactory.getType(double.class));
                        if (expandCat2Types)
                            ++i; //two local slots
                    } else if (t.equals(Opcodes.LONG)) {
                        values[i] = new PhiInst(typeFactory.getType(long.class));
                        if (expandCat2Types)
                            ++i; //two local slots
                    } else if (t.equals(Opcodes.NULL))
                        values[i] = module.constants().getNullConstant();
                    else if (t.equals(Opcodes.UNINITIALIZED_THIS)) {
                        assert uninitializedThis != null;
                        values[i] = uninitializedThis;
                    }
                    //TOP ignored
                } else if (o instanceof String) {
                    Type t = typeFactory.getType(getKlassByInternalName((String) o));
                    values[i] = new PhiInst(t);
                } else if (o instanceof LabelNode) {
                    AbstractInsnNode newInst = nextRealInstruction((LabelNode) o);
                    assert newInst.getOpcode() == Opcodes.NEW;
                    UninitializedValue v = newValues.get(newInst);
                    assert v != null;
                    values[i] = v;
                }
                ++i;
            }
        }
    }

    private static AbstractInsnNode nextRealInstruction(AbstractInsnNode node) {
        assert node.getOpcode() == -1;
        while (node.getOpcode() == -1)
            node = node.getNext();
        return node;
    }

    private final class FrameState {
        private final Value[] locals;
        private final Deque<Value> stack;

        private FrameState(int localSize) {
            this.locals = new Value[localSize];
            this.stack = new ArrayDeque<>();
        }

        private FrameState copy() {
            FrameState s = new FrameState(locals.length);
            System.arraycopy(locals, 0, s.locals, 0, locals.length);
            Iterables.addAll(s.stack, stack);
            return s;
        }

        private void replace(Value toBeReplaced, Value replacement) {
            for (int i = 0; i < locals.length; ++i)
                if (toBeReplaced.equals(locals[i]))
                    locals[i] = replacement;
            //Best we can do with a deque.
            Value[] v = stack.toArray(new Value[0]);
            for (int i = 0; i < v.length; ++i)
                if (toBeReplaced.equals(v[i]))
                    v[i] = replacement;
            stack.clear();
            stack.addAll(Arrays.asList(v));
        }

        @Override
        public String toString() {
            return "Locals: " + Arrays.toString(locals) + ", Stack: " + stack.toString();
        }
    }
}