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

Java tutorial

Introduction

Here is the source code for edu.mit.streamjit.util.bytecode.MethodUnresolver.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 static com.google.common.base.Preconditions.*;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
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.Instruction;
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.TerminatorInst;
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.NullType;
import edu.mit.streamjit.util.bytecode.types.PrimitiveType;
import edu.mit.streamjit.util.bytecode.types.ReferenceType;
import edu.mit.streamjit.util.bytecode.types.RegularType;
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.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Map;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
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.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.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;

/**
 * Builds bytecode from methods.
 * @author Jeffrey Bosboom <jbosboom@csail.mit.edu>
 * @since 4/17/2013
 */
public final class MethodUnresolver {
    public static MethodNode unresolve(Method m) {
        checkNotNull(m);
        //Unresolving immutable methods (live Class methods) is only useful
        //during testing.
        //checkArgument(m.isMutable(), "unresolving immutable method %s", m);
        if (!m.modifiers().contains(Modifier.ABSTRACT))
            checkArgument(m.isResolved(), "unresolving unresolved method %s", m);
        return new MethodUnresolver(m).unresolve();
    }

    private final Method method;
    private final MethodNode methodNode;
    private final Map<Value, Integer> registers;
    private final Map<BasicBlock, LabelNode> labels;
    private final PrimitiveType booleanType, byteType, charType, shortType, intType, longType, floatType,
            doubleType;

    private MethodUnresolver(Method m) {
        this.method = m;
        this.methodNode = new MethodNode(Opcodes.ASM4);
        this.registers = new IdentityHashMap<>();
        this.labels = new IdentityHashMap<>();
        TypeFactory tf = m.getParent().getParent().types();
        this.booleanType = tf.getPrimitiveType(boolean.class);
        this.byteType = tf.getPrimitiveType(byte.class);
        this.charType = tf.getPrimitiveType(char.class);
        this.shortType = tf.getPrimitiveType(short.class);
        this.intType = tf.getPrimitiveType(int.class);
        this.longType = tf.getPrimitiveType(long.class);
        this.floatType = tf.getPrimitiveType(float.class);
        this.doubleType = tf.getPrimitiveType(double.class);
    }

    public MethodNode unresolve() {
        this.methodNode.access = Modifier.toBits(method.modifiers());
        this.methodNode.name = method.getName();
        this.methodNode.desc = methodDescriptor(method);
        this.methodNode.exceptions = Collections.emptyList();

        if (!method.modifiers().contains(Modifier.ABSTRACT)) {
            allocateRegisters();
            createLabels();
            for (BasicBlock b : method.basicBlocks())
                methodNode.instructions.add(emit(b));
            peepholeOptimizations();
            int maxRegister = registers.values().isEmpty() ? 0 : Collections.max(registers.values());
            this.methodNode.maxLocals = maxRegister + 2;
            //We'd like to use ClassWriter's COMPUTE_MAXS option to compute this
            //for us, but we also want to use CheckClassAdapter before
            //ClassWriter to get useful errors.  But CheckClassAdapter will
            //raise an error if we get this too low and run out of memory if we
            //just say Short.MAX_VALUE.  I think any bytecode can push at most
            //+2 net, so conservatively try 2*instructions.size().  Note that
            //this counts labels and other pseudo-instructions, as well as
            //instructions that have obviously no stack increase or a decrease.
            this.methodNode.maxStack = Math.min(2 * methodNode.instructions.size(), Short.MAX_VALUE);
            buildLocalVariableTable();
        }

        return methodNode;
    }

    private void allocateRegisters() {
        //We allocate one or two registers (depending on type category) to each
        //instruction producing a non-void value, the method arguments, and the
        //local variables.
        int regNum = 0;
        for (Argument a : method.arguments()) {
            registers.put(a, regNum);
            regNum += a.getType().getCategory();
        }
        if (method.isMutable())
            for (LocalVariable v : method.localVariables()) {
                registers.put(v, regNum);
                regNum += v.getType().getFieldType().getCategory();
            }
        for (BasicBlock b : method.basicBlocks())
            for (Instruction i : b.instructions())
                if (!(i.getType() instanceof VoidType)) {
                    registers.put(i, regNum);
                    regNum += i.getType().getCategory();
                }
    }

