package org.raku.nqp.jast2bc;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.IincInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.LineNumberNode;
import org.objectweb.asm.tree.LookupSwitchInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.MultiANewArrayInsnNode;
import org.objectweb.asm.tree.TableSwitchInsnNode;
import org.objectweb.asm.tree.TryCatchBlockNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

@SuppressWarnings("unchecked") /* our asm is stripped */
class AutosplitMethodWriter extends MethodNode {

    /** Maximum size of a method to leave alone. */
    private static final int MAX_UNSPLIT_METHOD = 65535;
    private static final int MAX_FRAGMENT = 65535;
    private static final int MAX_SWITCH = 256;

    /** True to dump control flow analysis. */
    private static final boolean DEBUG_CONTROL = false;
    private static final boolean DEBUG_FRAGMENT = false;
    private static final boolean TYPE_TRACE = false;

    /** The real instructions (not branches) in program order.  Filled out by {@link getControlFlow()}. */
    private AbstractInsnNode[] insnList;
    private Map<AbstractInsnNode, Integer> insnMap;
    private int[] lineNumbers;

    /** Array of (source, target) pairs.  Filled out by {@link getControlFlow()}.  -1 means from-outside. */
    private ControlEdge[] controlEdges;
    private ControlEdge[][] successors;
    private int[] baselineSize;
    private Frame[] types;

    private int nextJumpNo;
    private int[] jumpNoMap;
    private List<Integer> firstJump = new ArrayList< >();

    private static class ControlEdge {
        public final int from, to;
        public final String exn; // if non-null, replace the stack with this
        public ControlEdge(int f, int t, String e) { from = f; to = t; exn = e; }
    }

    private int nlocal;

    private final ClassVisitor target;
    private final String tgtype;

    public AutosplitMethodWriter(ClassVisitor target, String tgtype, int access, String name, String desc, String sig, String[] exn) {
        super(Opcodes.ASM4, access, name, desc, sig, exn);
        this.target = target;
        this.tgtype = tgtype;
    }

    @Override
    public void visitEnd() {
        super.visitEnd();

        int maxsize = 0;
        for (AbstractInsnNode ai = instructions.getFirst(); ai != null; ai = ai.getNext())
            maxsize += insnSize(ai);

        if (maxsize <= MAX_UNSPLIT_METHOD) {
            // hey cool, we don't need to do anything fancy here
            MethodVisitor mw = target.visitMethod(access, name, desc, signature, ((List<String>)exceptions).toArray(new String[0]));
            accept(mw);
            return;
        }

        /* we need to split this thing */

        if (DEBUG_FRAGMENT) System.out.printf("method=%s max=%d\n", name, maxsize);

        splitSwitches();
        getInstructions();
        getControlFlow();
        if (DEBUG_CONTROL) printControlFlow();
        getTypes();
        getBaselineSize();

        if (DEBUG_CONTROL) {
            for (int i = 1; i <= insnList.length; i++)
                System.out.printf("from=%d to=%d frag=%d\n", 0,i,calcFragmentSize(0, i));
        }

        jumpNoMap = new int[insnList.length];
        Arrays.fill(jumpNoMap, -1);
        List<Integer> fragmentSizes = new ArrayList< >();
        int taken = 0;
        while (taken < insnList.length) {
            if (calcFragmentSize(taken, taken+1) > MAX_FRAGMENT)
                throw new RuntimeException("cannot take even one more instruction at "+taken);
            int takeable = bite(taken, 1, insnList.length - taken);

            if (DEBUG_FRAGMENT) System.out.printf("fragment: %d - %d (max %d bytes)\n", taken, taken + takeable - 1, calcFragmentSize(taken, taken+takeable));
            fragmentSizes.add(takeable);
            allocateJumpNrs(taken, taken+takeable);
            taken += takeable;
        }

        taken = 0;
        int fno = 0;
        for (int sz : fragmentSizes) {
            emitFragment(fno++, taken, taken+sz);
            taken += sz;
        }

        becomeWrapper();
        accept(target);
    }

    private int bite(int from, int min_take, int max_take) { /* min_take is known good */
        while (true) {
            if (min_take == max_take) return min_take;
            int mid_take = (min_take + max_take + 1) / 2;

            if (calcFragmentSize(from, from+mid_take) <= MAX_FRAGMENT) {
                min_take = mid_take;
            } else {
                max_take = mid_take-1;
            }
        }
    }

    private boolean isRealInsn(AbstractInsnNode node) {
        switch (node.getType()) {
            case AbstractInsnNode.LINE:
            case AbstractInsnNode.LABEL:
            case AbstractInsnNode.FRAME:
                return false;
            default:
                return true;
        }
    }

    /** Break apart large switch instructions so that they may fit in a fragment. Runs before {@link getInstructions} because it changes instruction sequence. */
    private void splitSwitches() {
        AbstractInsnNode ptr = instructions.getFirst();

        while (ptr != null) {
            int cutoff = 0;
            AbstractInsnNode left = null, right = null;

            switch (ptr.getType()) {
                case AbstractInsnNode.LOOKUPSWITCH_INSN:
                    {
                        LookupSwitchInsnNode lsi = (LookupSwitchInsnNode) ptr;
                        if (lsi.labels.size() <= MAX_SWITCH) break;
                        LookupSwitchInsnNode lsl = new LookupSwitchInsnNode(lsi.dflt, new int[0], new LabelNode[0]);
                        LookupSwitchInsnNode lsr = new LookupSwitchInsnNode(lsi.dflt, new int[0], new LabelNode[0]);

                        int lsisz = lsi.labels.size();
                        lsl.keys.addAll(lsi.keys.subList(0, lsisz / 2));
                        lsr.keys.addAll(lsi.keys.subList(lsisz / 2, lsisz));
                        lsl.labels.addAll(lsi.labels.subList(0, lsisz / 2));
                        lsr.labels.addAll(lsi.labels.subList(lsisz / 2, lsisz));
                        left = lsl;
                        right = lsr;
                        cutoff = (Integer)lsr.keys.get(0);
                    }
                    break;

                case AbstractInsnNode.TABLESWITCH_INSN:
                    {
                        TableSwitchInsnNode lsi = (TableSwitchInsnNode) ptr;
                        if (lsi.labels.size() <= MAX_SWITCH) break;
                        cutoff = (lsi.min + lsi.max) / 2;
                        TableSwitchInsnNode lsl = new TableSwitchInsnNode(lsi.min, cutoff-1, lsi.dflt);
                        TableSwitchInsnNode lsr = new TableSwitchInsnNode(cutoff, lsi.max, lsi.dflt);

                        lsl.labels.addAll(lsi.labels.subList(0, cutoff - lsi.min));
                        lsr.labels.addAll(lsi.labels.subList(cutoff - lsi.min, lsi.max + 1 - lsi.min));
                        left = lsl;
                        right = lsr;
                    }
                    break;

                default: break;
            }

            if (left != null) {
                if (DEBUG_FRAGMENT) System.out.printf("Breaking switch at %d\n", cutoff);

                LabelNode high = new LabelNode();
                instructions.insertBefore(ptr, new InsnNode(Opcodes.DUP));
                instructions.insertBefore(ptr, intNode(cutoff));
                instructions.insertBefore(ptr, new JumpInsnNode(Opcodes.IF_ICMPGE, high));
                instructions.insertBefore(ptr, left);
                instructions.insertBefore(ptr, high);
                instructions.insertBefore(ptr, right);
                instructions.remove(ptr);

                ptr = left;
            } else {
                ptr = ptr.getNext();
            }
        }
    }

    private AbstractInsnNode intNode(int value) {
        return (value >= -1   && value <= 5) ? new InsnNode(Opcodes.ICONST_0 + value) :
               (value >= -128 && value <= 127) ? new IntInsnNode(Opcodes.BIPUSH, value) :
               (value >= -32768 && value <= 32767) ? new IntInsnNode(Opcodes.SIPUSH, value) :
               new LdcInsnNode(value);
    }

    /** Extract the real instructions from the instruction list. */
    private void getInstructions() {
        // munge the linked list of insns we got from ASM into something saner
        ArrayList<AbstractInsnNode> tempInsnList = new ArrayList< >();
        insnMap = new HashMap< >();
        HashMap<AbstractInsnNode, Integer> linesMap = new HashMap< >();

        for (AbstractInsnNode n = instructions.getFirst(); n != null; n = n.getNext()) {
            insnMap.put(n, tempInsnList.size());
            if (isRealInsn(n)) tempInsnList.add(n);
            if (n.getType() == AbstractInsnNode.LINE) {
                LineNumberNode nn = (LineNumberNode) n;
                AbstractInsnNode start = nn.start;
                while (start != null && !isRealInsn(start)) start = start.getNext();
                linesMap.put(start, nn.line);
            }
        }
        insnList = tempInsnList.toArray(new AbstractInsnNode[0]);

        int curLine = 0;
        lineNumbers = new int[insnList.length];

        for (int i = 0; i < insnList.length; i++) {
            Integer ll = linesMap.get(insnList[i]);
            if (ll != null) curLine = ll.intValue();
            lineNumbers[i] = curLine;
        }
    }

