/**
 * Copyright (c) André Bargull
 * Alle Rechte vorbehalten / All Rights Reserved.  Use is subject to license terms.
 *
 * <https://github.com/anba/es6draft>
 */
package com.github.anba.es6draft.compiler.assembler;

import java.util.Arrays;

/**
 * Basic stack type information tracking, complex control instructions are not supported.
 */
public class Stack {
    private static final int MIN_STACK_SIZE = 8;
    private static final Type OBJECT_TYPE = Types.Object;
    private static final Type STRING_TYPE = Types.String;
    private static final Type[] EMPTY_STACK = new Type[0];

    private Type[] stack;
    private int sp;

    public Stack() {
        newStack();
    }

    /**
     * Return a copy of the current stack.
     * 
     * @return the current stack
     */
    public final Type[] getStack() {
        if (sp == 0) {
            return EMPTY_STACK;
        }
        return Arrays.copyOf(stack, sp);
    }

    /**
     * Return the current stack pointer.
     * 
     * @return the current stack pointer
     */
    public final int getStackPointer() {
        return sp;
    }

    /**
     * Start exception handler for {@code exception}.
     * 
     * @param exception
     *            the exception type for the catch handler
     */
    void catchHandler(Type exception) {
        if (stack == null) {
            newStack();
        }
        sp = 0;
        push(exception);
    }

    /**
     * Replace the stack type information with the supplied types.
     * 
     * @param stack
     *            the new stack
     */
    void setStack(Type[] stack) {
        assert stack != null;
        int newSize = Math.max(MIN_STACK_SIZE, Integer.highestOneBit(stack.length) << 1);
        setStack(Arrays.copyOf(stack, newSize), stack.length);
    }

    Type[] getStackDirect() {
        return stack;
    }

    void setStack(Type[] stack, int sp) {
        this.stack = stack;
        this.sp = sp;
    }

    private void increaseStack(int newSize) {
        stack = Arrays.copyOf(stack, newSize);
    }

    private void newStack() {
        assert sp == 0 : getStackString();
        setStack(new Type[MIN_STACK_SIZE], 0);
    }

    private void discardStack() {
        // assert stack != null;
        setStack(null, 0);
    }

    private String getStackString() {
        if (stack == null) {
            return "<null>";
        }
        // Replace [] with {}
        StringBuilder sb = new StringBuilder(Arrays.toString(getStack()));
        sb.setCharAt(0, '{');
        sb.setCharAt(sb.length() - 1, '}');
        return sb.toString();
    }

    private static boolean assertEqualTypes(Type[] stack, int sp, Type[] labelStack, int labelsp) {
        assert sp == labelsp : String.format("%d != %d (%s, %s)", sp, labelsp, Arrays.toString(stack),
                Arrays.toString(labelStack));
        for (int i = 0; i < sp; ++i) {
            Type t0 = stack[i], t1 = labelStack[i];
            assert t0.equals(t1) : String.format("%s != %s", t0, t1);
        }
        return true;
    }

    private void alignStack(Type[] stack, int sp, Type[] labelStack, int labelsp) {
        assert sp == labelsp : String.format("%d != %d (%s, %s)", sp, labelsp, Arrays.toString(stack),
                Arrays.toString(labelStack));
        for (int i = 0; i < sp; ++i) {
            Type t0 = stack[i], t1 = labelStack[i];
            if (!t0.equals(t1)) {
                assert compatibleTypes(t0, t1);
                if (t0.getSort() <= Type.Sort.INT) {
                    stack[i] = Type.of(Math.max(Math.max(t0.getSort(), t1.getSort()), Type.Sort.INT));
                } else if (t0.getSort() <= Type.Sort.DOUBLE) {
                    stack[i] = Type.DOUBLE_TYPE;
                } else {
                    stack[i] = intersectionType(t0, t1);
                }
            }
        }
    }