    private void createLabels() {
        for (BasicBlock b : method.basicBlocks())
            labels.put(b, new LabelNode(new Label()));
    }

    private void buildLocalVariableTable() {
        LabelNode first = new LabelNode(), last = new LabelNode();
        methodNode.instructions.insert(first);
        methodNode.instructions.add(last);
        methodNode.localVariables = new ArrayList<>(registers.size());
        for (Map.Entry<Value, Integer> r : registers.entrySet()) {
            RegularType type = r.getKey() instanceof LocalVariable
                    ? ((LocalVariable) r.getKey()).getType().getFieldType()
                    : (RegularType) r.getKey().getType();
            methodNode.localVariables.add(new LocalVariableNode(r.getKey().getName(), type.getDescriptor(), null,
                    first, last, r.getValue()));
        }
    }

    private InsnList emit(BasicBlock block) {
        FluentIterable<TerminatorInst> terminators = FluentIterable.from(block.instructions())
                .filter(TerminatorInst.class);
        if (terminators.isEmpty())
            throw new IllegalArgumentException("block " + block.getName() + " in method "
                    + block.getParent().getName() + " lacks a terminator");
        if (terminators.size() > 1)
            throw new IllegalArgumentException("block " + block.getName() + " in method "
                    + block.getParent().getName() + " has multiple terminators: " + terminators);

        InsnList insns = new InsnList();
        insns.add(labels.get(block));
        for (Instruction i : block.instructions()) {
            if (i instanceof TerminatorInst)
                emitPhiMoves(block, insns);

            if (i instanceof ArrayLengthInst)
                emit((ArrayLengthInst) i, insns);
            else if (i instanceof ArrayLoadInst)
                emit((ArrayLoadInst) i, insns);
            else if (i instanceof ArrayStoreInst)
                emit((ArrayStoreInst) i, insns);
            else if (i instanceof BinaryInst)
                emit((BinaryInst) i, insns);
            else if (i instanceof BranchInst)
                emit((BranchInst) i, insns);
            else if (i instanceof CallInst)
                emit((CallInst) i, insns);
            else if (i instanceof CastInst)
                emit((CastInst) i, insns);
            else if (i instanceof InstanceofInst)
                emit((InstanceofInst) i, insns);
            else if (i instanceof JumpInst)
                emit((JumpInst) i, insns);
            else if (i instanceof LoadInst)
                emit((LoadInst) i, insns);
            else if (i instanceof NewArrayInst)
                emit((NewArrayInst) i, insns);
            else if (i instanceof PhiInst)
                //PhiInst deliberately omitted
                ;
            else if (i instanceof ReturnInst)
                emit((ReturnInst) i, insns);
            else if (i instanceof StoreInst)
                emit((StoreInst) i, insns);
            else if (i instanceof SwitchInst)
                emit((SwitchInst) i, insns);
            else if (i instanceof ThrowInst)
                emit((ThrowInst) i, insns);
            else
                throw new AssertionError("can't emit " + i);
        }
        return insns;
    }

    private void emit(ArrayLengthInst i, InsnList insns) {
        load(i.getOperand(0), insns);
        insns.add(new InsnNode(Opcodes.ARRAYLENGTH));
        store(i, insns);
    }

