org.eclipse.golo.compiler.JavaBytecodeGenerationGoloIrVisitor.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.golo.compiler.JavaBytecodeGenerationGoloIrVisitor.java

Source

/*
 * Copyright (c) 2012-2018 Institut National des Sciences Appliques de Lyon (INSA Lyon) and others
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0
 */

package org.eclipse.golo.compiler;

import gololang.FunctionReference;
import gololang.ir.*;
import org.objectweb.asm.*;

import java.lang.invoke.MethodType;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;

import static gololang.Messages.message;
import static gololang.Messages.prefixed;
import static java.lang.invoke.MethodType.genericMethodType;
import static java.lang.invoke.MethodType.methodType;
import static org.eclipse.golo.compiler.JavaBytecodeUtils.*;
import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS;
import static org.objectweb.asm.Opcodes.*;

class JavaBytecodeGenerationGoloIrVisitor implements GoloIrVisitor {

    private static final String JOBJECT = "java/lang/Object";
    private static final String TOBJECT = "Ljava/lang/Object;";
    private static final Handle FUNCTION_INVOCATION_HANDLE = makeHandle("FunctionCallSupport",
            "[Ljava/lang/Object;");
    private static final Handle OPERATOR_HANDLE = makeHandle("OperatorSupport", "I");
    private static final Handle METHOD_INVOCATION_HANDLE = makeHandle("MethodInvocationSupport",
            "[Ljava/lang/Object;");
    private static final Handle CLASSREF_HANDLE = makeHandle("ClassReferenceSupport", "");
    private static final Handle CLOSUREREF_HANDLE = makeHandle("ClosureReferenceSupport", "Ljava/lang/String;II");
    private static final Handle CLOSURE_INVOCATION_HANDLE = makeHandle("ClosureCallSupport", "[Ljava/lang/Object;");

    private static final JavaBytecodeStructGenerator STRUCT_GENERATOR = new JavaBytecodeStructGenerator();
    private static final JavaBytecodeUnionGenerator UNION_GENERATOR = new JavaBytecodeUnionGenerator();

    private static final boolean USE_TCE = gololang.Runtime.loadBoolean("golo.optimize.tce", "GOLO_OPTIMIZE_TCE",
            true);

    private ClassWriter classWriter;
    private String klass;
    private String jvmKlass;
    private MethodVisitor currentMethodVisitor;
    private List<CodeGenerationResult> generationResults;
    private String sourceFilename;
    private Context context;
    private GoloModule currentModule;
    private String returnTypeCast;
    private GoloFunction currentFunction;
    private final Map<GoloFunction, Label> functionLabels = new HashMap<>();

    private static final class Context {
        private final Deque<ReferenceTable> referenceTableStack = new LinkedList<>();
        private final Map<LoopStatement, Label> loopStartMap = new HashMap<>();
        private final Map<LoopStatement, Label> loopEndMap = new HashMap<>();
    }

    private static Handle makeHandle(String methodName, String description) {
        return new Handle(H_INVOKESTATIC, "org/eclipse/golo/runtime/" + methodName, "bootstrap",
                "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;"
                        + description + ")Ljava/lang/invoke/CallSite;",
                false);
    }

    private static RuntimeException invalidElement(GoloElement<?> element) {
        return new IllegalStateException(prefixed("bug", message("no_element_remains", element.getClass())));
    }

    public MethodVisitor getMethodVisitor() {
        return currentMethodVisitor;
    }

    public void setMethodVisitor(MethodVisitor visitor) {
        currentMethodVisitor = visitor;
    }

    public List<CodeGenerationResult> generateBytecode(GoloModule module, String sourceFilename) {
        this.sourceFilename = sourceFilename;
        this.classWriter = new ClassWriter(COMPUTE_FRAMES | COMPUTE_MAXS);
        this.generationResults = new LinkedList<>();
        this.context = new Context();
        module.accept(this);
        this.classWriter.visitEnd();
        this.generationResults
                .add(new CodeGenerationResult(classWriter.toByteArray(), module.getPackageAndClass()));
        return this.generationResults;
    }

    @Override
    public void visitCollectionComprehension(CollectionComprehension coll) {
        throw invalidElement(coll);
    }

    @Override
    public void visitMatchExpression(MatchExpression match) {
        throw invalidElement(match);
    }

    @Override
    public void visitCaseStatement(CaseStatement caseStatement) {
        throw invalidElement(caseStatement);
    }

    @Override
    public void visitWhenClause(WhenClause<?> whenClause) {
        throw invalidElement(whenClause);
    }

    @Override
    public void visitForEachLoopStatement(ForEachLoopStatement statement) {
        throw invalidElement(statement);
    }