    private static boolean compatibleTypes(Type left, Type right) {
        return left == right || left.getSize() == right.getSize()
                && left.getSort() < Type.Sort.ARRAY == right.getSort() < Type.Sort.ARRAY;
    }

    protected Type intersectionType(Type left, Type right) {
        return OBJECT_TYPE;
    }

    /* stack operations */

    protected Type peek() {
        assert sp > 0 : getStackString();
        return stack[sp - 1];
    }

    protected Type peek(int n) {
        assert n < 0;
        assert sp + n >= 0 : getStackString();
        return stack[sp + n];
    }

    protected Type pop(int size) {
        assert sp > 0 : getStackString();
        Type t = stack[--sp];
        assert t.getSize() == size : String.format("%d[%s] != %d, %s", t.getSize(), t, size, getStackString());
        return t;
    }

    protected void pop(Type type) {
        assert sp > 0 : getStackString();
        Type t = stack[--sp];
        assert compatibleTypes(t, type) : String.format("%s != %s, %s", t, type, getStackString());
    }

    protected void push(Type type) {
        if (sp == stack.length) {
            increaseStack(sp << 1);
        }
        stack[sp++] = type;
    }

    /* */

    protected void jmp(Jump jump) {
        Type[] labelStack = jump.stack();
        if (labelStack == null) {
            // label not yet visited => forward jump
            jump.setStack(getStack());
        } else if (jump.isResolved()) {
            // label already resolved
            assert assertEqualTypes(stack, sp, labelStack, labelStack.length);
        } else {
            // update label stack state
            alignStack(labelStack, labelStack.length, stack, sp);
        }
    }

    protected void label(Jump jump) {
        Type[] labelStack = jump.stack();
        if (labelStack == null) {
            // new backward label
            if (stack == null) {
                // label after discard stack (goto, return, throw), create new stack (or assert in
                // debug mode)
                assert false : "newStack after discard stack";
                newStack();
            }
            jump.setStack(getStack());
        } else if (stack == null) {
            // label after discard stack (goto, return, throw), retrieve stack from label
            setStack(labelStack);
        } else {
            // update stack state
            alignStack(stack, sp, labelStack, labelStack.length);
        }
    }

    /* */

    public final void mark(Jump jump) {
        label(jump);
    }

    /* constant value instructions */

    public final void anull() {
        push(OBJECT_TYPE);
    }

    public final void aconst() {
        push(STRING_TYPE);
    }

    public final void iconst() {
        push(Type.INT_TYPE);
    }

    public final void lconst() {
        push(Type.LONG_TYPE);
    }

    public final void fconst() {
        push(Type.FLOAT_TYPE);
    }

    public final void dconst() {
        push(Type.DOUBLE_TYPE);
    }

    public final void bipush() {
        push(Type.BYTE_TYPE);
    }

    public final void sipush() {
        push(Type.SHORT_TYPE);
    }

    public final void hconst() {
        push(Types.MethodHandle);
    }

    public final void tconst(Type type) {
        push(Types.Class);
    }

    public final void tconst(MethodTypeDescriptor type) {
        push(Types.MethodType);
    }

    public final void ldc(Object cst) {
        if (cst instanceof Integer) {
            push(Type.INT_TYPE);
        } else if (cst instanceof Byte) {
            push(Type.BYTE_TYPE);
        } else if (cst instanceof Character) {
            push(Type.CHAR_TYPE);
        } else if (cst instanceof Short) {
            push(Type.SHORT_TYPE);
        } else if (cst instanceof Boolean) {
            push(Type.BOOLEAN_TYPE);
        } else if (cst instanceof Float) {
            push(Type.FLOAT_TYPE);
        } else if (cst instanceof Long) {
            push(Type.LONG_TYPE);
        } else if (cst instanceof Double) {
            push(Type.DOUBLE_TYPE);
        } else if (cst instanceof String) {
            push(Types.String);
        } else {
            throw new IllegalArgumentException();
        }
    }

    /* local load instructions */