    private void emit(ArrayLoadInst i, InsnList insns) {
        load(i.getArray(), insns);
        load(i.getIndex(), insns);
        if (i.getType() instanceof ReferenceType)
            insns.add(new InsnNode(Opcodes.AALOAD));
        else if (i.getType().equals(booleanType) || i.getType().equals(byteType))
            insns.add(new InsnNode(Opcodes.BALOAD));
        else if (i.getType().equals(charType))
            insns.add(new InsnNode(Opcodes.CALOAD));
        else if (i.getType().equals(shortType))
            insns.add(new InsnNode(Opcodes.SALOAD));
        else if (i.getType().equals(intType))
            insns.add(new InsnNode(Opcodes.IALOAD));
        else if (i.getType().equals(longType))
            insns.add(new InsnNode(Opcodes.LALOAD));
        else if (i.getType().equals(floatType))
            insns.add(new InsnNode(Opcodes.FALOAD));
        else if (i.getType().equals(doubleType))
            insns.add(new InsnNode(Opcodes.DALOAD));
        else
            throw new AssertionError(i);
        store(i, insns);
    }

    private void emit(ArrayStoreInst i, InsnList insns) {
        load(i.getArray(), insns);
        load(i.getIndex(), insns);
        load(i.getData(), insns);
        //TODO: what if the array is a null constant?
        RegularType componentType = ((ArrayType) i.getArray().getType()).getComponentType();
        if (componentType instanceof ReferenceType)
            insns.add(new InsnNode(Opcodes.AASTORE));
        else if (componentType.equals(booleanType) || componentType.equals(byteType))
            insns.add(new InsnNode(Opcodes.BASTORE));
        else if (componentType.equals(charType))
            insns.add(new InsnNode(Opcodes.CASTORE));
        else if (componentType.equals(shortType))
            insns.add(new InsnNode(Opcodes.SASTORE));
        else if (componentType.equals(intType))
            insns.add(new InsnNode(Opcodes.IASTORE));
        else if (componentType.equals(longType))
            insns.add(new InsnNode(Opcodes.LASTORE));
        else if (componentType.equals(floatType))
            insns.add(new InsnNode(Opcodes.FASTORE));
        else if (componentType.equals(doubleType))
            insns.add(new InsnNode(Opcodes.DASTORE));
        else
            throw new AssertionError(i);
    }

    private void emit(BinaryInst i, InsnList insns) {
        load(i.getOperand(0), insns);
        load(i.getOperand(1), insns);
        int opcode = 0;
        if (i.getOperand(0).getType().isSubtypeOf(intType)) {
            switch (i.getOperation()) {
            case ADD:
                opcode = Opcodes.IADD;
                break;
            case SUB:
                opcode = Opcodes.ISUB;
                break;
            case MUL:
                opcode = Opcodes.IMUL;
                break;
            case DIV:
                opcode = Opcodes.IDIV;
                break;
            case REM:
                opcode = Opcodes.IREM;
                break;
            case SHL:
                opcode = Opcodes.ISHL;
                break;
            case SHR:
                opcode = Opcodes.ISHR;
                break;
            case USHR:
                opcode = Opcodes.ISHR;
                break;
            case AND:
                opcode = Opcodes.IAND;
                break;
            case OR:
                opcode = Opcodes.IOR;
                break;
            case XOR:
                opcode = Opcodes.IXOR;
                break;
            default:
                throw new AssertionError(i);
            }
        } else if (i.getOperand(0).getType().equals(longType)) {
            switch (i.getOperation()) {
            case ADD:
                opcode = Opcodes.LADD;
                break;
            case SUB:
                opcode = Opcodes.LSUB;
                break;
            case MUL:
                opcode = Opcodes.LMUL;
                break;
            case DIV:
                opcode = Opcodes.LDIV;
                break;
            case REM:
                opcode = Opcodes.LREM;
                break;
            case SHL:
                opcode = Opcodes.LSHL;
                break;
            case SHR:
                opcode = Opcodes.LSHR;
                break;
            case USHR:
                opcode = Opcodes.LSHR;
                break;
            case AND:
                opcode = Opcodes.LAND;
                break;
            case OR:
                opcode = Opcodes.LOR;
                break;
            case XOR:
                opcode = Opcodes.LXOR;
                break;
            case CMP:
                opcode = Opcodes.LCMP;
                break;
            default:
                throw new AssertionError(i);
            }
        } else if (i.getOperand(0).getType().equals(floatType)) {
            switch (i.getOperation()) {
            case ADD:
                opcode = Opcodes.FADD;
                break;
            case SUB:
                opcode = Opcodes.FSUB;
                break;
            case MUL:
                opcode = Opcodes.FMUL;
                break;
            case DIV:
                opcode = Opcodes.FDIV;
                break;
            case REM:
                opcode = Opcodes.FREM;
                break;
            case CMP:
                opcode = Opcodes.FCMPL;
                break;
            case CMPG:
                opcode = Opcodes.FCMPG;
                break;
            default:
                throw new AssertionError(i);
            }
        } else if (i.getOperand(0).getType().equals(doubleType)) {
            switch (i.getOperation()) {
            case ADD:
                opcode = Opcodes.DADD;
                break;
            case SUB:
                opcode = Opcodes.DSUB;
                break;
            case MUL:
                opcode = Opcodes.DMUL;
                break;
            case DIV:
                opcode = Opcodes.DDIV;
                break;
            case REM:
                opcode = Opcodes.DREM;
                break;
            case CMP:
                opcode = Opcodes.DCMPL;
                break;
            case CMPG:
                opcode = Opcodes.DCMPG;
                break;
            default:
                throw new AssertionError(i);
            }
        } else
            throw new AssertionError(i);
        insns.add(new InsnNode(opcode));
        store(i, insns);
    }

