/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set shiftwidth=4 tabstop=4 autoindent cindent expandtab: */

'use strict';

var VM = {};

VM.execute = function(frame) {
    var cp = frame.cp;
    var stack = frame.stack;

    while (true) {
        var op = frame.read8();
        // console.log(frame.methodInfo.classInfo.className + " " + frame.methodInfo.name + " " + (frame.ip - 1) + " " + OPCODES[op] + " " + stack.length);
        switch (op) {
        case 0x00: // nop
            break;
        case 0x01: // aconst_null
            stack.push(null);
            break;
        case 0x02: // aconst_m1
            stack.push(-1);
            break;
        case 0x03: // iconst_0
        case 0x0b: // fconst_0
        case 0x0e: // dconst_0
            stack.push(0);
            break;
        case 0x04: // iconst_1
        case 0x0c: // fconst_1
        case 0x0f: // dconst_1
            stack.push(1);
            break;
        case 0x05: // iconst_2
        case 0x0d: // fconst_2
            stack.push(2);
            break;
        case 0x06: // iconst_3
            stack.push(3);
            break;
        case 0x07: // iconst_4
            stack.push(4);
            break;
        case 0x08: // iconst_5
            stack.push(5);
            break;
        case 0x09: // lconst_0
            stack.push2(Long.fromInt(0));
            break;
        case 0x0a: // lconst_1
            stack.push2(Long.fromInt(1));
            break;
        case 0x10: // bipush
            stack.push(frame.read8signed());
            break;
        case 0x11: // sipush
            stack.push(frame.read16signed());
            break;
        case 0x12: // ldc
        case 0x13: // ldc_w
            var constant = cp[(op === 0x12) ? frame.read8() : frame.read16()];
            switch(constant.tag) {
            case TAGS.CONSTANT_Integer:
                stack.push(constant.integer);
                break;
            case TAGS.CONSTANT_Float:
                stack.push(constant.float);
                break;
            case TAGS.CONSTANT_String:
                stack.push(CLASSES.newString(cp[constant.string_index].bytes));
                break;
            default:
                throw new Error("not support constant type");
            }
            break;
        case 0x14: // ldc2_w
            var constant = cp[frame.read16()];
            switch(constant.tag) {
            case TAGS.CONSTANT_Long:
                stack.push2(Long.fromBits(constant.lowBits, constant.highBits));
                break;
            case TAGS.CONSTANT_Double:
                stack.push2(constant.double);
                break;
            default:
                throw new Error("not support constant type");
            }
            break;
        case 0x15: // iload
        case 0x17: // fload
        case 0x19: // aload
            var idx = frame.isWide() ? frame.read16() : frame.read8();
            stack.push(frame.getLocal(idx));
            break;
        case 0x16: // lload
        case 0x18: // dload
            var idx = frame.isWide() ? frame.read16() : frame.read8();
            stack.push2(frame.getLocal(idx));
            break;
        case 0x1a: // iload_0
        case 0x22: // fload_0
        case 0x2a: // aload_0
            stack.push(frame.getLocal(0));
            break;
        case 0x1b: // iload_1
        case 0x23: // fload_1
        case 0x2b: // aload_1
            stack.push(frame.getLocal(1));
            break;
        case 0x1c: // iload_2
        case 0x24: // fload_2
        case 0x2c: // aload_2
            stack.push(frame.getLocal(2));
            break;
        case 0x1d: // iload_3
        case 0x25: // fload_3
        case 0x2d: // aload_3
            stack.push(frame.getLocal(3));
            break;
        case 0x1e: // lload_0
        case 0x26: // dload_0
            stack.push2(frame.getLocal(0));
            break;
        case 0x1f: // lload_1
        case 0x27: // dload_1
            stack.push2(frame.getLocal(1));
            break;
        case 0x20: // lload_2
        case 0x28: // dload_2
            stack.push2(frame.getLocal(2));
            break;
        case 0x21: // lload_3
        case 0x29: // dload_3
            stack.push2(frame.getLocal(3));
            break;
        case 0x2e: // iaload
        case 0x30: // faload
        case 0x32: // aaload
        case 0x33: // baload
        case 0x34: // caload
        case 0x35: // saload
            var idx = stack.pop();
            var refArray = stack.pop();
            if (!frame.checkArrayAccess(refArray, idx))
                break;
            stack.push(refArray[idx]);
            break;
        case 0x2f: // laload
        case 0x31: // daload
            var idx = stack.pop();
            var refArray = stack.pop();
            if (!frame.checkArrayAccess(refArray, idx))
                break;
            stack.push2(refArray[idx]);
            break;
        case 0x36: // istore
        case 0x38: // fstore
        case 0x3a: // astore
            var idx = frame.isWide() ? frame.read16() : frame.read8();
            frame.setLocal(idx, stack.pop());
            break;
        case 0x37: // lstore
        case 0x39: // dstore
            var idx = frame.isWide() ? frame.read16() : frame.read8();
            frame.setLocal(idx, stack.pop2());
            break;
        case 0x3b: // istore_0
        case 0x43: // fstore_0
        case 0x4b: // astore_0
            frame.setLocal(0, stack.pop());
            break;
        case 0x3c: // istore_1
        case 0x44: // fstore_1
        case 0x4c: // astore_1
            frame.setLocal(1, stack.pop());
            break;
        case 0x3d: // istore_2
        case 0x45: // fstore_2
        case 0x4d: // astore_2
            frame.setLocal(2, stack.pop());
            break;
        case 0x3e: // istore_3
        case 0x46: // fstore_3
        case 0x4e: // astore_3
            frame.setLocal(3, stack.pop());
            break;
        case 0x3f: // lstore_0
        case 0x47: // dstore_0
            frame.setLocal(0, stack.pop2());
            break;
        case 0x40: // lstore_1
        case 0x48: // dstore_1
            frame.setLocal(1, stack.pop2());
            break;
        case 0x41: // lstore_2
        case 0x49: // dstore_2
            frame.setLocal(2, stack.pop2());
            break;
        case 0x42: // lstore_3
        case 0x4a: // dstore_3
            frame.setLocal(3, stack.pop2());
            break;
        case 0x4f: // iastore
        case 0x51: // fastore
        case 0x53: // aastore
        case 0x54: // bastore
        case 0x55: // castore
        case 0x56: // sastore
            var val = stack.pop();
            var idx = stack.pop();
            var refArray = stack.pop();
            if (!frame.checkArrayAccess(refArray, idx))
                break;
            refArray[idx] = val;
            break;
        case 0x50: // lastore
        case 0x52: // dastore
            var val = stack.pop2();
            var idx = stack.pop();
            var refArray = stack.pop();
            if (!frame.checkArrayAccess(refArray, idx))
                break;
            refArray[idx] = val;
            break;
        case 0x57: // pop
            stack.pop();
            break;
        case 0x58: // pop2
            stack.pop2();
            break;
        case 0x59: // dup
            var val = stack.pop();
            stack.push(val);
            stack.push(val);
            break;
        case 0x5a: // dup_x1
            var a = stack.pop();
            var b = stack.pop();
            stack.push(a);
            stack.push(b);
            stack.push(a);
            break;
        case 0x5b: // dup_x2
            var a = stack.pop();
            var b = stack.pop();
            var c = stack.pop();
            stack.push(a);
            stack.push(c);
            stack.push(b);
            stack.push(a);
            break;
        case 0x5c: // dup2
            var a = stack.pop();
            var b = stack.pop();
            stack.push(b);
            stack.push(a);
            stack.push(b);
            stack.push(a);
            break;
        case 0x5d: // dup2_x1
            var a = stack.pop();
            var b = stack.pop();
            var c = stack.pop();
            stack.push(b);
            stack.push(a);
            stack.push(c);
            stack.push(b);
            stack.push(a);
            break;
        case 0x5e: // dup2_x2
            var a = stack.pop();
            var b = stack.pop();
            var c = stack.pop();
            var d = stack.pop();
            stack.push(b);
            stack.push(a);
            stack.push(d);
            stack.push(c);
            stack.push(b);
            stack.push(a);
            break;
        case 0x5f: // swap
            var a = stack.pop();
            var b = stack.pop();
            stack.push(a);
            stack.push(b);
            break;
        case 0x84: // iinc
            var wide = frame.isWide();
            var idx = wide ? frame.read16() : frame.read8();
            var val = wide ? frame.read16signed() : frame.read8signed();
            frame.setLocal(idx, frame.getLocal(idx) + val);
            break;
        case 0x60: // iadd
            stack.push((stack.pop() + stack.pop())|0);
            break;
        case 0x61: // ladd
            stack.push2(stack.pop2().add(stack.pop2()));
            break;
        case 0x62: // fadd
            stack.push(util.double2float(stack.pop() + stack.pop()));
            break;
        case 0x63: // dadd
            stack.push2(stack.pop2() + stack.pop2());
            break;
        case 0x64: // isub
            stack.push((- stack.pop() + stack.pop())|0);
            break;
        case 0x65: // lsub
            stack.push2(stack.pop2().add(stack.pop2()).negate());
            break;
        case 0x66: // fsub
            stack.push(util.double2float(- stack.pop() + stack.pop()));
            break;
        case 0x67: // dsub
            stack.push2(- stack.pop2() + stack.pop2());
            break;
        case 0x68: // imul
            stack.push(Math.imul(stack.pop(), stack.pop()));
            break;
        case 0x69: // lmul
            stack.push2(stack.pop2().multiply(stack.pop2()));
            break;
        case 0x6a: // fmul
            stack.push(util.double2float(stack.pop() * stack.pop()));
            break;
        case 0x6b: // dmul
            stack.push2(stack.pop2() * stack.pop2());
            break;
        case 0x6c: // idiv
            var b = stack.pop();
            var a = stack.pop();
            if (!b) {
                frame.raiseException("java/lang/ArithmeticException", "/ by zero");
                break;
            }
            stack.push((a === util.INT_MIN && b === -1) ? a : ((a / b)|0));
            break;
        case 0x6d: // ldiv
            var b = stack.pop2();
            var a = stack.pop2();
            if (b.isZero()) {
                frame.raiseException("java/lang/ArithmeticException", "/ by zero");
                break;
            }
            stack.push2(a.div(b));
            break;
        case 0x6e: // fdiv
            var b = stack.pop();
            var a = stack.pop();
            stack.push(util.double2float(a / b));
            break;
        case 0x6f: // ddiv
            var b = stack.pop2();
            var a = stack.pop2();
            stack.push2(a / b);
            break;
        case 0x70: // irem
            var b = stack.pop();
            var a = stack.pop();
            if (!b) {
                frame.raiseException("java/lang/ArithmeticException", "/ by zero");
                break;
            }
            stack.push(a % b);
            break;
        case 0x71: // lrem
            var b = stack.pop2();
            var a = stack.pop2();
            if (b.isZero()) {
                frame.raiseException("java/lang/ArithmeticException", "/ by zero");
                break;
            }
            stack.push2(a.modulo(b));
            break;
        case 0x72: // frem
            var b = stack.pop();
            var a = stack.pop();
            stack.push(util.double2float(a % b));
            break;
        case 0x73: // drem
            var b = stack.pop2();
            var a = stack.pop2();
            stack.push2(a % b);
            break;
        case 0x74: // ineg
            stack.push((- stack.pop())|0);
            break;
        case 0x75: // lneg
            stack.push2(stack.pop2().negate());
            break;
        case 0x76: // fneg
            stack.push(- stack.pop());
            break;
        case 0x77: // dneg
            stack.push2(- stack.pop2());
            break;
        case 0x78: // ishl
            var b = stack.pop();
            var a = stack.pop();
            stack.push(a << b);
            break;
        case 0x79: // lshl
            var b = stack.pop2();
            var a = stack.pop2();
            stack.push2(a.shiftLeft(b));
            break;
        case 0x7a: // ishr
            var b = stack.pop();
            var a = stack.pop();
            stack.push(a >> b);
            break;
        case 0x7b: // lshr
            var b = stack.pop2();
            var a = stack.pop2();
            stack.push2(a.shiftRight(b));
            break;
        case 0x7c: // iushr
            var b = stack.pop();
            var a = stack.pop();
            stack.push(a >>> b);
            break;
        case 0x7d: // lushr
            var b = stack.pop2();
            var a = stack.pop2();
            stack.push2(a.shiftRightUnsigned(b));
            break;
        case 0x7e: // iand
            stack.push(stack.pop() & stack.pop());
            break;
        case 0x7f: // land
            stack.push2(stack.pop2().and(stack.pop2()));
            break;
        case 0x80: // ior
            stack.push(stack.pop() | stack.pop());
            break;
        case 0x81: // lor
            stack.push2(stack.pop2().or(stack.pop2()));
            break;
        case 0x82: // ixor
            stack.push(stack.pop() ^ stack.pop());
            break;
        case 0x83: // lxor
            stack.push2(stack.pop2().xor(stack.pop2()));
            break;
        case 0x94: // lcmp
            var b = stack.pop2();
            var a = stack.pop2();
            if (a.greaterThan(b)) {
                stack.push(1);
            } else if (a.lessThan(b)) {
                stack.push(-1);
            } else {
                stack.push(0);
            }
            break;
        case 0x95: // fcmpl
            var b = stack.pop();
            var a = stack.pop();
            if (isNaN(a) || isNaN(b)) {
                stack.push(-1);
            } else if (a > b) {
                stack.push(1);
            } else if (a < b) {
                stack.push(-1);
            } else {
                stack.push(0);
            }
            break;
        case 0x96: // fcmpg
            var b = stack.pop();
            var a = stack.pop();
            if (isNaN(a) || isNaN(b)) {
                stack.push(1);
            } else if (a > b) {
                stack.push(1);
            } else if (a < b) {
                stack.push(-1);
            } else {
                stack.push(0);
            }
            break;
        case 0x97: // dcmpl
            var b = stack.pop2();
            var a = stack.pop2();
            if (isNaN(a) || isNaN(b)) {
                stack.push(-1);
            } else if (a > b) {
                stack.push(1);
            } else if (a < b) {
                stack.push(-1);
            } else {
                stack.push(0);
            }
            break;
        case 0x98: // dcmpg
            var b = stack.pop2();
            var a = stack.pop2();
            if (isNaN(a) || isNaN(b)) {
                stack.push(1);
            } else if (a > b) {
                stack.push(1);
            } else if (a < b) {
                stack.push(-1);
            } else {
                stack.push(0);
            }
            break;
        case 0x99: // ifeq
            var jmp = frame.ip - 1 + frame.read16signed();
            frame.ip = stack.pop() === 0 ? jmp : frame.ip;
            break;
        case 0x9a: // ifne
            var jmp = frame.ip - 1 + frame.read16signed();
            frame.ip = stack.pop() !== 0 ? jmp : frame.ip;
            break;
        case 0x9b: // iflt
            var jmp = frame.ip - 1 + frame.read16signed();
            frame.ip = stack.pop() < 0 ? jmp : frame.ip;
            break;
        case 0x9c: // ifge
            var jmp = frame.ip - 1 + frame.read16signed();
            frame.ip = stack.pop() >= 0 ? jmp : frame.ip;
            break;
        case 0x9d: // ifgt
            var jmp = frame.ip - 1 + frame.read16signed();
            frame.ip = stack.pop() > 0 ? jmp : frame.ip;
            break;
        case 0x9e: // ifle
            var jmp = frame.ip - 1 + frame.read16signed();
            frame.ip = stack.pop() <= 0 ? jmp : frame.ip;
            break;
        case 0x9f: // if_icmpeq
            var jmp = frame.ip - 1 + frame.read16signed();
            frame.ip = stack.pop() === stack.pop() ? jmp : frame.ip;
            break;
        case 0xa0: // if_cmpne
            var jmp = frame.ip - 1 + frame.read16signed();
            frame.ip = stack.pop() !== stack.pop() ? jmp : frame.ip;
            break;
        case 0xa1: // if_icmplt
            var jmp = frame.ip - 1 + frame.read16signed();
            frame.ip = stack.pop() > stack.pop() ? jmp : frame.ip;
            break;
        case 0xa2: // if_icmpge
            var jmp = frame.ip - 1 + frame.read16signed();
            frame.ip = stack.pop() <= stack.pop() ? jmp : frame.ip;
            break;
        case 0xa3: // if_icmpgt
            var jmp = frame.ip - 1 + frame.read16signed();
            frame.ip = stack.pop() < stack.pop() ? jmp : frame.ip;
            break;
        case 0xa4: // if_icmple
            var jmp = frame.ip - 1 + frame.read16signed();
            frame.ip = stack.pop() >= stack.pop() ? jmp : frame.ip;
            break;
        case 0xa5: // if_acmpeq
            var jmp = frame.ip - 1 + calee.read16signed();
            frame.ip = stack.pop() === stack.pop() ? jmp : frame.ip;
            break;
        case 0xa6: // if_acmpne
            var jmp = frame.ip - 1 + frame.read16signed();
            frame.ip = stack.pop() !== stack.pop() ? jmp : frame.ip;
            break;
        case 0xc6: // ifnull
            var ref = stack.pop();
            if (!ref)
                frame.ip += frame.read16signed() - 1;
            break;
        case 0xc7: // ifnonnull
            var ref = stack.pop();
            if (!!ref)
                frame.ip += frame.read16signed() - 1;
            break;
        case 0xa7: // goto
            frame.ip += frame.read16signed() - 1;
            break;
        case 0xc8: // goto_w
            frame.ip += frame.read32signed() - 1;
            break;
        case 0xa8: // jsr
            var jmp = frame.read16();
            stack.push(frame.ip);
            frame.ip = jmp;
            break;
        case 0xc9: // jsr_w
            var jmp = frame.read32();
            stack.push(frame.ip);
            frame.ip = jmp;
            break;
        case 0xa9: // ret
            var idx = frame.isWide() ? frame.read16() : frame.read8();
            frame.ip = frame.getLocal(idx);
            break;
        case 0x85: // i2l
            stack.push2(Long.fromInt(stack.pop()));
            break;
        case 0x86: // i2f
            break;
        case 0x87: // i2d
            stack.push2(stack.pop());
            break;
        case 0x88: // l2i
            stack.push(stack.pop2().toInt());
            break;
        case 0x89: // l2f
            stack.push(util.double2float(stack.pop2().toNumber()));
            break;
        case 0x8a: // l2d
            stack.push2(stack.pop2().toNumber());
            break;
        case 0x8b: // f2i
            stack.push(util.double2int(stack.pop()));
            break;
        case 0x8c: // f2l
            stack.push2(Long.fromNumber(stack.pop()));
            break;
        case 0x8d: // f2d
            stack.push2(stack.pop());
            break;
        case 0x8e: // d2i
            stack.push(util.double2int(stack.pop2()));
            break;
        case 0x8f: // d2l
            stack.push2(util.double2long(stack.pop2()));
            break;
        case 0x90: // d2f
            stack.push(util.double2float(stack.pop2()));
            break;
        case 0x91: // i2b
            stack.push((stack.pop() << 24) >> 24);
            break;
        case 0x92: // i2c
            stack.push(stack.pop() & 0xffff);
            break;
        case 0x93: // i2s
            stack.push((stack.pop() << 16) >> 16);
            break;
        case 0xaa: // tableswitch
            var startip = frame.ip;
            while ((frame.ip & 3) != 0)
                frame.ip++;
            var def = frame.read32signed();
            var low = frame.read32signed();
            var high = frame.read32signed();
            var val = stack.pop();
            var jmp;
            if (val < low || val > high) {
                jmp = def;
            } else {
                frame.ip  += (val - low) << 2;
                jmp = frame.read32signed();
            }
            frame.ip = startip - 1 + jmp;
            break;
        case 0xab: // lookupswitch
            var startip = frame.ip;
            while ((frame.ip & 3) != 0)
                frame.ip++;
            var jmp = frame.read32signed();
            var size = frame.read32();
            var val = frame.stack.pop();
          lookup:
            for (var i=0; i<size; i++) {
                var key = frame.read32signed();
                var offset = frame.read32signed();
                if (key === val) {
                    jmp = offset;
                }
                if (key >= val) {
                    break lookup;
                }
            }
            frame.ip = startip - 1 + jmp;
            break;
        case 0xbc: // newarray
            var type = frame.read8();
            var size = stack.pop();
            if (size < 0) {
                frame.raiseException("java/lang/NegativeSizeException");
                break;
            }
            stack.push(CLASSES.newPrimitiveArray(ARRAY_TYPE[type], size));
            break;
        case 0xbd: // anewarray
            var idx = frame.read16();
            var className = cp[cp[idx].name_index].bytes;
            var size = stack.pop();
            if (size < 0) {
                frame.raiseException("java/lang/NegativeSizeException");
                break;
            }
            stack.push(CLASSES.newArray(className, size));
            break;
        case 0xc5: // multianewarray
            var idx = frame.read16();
            var typeName = cp[cp[idx].name_index].bytes;
            var dimensions = frame.read8();
            var lengths = new Array(dimensions);
            for (var i=0; i<dimensions; i++)
                lengths[i] = stack.pop();
            stack.push(CLASSES.newMultiArray(typeName, lengths));
            break;
        case 0xbe: // arraylength
            stack.push(stack.pop().length);
            break;
        case 0xb4: // getfield
            var idx = frame.read16();
            var fieldName = cp[cp[cp[idx].name_and_type_index].name_index].bytes;
            var signature = cp[cp[cp[idx].name_and_type_index].signature_index].bytes;
            var obj = stack.pop();
            if (!obj) {
                frame.raiseException("java/lang/NullPointerException");
                break;
            }
            var value = obj[fieldName];
            if (typeof value === "undefined") {
                value = util.defaultValue(signature);
            }
            stack.pushType(signature, value);
            break;
        case 0xb5: // putfield
            var idx = frame.read16();
            var fieldName = cp[cp[cp[idx].name_and_type_index].name_index].bytes;
            var signature = cp[cp[cp[idx].name_and_type_index].signature_index].bytes;
            var val = stack.popType(signature);
            var obj = stack.pop();
            if (!obj) {
                frame.raiseException("java/lang/NullPointerException");
                break;
            }
            obj[fieldName] = val;
            break;
        case 0xb2: // getstatic
            var idx = frame.read16();
            var className = cp[cp[cp[idx].class_index].name_index].bytes;
            var fieldName = cp[cp[cp[idx].name_and_type_index].name_index].bytes;
            var signature = cp[cp[cp[idx].name_and_type_index].signature_index].bytes;
            stack.pushType(signature, CLASSES.getStaticField(className, fieldName));
            break;
        case 0xb3: // putstatic
            var idx = frame.read16();
            var className = cp[cp[cp[idx].class_index].name_index].bytes;
            var fieldName = cp[cp[cp[idx].name_and_type_index].name_index].bytes;
            var signature = cp[cp[cp[idx].name_and_type_index].signature_index].bytes;
            CLASSES.setStaticField(className, fieldName, stack.popType(signature));
            break;
        case 0xbb: // new
            var idx = frame.read16();
            var className = cp[cp[idx].name_index].bytes;
            stack.push(CLASSES.newObject(className));
            break;
        case 0xc0: // checkcast
            var idx = frame.read16();
            var type = cp[cp[idx].name_index].bytes;
            break;
        case 0xc1: // instanceof
            var idx = frame.read16();
            var className = cp[cp[idx].name_index].bytes;
            var obj = stack.pop();
            stack.push(obj.class.className === className);
            break;
        case 0xbf: // athrow
            frame.throw(stack.pop());
            break;
        case 0xc2: // monitorenter
            var obj = stack.pop();
            if (!obj) {
                frame.raiseException("java/lang/NullPointerException");
                break;
            }
            // if (obj.hasOwnProperty("$lock$")) {
            // stack.push(obj);
            // frame.ip--;
            // SCHEDULER.yield();
            // } else {
            // obj["$lock$"] = "locked";
            // }
            break;
        case 0xc3: // monitorexit
            var obj = stack.pop();
            if (!obj) {
                frame.raiseException("java/lang/NullPointerException");
                break;
            }
            // delete obj["$lock$"];
            // SCHEDULER.yield();
            break;
        case 0xc4: // wide
            break;
        case 0xb6: // invokevirtual
        case 0xb7: // invokespecial
        case 0xb8: // invokestatic
        case 0xb9: // invokeinterface
            var idx = frame.read16();
            if (op === 0xb9) {
                var argsNumber = frame.read8();
                var zero = frame.read8();
            }
            var className = cp[cp[cp[idx].class_index].name_index].bytes;
            var methodName = cp[cp[cp[idx].name_and_type_index].name_index].bytes;
            var signature = cp[cp[cp[idx].name_and_type_index].signature_index].bytes;
            var classInfo = CLASSES.getClass(className, op === 0xb8);
            var methodInfo = CLASSES.getMethod(classInfo, methodName, signature, op === 0xb8);
            var consumes = Signature.parse(methodInfo.signature).IN.slots;
            if (op !== OPCODES.invokestatic) {
                ++consumes;
                var obj = stack[stack.length - consumes];
                if (!obj) {
                    frame.raiseException("java/lang/NullPointerException");
                    return;
                }
                switch (op) {
                case OPCODES.invokevirtual:
                    // console.log("virtual dispatch", methodInfo.classInfo.className, obj.class.className, methodInfo.name, methodInfo.signature);
                    if (methodInfo.classInfo != obj.class)
                        methodInfo = CLASSES.getMethod(obj.class, methodInfo.name, methodInfo.signature, op === OPCODES.invokestatic);
                    break;
                }
            }
            if (ACCESS_FLAGS.isNative(methodInfo.access_flags)) {
                NATIVE.invokeNative(frame, methodInfo);
                continue;
            }
            VM.execute(frame.pushFrame(methodInfo, consumes));
            break;
        case 0xb1: // return
            frame.popFrame();
            return;
        case 0xac: // ireturn
        case 0xae: // freturn
        case 0xb0: // areturn
            frame.popFrame().stack.push(frame.stack.pop());
            return;
        case 0xad: // lreturn
        case 0xaf: // dreturn
            frame.popFrame().stack.push2(frame.stack.pop2());
            return;
        default:
            var opName = OPCODES[op];
            throw new Error("Opcode " + opName + " [" + op + "] not supported.");
        }
    };
}