    @Override
    public void visitDestructuringAssignment(DestructuringAssignment statement) {
        throw invalidElement(statement);
    }

    @Override
    public void visitToplevelElements(ToplevelElements toplevels) {
        throw invalidElement(toplevels);
    }

    @Override
    public void visitNoop(Noop noop) {
        // do nothing...
    }

    @Override
    public void visitModule(GoloModule module) {
        this.currentModule = module;
        classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, module.getPackageAndClass().toJVMType(), null, JOBJECT,
                null);
        classWriter.visitSource(sourceFilename, null);
        writeImportMetaData(module.getImports());
        klass = module.getPackageAndClass().toString();
        jvmKlass = module.getPackageAndClass().toJVMType();
        writeAugmentsMetaData();
        writeAugmentationApplicationsMetaData();
        module.walk(this);
    }

    @Override
    public void visitModuleImport(ModuleImport moduleImport) {
        // TODO: deal with metadata here
    }

    @Override
    public void visitLocalReference(LocalReference moduleState) {
        if (moduleState.isModuleState()) {
            String name = moduleState.getName();
            classWriter.visitField(ACC_PRIVATE | ACC_STATIC, name, "Ljava/lang/Object;", null, null).visitEnd();

            MethodVisitor mv = classWriter.visitMethod(ACC_PRIVATE | ACC_STATIC | ACC_SYNTHETIC, name,
                    "()Ljava/lang/Object;", null, null);
            mv.visitCode();
            mv.visitFieldInsn(GETSTATIC, jvmKlass, name, "Ljava/lang/Object;");
            mv.visitInsn(ARETURN);
            mv.visitMaxs(0, 0);
            mv.visitEnd();

            mv = classWriter.visitMethod(ACC_PRIVATE | ACC_STATIC | ACC_SYNTHETIC, name, "(Ljava/lang/Object;)V",
                    null, null);
            mv.visitCode();
            mv.visitVarInsn(ALOAD, 0);
            mv.visitFieldInsn(PUTSTATIC, jvmKlass, name, "Ljava/lang/Object;");
            mv.visitInsn(RETURN);
            mv.visitMaxs(0, 0);
            mv.visitEnd();
        }
    }

    private void writeMetaData(String name, String[] data) {
        MethodVisitor mv = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC | ACC_SYNTHETIC, "$" + name,
                "()[Ljava/lang/String;", null, null);
        mv.visitCode();
        loadInteger(mv, data.length);
        mv.visitTypeInsn(ANEWARRAY, "java/lang/String");
        for (int i = 0; i < data.length; i++) {
            mv.visitInsn(DUP);
            loadInteger(mv, i);
            mv.visitLdcInsn(data[i]);
            mv.visitInsn(AASTORE);
        }
        mv.visitInsn(ARETURN);
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }

    private void writeAugmentationApplicationsMetaData() {
        /* create a metadata method that given a target class name hashcode
         * returns a String array containing the names of applied
         * augmentations
         */
        List<Augmentation> applications = new ArrayList<>(this.currentModule.getAugmentations());
        int applicationsSize = applications.size();
        writeMetaData("augmentationApplications", applications.stream().map(Augmentation::getTarget)
                .map(PackageAndClass::toString).toArray(String[]::new));
        Label defaultLabel = new Label();
        Label[] labels = new Label[applicationsSize];
        int[] keys = new int[applicationsSize];
        String[][] namesArrays = new String[applicationsSize][];
        // cases of the switch statement MUST be sorted
        applications.sort(Comparator.comparingInt(o -> o.getTarget().toString().hashCode()));
        int i = 0;
        for (Augmentation application : applications) {
            labels[i] = new Label();
            keys[i] = application.getTarget().toString().hashCode();
            namesArrays[i] = application.getNames().toArray(new String[application.getNames().size()]);
            i++;
        }
        MethodVisitor mv = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC | ACC_SYNTHETIC,
                "$augmentationApplications", "(I)[Ljava/lang/String;", null, null);
        mv.visitCode();
        mv.visitVarInsn(ILOAD, 0);
        mv.visitLookupSwitchInsn(defaultLabel, keys, labels);
        for (i = 0; i < applicationsSize; i++) {
            mv.visitLabel(labels[i]);
            loadInteger(mv, namesArrays[i].length);
            mv.visitTypeInsn(ANEWARRAY, "java/lang/String");
            for (int j = 0; j < namesArrays[i].length; j++) {
                mv.visitInsn(DUP);
                loadInteger(mv, j);
                mv.visitLdcInsn(namesArrays[i][j]);
                mv.visitInsn(AASTORE);
            }
            mv.visitInsn(ARETURN);
        }
        mv.visitLabel(defaultLabel);
        loadInteger(mv, 0);
        mv.visitTypeInsn(ANEWARRAY, "java/lang/String");
        mv.visitInsn(ARETURN);
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }

    private void writeImportMetaData(Set<ModuleImport> imports) {
        writeMetaData("imports", imports.stream().map(ModuleImport::getPackageAndClass)
                .map(PackageAndClass::toString).toArray(String[]::new));
    }

    private void writeAugmentsMetaData() {
        writeMetaData("augmentations", currentModule.getAugmentations().stream().map(Augmentation::getTarget)
                .map(PackageAndClass::toString).toArray(String[]::new));
    }

    @Override
    public void visitStruct(Struct struct) {
        this.generationResults.add(STRUCT_GENERATOR.compile(struct, this.sourceFilename));
    }

    @Override
    public void visitUnion(Union union) {
        this.generationResults.addAll(UNION_GENERATOR.compile(union, this.sourceFilename));
    }

    @Override
    public void visitUnionValue(UnionValue value) {
        // dealt in the UNION_GENERATOR visitor
    }

    @Override
    public void visitAugmentation(Augmentation augmentation) {
        generateAugmentationBytecode(augmentation.getTarget(), augmentation.getFunctions());
    }

    @Override
    public void visitNamedAugmentation(NamedAugmentation namedAugmentation) {
        generateAugmentationBytecode(namedAugmentation.getPackageAndClass(), namedAugmentation.getFunctions());
    }

    private void generateAugmentationBytecode(PackageAndClass target, Collection<GoloFunction> functions) {
        if (functions.isEmpty()) {
            return;
        }
        ClassWriter mainClassWriter = classWriter;
        String mangledClass = target.mangledName();
        PackageAndClass packageAndClass = this.currentModule.getPackageAndClass().createInnerClass(mangledClass);
        String augmentationClassInternalName = packageAndClass.toJVMType();
        String outerName = this.currentModule.getPackageAndClass().toJVMType();

        mainClassWriter.visitInnerClass(augmentationClassInternalName, outerName, mangledClass,
                ACC_PUBLIC | ACC_STATIC);

        classWriter = new ClassWriter(COMPUTE_FRAMES | COMPUTE_MAXS);
        classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, augmentationClassInternalName, null, JOBJECT, null);
        classWriter.visitSource(sourceFilename, null);
        classWriter.visitOuterClass(outerName, null, null);

        for (GoloFunction function : functions) {
            function.accept(this);
        }

        Set<ModuleImport> imports = new HashSet<>(this.currentModule.getImports());
        imports.add(ModuleImport.of(this.currentModule.getPackageAndClass()));
        writeImportMetaData(imports);

        classWriter.visitEnd();
        generationResults.add(new CodeGenerationResult(classWriter.toByteArray(), packageAndClass));
        classWriter = mainClassWriter;
    }

    @Override
    public void visitFunction(GoloFunction function) {
        currentMethodVisitor = classWriter.visitMethod(functionFlags(function), function.getName(),
                functionSignature(function), null, null);
        if (function.isDecorated()) {
            AnnotationVisitor annotation = currentMethodVisitor
                    .visitAnnotation("Lgololang/annotations/DecoratedBy;", true);
            annotation.visit("value", function.getDecoratorRef());
            annotation.visitEnd();
        }
        for (String parameter : function.getParameterNames()) {
            currentMethodVisitor.visitParameter(parameter, ACC_FINAL);
        }
        currentMethodVisitor.visitCode();
        functionLabels.put(function, visitLine(function, currentMethodVisitor));
        currentFunction = function;
        function.walk(this);
        returnTypeCast = null;
        currentFunction = null;
        if (function.isModuleInit()) {
            currentMethodVisitor.visitInsn(RETURN);
        }
        currentMethodVisitor.visitMaxs(0, 0);
        currentMethodVisitor.visitEnd();
    }

    private int functionFlags(GoloFunction function) {
        int accessFlags = ACC_STATIC | (function.isLocal() ? ACC_PRIVATE : ACC_PUBLIC);
        if (function.isSynthetic() || function.isDecorator()) {
            accessFlags |= ACC_SYNTHETIC;
        }
        if (function.isVarargs()) {
            accessFlags |= ACC_VARARGS;
        }
        return accessFlags;
    }

    private String functionSignature(GoloFunction function) {
        if (function.isMain()) {
            return "([Ljava/lang/String;)V";
        }
        if (function.isModuleInit()) {
            return "()V";
        }
        MethodType signature;
        if (function.isVarargs()) {
            signature = MethodType.genericMethodType(function.getArity() - 1, true);
        } else {
            signature = MethodType.genericMethodType(function.getArity());
        }
        return signature.toMethodDescriptorString();
    }

    private String goloFunctionSignature(int arity) {
        return MethodType.genericMethodType(arity).toMethodDescriptorString();
    }

    @Override
    public void visitDecorator(Decorator deco) {
        // do nothing since decorators are dealt with at an earlier stage
    }

    @Override
    public void visitBlock(Block block) {
        ReferenceTable referenceTable = block.getReferenceTable();
        context.referenceTableStack.push(referenceTable);
        Label blockStart = new Label();
        Label blockEnd = new Label();
        currentMethodVisitor.visitLabel(blockStart);
        for (GoloStatement<?> statement : block.getStatements()) {
            visitLine(statement, currentMethodVisitor);
            statement.accept(this);
            insertMissingPop(statement);
        }
        currentMethodVisitor.visitLabel(blockEnd);
        for (LocalReference localReference : referenceTable.ownedReferences()) {
            if (localReference.isModuleState()) {
                continue;
            }
            currentMethodVisitor.visitLocalVariable(localReference.getName(), TOBJECT, null, blockStart, blockEnd,
                    localReference.getIndex());
        }
        context.referenceTableStack.pop();
    }

    private void insertMissingPop(GoloStatement<?> statement) {
        Class<?> statementClass = statement.getClass();
        if (statementClass == FunctionInvocation.class) {
            currentMethodVisitor.visitInsn(POP);
        } else if (statementClass == BinaryOperation.class) {
            BinaryOperation operation = (BinaryOperation) statement;
            if (operation.isMethodCall()) {
                currentMethodVisitor.visitInsn(POP);
            }
        }
    }

    @Override
    public void visitConstantStatement(ConstantStatement constantStatement) {
        Object value = constantStatement.value();
        if (value == null) {
            currentMethodVisitor.visitInsn(ACONST_NULL);
            return;
        }
        if (value instanceof Integer) {
            int i = (Integer) value;
            loadInteger(currentMethodVisitor, i);
            currentMethodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf",
                    "(I)Ljava/lang/Integer;", false);
            return;
        }
        if (value instanceof Long) {
            long l = (Long) value;
            loadLong(currentMethodVisitor, l);
            currentMethodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;",
                    false);
            return;
        }
        if (value instanceof Boolean) {
            boolean b = (Boolean) value;
            loadInteger(currentMethodVisitor, b ? 1 : 0);
            currentMethodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf",
                    "(Z)Ljava/lang/Boolean;", false);
            return;
        }
        if (value instanceof BigDecimal) {
            currentMethodVisitor.visitTypeInsn(NEW, "java/math/BigDecimal");
            currentMethodVisitor.visitInsn(DUP);
            currentMethodVisitor.visitLdcInsn(value.toString());
            currentMethodVisitor.visitMethodInsn(INVOKESPECIAL, "java/math/BigDecimal", "<init>",
                    "(Ljava/lang/String;)V", false);
            return;
        }
        if (value instanceof BigInteger) {
            currentMethodVisitor.visitTypeInsn(NEW, "java/math/BigInteger");
            currentMethodVisitor.visitInsn(DUP);
            currentMethodVisitor.visitLdcInsn(value.toString());
            currentMethodVisitor.visitMethodInsn(INVOKESPECIAL, "java/math/BigInteger", "<init>",
                    "(Ljava/lang/String;)V", false);
            return;
        }
        if (value instanceof String) {
            currentMethodVisitor.visitLdcInsn(value);
            return;
        }
        if (value instanceof Character) {
            loadInteger(currentMethodVisitor, (Character) value);
            currentMethodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf",
                    "(C)Ljava/lang/Character;", false);
            return;
        }
        if (value instanceof ClassReference) {
            currentMethodVisitor.visitInvokeDynamicInsn(((ClassReference) value).toJVMType(), "()Ljava/lang/Class;",
                    CLASSREF_HANDLE);
            return;
        }
        if (value instanceof Double) {
            double d = (Double) value;
            currentMethodVisitor.visitLdcInsn(d);
            currentMethodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf",
                    "(D)Ljava/lang/Double;", false);
            return;
        }
        if (value instanceof Float) {
            float f = (Float) value;
            currentMethodVisitor.visitLdcInsn(f);
            currentMethodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;",
                    false);
            return;
        }
        throw new IllegalArgumentException("Constants of type " + value.getClass() + " cannot be handled.");
    }

    @Override
    public void visitReturnStatement(ReturnStatement returnStatement) {
        GoloStatement<?> expr = returnStatement.expression();
        if (isRecursiveTailCall(expr)) {
            storeInvocationArguments((FunctionInvocation) expr, currentFunction);
            currentMethodVisitor.visitJumpInsn(GOTO, functionLabels.get(currentFunction));
            return;
        }
        if (expr != null) {
            expr.accept(this);
        }
        if (returnStatement.isReturningVoid()) {
            currentMethodVisitor.visitInsn(RETURN);
        } else {
            if (returnTypeCast != null) {
                currentMethodVisitor.visitTypeInsn(CHECKCAST, returnTypeCast);
            }
            currentMethodVisitor.visitInsn(ARETURN);
        }
    }

    private boolean isRecursiveTailCall(GoloStatement<?> statement) {
        if (USE_TCE && statement instanceof FunctionInvocation) {
            FunctionInvocation invoke = (FunctionInvocation) statement;
            if (currentFunction.isDecorated()) {
                return false;
            }
            if (invoke.isOnReference()) {
                return invoke.getName().equals(currentFunction.getSyntheticSelfName()) && invoke
                        .getArity() == currentFunction.getArity() - currentFunction.getSyntheticParameterCount();
            }
            return invoke.getName().equals(currentFunction.getName())
                    && invoke.getArity() == currentFunction.getArity();
        }
        return false;
    }

    @Override
    public void visitThrowStatement(ThrowStatement throwStatement) {
        throwStatement.walk(this);
        currentMethodVisitor.visitTypeInsn(CHECKCAST, "java/lang/Throwable");
        currentMethodVisitor.visitInsn(ATHROW);
    }

    private List<String> visitInvocationArguments(AbstractInvocation<?> invocation) {
        List<String> argumentNames = new ArrayList<>();
        for (GoloElement<?> argument : invocation.getArguments()) {
            if (invocation.usesNamedArguments()) {
                NamedArgument namedArgument = (NamedArgument) argument;
                argumentNames.add(namedArgument.getName());
                argument = namedArgument.expression();
            }
            argument.accept(this);
        }
        return argumentNames;
    }

    private void storeInvocationArguments(AbstractInvocation<?> invocation, GoloFunction function) {
        int offset = function.getBlock().getReferenceTable().size();
        storeRegularInvocationArguments(reorderArguments(invocation.getArguments(), function.getParameterNames()),
                offset);
        reloadNextCallArguments(function.getArity(), offset, function.getSyntheticParameterCount());
    }

    private void reloadNextCallArguments(int paramNumber, int tmpOffset, int paramOffset) {
        for (int i = 0; i < paramNumber - paramOffset; i++) {
            currentMethodVisitor.visitVarInsn(ALOAD, i + tmpOffset);
            currentMethodVisitor.visitVarInsn(ASTORE, i + paramOffset);
        }
    }

    private static List<GoloElement<?>> reorderArguments(List<GoloElement<?>> arguments,
            List<String> parameterNames) {
        if (!arguments.stream().allMatch(e -> e instanceof NamedArgument)) {
            return arguments;
        }
        List<GoloElement<?>> ordered = new ArrayList<>(arguments);
        for (GoloElement<?> arg : arguments) {
            NamedArgument named = (NamedArgument) arg;
            ordered.set(parameterNames.indexOf(named.getName()), named.expression());
        }
        return ordered;
    }

    private void storeRegularInvocationArguments(List<GoloElement<?>> arguments, int offset) {
        for (int i = 0; i < arguments.size(); i++) {
            arguments.get(i).accept(this);
            currentMethodVisitor.visitVarInsn(ASTORE, i + offset);
        }
    }

    @Override
    public void visitFunctionInvocation(FunctionInvocation functionInvocation) {
        String name = functionInvocation.getName().replaceAll("\\.", "#");
        String typeDef = goloFunctionSignature(functionInvocation.getArity());
        Handle handle = FUNCTION_INVOCATION_HANDLE;
        List<Object> bootstrapArgs = new ArrayList<>();
        bootstrapArgs.add(functionInvocation.isConstant() ? 1 : 0);
        if (functionInvocation.isOnReference()) {
            ReferenceTable table = context.referenceTableStack.peek();
            currentMethodVisitor.visitVarInsn(ALOAD, table.get(functionInvocation.getName()).getIndex());
        }
        if (functionInvocation.isOnModuleState()) {
            ReferenceLookup.of(functionInvocation.getName()).accept(this);
        }
        if (functionInvocation.isAnonymous() || functionInvocation.isOnReference()
                || functionInvocation.isOnModuleState()) {
            currentMethodVisitor.visitTypeInsn(CHECKCAST, "gololang/FunctionReference");
            MethodType type = genericMethodType(functionInvocation.getArity() + 1).changeParameterType(0,
                    FunctionReference.class);
            typeDef = type.toMethodDescriptorString();
            handle = CLOSURE_INVOCATION_HANDLE;
        }
        List<String> argumentNames = visitInvocationArguments(functionInvocation);
        bootstrapArgs.addAll(argumentNames);
        currentMethodVisitor.visitInvokeDynamicInsn(name, typeDef, handle, bootstrapArgs.toArray());
    }

    @Override
    public void visitMethodInvocation(MethodInvocation methodInvocation) {
        List<Object> bootstrapArgs = new ArrayList<>();
        bootstrapArgs.add(methodInvocation.isNullSafeGuarded() ? 1 : 0);
        List<String> argumentNames = visitInvocationArguments(methodInvocation);
        bootstrapArgs.addAll(argumentNames);
        currentMethodVisitor.visitInvokeDynamicInsn(methodInvocation.getName().replaceAll("\\.", "#"),
                goloFunctionSignature(methodInvocation.getArity() + 1), METHOD_INVOCATION_HANDLE,
                bootstrapArgs.toArray());
    }

    @Override
    public void visitAssignmentStatement(AssignmentStatement assignmentStatement) {
        assignmentStatement.walk(this);
        LocalReference reference = assignmentStatement.getLocalReference();
        if (reference.isModuleState()) {
            currentMethodVisitor.visitInvokeDynamicInsn((klass + "." + reference.getName()).replaceAll("\\.", "#"),
                    "(Ljava/lang/Object;)V", FUNCTION_INVOCATION_HANDLE, (Object) 0);
        } else {
            currentMethodVisitor.visitVarInsn(ASTORE, reference.getIndex());
        }
    }

    @Override
    public void visitReferenceLookup(ReferenceLookup referenceLookup) {
        LocalReference reference = referenceLookup.resolveIn(context.referenceTableStack.peek());
        if (reference.isModuleState()) {
            currentMethodVisitor.visitInvokeDynamicInsn(
                    (klass + "." + referenceLookup.getName()).replaceAll("\\.", "#"), "()Ljava/lang/Object;",
                    FUNCTION_INVOCATION_HANDLE, (Object) 0);
        } else {
            currentMethodVisitor.visitVarInsn(ALOAD, reference.getIndex());
        }
    }

    @Override
    public void visitConditionalBranching(ConditionalBranching conditionalBranching) {
        Label branchingElseLabel = new Label();
        Label branchingExitLabel = new Label();
        conditionalBranching.getCondition().accept(this);
        asmBooleanValue();
        currentMethodVisitor.visitJumpInsn(IFEQ, branchingElseLabel);
        conditionalBranching.getTrueBlock().accept(this);
        if (conditionalBranching.hasFalseBlock()) {
            if (!conditionalBranching.getTrueBlock().hasReturn()) {
                currentMethodVisitor.visitJumpInsn(GOTO, branchingExitLabel);
            }
            currentMethodVisitor.visitLabel(branchingElseLabel);
            conditionalBranching.getFalseBlock().accept(this);
            currentMethodVisitor.visitLabel(branchingExitLabel);
        } else if (conditionalBranching.hasElseConditionalBranching()) {
            if (!conditionalBranching.getTrueBlock().hasReturn()) {
                currentMethodVisitor.visitJumpInsn(GOTO, branchingExitLabel);
            }
            currentMethodVisitor.visitLabel(branchingElseLabel);
            conditionalBranching.getElseConditionalBranching().accept(this);
            currentMethodVisitor.visitLabel(branchingExitLabel);
        } else {
            currentMethodVisitor.visitLabel(branchingElseLabel);
        }
    }

    @Override
    public void visitLoopStatement(LoopStatement loopStatement) {
        // TODO: handle init and post statement and potential reference scoping issues
        Label loopStart = new Label();
        Label loopEnd = new Label();
        context.loopStartMap.put(loopStatement, loopStart);
        context.loopEndMap.put(loopStatement, loopEnd);
        if (loopStatement.hasInitStatement()) {
            loopStatement.init().accept(this);
        }
        currentMethodVisitor.visitLabel(loopStart);
        loopStatement.condition().accept(this);
        asmBooleanValue();
        currentMethodVisitor.visitJumpInsn(IFEQ, loopEnd);
        loopStatement.getBlock().accept(this);
        if (loopStatement.hasPostStatement()) {
            loopStatement.post().accept(this);
        }
        currentMethodVisitor.visitJumpInsn(GOTO, loopStart);
        currentMethodVisitor.visitLabel(loopEnd);
    }

    @Override
    public void visitLoopBreakFlowStatement(LoopBreakFlowStatement loopBreakFlowStatement) {
        Label jumpTarget;
        if (LoopBreakFlowStatement.Type.BREAK.equals(loopBreakFlowStatement.getType())) {
            jumpTarget = context.loopEndMap.get(loopBreakFlowStatement.getEnclosingLoop());
        } else {
            jumpTarget = context.loopStartMap.get(loopBreakFlowStatement.getEnclosingLoop());
        }
        currentMethodVisitor.visitLdcInsn(0);
        currentMethodVisitor.visitJumpInsn(IFEQ, jumpTarget);
        // NOP + ATHROW invalid frames if the GOTO is followed by an else branch code...
        // currentMethodVisitor.visitJumpInsn(GOTO, jumpTarget);
    }

    @Override
    public void visitNamedArgument(NamedArgument namedArgument) {
        // Nothing to do, it's already been done for us
    }

    @Override
    public void visitCollectionLiteral(CollectionLiteral collectionLiteral) {
        throw invalidElement(collectionLiteral);
    }

    @Override
    public void visitTryCatchFinally(TryCatchFinally tryCatchFinally) {
        Label tryStart = new Label();
        Label tryEnd = new Label();
        Label catchStart = new Label();
        Label catchEnd = new Label();

        Label rethrowStart = null;
        Label rethrowEnd = null;
        if (tryCatchFinally.isTryCatchFinally()) {
            rethrowStart = new Label();
            rethrowEnd = new Label();
        }

        currentMethodVisitor.visitLabel(tryStart);
        tryCatchFinally.getTryBlock().accept(this);
        if (tryCatchFinally.isTryCatch() || tryCatchFinally.isTryCatchFinally()) {
            currentMethodVisitor.visitJumpInsn(GOTO, catchEnd);
        }
        currentMethodVisitor.visitTryCatchBlock(tryStart, tryEnd, catchStart, null);
        currentMethodVisitor.visitLabel(tryEnd);

        if (tryCatchFinally.isTryFinally()) {
            tryCatchFinally.getFinallyBlock().accept(this);
            currentMethodVisitor.visitJumpInsn(GOTO, catchEnd);
        }

        if (tryCatchFinally.isTryCatchFinally()) {
            currentMethodVisitor.visitTryCatchBlock(catchStart, catchEnd, rethrowStart, null);
        }

        currentMethodVisitor.visitLabel(catchStart);
        if (tryCatchFinally.isTryCatch() || tryCatchFinally.isTryCatchFinally()) {
            Block catchBlock = tryCatchFinally.getCatchBlock();
            int exceptionRefIndex = catchBlock.getReferenceTable().get(tryCatchFinally.getExceptionId()).getIndex();
            currentMethodVisitor.visitVarInsn(ASTORE, exceptionRefIndex);
            tryCatchFinally.getCatchBlock().accept(this);
        } else {
            tryCatchFinally.getFinallyBlock().accept(this);
            currentMethodVisitor.visitInsn(ATHROW);
        }
        currentMethodVisitor.visitLabel(catchEnd);

        if (tryCatchFinally.isTryCatchFinally()) {
            tryCatchFinally.getFinallyBlock().accept(this);
            currentMethodVisitor.visitJumpInsn(GOTO, rethrowEnd);
            currentMethodVisitor.visitLabel(rethrowStart);
            tryCatchFinally.getFinallyBlock().accept(this);
            currentMethodVisitor.visitInsn(ATHROW);
            currentMethodVisitor.visitLabel(rethrowEnd);
        }
    }

    @Override
    public void visitClosureReference(ClosureReference closureReference) {
        GoloFunction target = closureReference.getTarget();
        final boolean isVarArgs = target.isVarargs();
        final int arity = (isVarArgs) ? target.getArity() - 1 : target.getArity();
        final int syntheticCount = target.getSyntheticParameterCount();
        currentMethodVisitor.visitInvokeDynamicInsn(target.getName(),
                methodType(FunctionReference.class).toMethodDescriptorString(), CLOSUREREF_HANDLE, klass,
                (Integer) arity, (Boolean) isVarArgs);
        if (syntheticCount > 0) {
            String[] refs = closureReference.getCapturedReferenceNames().toArray(new String[syntheticCount]);
            loadInteger(currentMethodVisitor, 0);
            loadInteger(currentMethodVisitor, syntheticCount);
            currentMethodVisitor.visitTypeInsn(ANEWARRAY, "java/lang/Object");
            ReferenceTable table = context.referenceTableStack.peek();
            for (int i = 0; i < syntheticCount; i++) {
                currentMethodVisitor.visitInsn(DUP);
                loadInteger(currentMethodVisitor, i);
                currentMethodVisitor.visitVarInsn(ALOAD, table.get(refs[i]).getIndex());
                currentMethodVisitor.visitInsn(AASTORE);
            }
            currentMethodVisitor.visitMethodInsn(INVOKEVIRTUAL, "gololang/FunctionReference", "insertArguments",
                    "(I[Ljava/lang/Object;)Lgololang/FunctionReference;", false);
            if (isVarArgs) {
                currentMethodVisitor.visitLdcInsn(Type.getType(Object[].class));
                currentMethodVisitor.visitMethodInsn(INVOKEVIRTUAL, "gololang/FunctionReference",
                        "asVarargsCollector", "(Ljava/lang/Class;)Lgololang/FunctionReference;", false);
            }
        }
    }

    @Override
    public void visitBinaryOperation(BinaryOperation binaryOperation) {
        switch (binaryOperation.getType()) {
        case AND:
            andOperator(binaryOperation);
            break;
        case OR:
            orOperator(binaryOperation);
            break;
        case ORIFNULL:
            orIfNullOperator(binaryOperation);
            break;
        default:
            binaryOperation.walk(this);
            genericBinaryOperator(binaryOperation);
        }
    }

    private void genericBinaryOperator(BinaryOperation binaryOperation) {
        if (!binaryOperation.isMethodCall()) {
            String name = binaryOperation.getType().name().toLowerCase();
            currentMethodVisitor.visitInvokeDynamicInsn(name, goloFunctionSignature(2), OPERATOR_HANDLE,
                    (Integer) 2);
        }
    }

    private void orIfNullOperator(BinaryOperation binaryOperation) {
        int idx = context.referenceTableStack.peek().size();
        Label nullLabel = new Label();
        Label exitLabel = new Label();
        binaryOperation.left().accept(this);
        currentMethodVisitor.visitVarInsn(ASTORE, idx);
        currentMethodVisitor.visitVarInsn(ALOAD, idx);
        currentMethodVisitor.visitJumpInsn(IFNULL, nullLabel);
        currentMethodVisitor.visitJumpInsn(GOTO, exitLabel);
        currentMethodVisitor.visitLabel(nullLabel);
        binaryOperation.right().accept(this);
        currentMethodVisitor.visitVarInsn(ASTORE, idx);
        currentMethodVisitor.visitLabel(exitLabel);
        currentMethodVisitor.visitVarInsn(ALOAD, idx);
    }

    private void orOperator(BinaryOperation binaryOperation) {
        Label exitLabel = new Label();
        Label trueLabel = new Label();
        binaryOperation.left().accept(this);
        asmBooleanValue();
        currentMethodVisitor.visitJumpInsn(IFNE, trueLabel);
        binaryOperation.right().accept(this);
        asmBooleanValue();
        currentMethodVisitor.visitJumpInsn(IFNE, trueLabel);
        asmFalseObject();
        currentMethodVisitor.visitJumpInsn(GOTO, exitLabel);
        currentMethodVisitor.visitLabel(trueLabel);
        asmTrueObject();
        currentMethodVisitor.visitLabel(exitLabel);
    }

    private void andOperator(BinaryOperation binaryOperation) {
        Label exitLabel = new Label();
        Label falseLabel = new Label();
        binaryOperation.left().accept(this);
        asmBooleanValue();
        currentMethodVisitor.visitJumpInsn(IFEQ, falseLabel);
        binaryOperation.right().accept(this);
        asmBooleanValue();
        currentMethodVisitor.visitJumpInsn(IFEQ, falseLabel);
        asmTrueObject();
        currentMethodVisitor.visitJumpInsn(GOTO, exitLabel);
        currentMethodVisitor.visitLabel(falseLabel);
        asmFalseObject();
        currentMethodVisitor.visitLabel(exitLabel);
    }

    private void asmFalseObject() {
        currentMethodVisitor.visitFieldInsn(GETSTATIC, "java/lang/Boolean", "FALSE", "Ljava/lang/Boolean;");
    }

    private void asmTrueObject() {
        currentMethodVisitor.visitFieldInsn(GETSTATIC, "java/lang/Boolean", "TRUE", "Ljava/lang/Boolean;");
    }

    private void asmBooleanValue() {
        currentMethodVisitor.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
        currentMethodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
    }

    @Override
    public void visitUnaryOperation(UnaryOperation unaryOperation) {
        String name = unaryOperation.getType().name().toLowerCase();
        unaryOperation.walk(this);
        currentMethodVisitor.visitInvokeDynamicInsn(name, goloFunctionSignature(1), OPERATOR_HANDLE, (Integer) 1);
    }

    @Override
    public void visitMember(Member member) {
        // Do nothing since default values are dealt with at an earlier stage
        // and member names are dealt with by the specific generator.
    }

}