    private void emit(BranchInst i, InsnList insns) {
        //TODO: accessor methods on BranchInst
        Value left = i.getOperand(0), right = i.getOperand(1);
        BasicBlock target = (BasicBlock) i.getOperand(2), fallthrough = (BasicBlock) i.getOperand(3);
        if (!method.basicBlocks().contains(target))
            throw new IllegalArgumentException("Branch targets block not in method: " + i);
        if (!method.basicBlocks().contains(fallthrough))
            throw new IllegalArgumentException("Branch falls through to block not in method: " + i);
        load(i.getOperand(0), insns);
        load(i.getOperand(1), insns);
        //TODO: long, float, doubles need to go through CMP inst first
        int opcode;
        if (left.getType() instanceof ReferenceType || left.getType() instanceof VoidType) {
            switch (i.getSense()) {
            case EQ:
                opcode = Opcodes.IF_ACMPEQ;
                break;
            case NE:
                opcode = Opcodes.IF_ACMPNE;
                break;
            default:
                throw new AssertionError(i);
            }
        } else if (left.getType().isSubtypeOf(intType)) {
            switch (i.getSense()) {
            case EQ:
                opcode = Opcodes.IF_ICMPEQ;
                break;
            case NE:
                opcode = Opcodes.IF_ICMPNE;
                break;
            case LT:
                opcode = Opcodes.IF_ICMPLT;
                break;
            case GT:
                opcode = Opcodes.IF_ICMPGT;
                break;
            case LE:
                opcode = Opcodes.IF_ICMPLE;
                break;
            case GE:
                opcode = Opcodes.IF_ICMPGE;
                break;
            default:
                throw new AssertionError(i);
            }
        } else
            throw new AssertionError(i);
        insns.add(new JumpInsnNode(opcode, labels.get(target)));
        insns.add(new JumpInsnNode(Opcodes.GOTO, labels.get(fallthrough)));
    }