    private void load(int var, Type type, Type kind) {
        assert compatibleTypes(type, kind) : String.format("%s != %s [%d]", type, kind, var);
        push(type);
    }

    public final void iload(int var, Type type) {
        load(var, type, Type.INT_TYPE);
    }

    public final void lload(int var, Type type) {
        load(var, type, Type.LONG_TYPE);
    }

    public final void fload(int var, Type type) {
        load(var, type, Type.FLOAT_TYPE);
    }

    public final void dload(int var, Type type) {
        load(var, type, Type.DOUBLE_TYPE);
    }

    public final void aload(int var, Type type) {
        load(var, type, OBJECT_TYPE);
    }

    /* array load instructions */

    private void aload(Type type) {
        pop(Type.INT_TYPE);
        pop(OBJECT_TYPE);
        push(type);
    }

    public final void iaload() {
        aload(Type.INT_TYPE);
    }

    public final void laload() {
        aload(Type.LONG_TYPE);
    }

    public final void faload() {
        aload(Type.FLOAT_TYPE);
    }

    public final void daload() {
        aload(Type.DOUBLE_TYPE);
    }

    public final void aaload() {
        aload(OBJECT_TYPE);
    }

    public final void baload() {
        aload(Type.BYTE_TYPE);
    }

    public final void caload() {
        aload(Type.CHAR_TYPE);
    }

    public final void saload() {
        aload(Type.SHORT_TYPE);
    }

    /* local store instructions */

    private void store(int var, Type type, Type kind) {
        assert compatibleTypes(type, kind) : String.format("%s != %s [%d]", type, kind, var);
        pop(type);
    }

    public final void istore(int var, Type type) {
        store(var, type, Type.INT_TYPE);
    }

    public final void lstore(int var, Type type) {
        store(var, type, Type.LONG_TYPE);
    }

    public final void fstore(int var, Type type) {
        store(var, type, Type.FLOAT_TYPE);
    }

    public final void dstore(int var, Type type) {
        store(var, type, Type.DOUBLE_TYPE);
    }

    public final void astore(int var, Type type) {
        store(var, type, OBJECT_TYPE);
    }

    /* array store instructions */

    private void astore(Type type) {
        pop(type);
        pop(Type.INT_TYPE);
        pop(OBJECT_TYPE);
    }

    public final void iastore() {
        astore(Type.INT_TYPE);
    }

    public final void lastore() {
        astore(Type.LONG_TYPE);
    }

    public final void fastore() {
        astore(Type.FLOAT_TYPE);
    }

    public final void dastore() {
        astore(Type.DOUBLE_TYPE);
    }

    public final void aastore() {
        astore(OBJECT_TYPE);
    }

    public final void bastore() {
        astore(Type.BYTE_TYPE);
    }

    public final void castore() {
        astore(Type.CHAR_TYPE);
    }

    public final void sastore() {
        astore(Type.SHORT_TYPE);
    }

    /* stack instructions */

    /**
     * {@code pop} bytecode instruction.
     */
    public final void pop() {
        pop(1);
    }

    /**
     * {@code pop2} bytecode instruction.
     */
    public final void pop2() {
        if (peek().getSize() == 2) {
            pop(2);
        } else {
            pop(1);
            pop(1);
        }
    }

    /**
     * {@code dup} bytecode instruction.
     */
    public final void dup() {
        Type t0 = pop(1);
        push(t0);
        push(t0);
    }

    /**
     * {@code dupX1} bytecode instruction.
     */
    public final void dupX1() {
        Type t0 = pop(1);
        Type t1 = pop(1);
        push(t0);
        push(t1);
        push(t0);
    }

    /**
     * {@code dupX2} bytecode instruction.
     */
    public final void dupX2() {
        Type t0 = pop(1);
        if (peek().getSize() == 2) {
            Type t1 = pop(2);
            push(t0);
            push(t1);
            push(t0);
        } else {
            Type t1 = pop(1);
            Type t2 = pop(1);
            push(t0);
            push(t2);
            push(t1);
            push(t0);
        }
    }