    /** Build the control flow graph. */
    private void getControlFlow() {
        List<ControlEdge> controlTemp = new ArrayList< >();
        List<ControlEdge>[] succTemps = new List[insnList.length];

        successors = new ControlEdge[insnList.length][];


        for (int insnNo = 0; insnNo < insnList.length; insnNo++) {
            AbstractInsnNode node = insnList[insnNo];

            List<ControlEdge> succTemp = new ArrayList< >();
            succTemps[insnNo] = succTemp;

            switch (node.getType()) {
                case AbstractInsnNode.JUMP_INSN:
                    JumpInsnNode ji = (JumpInsnNode) node;
                    succTemp.add(new ControlEdge(insnNo, insnMap.get(ji.label), null));
                    if (node.getOpcode() != Opcodes.GOTO)
                        succTemp.add(new ControlEdge(insnNo, insnNo+1, null));
                    break;

                case AbstractInsnNode.TABLESWITCH_INSN:
                    TableSwitchInsnNode tsi = (TableSwitchInsnNode) node;
                    succTemp.add(new ControlEdge(insnNo, insnMap.get(tsi.dflt), null));
                    for (int i = 0; i  < tsi.labels.size(); i++)
                        succTemp.add(new ControlEdge(insnNo, insnMap.get(tsi.labels.get(i)), null));
                    break;

                case AbstractInsnNode.LOOKUPSWITCH_INSN:
                    LookupSwitchInsnNode lsi = (LookupSwitchInsnNode) node;
                    succTemp.add(new ControlEdge(insnNo, insnMap.get(lsi.dflt), null));
                    for (int i = 0; i  < lsi.labels.size(); i++)
                        succTemp.add(new ControlEdge(insnNo, insnMap.get(lsi.labels.get(i)), null));
                    break;

                default:
                    int opcode = node.getOpcode();

                    if (! (opcode == Opcodes.ATHROW || opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN))
                        succTemp.add(new ControlEdge(insnNo, insnNo+1, null));
                    break;
            }
        }

        for (TryCatchBlockNode tcb : (List<TryCatchBlockNode>) tryCatchBlocks) {
            int start = insnMap.get(tcb.start);
            int end = insnMap.get(tcb.end);
            int handler = insnMap.get(tcb.handler);
            String type = tcb.type == null ? "java/lang/Throwable" : tcb.type;

            for (int i = start; i < end; i++)
                succTemps[i].add(new ControlEdge(i, handler, type));
        }

        for (int insnNo = 0; insnNo < insnList.length; insnNo++) {
            successors[insnNo] = succTemps[insnNo].toArray(new ControlEdge[0]);
            controlTemp.addAll(succTemps[insnNo]);
        }

        controlEdges = controlTemp.toArray(new ControlEdge[0]);
    }

    private static class StackEffect {
        public final String[] pop;
        public final String[] push;
        public StackEffect(String... ops) {
            int nul = 0;
            while (!ops[nul].isEmpty()) nul++;
            this.pop = Arrays.copyOfRange(ops, 0, nul);
            this.push = Arrays.copyOfRange(ops, nul+1, ops.length);
        }
    }

    private static final StackEffect[] simpleEffects;

    static {
        simpleEffects = new StackEffect[256];

        simpleEffects[Opcodes.AASTORE] = new StackEffect("L", "I", "L", "");
        simpleEffects[Opcodes.ACONST_NULL] = new StackEffect("", "0");
        simpleEffects[Opcodes.ARETURN] = new StackEffect("");
        simpleEffects[Opcodes.ARRAYLENGTH] = new StackEffect("L", "", "I");
        simpleEffects[Opcodes.ATHROW] = new StackEffect("");
        simpleEffects[Opcodes.BALOAD] = new StackEffect("L", "I", "", "I");
        simpleEffects[Opcodes.BASTORE] = new StackEffect("L", "I", "I", "");
        simpleEffects[Opcodes.BIPUSH] = new StackEffect("", "I");
        simpleEffects[Opcodes.CALOAD] = new StackEffect("[C", "I", "", "I");
        simpleEffects[Opcodes.CASTORE] = new StackEffect("[C", "I", "I", "");
        simpleEffects[Opcodes.D2F]   = new StackEffect("D", "", "F");
        simpleEffects[Opcodes.D2I]   = new StackEffect("D", "", "I");
        simpleEffects[Opcodes.D2L]   = new StackEffect("D", "", "J");
        simpleEffects[Opcodes.DADD] = new StackEffect("D", "D", "", "D");
        simpleEffects[Opcodes.DALOAD] = new StackEffect("[D", "I", "", "D");
        simpleEffects[Opcodes.DASTORE] = new StackEffect("[D", "I", "D", "");
        simpleEffects[Opcodes.DCMPG] = new StackEffect("D", "D", "", "I");
        simpleEffects[Opcodes.DCMPL] = new StackEffect("D", "D", "", "I");
        simpleEffects[Opcodes.DCONST_0] = new StackEffect("", "D");
        simpleEffects[Opcodes.DCONST_1] = new StackEffect("", "D");
        simpleEffects[Opcodes.DDIV] = new StackEffect("D", "D", "", "D");
        simpleEffects[Opcodes.DLOAD] = new StackEffect("", "D");
        simpleEffects[Opcodes.DMUL] = new StackEffect("D", "D", "", "D");
        simpleEffects[Opcodes.DNEG] = new StackEffect("D", "", "D");
        simpleEffects[Opcodes.DREM] = new StackEffect("D", "D", "", "D");
        simpleEffects[Opcodes.DRETURN] = new StackEffect("");
        simpleEffects[Opcodes.DSUB] = new StackEffect("D", "D", "", "D");
        simpleEffects[Opcodes.F2D]   = new StackEffect("F", "", "D");
        simpleEffects[Opcodes.F2I]   = new StackEffect("F", "", "I");
        simpleEffects[Opcodes.F2L]   = new StackEffect("F", "", "J");
        simpleEffects[Opcodes.FADD] = new StackEffect("F", "F", "", "F");
        simpleEffects[Opcodes.FALOAD] = new StackEffect("[F", "I", "", "F");
        simpleEffects[Opcodes.FASTORE] = new StackEffect("[F", "I", "F", "");
        simpleEffects[Opcodes.FCMPG] = new StackEffect("F", "F", "", "I");
        simpleEffects[Opcodes.FCMPL] = new StackEffect("F", "F", "", "I");
        simpleEffects[Opcodes.FCONST_0] = new StackEffect("", "F");
        simpleEffects[Opcodes.FCONST_1] = new StackEffect("", "F");
        simpleEffects[Opcodes.FCONST_2] = new StackEffect("", "F");
        simpleEffects[Opcodes.FDIV] = new StackEffect("F", "F", "", "F");
        simpleEffects[Opcodes.FLOAD] = new StackEffect("", "F");
        simpleEffects[Opcodes.FMUL] = new StackEffect("F", "F", "", "F");
        simpleEffects[Opcodes.FNEG] = new StackEffect("F", "", "F");
        simpleEffects[Opcodes.FREM] = new StackEffect("F", "F", "", "F");
        simpleEffects[Opcodes.FRETURN] = new StackEffect("");
        simpleEffects[Opcodes.FSUB] = new StackEffect("F", "F", "", "F");
        simpleEffects[Opcodes.GOTO] = new StackEffect("");
        simpleEffects[Opcodes.I2B]   = new StackEffect("I", "", "I");
        simpleEffects[Opcodes.I2C]   = new StackEffect("I", "", "I");
        simpleEffects[Opcodes.I2D]   = new StackEffect("I", "", "D");
        simpleEffects[Opcodes.I2F]   = new StackEffect("I", "", "F");
        simpleEffects[Opcodes.I2L]   = new StackEffect("I", "", "J");
        simpleEffects[Opcodes.I2S]   = new StackEffect("I", "", "I");
        simpleEffects[Opcodes.IADD] = new StackEffect("I", "I", "", "I");
        simpleEffects[Opcodes.IALOAD] = new StackEffect("[I", "I", "", "I");
        simpleEffects[Opcodes.IAND]  = new StackEffect("I", "I", "", "I");
        simpleEffects[Opcodes.IASTORE] = new StackEffect("[I", "I", "I", "");
        simpleEffects[Opcodes.ICONST_0] = new StackEffect("", "I");
        simpleEffects[Opcodes.ICONST_1] = new StackEffect("", "I");
        simpleEffects[Opcodes.ICONST_2] = new StackEffect("", "I");
        simpleEffects[Opcodes.ICONST_3] = new StackEffect("", "I");
        simpleEffects[Opcodes.ICONST_4] = new StackEffect("", "I");
        simpleEffects[Opcodes.ICONST_5] = new StackEffect("", "I");
        simpleEffects[Opcodes.ICONST_M1] = new StackEffect("", "I");
        simpleEffects[Opcodes.IDIV] = new StackEffect("I", "I", "", "I");
        simpleEffects[Opcodes.IFEQ] = new StackEffect("I", "");
        simpleEffects[Opcodes.IFGE] = new StackEffect("I", "");
        simpleEffects[Opcodes.IFGT] = new StackEffect("I", "");
        simpleEffects[Opcodes.IFLE] = new StackEffect("I", "");
        simpleEffects[Opcodes.IFLT] = new StackEffect("I", "");
        simpleEffects[Opcodes.IFNE] = new StackEffect("I", "");
        simpleEffects[Opcodes.IFNONNULL] = new StackEffect("L", "");
        simpleEffects[Opcodes.IFNULL] = new StackEffect("L", "");
        simpleEffects[Opcodes.IF_ACMPEQ] = new StackEffect("I", "I", "");
        simpleEffects[Opcodes.IF_ACMPNE] = new StackEffect("I", "I", "");
        simpleEffects[Opcodes.IF_ICMPEQ] = new StackEffect("I", "I", "");
        simpleEffects[Opcodes.IF_ICMPGE] = new StackEffect("I", "I", "");
        simpleEffects[Opcodes.IF_ICMPGT] = new StackEffect("I", "I", "");
        simpleEffects[Opcodes.IF_ICMPLE] = new StackEffect("I", "I", "");
        simpleEffects[Opcodes.IF_ICMPLT] = new StackEffect("I", "I", "");
        simpleEffects[Opcodes.IF_ICMPNE] = new StackEffect("I", "I", "");
        simpleEffects[Opcodes.IINC]  = new StackEffect("");
        simpleEffects[Opcodes.ILOAD] = new StackEffect("", "I");
        simpleEffects[Opcodes.IMUL] = new StackEffect("I", "I", "", "I");
        simpleEffects[Opcodes.INEG] = new StackEffect("I", "", "I");
        simpleEffects[Opcodes.INSTANCEOF] = new StackEffect("L", "", "I");
        simpleEffects[Opcodes.IOR]   = new StackEffect("I", "I", "", "I");
        simpleEffects[Opcodes.IREM] = new StackEffect("I", "I", "", "I");
        simpleEffects[Opcodes.IRETURN] = new StackEffect("");
        simpleEffects[Opcodes.ISHL]  = new StackEffect("I", "I", "", "I");
        simpleEffects[Opcodes.ISHR]  = new StackEffect("I", "I", "", "I");
        simpleEffects[Opcodes.ISUB] = new StackEffect("I", "I", "", "I");
        simpleEffects[Opcodes.IUSHR] = new StackEffect("I", "I", "", "I");
        simpleEffects[Opcodes.IXOR]  = new StackEffect("I", "I", "", "I");
        simpleEffects[Opcodes.L2D]   = new StackEffect("J", "", "D");
        simpleEffects[Opcodes.L2F]   = new StackEffect("J", "", "F");
        simpleEffects[Opcodes.L2I]   = new StackEffect("J", "", "I");
        simpleEffects[Opcodes.LADD] = new StackEffect("J", "J", "", "J");
        simpleEffects[Opcodes.LALOAD] = new StackEffect("[J", "I", "", "J");
        simpleEffects[Opcodes.LAND]  = new StackEffect("J", "J", "", "J");
        simpleEffects[Opcodes.LASTORE] = new StackEffect("[J", "I", "J", "");
        simpleEffects[Opcodes.LCMP]  = new StackEffect("J", "J", "", "I");
        simpleEffects[Opcodes.LCONST_0] = new StackEffect("", "J");
        simpleEffects[Opcodes.LCONST_1] = new StackEffect("", "J");
        simpleEffects[Opcodes.LDIV] = new StackEffect("J", "J", "", "J");
        simpleEffects[Opcodes.LLOAD] = new StackEffect("", "J");
        simpleEffects[Opcodes.LMUL] = new StackEffect("J", "J", "", "J");
        simpleEffects[Opcodes.LNEG] = new StackEffect("J", "", "J");
        simpleEffects[Opcodes.LOOKUPSWITCH] = new StackEffect("I", "");
        simpleEffects[Opcodes.LOR]   = new StackEffect("J", "J", "", "J");
        simpleEffects[Opcodes.LREM] = new StackEffect("J", "J", "", "J");
        simpleEffects[Opcodes.LRETURN] = new StackEffect("");
        simpleEffects[Opcodes.LSHL]  = new StackEffect("J", "I", "", "J");
        simpleEffects[Opcodes.LSHR]  = new StackEffect("J", "I", "", "J");
        simpleEffects[Opcodes.LSUB] = new StackEffect("J", "J", "", "J");
        simpleEffects[Opcodes.LUSHR] = new StackEffect("J", "I", "", "J");
        simpleEffects[Opcodes.LXOR]  = new StackEffect("J", "J", "", "J");
        simpleEffects[Opcodes.MONITORENTER] = new StackEffect("L", "");
        simpleEffects[Opcodes.MONITOREXIT] = new StackEffect("L", "");
        simpleEffects[Opcodes.NOP] = new StackEffect("");
        simpleEffects[Opcodes.PUTFIELD] = new StackEffect("X", "");
        simpleEffects[Opcodes.PUTSTATIC] = new StackEffect("X", "X", "");
        simpleEffects[Opcodes.RETURN]  = new StackEffect("");
        simpleEffects[Opcodes.SALOAD] = new StackEffect("[S", "I", "", "I");
        simpleEffects[Opcodes.SASTORE] = new StackEffect("[S", "I", "I", "");
        simpleEffects[Opcodes.SIPUSH] = new StackEffect("", "I");
        simpleEffects[Opcodes.TABLESWITCH] = new StackEffect("I", "");

    }