    private void emit(CallInst i, InsnList insns) {
        Method m = i.getMethod();
        boolean callingSuperCtor = false;
        if (m.isConstructor()) {
            //If we're calling super(), load this.
            //TODO: this will get confused if we call a superclass constructor
            //for any reason other than our own initialization!
            if (method.isConstructor() && method.getParent().getSuperclass().equals(m.getParent())) {
                load(method.arguments().get(0), insns);
                callingSuperCtor = true;
            } else {
                insns.add(new TypeInsnNode(Opcodes.NEW, internalName(m.getType().getReturnType().getKlass())));
                insns.add(new InsnNode(Opcodes.DUP));
            }
        }
        int opcode;
        if (m.modifiers().contains(Modifier.STATIC))
            opcode = Opcodes.INVOKESTATIC;
        else if (m.isConstructor() || m.getAccess().equals(Access.PRIVATE) ||
        //We're calling a superclass method we've overridden.
                (Iterables.contains(method.getParent().superclasses(), m.getParent()))
                        && method.getParent().getMethodByVirtual(m.getName(), m.getType()) != m)
            opcode = Opcodes.INVOKESPECIAL;
        else if (m.getParent().modifiers().contains(Modifier.INTERFACE))
            //TODO: may not be correct?
            opcode = Opcodes.INVOKEINTERFACE;
        else
            opcode = Opcodes.INVOKEVIRTUAL;

        String owner = internalName(m.getParent());
        //hack to make cloning arrays work
        if (opcode == Opcodes.INVOKESPECIAL && m.getName().equals("clone")
                && i.getArgument(0).getType() instanceof ArrayType) {
            opcode = Opcodes.INVOKEVIRTUAL;
            owner = internalName(((ArrayType) i.getArgument(0).getType()).getKlass());
        }

        for (Value v : i.arguments())
            load(v, insns);
        insns.add(new MethodInsnNode(opcode, owner, m.getName(), i.callDescriptor()));

        if (!(i.getType() instanceof VoidType) && !callingSuperCtor)
            store(i, insns);
    }

    private void emit(CastInst i, InsnList insns) {
        load(i.getOperand(0), insns);
        if (i.getType() instanceof ReferenceType) {
            insns.add(new TypeInsnNode(Opcodes.CHECKCAST, internalName(((ReferenceType) i.getType()).getKlass())));
        } else {
            PrimitiveType from = (PrimitiveType) i.getOperand(0).getType();
            PrimitiveType to = (PrimitiveType) i.getType();
            for (int op : from.getCastOpcode(to))
                insns.add(new InsnNode(op));
        }
        store(i, insns);
    }

    private void emit(InstanceofInst i, InsnList insns) {
        load(i.getOperand(0), insns);
        insns.add(new TypeInsnNode(Opcodes.INSTANCEOF, internalName(i.getTestType().getKlass())));
        store(i, insns);
    }

    private void emit(JumpInst i, InsnList insns) {
        BasicBlock target = (BasicBlock) i.getOperand(0);
        if (!method.basicBlocks().contains(target))
            throw new IllegalArgumentException("Jump to block not in method: " + i);
        insns.add(new JumpInsnNode(Opcodes.GOTO, labels.get(target)));
    }

    private void emit(LoadInst i, InsnList insns) {
        Value location = i.getLocation();
        if (location instanceof LocalVariable) {
            load(location, insns);
            store(i, insns);
        } else {
            Field f = (Field) location;
            if (!f.isStatic())
                load(i.getInstance(), insns);
            insns.add(new FieldInsnNode(f.isStatic() ? Opcodes.GETSTATIC : Opcodes.GETFIELD,
                    internalName(f.getParent()), f.getName(), f.getType().getFieldType().getDescriptor()));
            store(i, insns);
        }
    }