    /**
     * {@code dup2} bytecode instruction.
     */
    public final void dup2() {
        if (peek().getSize() == 2) {
            Type t0 = pop(2);
            push(t0);
            push(t0);
        } else {
            Type t0 = pop(1);
            Type t1 = pop(1);
            push(t1);
            push(t0);
            push(t1);
            push(t0);
        }
    }

    /**
     * {@code dup2X1} bytecode instruction.
     */
    public final void dup2X1() {
        if (peek().getSize() == 2) {
            Type t0 = pop(2);
            Type t2 = pop(1);
            push(t0);
            push(t2);
            push(t0);
        } else {
            Type t0 = pop(1);
            Type t1 = pop(1);
            Type t2 = pop(1);
            push(t1);
            push(t0);
            push(t2);
            push(t1);
            push(t0);
        }
    }

    /**
     * {@code dup2X2} bytecode instruction.
     */
    public final void dup2X2() {
        if (peek().getSize() == 2) {
            Type t0 = pop(2);
            if (peek().getSize() == 2) {
                Type t2 = pop(2);
                push(t0);
                push(t2);
                push(t0);
            } else {
                Type t2 = pop(1);
                Type t3 = pop(1);
                push(t0);
                push(t3);
                push(t2);
                push(t0);
            }
        } else {
            Type t0 = pop(1);
            Type t1 = pop(1);
            if (peek().getSize() == 2) {
                Type t2 = pop(2);
                push(t1);
                push(t0);
                push(t2);
                push(t1);
                push(t0);
            } else {
                Type t2 = pop(1);
                Type t3 = pop(1);
                push(t1);
                push(t0);
                push(t3);
                push(t2);
                push(t1);
                push(t0);
            }
        }
    }

    /**
     * {@code swap} bytecode instruction.
     */
    public final void swap() {
        Type t0 = pop(1);
        Type t1 = pop(1);
        push(t0);
        push(t1);
    }

    /* math instructions */

    private void arithmetic(Type type) {
        pop(type);
        pop(type);
        push(type);
    }

    private void neg(Type type) {
        pop(type);
        push(type);
    }

    private void shift(Type type) {
        pop(Type.INT_TYPE);
        pop(type);
        push(type);
    }

    public final void iadd() {
        arithmetic(Type.INT_TYPE);
    }

    public final void ladd() {
        arithmetic(Type.LONG_TYPE);
    }

    public final void fadd() {
        arithmetic(Type.FLOAT_TYPE);
    }

    public final void dadd() {
        arithmetic(Type.DOUBLE_TYPE);
    }

    public final void isub() {
        arithmetic(Type.INT_TYPE);
    }

    public final void lsub() {
        arithmetic(Type.LONG_TYPE);
    }

    public final void fsub() {
        arithmetic(Type.FLOAT_TYPE);
    }

    public final void dsub() {
        arithmetic(Type.DOUBLE_TYPE);
    }

    public final void imul() {
        arithmetic(Type.INT_TYPE);
    }

    public final void lmul() {
        arithmetic(Type.LONG_TYPE);
    }

    public final void fmul() {
        arithmetic(Type.FLOAT_TYPE);
    }

    public final void dmul() {
        arithmetic(Type.DOUBLE_TYPE);
    }

    public final void idiv() {
        arithmetic(Type.INT_TYPE);
    }

    public final void ldiv() {
        arithmetic(Type.LONG_TYPE);
    }

    public final void fdiv() {
        arithmetic(Type.FLOAT_TYPE);
    }

    public final void ddiv() {
        arithmetic(Type.DOUBLE_TYPE);
    }

    public final void irem() {
        arithmetic(Type.INT_TYPE);
    }

    public final void lrem() {
        arithmetic(Type.LONG_TYPE);
    }

    public final void frem() {
        arithmetic(Type.FLOAT_TYPE);
    }