    private void printControlFlow() {
        for (int i = 0; i < insnList.length; i++) {
            System.out.printf("%5d: %s\n", i, insnList[i]);
            for (ControlEdge ce : successors[i])
                System.out.printf("     %5d -> %d %s\n", ce.from, ce.to, ce.exn == null ? "-" : ce.exn);
        }
    }

    /** Infer types. */
    private static class Frame {
        public String[] stack;
        public int sp;
        public int sbase;

        public Frame(String[] stack, int sp, int sbase) {
            this.stack = stack.clone(); this.sp = sp; this.sbase = sbase;
        }

        public Frame clone() {
            return new Frame(stack, sp, sbase);
        }

        public void grow(int len) {
            if (len <= stack.length) return;

            int olen = stack.length;
            stack = Arrays.copyOf(stack, len);
            Arrays.fill(stack, olen, len, "T");
        }

        public String describe() {
            return String.format("locals=[%s] stack=[%s]", Arrays.toString(Arrays.copyOfRange(stack, 0, sbase)), Arrays.toString(Arrays.copyOfRange(stack, sbase, sp)));
        }

        public void thrown(String ex) {
            sp = sbase;
            stack[sp++] = ('L'+ex+';').intern();
        }

        private void pushReturn(String s) {
            switch (s.charAt(0)) {
                case 'V': break;
                case 'B': case 'Z': case 'S': case 'C': stack[sp++] = "I"; break;
                default: stack[sp++] = s; break;
            }
        }