    private void emit(NewArrayInst i, InsnList insns) {
        ArrayType t = i.getType();
        if (t.getDimensions() == 1) {
            load(i.getOperand(0), insns);
            RegularType ct = t.getComponentType();
            if (ct instanceof PrimitiveType) {
                if (ct.equals(booleanType))
                    insns.add(new IntInsnNode(Opcodes.NEWARRAY, Opcodes.T_BOOLEAN));
                else if (ct.equals(byteType))
                    insns.add(new IntInsnNode(Opcodes.NEWARRAY, Opcodes.T_BYTE));
                else if (ct.equals(charType))
                    insns.add(new IntInsnNode(Opcodes.NEWARRAY, Opcodes.T_CHAR));
                else if (ct.equals(shortType))
                    insns.add(new IntInsnNode(Opcodes.NEWARRAY, Opcodes.T_SHORT));
                else if (ct.equals(intType))
                    insns.add(new IntInsnNode(Opcodes.NEWARRAY, Opcodes.T_INT));
                else if (ct.equals(longType))
                    insns.add(new IntInsnNode(Opcodes.NEWARRAY, Opcodes.T_LONG));
                else if (ct.equals(floatType))
                    insns.add(new IntInsnNode(Opcodes.NEWARRAY, Opcodes.T_FLOAT));
                else if (ct.equals(doubleType))
                    insns.add(new IntInsnNode(Opcodes.NEWARRAY, Opcodes.T_DOUBLE));
            } else {
                insns.add(new TypeInsnNode(Opcodes.ANEWARRAY, internalName(ct.getKlass())));
            }
        } else {
            for (Value v : i.operands())
                load(v, insns);
            insns.add(new MultiANewArrayInsnNode(t.getDescriptor(), i.getNumOperands()));
        }
        store(i, insns);
    }

    private void emit(ReturnInst i, InsnList insns) {
        ReturnType rt = i.getReturnType();
        if (rt instanceof VoidType)
            insns.add(new InsnNode(Opcodes.RETURN));
        else {
            load(i.getOperand(0), insns);
            if (rt instanceof ReferenceType)
                insns.add(new InsnNode(Opcodes.ARETURN));
            else if (rt.isSubtypeOf(intType))
                insns.add(new InsnNode(Opcodes.IRETURN));
            else if (rt.equals(longType))
                insns.add(new InsnNode(Opcodes.LRETURN));
            else if (rt.equals(floatType))
                insns.add(new InsnNode(Opcodes.FRETURN));
            else if (rt.equals(doubleType))
                insns.add(new InsnNode(Opcodes.DRETURN));
            else
                throw new AssertionError(i);
        }
    }

    private void emit(StoreInst i, InsnList insns) {
        Value location = i.getLocation();
        if (location instanceof LocalVariable) {
            load(i.getData(), insns);
            store(location, insns);
        } else {
            Field f = (Field) location;
            if (!f.isStatic())
                load(i.getInstance(), insns);
            load(i.getData(), insns);
            insns.add(new FieldInsnNode(f.isStatic() ? Opcodes.PUTSTATIC : Opcodes.PUTFIELD,
                    internalName(f.getParent()), f.getName(), f.getType().getFieldType().getDescriptor()));
        }
    }

    private void emit(SwitchInst i, InsnList insns) {
        load(i.getValue(), insns);
        LookupSwitchInsnNode insn = new LookupSwitchInsnNode(null, null, null);
        insn.dflt = labels.get(i.getDefault());
        Iterator<Constant<Integer>> cases = i.cases().iterator();
        Iterator<BasicBlock> targets = i.successors().iterator();
        while (cases.hasNext()) {
            insn.keys.add(cases.next().getConstant());
            insn.labels.add(labels.get(targets.next()));
        }
        insns.add(insn);
    }

    private void emit(ThrowInst i, InsnList insns) {
        load(i.getOperand(0), insns);
        insns.add(new InsnNode(Opcodes.ATHROW));
    }

    private void emitPhiMoves(BasicBlock block, InsnList insns) {
        //In case phi instructions refer to one another, load all values onto
        //the operand stack, then store all at once.
        Deque<Value> pendingStores = new ArrayDeque<>();
        for (BasicBlock b : block.successors())
            for (Instruction i : b.instructions())
                if (i instanceof PhiInst) {
                    PhiInst p = (PhiInst) i;
                    Value ourDef = p.get(block);
                    if (ourDef != null) {
                        load(ourDef, insns);
                        pendingStores.push(p);
                    }
                }
        while (!pendingStores.isEmpty())
            store(pendingStores.pop(), insns);
    }