    public final void drem() {
        arithmetic(Type.DOUBLE_TYPE);
    }

    public final void ineg() {
        neg(Type.INT_TYPE);
    }

    public final void lneg() {
        neg(Type.LONG_TYPE);
    }

    public final void fneg() {
        neg(Type.FLOAT_TYPE);
    }

    public final void dneg() {
        neg(Type.DOUBLE_TYPE);
    }

    public final void ishl() {
        shift(Type.INT_TYPE);
    }

    public final void lshl() {
        shift(Type.LONG_TYPE);
    }

    public final void ishr() {
        shift(Type.INT_TYPE);
    }

    public final void lshr() {
        shift(Type.LONG_TYPE);
    }

    public final void iushr() {
        shift(Type.INT_TYPE);
    }

    public final void lushr() {
        shift(Type.LONG_TYPE);
    }

    public final void iand() {
        arithmetic(Type.INT_TYPE);
    }

    public final void land() {
        arithmetic(Type.LONG_TYPE);
    }

    public final void ior() {
        arithmetic(Type.INT_TYPE);
    }

    public final void lor() {
        arithmetic(Type.LONG_TYPE);
    }

    public final void ixor() {
        arithmetic(Type.INT_TYPE);
    }

    public final void lxor() {
        arithmetic(Type.LONG_TYPE);
    }

    /* primitive type cast instructions */

    private void cast(Type from, Type to) {
        pop(from);
        push(to);
    }

    /**
     * {@code i2l} bytecode instruction.
     */
    public final void i2l() {
        cast(Type.INT_TYPE, Type.LONG_TYPE);
    }

    /**
     * {@code i2f} bytecode instruction.
     */
    public final void i2f() {
        cast(Type.INT_TYPE, Type.FLOAT_TYPE);
    }

    /**
     * {@code i2d} bytecode instruction.
     */
    public final void i2d() {
        cast(Type.INT_TYPE, Type.DOUBLE_TYPE);
    }

    /**
     * {@code l2i} bytecode instruction.
     */
    public final void l2i() {
        cast(Type.LONG_TYPE, Type.INT_TYPE);
    }

    /**
     * {@code l2f} bytecode instruction.
     */
    public final void l2f() {
        cast(Type.LONG_TYPE, Type.FLOAT_TYPE);
    }

    /**
     * {@code l2d} bytecode instruction.
     */
    public final void l2d() {
        cast(Type.LONG_TYPE, Type.DOUBLE_TYPE);
    }

    /**
     * {@code f2i} bytecode instruction.
     */
    public final void f2i() {
        cast(Type.FLOAT_TYPE, Type.INT_TYPE);
    }

    /**
     * {@code f2l} bytecode instruction.
     */
    public final void f2l() {
        cast(Type.FLOAT_TYPE, Type.LONG_TYPE);
    }

    /**
     * {@code f2d} bytecode instruction.
     */
    public final void f2d() {
        cast(Type.FLOAT_TYPE, Type.DOUBLE_TYPE);
    }

    /**
     * {@code d2i} bytecode instruction.
     */
    public final void d2i() {
        cast(Type.DOUBLE_TYPE, Type.INT_TYPE);
    }

    /**
     * {@code d2l} bytecode instruction.
     */
    public final void d2l() {
        cast(Type.DOUBLE_TYPE, Type.LONG_TYPE);
    }

    /**
     * {@code d2f} bytecode instruction.
     */
    public final void d2f() {
        cast(Type.DOUBLE_TYPE, Type.FLOAT_TYPE);
    }

    /**
     * {@code i2b} bytecode instruction.
     */
    public final void i2b() {
        cast(Type.INT_TYPE, Type.BYTE_TYPE);
    }

    /**
     * {@code i2c} bytecode instruction.
     */
    public final void i2c() {
        cast(Type.INT_TYPE, Type.CHAR_TYPE);
    }