        public void execute(int index, AbstractInsnNode anode) {
            StackEffect simp = simpleEffects[anode.getOpcode()];

            if (stack.length < sp + 5) grow(sp+8);// room for all fixed effects and a bit to spare

            if (simp != null) {
                sp -= simp.pop.length;
                if (sp < sbase) throw new RuntimeException("stack underflow");
                if (stack.length < sp + simp.push.length)
                    stack = Arrays.copyOf(stack, sp + simp.push.length);
                for (String s : simp.push)
                    stack[sp++] = s;
                return;
            }

            VarInsnNode vi = null;
            TypeInsnNode ti = null;
            FieldInsnNode fi = null;
            String tidesc = null;
            MethodInsnNode mi = null;
            Type midesc = null;

            switch (anode.getType()) {
                case AbstractInsnNode.VAR_INSN:
                    vi = (VarInsnNode) anode;
                    if (vi.var != 0 && ("D" == stack[vi.var-1] || "J" == stack[vi.var-1]))
                        stack[vi.var-1] = "T";
                    break;
                case AbstractInsnNode.TYPE_INSN:
                    ti = (TypeInsnNode) anode;
                    tidesc = ti.desc.charAt(0) == '[' ? ti.desc : ("L" + ti.desc + ";");
                    break;
                case AbstractInsnNode.METHOD_INSN:
                    mi = (MethodInsnNode) anode;
                    midesc = Type.getMethodType(mi.desc);
                    break;
                case AbstractInsnNode.INVOKE_DYNAMIC_INSN:
                    midesc = Type.getMethodType(((InvokeDynamicInsnNode)anode).desc);
                    break;
                case AbstractInsnNode.FIELD_INSN:
                    fi = (FieldInsnNode) anode;
                    break;
            }

            String a,b,c,d;
            int opcode = anode.getOpcode();
            switch (opcode) {
                case Opcodes.AALOAD:
                    stack[sp-2] = stack[sp-2].substring(1);
                    sp--;
                    break;
                case Opcodes.ALOAD:
                    stack[sp++] = stack[vi.var];
                    break;
                case Opcodes.ANEWARRAY:
                    stack[sp-1] = ("[" + tidesc).intern();
                    break;
                case Opcodes.ASTORE:
                    stack[vi.var] = stack[--sp];
                    break;
                case Opcodes.CHECKCAST:
                    stack[sp-1] = tidesc.intern();
                    break;
                case Opcodes.DSTORE:
                    stack[vi.var] = "D";
                    stack[vi.var+1] = "T";
                    sp--;
                    break;
                case Opcodes.DUP2:
                    // [b] a -> [b] a [b] a
                    a = stack[--sp];
                    b = ("D" == a || "J" == a) ? "X" : stack[--sp];

                    if (!"X".equals(b)) stack[sp++] = b;
                    stack[sp++] = a;
                    if (!"X".equals(b)) stack[sp++] = b;
                    stack[sp++] = a;
                    break;
                case Opcodes.DUP2_X1:
                    // c [b] a -> [b] a c [b] a
                    a = stack[--sp];
                    b = ("D" == a || "J" == a) ? "X" : stack[--sp];
                    c = stack[--sp];

                    if ("X" != b) stack[sp++] = b;
                    stack[sp++] = a;
                    stack[sp++] = c;
                    if ("X" != b) stack[sp++] = b;
                    stack[sp++] = a;
                    break;
                case Opcodes.DUP2_X2:
                    // [d] c [b] a -> b a d c b a
                    a = stack[--sp];
                    b = ("D" == a || "J" == a) ? "X" : stack[--sp];
                    c = stack[--sp];
                    d = ("D" == c || "J" == c) ? "X" : stack[--sp];

                    if ("X" != b) stack[sp++] = b;
                    stack[sp++] = a;
                    if ("X" != d) stack[sp++] = d;
                    stack[sp++] = c;
                    if ("X" != b) stack[sp++] = b;
                    stack[sp++] = a;

                    break;
                case Opcodes.DUP:
                    // D, J invalid...
                    stack[sp] = stack[sp-1];
                    sp++;
                    break;
                case Opcodes.DUP_X1:
                    // b a -> a b a
                    a = stack[--sp];
                    b = stack[--sp];

                    stack[sp++] = a;
                    stack[sp++] = b;
                    stack[sp++] = a;
                    break;

                case Opcodes.DUP_X2:
                    // [d] c a -> a [d] c a
                    a = stack[--sp];
                    c = stack[--sp];
                    d = ("D" == c || "J" == c) ? "X" : stack[--sp];

                    stack[sp++] = a;
                    if ("X" != d) stack[sp++] = d;
                    stack[sp++] = c;
                    stack[sp++] = a;
                    break;
                case Opcodes.FSTORE:
                    stack[vi.var] = "F";
                    sp--;
                    break;

                case Opcodes.GETFIELD:
                    sp--;
                    pushReturn(fi.desc.intern());
                    break;

                case Opcodes.GETSTATIC:
                    pushReturn(fi.desc.intern());
                    break;

                case Opcodes.INVOKEINTERFACE:
                case Opcodes.INVOKESPECIAL:
                case Opcodes.INVOKESTATIC:
                case Opcodes.INVOKEVIRTUAL:
                case Opcodes.INVOKEDYNAMIC:
                    sp -= midesc.getArgumentTypes().length; // pop arguments
                    if (opcode != Opcodes.INVOKESTATIC && opcode != Opcodes.INVOKEDYNAMIC) {
                        a = stack[--sp]; // pop this
                        // initialize
                        if (a.charAt(0) == 'U') {
                            b = a.substring(a.indexOf(':')+1).intern();
                            for (int i = 0; i < stack.length; i++)
                                if (a == stack[i]) stack[i] = b;
                        }
                    }
                    pushReturn(midesc.getReturnType().getDescriptor().intern());
                    break;

                case Opcodes.ISTORE:
                    stack[vi.var] = "I";
                    sp--;
                    break;

        //simpleEffects[Opcodes.JSR] = new StackEffect(null);

                case Opcodes.LDC:
                    {
                        Object cst = ((LdcInsnNode)anode).cst;
                        if (cst instanceof Integer)             { stack[sp++] = "I"; break; }
                        if (cst instanceof Byte)                { stack[sp++] = "I"; break; }
                        if (cst instanceof Character)           { stack[sp++] = "I"; break; }
                        if (cst instanceof Short)               { stack[sp++] = "I"; break; }
                        if (cst instanceof Boolean)             { stack[sp++] = "I"; break; }
                        if (cst instanceof Float)               { stack[sp++] = "F"; break; }
                        if (cst instanceof Long)                { stack[sp++] = "J"; break; }
                        if (cst instanceof Double)              { stack[sp++] = "D"; break; }
                        if (cst instanceof String)              { stack[sp++] = "Ljava/lang/String;"; break; }
                        if (cst instanceof Type)                { stack[sp++] = ((Type)cst).getSort() == Type.METHOD ? "Ljava/lang/invoke/MethodType;" : "Ljava/lang/Class;"; break; }
                        if (cst instanceof Handle)              { stack[sp++] = "Ljava/lang/invoke/MethodHandle;"; break; }
                        throw new RuntimeException("Unknown constant type "+cst);
                    }

                case Opcodes.LSTORE:
                    stack[vi.var] = "J";
                    stack[vi.var+1] = "T";
                    sp--;
                    break;

                case Opcodes.MULTIANEWARRAY:
                    {
                        MultiANewArrayInsnNode m = (MultiANewArrayInsnNode)anode;
                        sp -= m.dims;
                        stack[sp++] = m.desc;
                    }
                    break;

                case Opcodes.NEWARRAY:
                    {
                        int type = ((IntInsnNode)anode).operand;
                        switch (type) {
                            case Opcodes.T_BOOLEAN: stack[sp++] = "[Z"; break;
                            case Opcodes.T_CHAR: stack[sp++] = "[C"; break;
                            case Opcodes.T_FLOAT: stack[sp++] = "[F"; break;
                            case Opcodes.T_DOUBLE: stack[sp++] = "[D"; break;
                            case Opcodes.T_BYTE: stack[sp++] = "[B"; break;
                            case Opcodes.T_SHORT: stack[sp++] = "[S"; break;
                            case Opcodes.T_INT: stack[sp++] = "[I"; break;
                            case Opcodes.T_LONG: stack[sp++] = "[J"; break;
                            default: throw new RuntimeException("NEWARRAY "+type);
                        }
                        break;
                    }

                case Opcodes.NEW:
                    stack[sp++] = ("U"+index+':'+tidesc).intern();
                    break;

                case Opcodes.POP2:
                    // [d] c ->
                    c = stack[--sp];
                    d = ("D".equals(c) || "J".equals(c)) ? "X" : stack[--sp];
                    break;
                case Opcodes.POP:
                    sp--;
                    break;
        //simpleEffects[Opcodes.RET] = new StackEffect(null);
                case Opcodes.SWAP:
                    a = stack[--sp];
                    b = stack[--sp];
                    stack[sp++] = a;
                    stack[sp++] = b;
                    break;
                default:
                    throw new RuntimeException("unimplemented opcode " + anode.getOpcode());
            }
        }
    }

    private static class TypeInference {
        public Frame[] frames;
        int[] changedQueue;
        int changedHead;
        int changedTail;
        boolean[] changedVec;


        public TypeInference(int size) {
            frames = new Frame[size];

            changedQueue = new int[size+1];
            changedVec = new boolean[size];
        }

        public int next() {
            if (changedHead == changedTail) return -1;
            int n = changedQueue[changedTail++];
            if (changedTail == changedQueue.length) changedTail = 0;
            changedVec[n] = false;
            return n;
        }

        @SuppressWarnings("unused")
        public void merge(int index, Frame f) {
            Frame slot = frames[index];
            if (slot == null) {
                frames[index] = f.clone();
                mark(index);
                return;
            }
            slot.grow(f.stack.length);
            f.grow(slot.stack.length);

            if (f.sp != slot.sp) throw new RuntimeException(String.format("Insn %d can be reached with stack sizes of %d and %d", index, f.sp-f.sbase, slot.sp-slot.sbase));

            boolean changed = false;
            if (TYPE_TRACE) System.out.printf("MERGE INSN=%d\nOLD=   [%s]\nINPUT= [%s]\n", index, slot.describe(), f.describe());
            for (int i = 0; i < f.sp; i++) {
                String a = f.stack[i];
                String b = slot.stack[i];
                String c = lub(a,b);
                if (b != c) {
                    //System.out.printf("%d.%d %s -> %s\n", index, i, b, c);
                    if (DEBUG_FRAGMENT && a != c && b != c) System.out.printf("%d.%d  %s | %s => %s\n", index, i, a, b, c);
                    slot.stack[i] = c;
                    changed = true;
                }
            }
            if (TYPE_TRACE) System.out.printf("OUTPUT=[%s]\n%s\n", slot.describe(), changed ? "CHANGED" : "");

            if (changed) mark(index);
        }

        void mark(int index) {
            if (changedVec[index]) return;
            changedVec[index] = true;
            changedQueue[changedHead++] = index;
            if (changedHead == changedQueue.length) changedHead = 0;
        }

        // computes the least upper bound of two verification types; we use descriptors, but U44:Lbar; for uninitialized(44), 0 for null, and T for TOP
        String lub(String a,String b) {
            // same type?  trivial
            if (a == b) return a;
            if (a == "T") return a;
            if (b == "T") return b;

            char a0 = a.charAt(0);
            char b0 = b.charAt(0);

            // types other than initialized references cannot validly merge with anything
            if (a0 != '0' && a0 != 'L' && a0 != '[') return "T";
            if (b0 != '0' && b0 != 'L' && b0 != '[') return "T";

            // null is the bottom of what remains
            if (a0 == '0') return b;
            if (b0 == '0') return a;

            // an array and a non-array can merge only to Object
            if (a0 == '[') {
                if (b0 != '[') return "Ljava/lang/Object;";

                // array types are covariant
                String cc = lub(a.substring(1), b.substring(1));
                if (cc.charAt(0) == 'T') // children are not compatible, but we know we have *some* array, which is an object
                    return "Ljava/lang/Object;";
                return ('['+cc).intern();
            } else {
                if (b0 != 'L') return "Ljava/lang/Object;";

                // at this point in a real verifier we would load the named classes and use their common superclass
                // lub(P6OpaqueInstance, CodeRef) = SixModelObject
                // punt.
                if (a == "Lorg/raku/nqp/runtime/CodeRef;" && b == "Lorg/raku/nqp/sixmodel/SixModelObject;") return b;
                if (b == "Lorg/raku/nqp/runtime/CodeRef;" && a == "Lorg/raku/nqp/sixmodel/SixModelObject;") return a;

                return "Ljava/lang/Object;";
            }
        }
    }