    private void load(Value v, InsnList insns) {
        if (v instanceof Constant) {
            Object c = ((Constant<?>) v).getConstant();
            if (c == null)
                insns.add(new InsnNode(Opcodes.ACONST_NULL));
            else if (c instanceof Class)
                insns.add(new LdcInsnNode(org.objectweb.asm.Type.getType((Class) c)));
            else if (c instanceof Boolean)
                insns.add(loadIntegerConstant(((Boolean) c) ? 1 : 0));
            else if (c instanceof Character)
                insns.add(loadIntegerConstant((int) ((Character) c).charValue()));
            else if (c instanceof Byte || c instanceof Short || c instanceof Integer)
                insns.add(loadIntegerConstant(((Number) c).intValue()));
            else if (c instanceof Long)
                insns.add(loadLongConstant((Long) c));
            else if (c instanceof Float)
                insns.add(loadFloatConstant((Float) c));
            else if (c instanceof Double)
                insns.add(loadDoubleConstant((Double) c));
            else
                insns.add(new LdcInsnNode(c));
            return;
        }

        assert registers.containsKey(v) : v;
        int reg = registers.get(v);
        Type t = v instanceof LocalVariable ? ((LocalVariable) v).getType().getFieldType() : v.getType();
        if (t instanceof ReferenceType || t instanceof NullType)
            insns.add(new VarInsnNode(Opcodes.ALOAD, reg));
        else if (t.equals(longType))
            insns.add(new VarInsnNode(Opcodes.LLOAD, reg));
        else if (t.equals(floatType))
            insns.add(new VarInsnNode(Opcodes.FLOAD, reg));
        else if (t.equals(doubleType))
            insns.add(new VarInsnNode(Opcodes.DLOAD, reg));
        else if (t.isSubtypeOf(intType))
            insns.add(new VarInsnNode(Opcodes.ILOAD, reg));
        else
            throw new AssertionError("unloadable value: " + v);
    }

    private AbstractInsnNode loadIntegerConstant(int c) {
        if (c == -1)
            return new InsnNode(Opcodes.ICONST_M1);
        if (c == 0)
            return new InsnNode(Opcodes.ICONST_0);
        if (c == 1)
            return new InsnNode(Opcodes.ICONST_1);
        if (c == 2)
            return new InsnNode(Opcodes.ICONST_2);
        if (c == 3)
            return new InsnNode(Opcodes.ICONST_3);
        if (c == 4)
            return new InsnNode(Opcodes.ICONST_4);
        if (c == 5)
            return new InsnNode(Opcodes.ICONST_5);
        if (Byte.MIN_VALUE <= c && c <= Byte.MAX_VALUE)
            return new IntInsnNode(Opcodes.BIPUSH, c);
        if (Short.MIN_VALUE <= c && c <= Short.MAX_VALUE)
            return new IntInsnNode(Opcodes.SIPUSH, c);
        return new LdcInsnNode(c);
    }

    private AbstractInsnNode loadLongConstant(long c) {
        if (c == 0)
            return new InsnNode(Opcodes.LCONST_0);
        if (c == 1)
            return new InsnNode(Opcodes.LCONST_1);
        return new LdcInsnNode(c);
    }

    private AbstractInsnNode loadFloatConstant(float c) {
        if (c == 0.0f)
            return new InsnNode(Opcodes.FCONST_0);
        if (c == 1.0f)
            return new InsnNode(Opcodes.FCONST_1);
        if (c == 2.0f)
            return new InsnNode(Opcodes.FCONST_2);
        return new LdcInsnNode(c);
    }

    private AbstractInsnNode loadDoubleConstant(double c) {
        if (c == 0.0)
            return new InsnNode(Opcodes.DCONST_0);
        if (c == 1.0)
            return new InsnNode(Opcodes.DCONST_1);
        return new LdcInsnNode(c);
    }