    /**
     * {@code i2s} bytecode instruction.
     */
    public final void i2s() {
        cast(Type.INT_TYPE, Type.SHORT_TYPE);
    }

    /* compare instructions */

    /**
     * {@code lcmp} bytecode instruction.
     */
    public final void lcmp() {
        pop(Type.LONG_TYPE);
        pop(Type.LONG_TYPE);
        push(Type.INT_TYPE);
    }

    /**
     * {@code fcmpl} bytecode instruction.
     */
    public final void fcmpl() {
        pop(Type.FLOAT_TYPE);
        pop(Type.FLOAT_TYPE);
        push(Type.INT_TYPE);
    }

    /**
     * {@code fcmpg} bytecode instruction.
     */
    public final void fcmpg() {
        pop(Type.FLOAT_TYPE);
        pop(Type.FLOAT_TYPE);
        push(Type.INT_TYPE);
    }

    /**
     * {@code dcmpl} bytecode instruction.
     */
    public final void dcmpl() {
        pop(Type.DOUBLE_TYPE);
        pop(Type.DOUBLE_TYPE);
        push(Type.INT_TYPE);
    }

    /**
     * {@code dcmpg} bytecode instruction.
     */
    public final void dcmpg() {
        pop(Type.DOUBLE_TYPE);
        pop(Type.DOUBLE_TYPE);
        push(Type.INT_TYPE);
    }

    /* jump instructions */

    /**
     * {@code ifeq} bytecode instruction.
     * 
     * @param jump
     *            jump target
     */
    public final void ifeq(Jump jump) {
        pop(Type.INT_TYPE);
        jmp(jump);
    }

    /**
     * {@code ifne} bytecode instruction.
     * 
     * @param jump
     *            jump target
     */
    public final void ifne(Jump jump) {
        pop(Type.INT_TYPE);
        jmp(jump);
    }

    /**
     * {@code iflt} bytecode instruction.
     * 
     * @param jump
     *            jump target
     */
    public final void iflt(Jump jump) {
        pop(Type.INT_TYPE);
        jmp(jump);
    }

    /**
     * {@code ifge} bytecode instruction.
     * 
     * @param jump
     *            jump target
     */
    public final void ifge(Jump jump) {
        pop(Type.INT_TYPE);
        jmp(jump);
    }

    /**
     * {@code ifgt} bytecode instruction.
     * 
     * @param jump
     *            jump target
     */
    public final void ifgt(Jump jump) {
        pop(Type.INT_TYPE);
        jmp(jump);
    }

    /**
     * {@code ifle} bytecode instruction.
     * 
     * @param jump
     *            jump target
     */
    public final void ifle(Jump jump) {
        pop(Type.INT_TYPE);
        jmp(jump);
    }

    /**
     * {@code ificmpeq} bytecode instruction.
     * 
     * @param jump
     *            jump target
     */
    public final void ificmpeq(Jump jump) {
        pop(Type.INT_TYPE);
        pop(Type.INT_TYPE);
        jmp(jump);
    }

    /**
     * {@code ificmpne} bytecode instruction.
     * 
     * @param jump
     *            jump target
     */
    public final void ificmpne(Jump jump) {
        pop(Type.INT_TYPE);
        pop(Type.INT_TYPE);
        jmp(jump);
    }

    /**
     * {@code ificmplt} bytecode instruction.
     * 
     * @param jump
     *            jump target
     */
    public final void ificmplt(Jump jump) {
        pop(Type.INT_TYPE);
        pop(Type.INT_TYPE);
        jmp(jump);
    }

    /**
     * {@code ificmpge} bytecode instruction.
     * 
     * @param jump
     *            jump target
     */
    public final void ificmpge(Jump jump) {
        pop(Type.INT_TYPE);
        pop(Type.INT_TYPE);
        jmp(jump);
    }

    /**
     * {@code ificmpgt} bytecode instruction.
     * 
     * @param jump
     *            jump target
     */
    public final void ificmpgt(Jump jump) {
        pop(Type.INT_TYPE);
        pop(Type.INT_TYPE);
        jmp(jump);
    }