    @SuppressWarnings("unused")
    private void getTypes() {
        // first, establish a locals size so that we can merge locals and stacks
        nlocal = Type.getArgumentsAndReturnSizes(desc) >> 2;
        if ((access & Opcodes.ACC_STATIC) != 0) nlocal--;

        for (AbstractInsnNode an : insnList) {
            if (an.getType() != AbstractInsnNode.VAR_INSN) continue;
            int size = (an.getOpcode() == Opcodes.DSTORE || an.getOpcode() == Opcodes.LSTORE) ? 2 : 1;
            nlocal = Math.max(nlocal, ((VarInsnNode)an).var + size);
        }

        TypeInference state = new TypeInference(insnList.length);
        Frame initial = new Frame(new String[0], 0, 0);
        initial.grow(nlocal+10);

        int locwp = 0;

        if ((access & Opcodes.ACC_STATIC) == 0) {
            initial.stack[locwp++] = ('L'+tgtype+';').intern();
        }
        for (Type arg : Type.getArgumentTypes(desc)) {
            initial.stack[locwp] = arg.getDescriptor().intern();
            locwp += arg.getSize();
        }
        initial.sp = initial.sbase = nlocal;
        state.merge(0, initial);

        int insn;
        int step = 0;
        while ((insn = state.next()) >= 0) {
            Frame in = state.frames[insn].clone();

            if (TYPE_TRACE) System.out.printf("INFERENCE STEP: insn=%d [%d] locals=[%s] stack=[%s]\n", insn, insnList[insn].getOpcode(), Arrays.toString(Arrays.copyOfRange(in.stack, 0, nlocal)), Arrays.toString(Arrays.copyOfRange(in.stack, in.sbase, in.sp)));
            in.execute(insn, insnList[insn]);

            for (ControlEdge ce : successors[insn]) {
                // assume exceptions follow non-exceptions
                if (ce.exn != null) in.thrown(ce.exn);
                state.merge(ce.to, in);
            }
            step++;
            if (DEBUG_FRAGMENT && (step % 10000) == 0) System.out.printf("Inference step %d\n", step);
        }
        types = state.frames;

        if (DEBUG_FRAGMENT) {
            Map<String,Integer> histog = new HashMap< >();
            for (Frame fr : types) {
                for (int i = 0; i < fr.sp; i++) {
                    Integer r = histog.get(fr.stack[i]);
                    histog.put(fr.stack[i], r == null ? 1 : 1+r);
                }
            }
            for (Map.Entry<String,Integer> ent : histog.entrySet())
                System.out.printf("%s : %d\n", ent.getKey(), ent.getValue());
        }
    }

    private int insnSize(AbstractInsnNode ai) {
        int opc = ai.getOpcode();
        switch (ai.getType()) {
            case AbstractInsnNode.INSN:
                return 1;
            case AbstractInsnNode.INT_INSN:
                return (opc == Opcodes.SIPUSH) ? 3 : 2;
            case AbstractInsnNode.VAR_INSN:
                {
                    int var = ((VarInsnNode)ai).var;
                    return (var < 4 && opc != Opcodes.RET) ? 1 : (var >= 256) ? 4 : 2;
                }
            case AbstractInsnNode.TYPE_INSN:
                return 3;
            case AbstractInsnNode.FIELD_INSN:
                return 3;
            case AbstractInsnNode.METHOD_INSN:
                return (opc == Opcodes.INVOKEINTERFACE) ? 5 : 3;
            case AbstractInsnNode.INVOKE_DYNAMIC_INSN:
                return 5;
            case AbstractInsnNode.JUMP_INSN:
                return (opc == Opcodes.GOTO || opc == Opcodes.JSR) ? 5 : 8;
            case AbstractInsnNode.LDC_INSN:
                return 3;
            case AbstractInsnNode.IINC_INSN:
                {
                    IincInsnNode ii = (IincInsnNode) ai;
                    return (ii.var >= 256 || ii.incr > 127 || ii.incr < -128) ? 6 : 3;
                }
            case AbstractInsnNode.TABLESWITCH_INSN:
                {
                    TableSwitchInsnNode si = (TableSwitchInsnNode) ai;
                    return 16 + si.labels.size() * 4;
                }
            case AbstractInsnNode.LOOKUPSWITCH_INSN:
                {
                    LookupSwitchInsnNode si = (LookupSwitchInsnNode) ai;
                    return 12 + si.labels.size() * 8;
                }
            case AbstractInsnNode.MULTIANEWARRAY_INSN:
                return 4;
            default:
                return 0;
        }
    }

    // loosely based on the codesizeevaluator;
    private void getBaselineSize() {
        baselineSize = new int[insnList.length+1];

        int accum=0;

        for (int i = 0; i < insnList.length; i++) {
            AbstractInsnNode ai = insnList[i];
            MethodInsnNode mi;
            int size = insnSize(ai);

            // some of these require special handling
            switch (ai.getOpcode()) {
                case Opcodes.RETURN:
                    // iconst_m1; ireturn
                    size = 2;
                    break;
                case Opcodes.IRETURN:
                case Opcodes.FRETURN:
                case Opcodes.LRETURN:
                case Opcodes.DRETURN:
                case Opcodes.ARETURN:
                    // xstore tmp[0-2]; aload buf; iconst_0; new java/lang/Wrapper; dup; xload tmp; invokespecial; aastore; iconst_m1; ireturn
                    size = 1+(nlocal >= 254 ? 4 : 2)+1+3+1+1+3+1+1+1;
                    break;
                // Opcodes.NEW may be rewritten into aconst_null if a spill is needed, but that doesn't affect the max
                case Opcodes.INVOKESPECIAL:
                    mi = (MethodInsnNode) ai;
                    if ("<init>".equals(mi.name)) {
                        int skeep = 0;
                        Frame f1 = types[i];
                        Frame f2 = types[i+1];
                        String uninit = f1.stack[f2.sp];

                        while (skeep < f1.sp && !uninit.equals(f1.stack[skeep])) skeep++;

                        size = 0;
                        int ltmp = nlocal + 2; /*buf*/
                        // spill everything relevant above skeep into locals
                        for (int j = skeep; j < f1.sp; j++) {
                            if (uninit.equals(f1.stack[j])) continue;
                            size += (ltmp < 256) ? 2 : 4;
                            ltmp += 2;
                        }
                        ltmp++; // keep a copy of the value

                        int argc = Type.getArgumentTypes(mi.desc).length;

                        // newobj, dup, unspill, invokespecial
                        size += 3 + 1 + argc*(ltmp < 256 ? 2 : 4) + 3;

                        // store in locals, including <tmp>
                        for (int j = 0; j < nlocal; j++) {
                            if (uninit.equals(f1.stack[j]))
                                size += 1 + (j < 256 ? 2 : 4);
                        }
                        size += (ltmp < 256 ? 2 : 4);
                        // unspill
                        size += (ltmp < 256 ? 2 : 4) * (f2.sp - skeep);
                    }
                    break;
            }

            baselineSize[i] = accum;
            accum += size;
        }
        baselineSize[insnList.length] = accum;

        if (DEBUG_CONTROL) System.out.println(Arrays.toString(baselineSize));
    }

    private int[][] nonlocalEntryExit(int from, int to) {
        // need to include entry trampolines and exit trampolines
        int[] entryPts = new int[to-from];
        int entryCt = 0;
        boolean[] entryDedup = new boolean[to-from];
        int[] exitPts = new int[insnList.length];
        int exitCt = 0;
        boolean[] exitDedup = new boolean[insnList.length];

        for (ControlEdge ce : controlEdges) {
            boolean from_this = (ce.from >= from && ce.from < to);
            boolean to_this   = (ce.to >= from && ce.to < to);
            if (!from_this && to_this && !entryDedup[ce.to-from]) {
                entryDedup[ce.to-from] = true;
                entryPts[entryCt++] = ce.to;
            }
            if (from_this && !to_this && !exitDedup[ce.to]) {
                exitDedup[ce.to] = true;
                exitPts[exitCt++] = ce.to;
            }
        }

        if (from == 0 && !entryDedup[0]) {
            entryPts[entryCt++] = 0;
        }

        entryPts = Arrays.copyOf(entryPts, entryCt);
        exitPts = Arrays.copyOf(exitPts, exitCt);
        Arrays.sort(entryPts);
        Arrays.sort(exitPts);

        if (DEBUG_CONTROL) {
            System.out.printf("NONLOCAL ENTRY: %s\n", Arrays.toString(entryPts));
            System.out.printf("NONLOCAL EXIT: %s\n", Arrays.toString(exitPts));
        }
        return new int[][] { entryPts, exitPts };
    }