    private void store(Value v, InsnList insns) {
        assert registers.containsKey(v) : v;
        int reg = registers.get(v);
        Type t = v instanceof LocalVariable ? ((LocalVariable) v).getType().getFieldType() : v.getType();
        if (t instanceof ReferenceType || t instanceof NullType)
            insns.add(new VarInsnNode(Opcodes.ASTORE, reg));
        else if (t.equals(longType))
            insns.add(new VarInsnNode(Opcodes.LSTORE, reg));
        else if (t.equals(floatType))
            insns.add(new VarInsnNode(Opcodes.FSTORE, reg));
        else if (t.equals(doubleType))
            insns.add(new VarInsnNode(Opcodes.DSTORE, reg));
        else if (t.isSubtypeOf(intType))
            insns.add(new VarInsnNode(Opcodes.ISTORE, reg));
        else
            throw new AssertionError("unstorable value: " + v);
    }

    private static String methodDescriptor(Method m) {
        //TODO: maybe put this on Method?  I vaguely recall using it somewhere else...
        MethodType type = m.getType();
        if (m.isConstructor())
            type = type.withReturnType(type.getTypeFactory().getVoidType());
        if (m.hasReceiver())
            type = type.dropFirstArgument();
        return type.getDescriptor();
    }

    private String internalName(Klass k) {
        return k.getName().replace('.', '/');
    }

    /**
     * Performs peephole optimizations at the bytecode level, primarily to
     * reduce bytecode size for better inlining.  (HotSpot makes inlining
     * decisions based partially on the number of bytes in a method.)
     */
    private void peepholeOptimizations() {
        boolean progress;
        do {
            progress = false;
            progress |= removeLoadStore();
            progress |= removeUnnecessaryGotos();
        } while (progress);
    }

    private static final ImmutableList<Integer> LOADS = ImmutableList.of(Opcodes.ALOAD, Opcodes.DLOAD,
            Opcodes.FLOAD, Opcodes.ILOAD, Opcodes.LLOAD);
    private static final ImmutableList<Integer> STORES = ImmutableList.of(Opcodes.ASTORE, Opcodes.DSTORE,
            Opcodes.FSTORE, Opcodes.ISTORE, Opcodes.LSTORE);

    /**
     * Removes "xLOAD N xSTORE N".
     * @return true iff changes were made
     */
    private boolean removeLoadStore() {
        InsnList insns = methodNode.instructions;
        for (int i = 0; i < insns.size() - 1; ++i) {
            AbstractInsnNode first = insns.get(i);
            int index = LOADS.indexOf(first.getOpcode());
            if (index == -1)
                continue;
            AbstractInsnNode second = insns.get(i + 1);
            if (second.getOpcode() != STORES.get(index))
                continue;
            if (((VarInsnNode) first).var != ((VarInsnNode) second).var)
                continue;
            insns.remove(first);
            insns.remove(second);
            return true;
        }
        return false;
    }

    /**
     * Removes goto instructions that go to the label immediately following
     * them.
     * @return true iff changes were made
     */
    private boolean removeUnnecessaryGotos() {
        InsnList insns = methodNode.instructions;
        for (int i = 0; i < insns.size() - 1; ++i) {
            AbstractInsnNode first = insns.get(i);
            if (first.getOpcode() != Opcodes.GOTO)
                continue;
            AbstractInsnNode second = insns.get(i + 1);
            if (!(second instanceof LabelNode))
                continue;
            if (((JumpInsnNode) first).label != second)
                continue;
            insns.remove(first);
            return true;
        }
        return false;
    }

    public static void main(String[] args) {
        Module m = new Module();
        Klass k = m.getKlass(Module.class);
        Method ar = k.getMethods("getArrayKlass").iterator().next();
        ar.resolve();
        MethodNode mn = unresolve(ar);
        for (int i = 0; i < mn.instructions.size(); ++i) {
            AbstractInsnNode insn = mn.instructions.get(i);
            System.out.format("%d: %d %s%n", i, insn.getOpcode(), insn);
        }
    }
}