    /**
     * {@code ificmple} bytecode instruction.
     * 
     * @param jump
     *            jump target
     */
    public final void ificmple(Jump jump) {
        pop(Type.INT_TYPE);
        pop(Type.INT_TYPE);
        jmp(jump);
    }

    /**
     * {@code ifacmpeq} bytecode instruction.
     * 
     * @param jump
     *            jump target
     */
    public final void ifacmpeq(Jump jump) {
        pop(OBJECT_TYPE);
        pop(OBJECT_TYPE);
        jmp(jump);
    }

    /**
     * {@code ifacmpne} bytecode instruction.
     * 
     * @param jump
     *            jump target
     */
    public final void ifacmpne(Jump jump) {
        pop(OBJECT_TYPE);
        pop(OBJECT_TYPE);
        jmp(jump);
    }

    /**
     * {@code goto} bytecode instruction.
     * 
     * @param jump
     *            jump target
     */
    public final void goTo(Jump jump) {
        jmp(jump);
        discardStack();
    }

    /**
     * {@code ifnull} bytecode instruction.
     * 
     * @param jump
     *            jump target
     */
    public final void ifnull(Jump jump) {
        pop(OBJECT_TYPE);
        jmp(jump);
    }

    /**
     * {@code ifnonnull} bytecode instruction.
     * 
     * @param jump
     *            jump target
     */
    public final void ifnonnull(Jump jump) {
        pop(OBJECT_TYPE);
        jmp(jump);
    }

    /* switch instructions */

    private void jmp(Jump... jumps) {
        for (Jump jump : jumps) {
            jmp(jump);
        }
    }

    /**
     * {@code tableswitch} bytecode instruction.
     * 
     * @param min
     *            minimum entry key
     * @param max
     *            maximum entry key
     * @param dflt
     *            default case label
     * @param jumps
     *            case labels
     */
    public final void tableswitch(int min, int max, Jump dflt, Jump[] jumps) {
        pop(Type.INT_TYPE);
        jmp(dflt);
        jmp(jumps);
        discardStack();
    }

    /**
     * {@code lookupswitch} bytecode instruction.
     * 
     * @param dflt
     *            default case label
     * @param keys
     *            case entry keys
     * @param jumps
     *            case labels
     */
    public final void lookupswitch(Jump dflt, int[] keys, Jump[] jumps) {
        pop(Type.INT_TYPE);
        jmp(dflt);
        jmp(jumps);
        discardStack();
    }

    /* return instructions */

    private void _return() {
        assert sp == 0 : String.format("sp=%d, stack=%s", sp, getStackString());
        discardStack();
    }

    /**
     * {@code ireturn} bytecode instruction.
     */
    public final void ireturn() {
        pop(Type.INT_TYPE);
        _return();
    }

    /**
     * {@code lreturn} bytecode instruction.
     */
    public final void lreturn() {
        pop(Type.LONG_TYPE);
        _return();
    }

    /**
     * {@code freturn} bytecode instruction.
     */
    public final void freturn() {
        pop(Type.FLOAT_TYPE);
        _return();
    }

    /**
     * {@code dreturn} bytecode instruction.
     */
    public final void dreturn() {
        pop(Type.DOUBLE_TYPE);
        _return();
    }

    /**
     * {@code areturn} bytecode instruction.
     */
    public final void areturn() {
        pop(OBJECT_TYPE);
        _return();
    }

    /**
     * {@code return} bytecode instruction.
     */
    public final void voidreturn() {
        _return();
    }

    /* field instructions */

    /**
     * {@code getstatic} bytecode instruction.
     * 
     * @param desc
     *            the field descriptor
     */
    public final void getstatic(Type desc) {
        push(desc);
    }

    /**
     * {@code putstatic} bytecode instruction.
     * 
     * @param desc
     *            the field descriptor
     */
    public final void putstatic(Type desc) {
        pop(desc);
    }