    private int calcFragmentSize(int from, int to) {
        // we have to include the instructions
        int base = baselineSize[to] - baselineSize[from];

        int[][] ee = nonlocalEntryExit(from, to);
        int[] entryPts = ee[0];
        int[] exitPts = ee[1];
        // factor out commonalities from the trampolines
        String[] commonEntry = commonTrampoline(entryPts, null);
        String[] commonExit  = commonTrampoline(exitPts, null);

        // common entry code
        // iload; aload; {dup; ipush; aaload; UNBOX; xstore; }; swap; tableswitch

        int centry = 2;
        for (int i = 0; i < commonEntry.length; i++) {
            centry += localEntrySize(i, commonEntry[i]);
        }
        centry += 13; // swap+tswitch

        // uncommon entry code
        int uentry = 0;

        for (int ept : entryPts) {
            uentry += 4; // dispatch vector
            Frame f = types[ept];
            for (int j = 0; j < f.sp; j++) {
                if (j < commonEntry.length && commonEntry[j].equals(f.stack[j])) {
                    /* no action */
                } else if (j < nlocal) {
                    uentry += localEntrySize(j, f.stack[j]);
                } else {
                    uentry += stackEntrySize(j, f.stack[j]);
                }
            }
            uentry += (nlocal <= 255 ? 2 : 4); // astore
            uentry += 5; // final jump
        }

        // jump insertion
        int uexit = 3;

        // uncommon exit code
        for (int pt : exitPts) {
            Frame f = types[pt];
            for (int j = 0; j < f.sp; j++) {
                if (j < commonExit.length && commonExit[j].equals(f.stack[j])) {
                    /* no action */
                } else if (j < nlocal) {
                    uexit += localExitSize(j, f.stack[j]);
                } else {
                    uexit += stackExitSize(j, f.stack[j]);
                }
            }
            uexit += (nlocal <= 255 ? 2 : 4); // aload in middle
            uexit += 3; // ipush
            uexit += 5; // jump to combiner
        }

        // common exit code
        int cexit = 1; // swap
        for (int i = 0; i < commonExit.length; i++) {
            cexit += localExitSize(i, commonExit[i]);
        }
        cexit += 2; // pop; ireturn

        int total = centry + uentry + base + uexit + cexit;

        if (DEBUG_FRAGMENT) System.out.printf("calcSize: %d-%d : centry(%d) uentry(%d) base(%d) uexit(%d) cexit(%d) total(%d)\n", from, to, centry, uentry, base, uexit, cexit, total);

        return total;
    }

    private int localEntrySize(int loc, String desc) {
        char c0 = desc.charAt(0);
        if (c0 == 'T') return 0; // not loaded
        int sz;
        switch (c0) {
            case '0':
            case 'U':
                // just load as a null
                sz = 1;
                break;
            case 'L':
            case '[':
                // dup, ipush, aaload, checkcast
                sz = (loc < 128) ? 7 : 8;
                break;
            default:
                // dup, ipush, aaload, checkcast, fooValue
                sz = (loc < 128) ? 10 : 11;
                break;
        }
        return sz + ((loc < 256) ? 2 : 4);
    }

    private int localExitSize(int loc, String desc) {
        char c0 = desc.charAt(0);
        if (c0 == 'T' || c0 == '0' || c0 == 'U') return 0; // not saved
        int sz = (loc < 256) ? 2 : 4;
        switch (c0) {
            case 'L':
            case '[':
                // dup, ipush, xload, aastore
                return sz + ((loc < 128) ? 4 : 5);
            default:
                // dup, ipush, new, dup, xload, invokespecial, aastore
                return sz + ((loc < 128) ? 11 : 12);
        }
    }

    private int stackEntrySize(int loc, String desc) {
        char c0 = desc.charAt(0);
        switch (c0) {
            case '0':
            case 'U':
                // just load as a null
                return 1;
            case 'L':
            case '[':
                // aload, ipush, aaload, checkcast
                return (loc < 128) ? 10 : 11;
            default:
                // aload, ipush, aaload, checkcast, fooValue
                return (loc < 128) ? 13 : 14;
        }
    }

    private int stackExitSize(int loc, String desc) {
        char c0 = desc.charAt(0);
        if (c0 == 'T' || c0 == '0' || c0 == 'U') return 1; // not saved
        switch (c0) {
            case 'L':
            case '[':
                // xstore, aload, ipush, xload, aastore
                return (loc < 128) ? 15 : 16;
            default:
                // xstore, aload, ipush, new, dup, xload, invokespecial, aastore
                return (loc < 128) ? 22 : 23;
        }
    }

    private String[] commonTrampoline(int[] points, Set<String> spills) {
        String[] common = null;
        for (int i = 0; i < points.length; i++) {
            Frame f = types[points[i]];
            if (spills != null) {
                for (int j = 0; j < f.sp; j++) spills.add(f.stack[j]);
            }
            if (common == null) {
                common = Arrays.copyOf(f.stack, nlocal);
            } else {
                for (int j = 0; j < common.length; j++) {
                    if (j >= f.sp || !f.stack[j].equals(common[j]))
                        common[j] = "T";
                }
            }
        }
        return common == null ? new String[] { } : common;
    }

    private void allocateJumpNrs(int begin, int end) {
        int[][] ee = nonlocalEntryExit(begin, end);

        int firstEntry = nextJumpNo;
        for (int entry : ee[0]) {
            jumpNoMap[entry] = nextJumpNo++;
        }
        firstJump.add(firstEntry);

        if (DEBUG_FRAGMENT) System.out.printf("Fragment %d-%d has jump numbers %d-%d\n", begin, end, firstEntry, nextJumpNo);
    }

    private void emitFragment(int fno, int begin, int end) {

        MethodVisitor v = target.visitMethod(Opcodes.ACC_STATIC | Opcodes.ACC_PRIVATE | Opcodes.ACC_SYNTHETIC, name+"$f"+fno, "(I[Ljava/lang/Object;)I", null, null);
        v.visitCode();

        int[][] ee = nonlocalEntryExit(begin, end);
        int[] entryPts = ee[0];
        int[] exitPts = ee[1];
        // factor out commonalities from the trampolines
        Set<String> spilledUTypes = new HashSet< >();
        String[] commonEntry = commonTrampoline(entryPts, spilledUTypes);
        String[] commonExit  = commonTrampoline(exitPts, spilledUTypes);

        Label[] entryTrampolineLabels = new Label[entryPts.length];
        for (int i = 0; i < entryPts.length; i++)
            entryTrampolineLabels[i] = new Label();

        Map<Integer, Label> exitTrampolineLabels = new HashMap< >();
        for (int i = 0; i < exitPts.length; i++)
            exitTrampolineLabels.put(exitPts[i], new Label());

        Label[] insnLabels = new Label[end - begin + 1];
        for (int i = 0; i < end - begin + 1; i++)
            insnLabels[i] = new Label();

        // common entry code
        // aload; {dup; ipush; aaload; UNBOX; xstore; }; iload; tableswitch
        v.visitVarInsn(Opcodes.ILOAD, 0);
        v.visitVarInsn(Opcodes.ALOAD, 1);

        for (int i = 0; i < commonEntry.length; i++) {
            localEntryCode(v, i, commonEntry[i]);
        }
        v.visitInsn(Opcodes.SWAP);
        int firstj = firstJump.get(fno);
        v.visitTableSwitchInsn(firstj, firstj + entryPts.length - 1, entryTrampolineLabels[0] /*XXX*/, entryTrampolineLabels);

        int stash = nlocal;
        int scratch = nlocal+1;

        // emit salient tryblocks
        for (TryCatchBlockNode tcbn : (List<TryCatchBlockNode>) tryCatchBlocks) {
            int nstart = Math.max(begin, insnMap.get(tcbn.start));
            int nend   = Math.min(end, insnMap.get(tcbn.end));
            int nhndlr = insnMap.get(tcbn.handler);
            if (nstart >= nend) continue;

            v.visitTryCatchBlock(insnLabels[nstart - begin], insnLabels[nend - begin],
                    exitTrampolineLabels.containsKey(nhndlr) ? exitTrampolineLabels.get(nhndlr) : insnLabels[nhndlr - begin],
                    tcbn.type);
        }

        // uncommon entry code
        for (int ept : entryPts) {
            v.visitLabel(entryTrampolineLabels[jumpNoMap[ept]-firstj]);
            Frame f = types[ept];
            for (int j = 0; j < nlocal; j++) {
                if (j < commonEntry.length && commonEntry[j].equals(f.stack[j]))
                    continue;
                localEntryCode(v, j, f.stack[j]);
            }
            v.visitVarInsn(Opcodes.ASTORE, stash);
            for (int j = nlocal; j < f.sp; j++) {
                stackEntryCode(v, stash, j, f.stack[j]);
            }
            v.visitJumpInsn(Opcodes.GOTO, insnLabels[ept - begin]);
        }

        // we have to include the instructions
        for (int iix = begin; iix < end; iix++) {
            emitFragmentInsn(v, iix, begin, insnLabels, exitTrampolineLabels, spilledUTypes);
        }
        v.visitLabel(insnLabels[end - begin]);

        boolean fallthru = false;
        for (ControlEdge ce : successors[end-1]) {
            if (ce.to == end) {
                fallthru = true;
                break;
            }
        }

        if (fallthru)
            v.visitJumpInsn(Opcodes.GOTO, exitTrampolineLabels.get(end));

        int lineno = -1;
        for (int i = begin; i < end; i++) {
            if (lineNumbers[i] != lineno) {
                lineno = lineNumbers[i];
                v.visitLineNumber(lineno, insnLabels[i-begin]);
            }
        }

        Label commonExitLabel = new Label();

        // uncommon exit code
        for (int pt : exitPts) {
            v.visitLabel(exitTrampolineLabels.get(pt));
            Frame f = types[pt];
            for (int j = f.sp-1; j >= nlocal; j--) {
                stackExitCode(v, stash, scratch, j, f.stack[j]);
            }
            v.visitVarInsn(Opcodes.ALOAD, stash);
            for (int j = 0; j < nlocal; j++) {
                if (j < commonExit.length && commonExit[j].equals(f.stack[j]))
                    continue;

                localExitCode(v, j, f.stack[j]);
            }
            pushInt(v, jumpNoMap[pt]);
            v.visitJumpInsn(Opcodes.GOTO, commonExitLabel);
        }

        // common exit code
        if (exitPts.length > 0) {
            v.visitLabel(commonExitLabel);
            v.visitInsn(Opcodes.SWAP);
            for (int i = 0; i < commonExit.length; i++) {
                localExitCode(v, i, commonExit[i]);
            }
            v.visitInsn(Opcodes.POP);
            v.visitInsn(Opcodes.IRETURN);
        }
        v.visitMaxs(0,0);
        v.visitEnd();
    }

