org.classdump.luna.compiler.gen.asm.BytecodeEmitVisitor.java Source code

Java tutorial

Introduction

Here is the source code for org.classdump.luna.compiler.gen.asm.BytecodeEmitVisitor.java

Source

/*
 * Copyright 2016 Miroslav Jan?ek
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.classdump.luna.compiler.gen.asm;

import static org.objectweb.asm.Opcodes.AASTORE;
import static org.objectweb.asm.Opcodes.ACC_FINAL;
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
import static org.objectweb.asm.Opcodes.ACONST_NULL;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ANEWARRAY;
import static org.objectweb.asm.Opcodes.ARETURN;
import static org.objectweb.asm.Opcodes.ARRAYLENGTH;
import static org.objectweb.asm.Opcodes.ASTORE;
import static org.objectweb.asm.Opcodes.CHECKCAST;
import static org.objectweb.asm.Opcodes.DUP;
import static org.objectweb.asm.Opcodes.F_APPEND;
import static org.objectweb.asm.Opcodes.F_CHOP;
import static org.objectweb.asm.Opcodes.F_SAME;
import static org.objectweb.asm.Opcodes.GETFIELD;
import static org.objectweb.asm.Opcodes.GETSTATIC;
import static org.objectweb.asm.Opcodes.GOTO;
import static org.objectweb.asm.Opcodes.I2L;
import static org.objectweb.asm.Opcodes.IADD;
import static org.objectweb.asm.Opcodes.ICONST_1;
import static org.objectweb.asm.Opcodes.IFEQ;
import static org.objectweb.asm.Opcodes.IFNE;
import static org.objectweb.asm.Opcodes.IFNONNULL;
import static org.objectweb.asm.Opcodes.IFNULL;
import static org.objectweb.asm.Opcodes.IF_ICMPGE;
import static org.objectweb.asm.Opcodes.ILOAD;
import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.ISTORE;
import static org.objectweb.asm.Opcodes.IXOR;
import static org.objectweb.asm.Opcodes.LADD;
import static org.objectweb.asm.Opcodes.NEW;
import static org.objectweb.asm.Opcodes.PUTFIELD;
import static org.objectweb.asm.Opcodes.RETURN;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.classdump.luna.ByteString;
import org.classdump.luna.Table;
import org.classdump.luna.Variable;
import org.classdump.luna.compiler.CompilerSettings;
import org.classdump.luna.compiler.FunctionId;
import org.classdump.luna.compiler.analysis.SlotAllocInfo;
import org.classdump.luna.compiler.analysis.TypeInfo;
import org.classdump.luna.compiler.gen.ClassNameTranslator;
import org.classdump.luna.compiler.gen.asm.helpers.ASMUtils;
import org.classdump.luna.compiler.gen.asm.helpers.BoxedPrimitivesMethods;
import org.classdump.luna.compiler.gen.asm.helpers.ConversionMethods;
import org.classdump.luna.compiler.gen.asm.helpers.DispatchMethods;
import org.classdump.luna.compiler.gen.asm.helpers.ExecutionContextMethods;
import org.classdump.luna.compiler.gen.asm.helpers.ReturnBufferMethods;
import org.classdump.luna.compiler.gen.asm.helpers.TableMethods;
import org.classdump.luna.compiler.gen.asm.helpers.VariableMethods;
import org.classdump.luna.compiler.ir.AbstractVal;
import org.classdump.luna.compiler.ir.AbstractVar;
import org.classdump.luna.compiler.ir.BasicBlock;
import org.classdump.luna.compiler.ir.BinOp;
import org.classdump.luna.compiler.ir.Branch;
import org.classdump.luna.compiler.ir.CPUWithdraw;
import org.classdump.luna.compiler.ir.Call;
import org.classdump.luna.compiler.ir.Closure;
import org.classdump.luna.compiler.ir.CodeVisitor;
import org.classdump.luna.compiler.ir.Jmp;
import org.classdump.luna.compiler.ir.Label;
import org.classdump.luna.compiler.ir.Line;
import org.classdump.luna.compiler.ir.LoadConst;
import org.classdump.luna.compiler.ir.MultiGet;
import org.classdump.luna.compiler.ir.PhiLoad;
import org.classdump.luna.compiler.ir.PhiStore;
import org.classdump.luna.compiler.ir.Ret;
import org.classdump.luna.compiler.ir.TCall;
import org.classdump.luna.compiler.ir.TabGet;
import org.classdump.luna.compiler.ir.TabNew;
import org.classdump.luna.compiler.ir.TabRawAppendMulti;
import org.classdump.luna.compiler.ir.TabRawSet;
import org.classdump.luna.compiler.ir.TabRawSetInt;
import org.classdump.luna.compiler.ir.TabSet;
import org.classdump.luna.compiler.ir.ToNext;
import org.classdump.luna.compiler.ir.ToNumber;
import org.classdump.luna.compiler.ir.UnOp;
import org.classdump.luna.compiler.ir.UpLoad;
import org.classdump.luna.compiler.ir.UpStore;
import org.classdump.luna.compiler.ir.UpVar;
import org.classdump.luna.compiler.ir.VList;
import org.classdump.luna.compiler.ir.Val;
import org.classdump.luna.compiler.ir.Var;
import org.classdump.luna.compiler.ir.VarInit;
import org.classdump.luna.compiler.ir.VarLoad;
import org.classdump.luna.compiler.ir.VarStore;
import org.classdump.luna.compiler.ir.Vararg;
import org.classdump.luna.runtime.ExecutionContext;
import org.classdump.luna.runtime.ReturnBuffer;
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.FieldNode;
import org.objectweb.asm.tree.FrameNode;
import org.objectweb.asm.tree.IincInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.LineNumberNode;
import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;

class BytecodeEmitVisitor extends CodeVisitor {

    final Map<Object, LabelNode> labels;
    private final ASMBytecodeEmitter context;
    private final RunMethod runMethod;
    private final SlotAllocInfo slots;
    private final TypeInfo types;
    private final int segmentIdx;
    private final RunMethod.LabelResolver resolver;
    private final ArrayList<LabelNode> resumptionPoints;

    private final InsnList il;
    private final List<LocalVariableNode> locals;

    private final List<RunMethod.ClosureFieldInstance> instanceLevelClosures;

    private final List<RunMethod.ConstFieldInstance> constFields;
    private Label destLabel;

    public BytecodeEmitVisitor(ASMBytecodeEmitter context, RunMethod runMethod, SlotAllocInfo slots, TypeInfo types,
            List<RunMethod.ClosureFieldInstance> instanceLevelClosures,
            List<RunMethod.ConstFieldInstance> constFields, int segmentIdx, RunMethod.LabelResolver resolver) {

        this.context = Objects.requireNonNull(context);
        this.runMethod = Objects.requireNonNull(runMethod);
        this.slots = Objects.requireNonNull(slots);
        this.types = Objects.requireNonNull(types);

        this.segmentIdx = segmentIdx;
        this.resolver = Objects.requireNonNull(resolver);

        this.labels = new HashMap<>();
        this.resumptionPoints = new ArrayList<>();

        this.il = new InsnList();
        this.locals = new ArrayList<>();

        this.instanceLevelClosures = Objects.requireNonNull(instanceLevelClosures);
        this.constFields = Objects.requireNonNull(constFields);
    }

    private static InsnList newByteString(ByteString value) {
        InsnList il = new InsnList();
        il.add(new LdcInsnNode(value.toRawString()));
        il.add(new MethodInsnNode(INVOKESTATIC, Type.getInternalName(ByteString.class), "fromRaw",
                Type.getMethodDescriptor(Type.getType(ByteString.class), Type.getType(String.class)), false));
        return il;
    }

    static AbstractInsnNode loadReturnBuffer() {
        return new MethodInsnNode(INVOKEINTERFACE, Type.getInternalName(ExecutionContext.class), "getReturnBuffer",
                Type.getMethodDescriptor(Type.getType(ReturnBuffer.class)), true);
    }

    private static String dispatchMethodName(BinOp.Op op) {
        switch (op) {
        case ADD:
            return DispatchMethods.OP_ADD;
        case SUB:
            return DispatchMethods.OP_SUB;
        case MUL:
            return DispatchMethods.OP_MUL;
        case MOD:
            return DispatchMethods.OP_MOD;
        case POW:
            return DispatchMethods.OP_POW;
        case DIV:
            return DispatchMethods.OP_DIV;
        case IDIV:
            return DispatchMethods.OP_IDIV;
        case BAND:
            return DispatchMethods.OP_BAND;
        case BOR:
            return DispatchMethods.OP_BOR;
        case BXOR:
            return DispatchMethods.OP_BXOR;
        case SHL:
            return DispatchMethods.OP_SHL;
        case SHR:
            return DispatchMethods.OP_SHR;

        case CONCAT:
            return DispatchMethods.OP_CONCAT;

        case EQ:
            return DispatchMethods.OP_EQ;
        case NEQ:
            return DispatchMethods.OP_NEQ;
        case LT:
            return DispatchMethods.OP_LT;
        case LE:
            return DispatchMethods.OP_LE;

        default:
            throw new IllegalArgumentException("Illegal binary operation: " + op);
        }
    }

    private static String dispatchMethodName(UnOp.Op op) {
        switch (op) {
        case UNM:
            return DispatchMethods.OP_UNM;
        case BNOT:
            return DispatchMethods.OP_BNOT;
        case LEN:
            return DispatchMethods.OP_LEN;
        default:
            throw new IllegalArgumentException("Illegal unary operation: " + op);
        }
    }

    private boolean isSub() {
        return segmentIdx >= 0;
    }

    public InsnList instructions() {
        return il;
    }

    public List<LocalVariableNode> locals() {
        return locals;
    }

    public List<RunMethod.ClosureFieldInstance> instanceLevelClosures() {
        return instanceLevelClosures;
    }

    public List<RunMethod.ConstFieldInstance> constFields() {
        return constFields;
    }

    protected int slot(AbstractVal v) {
        return runMethod.slotOffset() + slots.slotOf(v);
    }

    protected int slot(Var v) {
        return runMethod.slotOffset() + slots.slotOf(v);
    }

    protected int nextLocalVariableIndex() {
        return runMethod.slotOffset() + slots.numSlots();
    }

    private LabelNode l(Object o) {
        LabelNode l = labels.get(o);

        if (l != null) {
            return l;
        } else {
            LabelNode nl = new LabelNode();
            labels.put(o, nl);
            return nl;
        }
    }

    private RunMethod.ConstFieldInstance newConstFieldInstance(final Object constValue, int idx) {
        Objects.requireNonNull(constValue);

        String fieldName = "_k_" + idx;

        final Type t;
        if (constValue instanceof Double || constValue instanceof Long) {
            t = Type.getType(constValue.getClass());
            return new RunMethod.ConstFieldInstance(constValue, fieldName, context.thisClassType(), t) {
                @Override
                public void doInstantiate(InsnList il) {
                    il.add(BoxedPrimitivesMethods.loadBoxedConstant(constValue));
                }
            };
        } else if (constValue instanceof ByteString) {
            t = Type.getType(constValue.getClass());
            return new RunMethod.ConstFieldInstance(constValue, fieldName, context.thisClassType(),
                    Type.getType(ByteString.class)) {
                @Override
                public void doInstantiate(InsnList il) {
                    il.add(newByteString((ByteString) constValue));
                }
            };
        } else {
            throw new UnsupportedOperationException("Illegal constant: " + constValue);
        }

    }

    private InsnList loadCachedConst(Object constValue) {
        for (RunMethod.ConstFieldInstance cfi : constFields) {
            if (cfi.value().equals(constValue)) {
                return cfi.accessInsns();
            }
        }

        RunMethod.ConstFieldInstance cfi = newConstFieldInstance(constValue, constFields.size());
        constFields.add(cfi);
        return cfi.accessInsns();
    }

    public AbstractInsnNode loadExecutionContext() {
        return new VarInsnNode(ALOAD, runMethod.LV_CONTEXT);
    }

    public InsnList retrieve_0() {
        InsnList il = new InsnList();

        il.add(loadExecutionContext());
        il.add(loadReturnBuffer());
        il.add(ReturnBufferMethods.get(0));

        return il;
    }

    public InsnList loadUpvalueRef(UpVar uv) {
        InsnList il = new InsnList();

        il.add(new VarInsnNode(ALOAD, 0));
        il.add(new FieldInsnNode(GETFIELD, context.thisClassType().getInternalName(),
                context.getUpvalueFieldName(uv), Type.getDescriptor(Variable.class)));

        return il;
    }

    private InsnList saveState(int state) {
        InsnList il = new InsnList();
        il.add(ASMUtils.loadInt(state));
        il.add(new VarInsnNode(ISTORE, runMethod.LV_RESUME));
        return il;
    }

    public void visitBlocks(List<BasicBlock> blocks) {
        Iterator<BasicBlock> it = blocks.iterator();
        while (it.hasNext()) {
            BasicBlock b = it.next();
            visit(b);
        }
    }

    protected ResumptionPoint newResumptionPoint() {
        int idx = resumptionPoints.size();
        ResumptionPoint rp = new ResumptionPoint(idx);
        resumptionPoints.add(rp.label());
        return rp;
    }

    public boolean isResumable() {
        return isSub() || resumptionPoints.size() > 0;
    }

    public List<LabelNode> resumptionLabels() {
        return resumptionPoints;
    }

    private InsnList _return() {
        InsnList il = new InsnList();
        if (!isSub()) {
            il.add(new InsnNode(RETURN));
        } else {
            il.add(new InsnNode(ACONST_NULL));
            il.add(new InsnNode(ARETURN));
        }
        return il;
    }

    private InsnList _nonLocalGoto(Label label) {
        InsnList il = new InsnList();
        int st = resolver.labelStateIndex(label);
        il.add(saveState(st));
        il.add(runMethod.createSnapshot());
        il.add(new InsnNode(ARETURN));
        return il;
    }

    private InsnList _goto(Label label) {
        InsnList il = new InsnList();
        if (!isSub() || resolver.isLocalLabel(label)) {
            il.add(new JumpInsnNode(GOTO, l(label)));
        } else {
            il.add(_nonLocalGoto(label));
        }
        return il;
    }

    private InsnList _next(Label label) {
        InsnList il = new InsnList();
        if (!isSub() || resolver.isLocalLabel(label)) {
            // no-op
        } else {
            il.add(_nonLocalGoto(label));
        }
        return il;
    }

    @Override
    public void visit(PhiStore node) {
        il.add(new VarInsnNode(ALOAD, slot(node.src())));
        il.add(new VarInsnNode(ASTORE, slot(node.dest())));
    }

    @Override
    public void visit(PhiLoad node) {
        il.add(new VarInsnNode(ALOAD, slot(node.src())));
        il.add(new VarInsnNode(ASTORE, slot(node.dest())));
    }

    @Override
    public void visit(VarInit node) {
        if (types.isReified(node.var())) {
            il.add(new TypeInsnNode(NEW, Type.getInternalName(Variable.class)));
            il.add(new InsnNode(DUP));
            il.add(new VarInsnNode(ALOAD, slot(node.src())));
            il.add(VariableMethods.constructor());
            il.add(new VarInsnNode(ASTORE, slot(node.var())));
        } else {
            il.add(new VarInsnNode(ALOAD, slot(node.src())));
            il.add(new VarInsnNode(ASTORE, slot(node.var())));
        }
    }

    @Override
    public void visit(VarStore node) {
        if (types.isReified(node.var())) {
            il.add(new VarInsnNode(ALOAD, slot(node.var())));
            il.add(new TypeInsnNode(CHECKCAST, Type.getInternalName(Variable.class)));
            il.add(new VarInsnNode(ALOAD, slot(node.src())));
            il.add(VariableMethods.set());
        } else {
            il.add(new VarInsnNode(ALOAD, slot(node.src())));
            il.add(new VarInsnNode(ASTORE, slot(node.var())));
        }
    }

    @Override
    public void visit(VarLoad node) {
        if (types.isReified(node.var())) {
            il.add(new VarInsnNode(ALOAD, slot(node.var())));
            il.add(new TypeInsnNode(CHECKCAST, Type.getInternalName(Variable.class)));
            il.add(VariableMethods.get());
        } else {
            il.add(new VarInsnNode(ALOAD, slot(node.var())));
        }
        il.add(new VarInsnNode(ASTORE, slot(node.dest())));
    }

    @Override
    public void visit(UpLoad node) {
        il.add(loadUpvalueRef(node.upval()));
        il.add(VariableMethods.get());
        il.add(new VarInsnNode(ASTORE, slot(node.dest())));
    }

    @Override
    public void visit(UpStore node) {
        il.add(loadUpvalueRef(node.upval()));
        il.add(new VarInsnNode(ALOAD, slot(node.src())));
        il.add(VariableMethods.set());
    }

    @Override
    public void visit(LoadConst.Nil node) {
        il.add(new InsnNode(ACONST_NULL));
        il.add(new VarInsnNode(ASTORE, slot(node.dest())));
    }

    @Override
    public void visit(LoadConst.Bool node) {
        il.add(BoxedPrimitivesMethods.loadBoxedBoolean(node.value()));
        il.add(new VarInsnNode(ASTORE, slot(node.dest())));
    }

    @Override
    public void visit(LoadConst.Int node) {
        if (context.compilerSettings.constCaching()) {
            il.add(loadCachedConst(node.value()));
        } else {
            il.add(ASMUtils.loadLong(node.value()));
            il.add(BoxedPrimitivesMethods.box(Type.LONG_TYPE, Type.getType(Long.class)));
        }
        il.add(new VarInsnNode(ASTORE, slot(node.dest())));
    }

    @Override
    public void visit(LoadConst.Flt node) {
        if (context.compilerSettings.constCaching()) {
            il.add(loadCachedConst(node.value()));
        } else {
            il.add(ASMUtils.loadDouble(node.value()));
            il.add(BoxedPrimitivesMethods.box(Type.DOUBLE_TYPE, Type.getType(Double.class)));
        }
        il.add(new VarInsnNode(ASTORE, slot(node.dest())));
    }

    @Override
    public void visit(LoadConst.Str node) {
        // use byte strings?
        if (context.compilerSettings.byteStrings()) {
            if (context.compilerSettings.constCaching()) {
                il.add(loadCachedConst(node.value()));
            } else {
                il.add(newByteString(node.value()));
            }
        } else {
            // java.lang.String
            il.add(new LdcInsnNode(node.value()));
        }

        il.add(new VarInsnNode(ASTORE, slot(node.dest())));
    }

    @Override
    public void visit(BinOp node) {
        ResumptionPoint rp = newResumptionPoint();
        il.add(rp.save());

        il.add(loadExecutionContext());
        il.add(new VarInsnNode(ALOAD, slot(node.left())));
        il.add(new VarInsnNode(ALOAD, slot(node.right())));
        il.add(DispatchMethods.dynamic(dispatchMethodName(node.op()), 2));

        il.add(rp.resume());
        il.add(retrieve_0());
        il.add(new VarInsnNode(ASTORE, slot(node.dest())));
    }

    @Override
    public void visit(UnOp node) {
        if (node.op() == UnOp.Op.NOT) {
            il.add(new VarInsnNode(ALOAD, slot(node.arg())));
            il.add(ConversionMethods.booleanValueOf());
            il.add(new InsnNode(ICONST_1));
            il.add(new InsnNode(IXOR));
            il.add(BoxedPrimitivesMethods.box(Type.BOOLEAN_TYPE, Type.getType(Boolean.class)));
        } else {
            ResumptionPoint rp = newResumptionPoint();
            il.add(rp.save());

            il.add(loadExecutionContext());
            il.add(new VarInsnNode(ALOAD, slot(node.arg())));
            il.add(DispatchMethods.dynamic(dispatchMethodName(node.op()), 1));

            il.add(rp.resume());
            il.add(retrieve_0());
        }

        il.add(new VarInsnNode(ASTORE, slot(node.dest())));
    }

    @Override
    public void visit(TabNew node) {
        il.add(loadExecutionContext());
        il.add(ExecutionContextMethods.newTable(node.array(), node.hash()));
        il.add(new VarInsnNode(ASTORE, slot(node.dest())));
    }

    @Override
    public void visit(TabGet node) {
        ResumptionPoint rp = newResumptionPoint();
        il.add(rp.save());

        il.add(loadExecutionContext());
        il.add(new VarInsnNode(ALOAD, slot(node.obj())));
        il.add(new VarInsnNode(ALOAD, slot(node.key())));
        il.add(DispatchMethods.index());

        il.add(rp.resume());
        il.add(retrieve_0());
        il.add(new VarInsnNode(ASTORE, slot(node.dest())));
    }

    @Override
    public void visit(TabSet node) {
        ResumptionPoint rp = newResumptionPoint();
        il.add(rp.save());

        il.add(loadExecutionContext());
        il.add(new VarInsnNode(ALOAD, slot(node.obj())));
        il.add(new VarInsnNode(ALOAD, slot(node.key())));
        il.add(new VarInsnNode(ALOAD, slot(node.value())));
        il.add(DispatchMethods.setindex());

        il.add(rp.resume());
    }

    @Override
    public void visit(TabRawSet node) {
        il.add(new VarInsnNode(ALOAD, slot(node.obj())));
        il.add(new TypeInsnNode(CHECKCAST, Type.getInternalName(Table.class)));
        il.add(new VarInsnNode(ALOAD, slot(node.key())));
        il.add(new VarInsnNode(ALOAD, slot(node.value())));
        il.add(TableMethods.rawset());
    }

    @Override
    public void visit(TabRawSetInt node) {
        il.add(new VarInsnNode(ALOAD, slot(node.obj())));
        il.add(new TypeInsnNode(CHECKCAST, Type.getInternalName(Table.class)));
        il.add(ASMUtils.loadLong(node.idx()));
        il.add(new VarInsnNode(ALOAD, slot(node.value())));
        il.add(TableMethods.rawset_int());
    }

    @Override
    public void visit(TabRawAppendMulti node) {
        /*
           In Java terms, we're translating this into the following loop:
            
             Table tab;
             ReturnBuffer rbuf = context.getReturnBuffer();
             int i = 0;
             context.registerTicks(rbuf.size());  // only when we care about ticks spent
             while (i < rbuf.size()) {
        tab.rawset(OFFSET + i, rbuf.get(i));
        i++;
             }
          */

        LabelNode begin = new LabelNode();
        LabelNode end = new LabelNode();
        LabelNode top = new LabelNode();

        int lv_idx_tab = nextLocalVariableIndex();
        int lv_idx_stack = nextLocalVariableIndex() + 1;
        int lv_idx_i = nextLocalVariableIndex() + 2;

        locals.add(new LocalVariableNode("tab", Type.getDescriptor(Table.class), null, begin, end, lv_idx_tab));
        locals.add(new LocalVariableNode("rbuf", Type.getDescriptor(ReturnBuffer.class), null, begin, end,
                lv_idx_stack));
        locals.add(new LocalVariableNode("i", Type.INT_TYPE.getDescriptor(), null, begin, end, lv_idx_i));

        il.add(begin);

        il.add(new VarInsnNode(ALOAD, slot(node.obj())));
        il.add(new TypeInsnNode(CHECKCAST, Type.getInternalName(Table.class)));
        il.add(new VarInsnNode(ASTORE, lv_idx_tab));

        il.add(loadExecutionContext());
        il.add(loadReturnBuffer());
        il.add(new VarInsnNode(ASTORE, lv_idx_stack));

        il.add(ASMUtils.loadInt(0));
        il.add(new VarInsnNode(ISTORE, lv_idx_i));

        // context.registerTicks(rbuf.size());
        if (countingTicks()) {
            il.add(loadExecutionContext());
            il.add(new VarInsnNode(ALOAD, lv_idx_stack));
            il.add(ReturnBufferMethods.size());
            il.add(ExecutionContextMethods.registerTicks());
        }

        il.add(top);
        il.add(new FrameNode(F_APPEND, 3, new Object[] { Type.getInternalName(Table.class),
                Type.getInternalName(ReturnBuffer.class), Opcodes.INTEGER }, 0, null));

        il.add(new VarInsnNode(ILOAD, lv_idx_i));
        il.add(new VarInsnNode(ALOAD, lv_idx_stack));
        il.add(ReturnBufferMethods.size());
        il.add(new JumpInsnNode(IF_ICMPGE, end));

        il.add(new VarInsnNode(ALOAD, lv_idx_tab));

        // OFFSET + i
        il.add(ASMUtils.loadLong(node.firstIdx()));
        il.add(new VarInsnNode(ILOAD, lv_idx_i));
        il.add(new InsnNode(I2L));
        il.add(new InsnNode(LADD));

        // stack.get(i)
        il.add(new VarInsnNode(ALOAD, lv_idx_stack));
        il.add(new VarInsnNode(ILOAD, lv_idx_i));
        il.add(ReturnBufferMethods.get());

        // tab.rawset(offset + i, stack.get(i))
        il.add(TableMethods.rawset_int());

        // increment i
        il.add(new IincInsnNode(lv_idx_i, 1));

        il.add(new JumpInsnNode(GOTO, top));

        il.add(end);
        il.add(new FrameNode(F_CHOP, 3, null, 0, null));
    }

    @Override
    public void visit(Vararg node) {
        il.add(loadExecutionContext());
        il.add(loadReturnBuffer());
        il.add(new VarInsnNode(ALOAD, runMethod.LV_VARARGS));
        il.add(ReturnBufferMethods.setTo(0));
    }

    private int loadVList(VList vl, int maxKind) {
        if (vl.isMulti()) {
            // variable number of arguments, stored on stack

            if (vl.addrs().size() == 0) {
                // no prefix, simply take the stack contents as an array
                il.add(loadExecutionContext());
                il.add(loadReturnBuffer());
                il.add(ReturnBufferMethods.toArray());
                return 0;
            } else {
                // a non-empty prefix followed by the stack contents

                LabelNode begin = new LabelNode();
                LabelNode end = new LabelNode();

                int lv_idx_stack = nextLocalVariableIndex();
                int lv_idx_args = nextLocalVariableIndex() + 1;

                Type arrayType = ASMUtils.arrayTypeFor(Object.class);

                locals.add(
                        new LocalVariableNode("stack", arrayType.getDescriptor(), null, begin, end, lv_idx_stack));
                locals.add(new LocalVariableNode("args", arrayType.getDescriptor(), null, begin, end, lv_idx_args));

                il.add(begin);

                // get stack contents as an array
                il.add(loadExecutionContext());
                il.add(loadReturnBuffer());
                il.add(ReturnBufferMethods.toArray());
                il.add(new VarInsnNode(ASTORE, lv_idx_stack));

                // compute the overall arg list length
                il.add(new VarInsnNode(ALOAD, lv_idx_stack));
                il.add(new InsnNode(ARRAYLENGTH));
                il.add(ASMUtils.loadInt(vl.addrs().size()));
                il.add(new InsnNode(IADD));

                // instantiate the actual arg list (length is on stack top)
                il.add(new TypeInsnNode(ANEWARRAY, Type.getInternalName(Object.class)));
                il.add(new VarInsnNode(ASTORE, lv_idx_args));

                // fill in the prefix
                int idx = 0;
                for (Val v : vl.addrs()) {
                    il.add(new VarInsnNode(ALOAD, lv_idx_args));
                    il.add(ASMUtils.loadInt(idx++));
                    il.add(new VarInsnNode(ALOAD, slot(v)));
                    il.add(new InsnNode(AASTORE));
                }

                // call System.arraycopy(stack, 0, args, prefix_length, stack.length)
                il.add(new VarInsnNode(ALOAD, lv_idx_stack));
                il.add(ASMUtils.loadInt(0));
                il.add(new VarInsnNode(ALOAD, lv_idx_args));
                il.add(ASMUtils.loadInt(vl.addrs().size()));
                il.add(new VarInsnNode(ALOAD, lv_idx_stack));
                il.add(new InsnNode(ARRAYLENGTH));
                il.add(new MethodInsnNode(INVOKESTATIC, Type.getInternalName(System.class), "arraycopy",
                        Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Object.class), Type.INT_TYPE,
                                Type.getType(Object.class), Type.INT_TYPE, Type.INT_TYPE),
                        false));

                // push result to stack
                il.add(new VarInsnNode(ALOAD, lv_idx_args));
                il.add(end);

                return 0;
            }
        } else {
            // fixed number of arguments

            int k = vl.addrs().size() + 1;
            if (k <= maxKind) {
                // pass arguments on the JVM stack
                for (Val v : vl.addrs()) {
                    il.add(new VarInsnNode(ALOAD, slot(v)));
                }
                return k;
            } else {
                // pass arguments packed in an array
                il.add(ASMUtils.loadInt(vl.addrs().size()));
                il.add(new TypeInsnNode(ANEWARRAY, Type.getInternalName(Object.class)));

                int idx = 0;
                for (Val v : vl.addrs()) {
                    il.add(new InsnNode(DUP));
                    il.add(ASMUtils.loadInt(idx++));
                    il.add(new VarInsnNode(ALOAD, slot(v)));
                    il.add(new InsnNode(AASTORE));
                }

                return 0;
            }
        }
    }

    @Override
    public void visit(Ret node) {
        il.add(loadExecutionContext());
        il.add(loadReturnBuffer());
        int kind = loadVList(node.args(), ReturnBufferMethods.MAX_SETTO_KIND); // values
        il.add(ReturnBufferMethods.setTo(kind));
        il.add(_return());
    }

    @Override
    public void visit(TCall node) {
        il.add(loadExecutionContext());
        il.add(loadReturnBuffer());
        il.add(new VarInsnNode(ALOAD, slot(node.target()))); // call target
        int kind = loadVList(node.args(), ReturnBufferMethods.MAX_TAILCALL_KIND); // call args
        il.add(ReturnBufferMethods.tailCall(kind));
        il.add(_return());
    }

    @Override
    public void visit(Call node) {
        ResumptionPoint rp = newResumptionPoint();
        il.add(rp.save());

        il.add(loadExecutionContext());
        il.add(new VarInsnNode(ALOAD, slot(node.fn()))); // call target
        int kind = loadVList(node.args(), DispatchMethods.MAX_CALL_KIND); // call args
        il.add(DispatchMethods.call(kind));

        il.add(rp.resume());
    }

    @Override
    public void visit(MultiGet node) {
        il.add(loadExecutionContext());
        il.add(loadReturnBuffer());
        il.add(ReturnBufferMethods.get(node.idx()));
        il.add(new VarInsnNode(ASTORE, slot(node.dest())));
    }

    @Override
    public void visit(Label node) {
        il.add(l(node));
        il.add(ASMUtils.frameSame());
    }

    @Override
    public void visit(Jmp node) {
        il.add(_goto(node.jmpDest()));
    }

    @Override
    public void visit(Closure node) {
        ClosureUse cu = new ClosureUse(node.id(), node.args(), instanceLevelClosures.size());

        if (cu.isClosed() && !cu.isPure()) {
            instanceLevelClosures.add(cu.toClosureFieldInstance());
        }

        il.add(cu.fetchInstanceInsns());
        il.add(new VarInsnNode(ASTORE, slot(node.dest())));
    }

    @Override
    public void visit(ToNumber node) {
        il.add(new VarInsnNode(ALOAD, slot(node.src())));
        il.add(ConversionMethods.toNumericalValue(node.desc()));
        il.add(new VarInsnNode(ASTORE, slot(node.dest())));
    }

    @Override
    public void visit(ToNext node) {
        il.add(_next(node.label()));
    }

    @Override
    public void visit(Branch branch) {
        assert (destLabel == null);

        try {
            destLabel = branch.jmpDest();
            branch.condition().accept(this);
            il.add(_next(branch.next()));
        } finally {
            destLabel = null;
        }
    }

    @Override
    public void visit(Branch.Condition.Nil cond) {
        assert (destLabel != null);
        il.add(new VarInsnNode(ALOAD, slot(cond.addr())));

        if (!isSub() || resolver.isLocalLabel(destLabel)) {
            // local jump
            il.add(new JumpInsnNode(IFNULL, l(destLabel)));
        } else {
            // non-local jump
            LabelNode l_nojump = new LabelNode();
            il.add(new JumpInsnNode(IFNONNULL, l_nojump));
            il.add(_nonLocalGoto(destLabel));
            il.add(l_nojump);
            il.add(new FrameNode(F_SAME, 0, null, 0, null));
        }
    }

    @Override
    public void visit(Branch.Condition.Bool cond) {
        assert (destLabel != null);
        il.add(new VarInsnNode(ALOAD, slot(cond.addr())));
        il.add(ConversionMethods.booleanValueOf());

        if (!isSub() || resolver.isLocalLabel(destLabel)) {
            // local jump
            il.add(new JumpInsnNode(cond.expected() ? IFNE : IFEQ, l(destLabel)));
        } else {
            // non-local jump
            LabelNode l_nojump = new LabelNode();
            il.add(new JumpInsnNode(cond.expected() ? IFEQ : IFNE, l_nojump));
            il.add(_nonLocalGoto(destLabel));
            il.add(l_nojump);
            il.add(new FrameNode(F_SAME, 0, null, 0, null));
        }

    }

    @Override
    public void visit(Branch.Condition.NumLoopEnd cond) {
        assert (destLabel != null);
        il.add(new VarInsnNode(ALOAD, slot(cond.var())));
        il.add(new TypeInsnNode(CHECKCAST, Type.getInternalName(Number.class)));
        il.add(new VarInsnNode(ALOAD, slot(cond.limit())));
        il.add(new TypeInsnNode(CHECKCAST, Type.getInternalName(Number.class)));
        il.add(new VarInsnNode(ALOAD, slot(cond.step())));
        il.add(new TypeInsnNode(CHECKCAST, Type.getInternalName(Number.class)));
        il.add(DispatchMethods.continueLoop());

        if (!isSub() || resolver.isLocalLabel(destLabel)) {
            // local jump
            il.add(new JumpInsnNode(IFEQ, l(destLabel)));
        } else {
            // non-local jump
            LabelNode l_nojump = new LabelNode();
            il.add(new JumpInsnNode(IFNE, l_nojump));
            il.add(_nonLocalGoto(destLabel));
            il.add(l_nojump);
            il.add(new FrameNode(F_SAME, 0, null, 0, null));
        }
    }

    private void staticCpuWithdraw(int cost) {
        switch (context.compilerSettings.cpuAccountingMode()) {
        case NO_CPU_ACCOUNTING: {
            // no-op
            break;
        }

        case IN_EVERY_BASIC_BLOCK: {
            ResumptionPoint rp = newResumptionPoint();
            il.add(rp.save());

            il.add(loadExecutionContext());
            il.add(new InsnNode(DUP));
            il.add(ASMUtils.loadInt(cost));
            il.add(ExecutionContextMethods.registerTicks());
            il.add(ExecutionContextMethods.checkCallYield());

            il.add(rp.resume());
            break;
        }

        default:
            throw new UnsupportedOperationException(
                    "Unsupported CPU accounting mode: " + context.compilerSettings.cpuAccountingMode());
        }
    }

    // do we care about counting ticks?
    private boolean countingTicks() {
        return (context.compilerSettings
                .cpuAccountingMode() != CompilerSettings.CPUAccountingMode.NO_CPU_ACCOUNTING);
    }

    @Override
    public void visit(CPUWithdraw node) {
        staticCpuWithdraw(node.cost());
    }

    @Override
    public void visit(Line node) {
        LabelNode l = new LabelNode();
        il.add(l);
        il.add(new LineNumberNode(node.lineNumber(), l));
    }

    class ResumptionPoint {

        public final int index;

        private ResumptionPoint(int index) {
            this.index = index;
        }

        public LabelNode label() {
            return l(this);
        }

        public InsnList save() {
            int st = !isSub() ? index + 1 : segmentIdx << RunMethod.ST_SHIFT_SEGMENT | (index + 1);
            return saveState(st);
        }

        public InsnList resume() {
            InsnList il = new InsnList();

            il.add(label());
            il.add(ASMUtils.frameSame());

            return il;
        }
    }

    private class ClosureUse {

        private final FunctionId id;
        private final List<AbstractVar> upvals;

        private final String fieldName; // may be null

        private ClosureUse(FunctionId id, List<AbstractVar> upvals, int idx) {
            this.id = Objects.requireNonNull(id);
            this.upvals = Objects.requireNonNull(upvals);

            if (isClosed() && !isPure()) {
                this.fieldName = context.addFieldName("c_" + idx);
            } else {
                this.fieldName = null;
            }
        }

        public boolean isClosed() {
            for (AbstractVar uv : upvals) {
                if (uv instanceof Var) {
                    return false;
                }
            }
            return true;
        }

        public boolean isPure() {
            return upvals.isEmpty();
        }

        public RunMethod.ClosureFieldInstance toClosureFieldInstance() {
            assert (this.isClosed());

            FieldNode fieldNode = instanceFieldNode();

            InsnList il = new InsnList();
            il.add(new VarInsnNode(ALOAD, 0));
            il.add(instantiationInsns());
            il.add(new FieldInsnNode(PUTFIELD, context.thisClassType().getInternalName(), instanceFieldName(),
                    instanceType().getDescriptor()));

            return new RunMethod.ClosureFieldInstance(instanceFieldNode(), il);
        }

        private InsnList instantiationInsns() {
            InsnList il = new InsnList();

            ClassNameTranslator tr = context.classNameTranslator;

            Type fnType = ASMUtils.typeForClassName(id.toClassName(tr));

            il.add(new TypeInsnNode(NEW, fnType.getInternalName()));
            il.add(new InsnNode(DUP));
            for (AbstractVar var : upvals) {
                if (var instanceof UpVar) {
                    il.add(loadUpvalueRef((UpVar) var));
                } else {
                    Var v = (Var) var;
                    assert (context.types.isReified(v));
                    il.add(new VarInsnNode(ALOAD, slot(v)));
                    il.add(new TypeInsnNode(CHECKCAST, Type.getInternalName(Variable.class)));
                }
            }

            Type[] ctorArgTypes = new Type[upvals.size()];
            Arrays.fill(ctorArgTypes, Type.getType(Variable.class));

            il.add(ASMUtils.ctor(fnType, ctorArgTypes));

            return il;
        }

        private String instanceFieldName() {
            return fieldName;
        }

        private Type instanceType() {
            return ASMUtils.typeForClassName(id.toClassName(context.classNameTranslator));
        }

        private FieldNode instanceFieldNode() {
            return new FieldNode(ACC_PRIVATE + ACC_FINAL, instanceFieldName(), instanceType().getDescriptor(), null,
                    null);
        }

        private InsnList fetchInstanceInsns() {
            InsnList il = new InsnList();

            if (this.isClosed()) {
                if (this.isPure()) {
                    il.add(new FieldInsnNode(GETSTATIC, instanceType().getInternalName(),
                            ASMBytecodeEmitter.instanceFieldName(), instanceType().getDescriptor()));
                } else {
                    il.add(new VarInsnNode(ALOAD, 0));
                    il.add(new FieldInsnNode(GETFIELD, context.thisClassType().getInternalName(),
                            instanceFieldName(), instanceType().getDescriptor()));
                }
            } else {
                il.add(instantiationInsns());
            }

            return il;
        }

    }

}