    /**
     * {@code getfield} bytecode instruction.
     * 
     * @param owner
     *            field owner
     * @param desc
     *            the field descriptor
     */
    public final void getfield(Type owner, Type desc) {
        pop(owner);
        push(desc);
    }

    /**
     * {@code putfield} bytecode instruction.
     * 
     * @param owner
     *            field owner
     * @param desc
     *            the field descriptor
     */
    public final void putfield(Type owner, Type desc) {
        pop(desc);
        pop(owner);
    }

    /* invoke instructions */

    private void invoke(MethodTypeDescriptor desc, boolean consumeThis) {
        for (int i = desc.parameterCount(); i > 0;) {
            pop(desc.parameterType(--i));
        }
        if (consumeThis) {
            pop(OBJECT_TYPE);
        }
        Type ret = desc.returnType();
        if (ret.getSort() != Type.Sort.VOID) {
            push(ret);
        }
    }

    /**
     * {@code invokevirtual} bytecode instruction.
     * 
     * @param desc
     *            the method descriptor
     */
    public final void invokevirtual(MethodTypeDescriptor desc) {
        invoke(desc, true);
    }

    /**
     * {@code invokespecial} bytecode instruction.
     * 
     * @param desc
     *            the method descriptor
     */
    public final void invokespecial(MethodTypeDescriptor desc) {
        invoke(desc, true);
    }

    /**
     * {@code invokestatic} bytecode instruction.
     * 
     * @param desc
     *            the method descriptor
     */
    public final void invokestatic(MethodTypeDescriptor desc) {
        invoke(desc, false);
    }

    /**
     * {@code invokeinterface} bytecode instruction.
     * 
     * @param desc
     *            the method descriptor
     */
    public final void invokeinterface(MethodTypeDescriptor desc) {
        invoke(desc, true);
    }

    /**
     * {@code invokedynamic} bytecode instruction.
     * 
     * @param desc
     *            the method descriptor
     */
    public final void invokedynamic(MethodTypeDescriptor desc) {
        invoke(desc, false);
    }

    /* other instructions */

    /**
     * {@code new} bytecode instruction.
     * 
     * @param type
     *            the object type
     */
    public final void anew(Type type) {
        push(type);
    }

    /**
     * {@code newarray} or {@code anewarray} bytecode instruction.
     * 
     * @param type
     *            the array component type
     */
    public final void newarray(Type type) {
        pop(Type.INT_TYPE);
        push(type.asArray());
    }

    /**
     * {@code arraylength} bytecode instruction.
     */
    public final void arraylength() {
        pop(OBJECT_TYPE);
        push(Type.INT_TYPE);
    }

    /**
     * {@code athrow} bytecode instruction.
     */
    public final void athrow() {
        pop(OBJECT_TYPE);
        discardStack();
    }

    /**
     * {@code checkcast} bytecode instruction.
     * 
     * @param type
     *            the object type
     */
    public final void checkcast(Type type) {
        pop(OBJECT_TYPE);
        push(type);
    }

    /**
     * {@code instanceOf} bytecode instruction.
     * 
     * @param type
     *            the object type
     */
    public final void instanceOf(Type type) {
        pop(OBJECT_TYPE);
        push(Type.INT_TYPE);
    }

    /**
     * {@code monitorenter} bytecode instruction.
     */
    public final void monitorenter() {
        pop(OBJECT_TYPE);
    }

    /**
     * {@code monitorexit} bytecode instruction.
     */
    public final void monitorexit() {
        pop(OBJECT_TYPE);
    }

    /**
     * {@code multianewarray} bytecode instruction.
     * 
     * @param type
     *            the array type
     * @param dims
     *            the array dimensions
     */
    public final void multianewarray(Type type, int dims) {
        for (int i = dims; i > 0; --i) {
            pop(Type.INT_TYPE);
        }
        push(type);
    }
}