    private String[] box_types = new String[] { "java/lang/Integer", "java/lang/Long", "java/lang/Float", "java/lang/Double" };
    private String[] box_descs = new String[] { "(I)V", "(J)V", "(F)V", "(D)V" };

    private void emitFragmentInsn(MethodVisitor v, int iix, int begin, Label[] insnLabels, Map<Integer,Label> exitTrampolineLabels, Set<String> spilledUTypes) {
        v.visitLabel(insnLabels[iix - begin]);
        AbstractInsnNode ai = insnList[iix];

        // some instructions require very special handling
        int opc = ai.getOpcode();
        if (opc == Opcodes.RETURN) {
            v.visitInsn(Opcodes.ICONST_M1);
            v.visitInsn(Opcodes.IRETURN);
            return;
        }

        if (opc >= Opcodes.IRETURN && opc <= Opcodes.ARETURN) {
            int t = opc - Opcodes.IRETURN;
            v.visitVarInsn(Opcodes.ISTORE + t, nlocal+1);
            v.visitVarInsn(Opcodes.ALOAD, nlocal);
            v.visitInsn(Opcodes.ICONST_0);
            if (opc != Opcodes.ARETURN) {
                v.visitTypeInsn(Opcodes.NEW, box_types[t]);
                v.visitInsn(Opcodes.DUP);
                v.visitVarInsn(Opcodes.ILOAD + t, nlocal+1);
                v.visitMethodInsn(Opcodes.INVOKESPECIAL, box_types[t], "<init>", box_descs[t]);
            } else {
                v.visitVarInsn(Opcodes.ILOAD + t, nlocal+1);
            }
            v.visitInsn(Opcodes.AASTORE);
            v.visitInsn(Opcodes.ICONST_M1);
            v.visitInsn(Opcodes.IRETURN);
            return;
        }

        if (opc == Opcodes.NEW) {
            Frame f = types[iix+1];
            if (spilledUTypes.contains(f.stack[f.sp-1])) {
                v.visitInsn(Opcodes.ACONST_NULL);
                return;
            }
        }

        if (opc == Opcodes.INVOKESPECIAL) {
            MethodInsnNode mi = (MethodInsnNode)ai;
            Frame f1 = types[iix];
            Frame f2 = types[iix+1];
            String uninit = f1.stack[f2.sp];
            if (mi.name.equals("<init>") && spilledUTypes.contains(uninit)) {
                if (!f2.stack[f2.sp-1].equals(uninit)) throw new RuntimeException("general case of INVOKESPECIAL spill not implemented");
                for (int i = 0; i < f2.sp-1; i++) if (f2.stack[i].equals(uninit)) throw new RuntimeException("general case of INVOKESPECIAL spill not implemented");

                int ltmp = nlocal+1;
                int argc = Type.getArgumentTypes(mi.desc).length;
                int[] spillarg = new int[argc];

                for (int d = 0; d < argc; d++) {
                    char ty0 = f1.stack[f1.sp-d-1].charAt(0);

                    spillarg[d] = ltmp;
                    v.visitVarInsn(ty0 == 'D' ? Opcodes.DSTORE : ty0 == 'J' ? Opcodes.LSTORE : ty0 == 'I' ? Opcodes.ISTORE : ty0 == 'F' ? Opcodes.FSTORE : Opcodes.ASTORE, ltmp);
                    ltmp += (ty0 == 'D' || ty0 == 'J') ? 2 : 1;
                }
                v.visitInsn(Opcodes.POP2);
                v.visitTypeInsn(Opcodes.NEW, mi.owner);
                v.visitInsn(Opcodes.DUP);

                for (int d = argc-1; d >= 0; d++) {
                    char ty0 = f1.stack[f1.sp-d-1].charAt(0);
                    v.visitVarInsn(ty0 == 'D' ? Opcodes.DLOAD : ty0 == 'J' ? Opcodes.LLOAD : ty0 == 'I' ? Opcodes.ILOAD : ty0 == 'F' ? Opcodes.FLOAD : Opcodes.ALOAD, spillarg[d]);
                }

                v.visitMethodInsn(Opcodes.INVOKESPECIAL, mi.owner, mi.name, mi.desc);
                return;
            }
        }

        // all other instructions can be processed normally, perhaps with some control-flow fudging

        switch (ai.getType()) {
            case AbstractInsnNode.JUMP_INSN:
                {
                    JumpInsnNode ji = (JumpInsnNode)ai;
                    v.visitJumpInsn(opc, mapLabel(ji.label, begin, insnLabels, exitTrampolineLabels));
                    break;
                }
            case AbstractInsnNode.TABLESWITCH_INSN:
                {
                    TableSwitchInsnNode si = (TableSwitchInsnNode)ai;
                    Label[] mapped = new Label[si.labels.size()];
                    for (int i = 0; i < mapped.length; i++)
                        mapped[i] = mapLabel((LabelNode)si.labels.get(i), begin, insnLabels, exitTrampolineLabels);
                    v.visitTableSwitchInsn(si.min, si.max, mapLabel(si.dflt, begin, insnLabels, exitTrampolineLabels), mapped);
                    break;
                }
            case AbstractInsnNode.LOOKUPSWITCH_INSN:
                {
                    LookupSwitchInsnNode si = (LookupSwitchInsnNode)ai;
                    Label[] mapped = new Label[si.labels.size()];
                    for (int i = 0; i < mapped.length; i++)
                        mapped[i] = mapLabel((LabelNode)si.labels.get(i), begin, insnLabels, exitTrampolineLabels);
                    int[] keys = new int[si.keys.size()];
                    for (int i = 0; i < keys.length; i++)
                        keys[i] = (int)si.keys.get(i);
                    v.visitLookupSwitchInsn(mapLabel(si.dflt, begin, insnLabels, exitTrampolineLabels), keys, mapped);
                    break;
                }
            default:
                ai.accept(v);
                break;
        }
    }

    private Label mapLabel(LabelNode ln, int begin, Label[] insnLabels, Map<Integer,Label> exitTrampolineLabels) {
        int lni = insnMap.get(ln);
        if (exitTrampolineLabels.containsKey(lni))
            return exitTrampolineLabels.get(lni);

        return insnLabels[lni - begin];
    }


    private void localEntryCode(MethodVisitor v, int loc, String desc) {
        char c0 = desc.charAt(0);
        if (c0 == 'T') return; // not loaded
        switch (c0) {
            case '0':
            case 'U':
                // just load as a null
                v.visitInsn(Opcodes.ACONST_NULL);
                break;
            case 'L':
            case '[':
                // dup, ipush, aaload, checkcast
                v.visitInsn(Opcodes.DUP);
                pushInt(v, loc);
                v.visitInsn(Opcodes.AALOAD);
                if (!desc.equals("Ljava/lang/Object;")) v.visitTypeInsn(Opcodes.CHECKCAST, c0 == '[' ? desc : desc.substring(1, desc.length()-1));
                break;
            default:
                // dup, ipush, aaload, checkcast, fooValue
                v.visitInsn(Opcodes.DUP);
                pushInt(v, loc);
                v.visitInsn(Opcodes.AALOAD);
                unbox(v, c0);
                v.visitVarInsn( c0=='I' ? Opcodes.ISTORE : c0=='J' ? Opcodes.LSTORE : c0=='F' ? Opcodes.FSTORE : Opcodes.DSTORE, loc );
                return;
        }
        v.visitVarInsn(Opcodes.ASTORE, loc);
    }

    private void pushInt(MethodVisitor v, int value) {
        if (value >= -1 && value <= 5)
            v.visitInsn(Opcodes.ICONST_0 + value);
        else if (value >= -128 && value <= 127)
            v.visitIntInsn(Opcodes.BIPUSH, value);
        else if (value >= -32768 && value <= 32767)
            v.visitIntInsn(Opcodes.SIPUSH, value);
        else
            v.visitLdcInsn(value);
    }

    private void unbox(MethodVisitor v, char c0) {
        String c,m,d;
        switch (c0) {
            case 'I': c = "java/lang/Integer"; m = "intValue";    d = "()I"; break;
            case 'J': c = "java/lang/Long";    m = "longValue";   d = "()J"; break;
            case 'F': c = "java/lang/Float";   m = "floatValue";  d = "()F"; break;
            case 'D': c = "java/lang/Double";  m = "doubleValue"; d = "()D"; break;
            default: throw new IllegalArgumentException();
        }
        v.visitTypeInsn(Opcodes.CHECKCAST, c);
        v.visitMethodInsn(Opcodes.INVOKEVIRTUAL, c, m, d);
    }

    private void localExitCode(MethodVisitor v, int loc, String desc) {
        char c0 = desc.charAt(0);
        if (c0 == 'T' || c0 == '0' || c0 == 'U') return; // not saved
        switch (c0) {
            case 'L':
            case '[':
                v.visitInsn(Opcodes.DUP);
                pushInt(v, loc);
                v.visitVarInsn(Opcodes.ALOAD, loc);
                v.visitInsn(Opcodes.AASTORE);
                break;
            default:
                v.visitInsn(Opcodes.DUP);
                pushInt(v, loc);
                {
                    String ty;
                    int load;
                    switch (c0) {
                        case 'I': ty = "java/lang/Integer"; load = Opcodes.ILOAD; break;
                        case 'J': ty = "java/lang/Long"; load = Opcodes.LLOAD; break;
                        case 'F': ty = "java/lang/Float"; load = Opcodes.FLOAD; break;
                        case 'D': ty = "java/lang/Double"; load = Opcodes.DLOAD; break;
                        default: throw new IllegalArgumentException(desc);
                    }

                    v.visitTypeInsn(Opcodes.NEW, ty);
                    v.visitInsn(Opcodes.DUP);
                    v.visitVarInsn(load, loc);
                    v.visitMethodInsn(Opcodes.INVOKESPECIAL, ty, "<init>", "("+c0+")V");
                }
                v.visitInsn(Opcodes.AASTORE);
                break;
        }
    }

    private void stackEntryCode(MethodVisitor v, int stash, int loc, String desc) {
        char c0 = desc.charAt(0);
        switch (c0) {
            case '0':
            case 'U':
                v.visitInsn(Opcodes.ACONST_NULL);
                break;
            case 'L':
            case '[':
                v.visitVarInsn(Opcodes.ALOAD, stash);
                pushInt(v, loc);
                v.visitInsn(Opcodes.AALOAD);
                if (!desc.equals("Ljava/lang/Object;")) v.visitTypeInsn(Opcodes.CHECKCAST, c0 == '[' ? desc : desc.substring(1, desc.length()-1));
                break;
            default:
                v.visitVarInsn(Opcodes.ALOAD, stash);
                pushInt(v, loc);
                v.visitInsn(Opcodes.AALOAD);
                unbox(v, c0);
                break;
        }
    }

    private void stackExitCode(MethodVisitor v, int stash, int scratch, int loc, String desc) {
        char c0 = desc.charAt(0);
        if (c0 == 'T' || c0 == '0' || c0 == 'U') {
            v.visitInsn(Opcodes.POP);
            return;
        }
        String ty;
        int load, store;
        switch (c0) {
            case 'L':
            case '[':
                v.visitVarInsn(Opcodes.ASTORE, scratch);
                v.visitVarInsn(Opcodes.ALOAD, stash);
                pushInt(v, loc);
                v.visitVarInsn(Opcodes.ALOAD, scratch);
                v.visitInsn(Opcodes.AASTORE);
                return;
            case 'I': ty = "java/lang/Integer"; load = Opcodes.ILOAD; store = Opcodes.ISTORE; break;
            case 'J': ty = "java/lang/Long"; load = Opcodes.LLOAD; store = Opcodes.LSTORE; break;
            case 'F': ty = "java/lang/Float"; load = Opcodes.FLOAD; store = Opcodes.FSTORE; break;
            case 'D': ty = "java/lang/Double"; load = Opcodes.DLOAD; store = Opcodes.DSTORE; break;
            default: throw new IllegalArgumentException(desc);
        }

        v.visitVarInsn(store, scratch);
        v.visitVarInsn(Opcodes.ALOAD, stash);
        pushInt(v, loc);
        v.visitTypeInsn(Opcodes.NEW, ty);
        v.visitInsn(Opcodes.DUP);
        v.visitVarInsn(load, scratch);
        v.visitMethodInsn(Opcodes.INVOKESPECIAL, ty, "<init>", "("+c0+")V");
        v.visitInsn(Opcodes.AASTORE);
    }

    private void becomeWrapper() {
        int maxStack = 0;
        for (Frame f : types)
            maxStack = Math.max(maxStack, f.sp);

        tryCatchBlocks = null;
        localVariables = null;
        instructions.clear();

        // allocate the scratchpad
        instructions.add(intNode(maxStack));
        instructions.add(new TypeInsnNode(Opcodes.ANEWARRAY, "java/lang/Object"));
        // move the arguments onto the scratchpad
        int ltmp = 0;
        if ((access & Opcodes.ACC_STATIC) == 0) ltmp += saveArg(instructions, ltmp, Type.getType(Object.class));
        for (Type at : Type.getArgumentTypes(desc)) ltmp += saveArg(instructions, ltmp, at);

        instructions.add(new VarInsnNode(Opcodes.ASTORE, 0));
        instructions.add(intNode(0));
        instructions.add(new VarInsnNode(Opcodes.ISTORE, 1));

        LabelNode loop = new LabelNode();
        instructions.add(loop);

        for (int i = firstJump.size() - 1; i >= 0; i--) {
            LabelNode not_my_problem = new LabelNode();
            instructions.add(new VarInsnNode(Opcodes.ILOAD, 1));
            instructions.add(intNode(firstJump.get(i)));
            instructions.add(new JumpInsnNode(Opcodes.IF_ICMPLT, not_my_problem));
            instructions.add(new VarInsnNode(Opcodes.ILOAD, 1));
            instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
            instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, tgtype, name + "$f" + i, "(I[Ljava/lang/Object;)I"));
            instructions.add(new VarInsnNode(Opcodes.ISTORE, 1));
            instructions.add(new JumpInsnNode(Opcodes.GOTO, loop));
            instructions.add(not_my_problem);
        }

        // time for return
        String rty = null, unboxName = null, unboxDesc = null;
        int retinst;
        Type rtyty = Type.getReturnType(desc);
        switch (rtyty.getSort()) {
            case Type.VOID:
                retinst = Opcodes.RETURN; break;
            case Type.BOOLEAN:
            case Type.CHAR:
            case Type.INT:
            case Type.SHORT:
            case Type.BYTE:
                retinst = Opcodes.IRETURN; rty = "java/lang/Integer"; unboxName = "intValue"; unboxDesc = "()I"; break;
            case Type.LONG:
                retinst = Opcodes.LRETURN; rty = "java/lang/Long"; unboxName = "longValue"; unboxDesc = "()J"; break;
            case Type.FLOAT:
                retinst = Opcodes.FRETURN; rty = "java/lang/Float"; unboxName = "floatValue"; unboxDesc = "()F"; break;
            case Type.DOUBLE:
                retinst = Opcodes.DRETURN; rty = "java/lang/Double"; unboxName = "doubleValue"; unboxDesc = "()D"; break;
            default:
                retinst = Opcodes.ARETURN; rty = rtyty.getInternalName(); break;
        }

        if (rty != null) {
            instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
            instructions.add(new InsnNode(Opcodes.ICONST_0));
            instructions.add(new InsnNode(Opcodes.AALOAD));
            instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, rty));
            if (unboxName != null)
                instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, rty, unboxName, unboxDesc));
        }
        instructions.add(new InsnNode(retinst));
    }

    private int saveArg(InsnList il, int ltmp, Type at) {
        il.add(new InsnNode(Opcodes.DUP));
        il.add(intNode(ltmp));
        int opc;
        String ty = null, desc = null;
        switch (at.getSort()) {
            case Type.BOOLEAN:
            case Type.CHAR:
            case Type.INT:
            case Type.SHORT:
            case Type.BYTE:
                opc = Opcodes.ILOAD; ty = "java/lang/Integer"; desc = "(I)V"; break;
            case Type.LONG:
                opc = Opcodes.LLOAD; ty = "java/lang/Long"; desc = "(J)V"; break;
            case Type.FLOAT:
                opc = Opcodes.FLOAD; ty = "java/lang/Float"; desc = "(F)V"; break;
            case Type.DOUBLE:
                opc = Opcodes.DLOAD; ty = "java/lang/Double"; desc = "(D)V"; break;
            default:
                opc = Opcodes.ALOAD; break;
        }

        if (ty != null) {
            il.add(new TypeInsnNode(Opcodes.NEW, ty));
            il.add(new InsnNode(Opcodes.DUP));
        }
        il.add(new VarInsnNode(opc, ltmp));
        if (ty != null) il.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, ty, "<init>", desc));
        il.add(new InsnNode(Opcodes.AASTORE));
        return at.getSize();
    }
}
