Package org.jruby.compiler.impl

Source Code of org.jruby.compiler.impl.BaseBodyCompiler

/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.jruby.compiler.impl;

import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.math.BigInteger;
import java.util.Arrays;
import org.jruby.MetaClass;
import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyBignum;
import org.jruby.RubyBoolean;
import org.jruby.RubyClass;
import org.jruby.RubyException;
import org.jruby.RubyFixnum;
import org.jruby.RubyFloat;
import org.jruby.RubyHash;
import org.jruby.RubyInstanceConfig;
import org.jruby.RubyMatchData;
import org.jruby.RubyModule;
import org.jruby.RubyRange;
import org.jruby.RubyRegexp;
import org.jruby.RubyString;
import org.jruby.RubySymbol;
import org.jruby.ast.NodeType;
import org.jruby.ast.util.ArgsUtil;
import org.jruby.compiler.ASTInspector;
import org.jruby.compiler.ArrayCallback;
import org.jruby.compiler.BranchCallback;
import org.jruby.compiler.CompilerCallback;
import org.jruby.compiler.InvocationCompiler;
import org.jruby.compiler.BodyCompiler;
import org.jruby.compiler.NotCompilableException;
import org.jruby.compiler.ScriptCompiler;
import org.jruby.compiler.VariableCompiler;
import org.jruby.exceptions.JumpException;
import org.jruby.exceptions.RaiseException;
import org.jruby.internal.runtime.GlobalVariables;
import org.jruby.internal.runtime.methods.CallConfiguration;
import org.jruby.internal.runtime.methods.DynamicMethod;
import org.jruby.javasupport.JavaUtil;
import org.jruby.javasupport.util.RuntimeHelpers;
import org.jruby.lexer.yacc.ISourcePosition;
import org.jruby.parser.ReOptions;
import org.jruby.parser.StaticScope;
import org.jruby.runtime.Block;
import org.jruby.runtime.BlockBody;
import org.jruby.runtime.CompiledBlockCallback;
import org.jruby.runtime.DynamicScope;
import org.jruby.runtime.Frame;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.builtin.InstanceVariables;
import org.jruby.util.ByteList;
import org.jruby.util.JavaNameMangler;
import org.objectweb.asm.Label;
import static org.objectweb.asm.Opcodes.*;
import static org.jruby.util.CodegenUtils.*;

/**
* BaseBodyCompiler encapsulates all common behavior between BodyCompiler
* implementations.
*/
public abstract class BaseBodyCompiler implements BodyCompiler {
    protected SkinnyMethodAdapter method;
    protected VariableCompiler variableCompiler;
    protected InvocationCompiler invocationCompiler;
    protected int argParamCount;
    protected Label[] currentLoopLabels;
    protected Label scopeStart = new Label();
    protected Label scopeEnd = new Label();
    protected Label redoJump;
    protected boolean inNestedMethod = false;
    private int lastLine = -1;
    private int lastPositionLine = -1;
    protected StaticScope scope;
    protected ASTInspector inspector;
    protected String methodName;
    protected StandardASMCompiler script;

    public BaseBodyCompiler(StandardASMCompiler scriptCompiler, String methodName, ASTInspector inspector, StaticScope scope) {
        this.script = scriptCompiler;
        this.scope = scope;
        this.inspector = inspector;
        this.methodName = methodName;
        if (scope.getRestArg() >= 0 || scope.getOptionalArgs() > 0 || scope.getRequiredArgs() > 3) {
            argParamCount = 1; // use IRubyObject[]
        } else {
            argParamCount = scope.getRequiredArgs(); // specific arity
        }

        method = new SkinnyMethodAdapter(script.getClassVisitor().visitMethod(ACC_PUBLIC, methodName, getSignature(), null, null));

        createVariableCompiler();
        if (StandardASMCompiler.invDynInvCompilerConstructor != null) {
            try {
                invocationCompiler = (InvocationCompiler) StandardASMCompiler.invDynInvCompilerConstructor.newInstance(this, method);
            } catch (InstantiationException ie) {
                // do nothing, fall back on default compiler below
                } catch (IllegalAccessException ie) {
                // do nothing, fall back on default compiler below
                } catch (InvocationTargetException ie) {
                // do nothing, fall back on default compiler below
                }
        }
        if (invocationCompiler == null) {
            invocationCompiler = new StandardInvocationCompiler(this, method);
        }
    }

    protected abstract String getSignature();

    protected abstract void createVariableCompiler();

    public abstract void beginMethod(CompilerCallback args, StaticScope scope);

    public abstract void endBody();

    public BodyCompiler chainToMethod(String methodName) {
        BodyCompiler compiler = outline(methodName);
        endBody();
        return compiler;
    }

    public abstract BaseBodyCompiler outline(String methodName);

    public StandardASMCompiler getScriptCompiler() {
        return script;
    }

    public void lineNumber(ISourcePosition position) {
        int thisLine = position.getStartLine();

        // No point in updating number if last number was same value.
        if (thisLine != lastLine) {
            lastLine = thisLine;
        } else {
            return;
        }

        Label line = new Label();
        method.label(line);
        method.visitLineNumber(thisLine + 1, line);
    }

    public void loadThreadContext() {
        method.aload(StandardASMCompiler.THREADCONTEXT_INDEX);
    }

    public void loadSelf() {
        method.aload(StandardASMCompiler.SELF_INDEX);
    }

    protected int getClosureIndex() {
        return StandardASMCompiler.ARGS_INDEX + argParamCount + StandardASMCompiler.CLOSURE_OFFSET;
    }

    protected int getRuntimeIndex() {
        return StandardASMCompiler.ARGS_INDEX + argParamCount + StandardASMCompiler.RUNTIME_OFFSET;
    }

    protected int getNilIndex() {
        return StandardASMCompiler.ARGS_INDEX + argParamCount + StandardASMCompiler.NIL_OFFSET;
    }

    protected int getPreviousExceptionIndex() {
        return StandardASMCompiler.ARGS_INDEX + argParamCount + StandardASMCompiler.PREVIOUS_EXCEPTION_OFFSET;
    }

    protected int getDynamicScopeIndex() {
        return StandardASMCompiler.ARGS_INDEX + argParamCount + StandardASMCompiler.DYNAMIC_SCOPE_OFFSET;
    }

    protected int getVarsArrayIndex() {
        return StandardASMCompiler.ARGS_INDEX + argParamCount + StandardASMCompiler.VARS_ARRAY_OFFSET;
    }

    protected int getFirstTempIndex() {
        return StandardASMCompiler.ARGS_INDEX + argParamCount + StandardASMCompiler.FIRST_TEMP_OFFSET;
    }

    protected int getExceptionIndex() {
        return StandardASMCompiler.ARGS_INDEX + argParamCount + StandardASMCompiler.EXCEPTION_OFFSET;
    }

    public void loadThis() {
        method.aload(StandardASMCompiler.THIS);
    }

    public void loadRuntime() {
        method.aload(getRuntimeIndex());
    }

    public void loadBlock() {
        method.aload(getClosureIndex());
    }

    public void loadNil() {
        method.aload(getNilIndex());
    }

    public void loadNull() {
        method.aconst_null();
    }

    public void loadSymbol(String symbol) {
        loadRuntime();

        method.ldc(symbol);

        invokeIRuby("newSymbol", sig(RubySymbol.class, params(String.class)));
    }

    public void loadObject() {
        loadRuntime();

        invokeIRuby("getObject", sig(RubyClass.class, params()));
    }

    /**
     * This is for utility methods used by the compiler, to reduce the amount of code generation
     * necessary.  All of these live in CompilerHelpers.
     */
    public void invokeUtilityMethod(String methodName, String signature) {
        method.invokestatic(p(RuntimeHelpers.class), methodName, signature);
    }

    public void invokeThreadContext(String methodName, String signature) {
        method.invokevirtual(StandardASMCompiler.THREADCONTEXT, methodName, signature);
    }

    public void invokeIRuby(String methodName, String signature) {
        method.invokevirtual(StandardASMCompiler.RUBY, methodName, signature);
    }

    public void invokeIRubyObject(String methodName, String signature) {
        method.invokeinterface(StandardASMCompiler.IRUBYOBJECT, methodName, signature);
    }

    public void consumeCurrentValue() {
        method.pop();
    }

    public void duplicateCurrentValue() {
        method.dup();
    }

    public void swapValues() {
        method.swap();
    }

    public void retrieveSelf() {
        loadSelf();
    }

    public void retrieveSelfClass() {
        loadSelf();
        metaclass();
    }

    public VariableCompiler getVariableCompiler() {
        return variableCompiler;
    }

    public InvocationCompiler getInvocationCompiler() {
        return invocationCompiler;
    }

    public void assignConstantInCurrent(String name) {
        loadThreadContext();
        method.ldc(name);
        method.dup2_x1();
        method.pop2();
        invokeThreadContext("setConstantInCurrent", sig(IRubyObject.class, params(String.class, IRubyObject.class)));
    }

    public void assignConstantInModule(String name) {
        method.ldc(name);
        loadThreadContext();
        invokeUtilityMethod("setConstantInModule", sig(IRubyObject.class, IRubyObject.class, IRubyObject.class, String.class, ThreadContext.class));
    }

    public void assignConstantInObject(String name) {
        // load Object under value
        loadRuntime();
        invokeIRuby("getObject", sig(RubyClass.class, params()));
        method.swap();

        assignConstantInModule(name);
    }

    public void retrieveConstant(String name) {
        script.getCacheCompiler().cacheConstant(this, name);
    }

    public void retrieveConstantFromModule(String name) {
        method.visitTypeInsn(CHECKCAST, p(RubyModule.class));
        method.ldc(name);
        method.invokevirtual(p(RubyModule.class), "fastGetConstantFrom", sig(IRubyObject.class, params(String.class)));
    }

    public void retrieveClassVariable(String name) {
        loadThreadContext();
        loadRuntime();
        loadSelf();
        method.ldc(name);

        invokeUtilityMethod("fastFetchClassVariable", sig(IRubyObject.class, params(ThreadContext.class, Ruby.class, IRubyObject.class, String.class)));
    }

    public void assignClassVariable(String name) {
        loadThreadContext();
        method.swap();
        loadRuntime();
        method.swap();
        loadSelf();
        method.swap();
        method.ldc(name);
        method.swap();

        invokeUtilityMethod("fastSetClassVariable", sig(IRubyObject.class, params(ThreadContext.class, Ruby.class, IRubyObject.class, String.class, IRubyObject.class)));
    }

    public void assignClassVariable(String name, CompilerCallback value) {
        loadThreadContext();
        loadRuntime();
        loadSelf();
        method.ldc(name);
        value.call(this);

        invokeUtilityMethod("fastSetClassVariable", sig(IRubyObject.class, params(ThreadContext.class, Ruby.class, IRubyObject.class, String.class, IRubyObject.class)));
    }

    public void declareClassVariable(String name) {
        loadThreadContext();
        method.swap();
        loadRuntime();
        method.swap();
        loadSelf();
        method.swap();
        method.ldc(name);
        method.swap();

        invokeUtilityMethod("fastDeclareClassVariable", sig(IRubyObject.class, params(ThreadContext.class, Ruby.class, IRubyObject.class, String.class, IRubyObject.class)));
    }

    public void declareClassVariable(String name, CompilerCallback value) {
        loadThreadContext();
        loadRuntime();
        loadSelf();
        method.ldc(name);
        value.call(this);

        invokeUtilityMethod("fastDeclareClassVariable", sig(IRubyObject.class, params(ThreadContext.class, Ruby.class, IRubyObject.class, String.class, IRubyObject.class)));
    }

    public void createNewFloat(double value) {
        loadRuntime();
        method.ldc(new Double(value));

        invokeIRuby("newFloat", sig(RubyFloat.class, params(Double.TYPE)));
    }

    public void createNewFixnum(long value) {
        script.getCacheCompiler().cacheFixnum(this, value);
    }

    public void createNewBignum(BigInteger value) {
        loadRuntime();
        script.getCacheCompiler().cacheBigInteger(this, value);
        method.invokestatic(p(RubyBignum.class), "newBignum", sig(RubyBignum.class, params(Ruby.class, BigInteger.class)));
    }

    public void createNewString(ArrayCallback callback, int count) {
        loadRuntime();
        invokeIRuby("newString", sig(RubyString.class, params()));
        for (int i = 0; i < count; i++) {
            callback.nextValue(this, null, i);
            method.invokevirtual(p(RubyString.class), "append", sig(RubyString.class, params(IRubyObject.class)));
        }
    }

    public void createNewSymbol(ArrayCallback callback, int count) {
        loadRuntime();
        createNewString(callback, count);
        toJavaString();
        invokeIRuby("newSymbol", sig(RubySymbol.class, params(String.class)));
    }

    public void createNewString(ByteList value) {
        // FIXME: this is sub-optimal, storing string value in a java.lang.String again
        loadRuntime();
        script.getCacheCompiler().cacheByteList(this, value);

        invokeIRuby("newStringShared", sig(RubyString.class, params(ByteList.class)));
    }

    public void createNewSymbol(String name) {
        script.getCacheCompiler().cacheSymbol(this, name);
    }

    public void createNewArray(boolean lightweight) {
        loadRuntime();
        // put under object array already present
        method.swap();

        if (lightweight) {
            method.invokestatic(p(RubyArray.class), "newArrayNoCopyLight", sig(RubyArray.class, params(Ruby.class, IRubyObject[].class)));
        } else {
            method.invokestatic(p(RubyArray.class), "newArrayNoCopy", sig(RubyArray.class, params(Ruby.class, IRubyObject[].class)));
        }
    }

    public void createNewArray(Object[] sourceArray, ArrayCallback callback, boolean lightweight) {
        loadRuntime();

        createObjectArray(sourceArray, callback);

        if (lightweight) {
            method.invokestatic(p(RubyArray.class), "newArrayNoCopyLight", sig(RubyArray.class, params(Ruby.class, IRubyObject[].class)));
        } else {
            method.invokestatic(p(RubyArray.class), "newArrayNoCopy", sig(RubyArray.class, params(Ruby.class, IRubyObject[].class)));
        }
    }

    public void createEmptyArray() {
        loadRuntime();

        invokeIRuby("newArray", sig(RubyArray.class, params()));
    }

    public void createObjectArray(Object[] sourceArray, ArrayCallback callback) {
        buildObjectArray(StandardASMCompiler.IRUBYOBJECT, sourceArray, callback);
    }

    public void createObjectArray(int elementCount) {
        // if element count is less than 6, use helper methods
        if (elementCount < 6) {
            Class[] params = new Class[elementCount];
            Arrays.fill(params, IRubyObject.class);
            invokeUtilityMethod("constructObjectArray", sig(IRubyObject[].class, params));
        } else {
            // This is pretty inefficient for building an array, so just raise an error if someone's using it for a lot of elements
            throw new NotCompilableException("Don't use createObjectArray(int) for more than 5 elements");
        }
    }

    private void buildObjectArray(String type, Object[] sourceArray, ArrayCallback callback) {
        if (sourceArray.length == 0) {
            method.getstatic(p(IRubyObject.class), "NULL_ARRAY", ci(IRubyObject[].class));
        } else if (sourceArray.length <= RuntimeHelpers.MAX_SPECIFIC_ARITY_OBJECT_ARRAY) {
            // if we have a specific-arity helper to construct an array for us, use that
            for (int i = 0; i < sourceArray.length; i++) {
                callback.nextValue(this, sourceArray, i);
            }
            invokeUtilityMethod("constructObjectArray", sig(IRubyObject[].class, params(IRubyObject.class, sourceArray.length)));
        } else {
            // brute force construction inline
            method.pushInt(sourceArray.length);
            method.anewarray(type);

            for (int i = 0; i < sourceArray.length; i++) {
                method.dup();
                method.pushInt(i);

                callback.nextValue(this, sourceArray, i);

                method.arraystore();
            }
        }
    }

    public void createEmptyHash() {
        loadRuntime();

        method.invokestatic(p(RubyHash.class), "newHash", sig(RubyHash.class, params(Ruby.class)));
    }

    public void createNewHash(Object elements, ArrayCallback callback, int keyCount) {
        loadRuntime();

        if (keyCount <= RuntimeHelpers.MAX_SPECIFIC_ARITY_HASH) {
            // we have a specific-arity method we can use to construct, so use that
            for (int i = 0; i < keyCount; i++) {
                callback.nextValue(this, elements, i);
            }

            invokeUtilityMethod("constructHash", sig(RubyHash.class, params(Ruby.class, IRubyObject.class, keyCount * 2)));
        } else {
            method.invokestatic(p(RubyHash.class), "newHash", sig(RubyHash.class, params(Ruby.class)));

            for (int i = 0; i < keyCount; i++) {
                method.dup();
                callback.nextValue(this, elements, i);
                method.invokevirtual(p(RubyHash.class), "fastASet", sig(void.class, params(IRubyObject.class, IRubyObject.class)));
            }
        }
    }

    public void createNewRange(boolean isExclusive) {
        loadRuntime();
        loadThreadContext();

        // could be more efficient with a callback
        method.dup2_x2();
        method.pop2();

        if (isExclusive) {
            method.invokestatic(p(RubyRange.class), "newExclusiveRange", sig(RubyRange.class, params(Ruby.class, ThreadContext.class, IRubyObject.class, IRubyObject.class)));
        } else {
            method.invokestatic(p(RubyRange.class), "newInclusiveRange", sig(RubyRange.class, params(Ruby.class, ThreadContext.class, IRubyObject.class, IRubyObject.class)));
        }
    }

    /**
     * Invoke IRubyObject.isTrue
     */
    private void isTrue() {
        invokeIRubyObject("isTrue", sig(Boolean.TYPE));
    }

    public void performBooleanBranch(BranchCallback trueBranch, BranchCallback falseBranch) {
        Label afterJmp = new Label();
        Label falseJmp = new Label();

        // call isTrue on the result
        isTrue();

        method.ifeq(falseJmp); // EQ == 0 (i.e. false)
        trueBranch.branch(this);
        method.go_to(afterJmp);

        // FIXME: optimize for cases where we have no false branch
        method.label(falseJmp);
        falseBranch.branch(this);

        method.label(afterJmp);
    }

    public void performLogicalAnd(BranchCallback longBranch) {
        Label falseJmp = new Label();

        // dup it since we need to return appropriately if it's false
        method.dup();

        // call isTrue on the result
        isTrue();

        method.ifeq(falseJmp); // EQ == 0 (i.e. false)
        // pop the extra result and replace with the send part of the AND
        method.pop();
        longBranch.branch(this);
        method.label(falseJmp);
    }

    public void performLogicalOr(BranchCallback longBranch) {
        // FIXME: after jump is not in here.  Will if ever be?
        //Label afterJmp = new Label();
        Label falseJmp = new Label();

        // dup it since we need to return appropriately if it's false
        method.dup();

        // call isTrue on the result
        isTrue();

        method.ifne(falseJmp); // EQ == 0 (i.e. false)
        // pop the extra result and replace with the send part of the AND
        method.pop();
        longBranch.branch(this);
        method.label(falseJmp);
    }

    public void performBooleanLoopSafe(BranchCallback condition, BranchCallback body, boolean checkFirst) {
        String mname = getNewRescueName();
        BaseBodyCompiler nested = outline(mname);
        nested.performBooleanLoopSafeInner(condition, body, checkFirst);
    }

    private void performBooleanLoopSafeInner(BranchCallback condition, BranchCallback body, boolean checkFirst) {
        performBooleanLoop(condition, body, checkFirst);

        endBody();
    }

    public void performBooleanLoop(BranchCallback condition, final BranchCallback body, boolean checkFirst) {
        Label tryBegin = new Label();
        Label tryEnd = new Label();
        Label catchNext = new Label();
        Label catchBreak = new Label();
        Label endOfBody = new Label();
        Label conditionCheck = new Label();
        final Label topOfBody = new Label();
        Label done = new Label();
        Label normalLoopEnd = new Label();
        method.trycatch(tryBegin, tryEnd, catchNext, p(JumpException.NextJump.class));
        method.trycatch(tryBegin, tryEnd, catchBreak, p(JumpException.BreakJump.class));

        method.label(tryBegin);
        {

            Label[] oldLoopLabels = currentLoopLabels;

            currentLoopLabels = new Label[]{endOfBody, topOfBody, done};

            // FIXME: if we terminate immediately, this appears to break while in method arguments
            // we need to push a nil for the cases where we will never enter the body
            if (checkFirst) {
                method.go_to(conditionCheck);
            }

            method.label(topOfBody);

            Runnable redoBody = new Runnable() { public void run() {
                Runnable raiseBody = new Runnable() { public void run() {
                    body.branch(BaseBodyCompiler.this);
                }};
                Runnable raiseCatch = new Runnable() { public void run() {
                    loadThreadContext();
                    invokeUtilityMethod("unwrapRedoNextBreakOrJustLocalJump", sig(Throwable.class, RaiseException.class, ThreadContext.class));
                    method.athrow();
                }};
                method.trycatch(p(RaiseException.class), raiseBody, raiseCatch);
            }};
            Runnable redoCatch = new Runnable() { public void run() {
                method.pop();
                method.go_to(topOfBody);
            }};
            method.trycatch(p(JumpException.RedoJump.class), redoBody, redoCatch);

            method.label(endOfBody);

            // clear body or next result after each successful loop
            method.pop();

            method.label(conditionCheck);

            // check the condition
            condition.branch(this);
            isTrue();
            method.ifne(topOfBody); // NE == nonzero (i.e. true)

            currentLoopLabels = oldLoopLabels;
        }

        method.label(tryEnd);
        // skip catch block
        method.go_to(normalLoopEnd);

        // catch logic for flow-control: next, break
        {
            // next jump
            {
                method.label(catchNext);
                method.pop();
                // exceptionNext target is for a next that doesn't push a new value, like this one
                method.go_to(conditionCheck);
            }

            // break jump
            {
                method.label(catchBreak);
                loadThreadContext();
                invokeUtilityMethod("breakJumpInWhile", sig(IRubyObject.class, JumpException.BreakJump.class, ThreadContext.class));
                method.go_to(done);
            }
        }

        method.label(normalLoopEnd);
        loadNil();
        method.label(done);
    }

    public void performBooleanLoopLight(BranchCallback condition, BranchCallback body, boolean checkFirst) {
        Label endOfBody = new Label();
        Label conditionCheck = new Label();
        Label topOfBody = new Label();
        Label done = new Label();

        Label[] oldLoopLabels = currentLoopLabels;

        currentLoopLabels = new Label[]{endOfBody, topOfBody, done};

        // FIXME: if we terminate immediately, this appears to break while in method arguments
        // we need to push a nil for the cases where we will never enter the body
        if (checkFirst) {
            method.go_to(conditionCheck);
        }

        method.label(topOfBody);

        body.branch(this);

        method.label(endOfBody);

        // clear body or next result after each successful loop
        method.pop();

        method.label(conditionCheck);

        // check the condition
        condition.branch(this);
        isTrue();
        method.ifne(topOfBody); // NE == nonzero (i.e. true)

        currentLoopLabels = oldLoopLabels;

        loadNil();
        method.label(done);
    }

    public void createNewClosure(
            int line,
            StaticScope scope,
            int arity,
            CompilerCallback body,
            CompilerCallback args,
            boolean hasMultipleArgsHead,
            NodeType argsNodeId,
            ASTInspector inspector) {
        String closureMethodName = "block_" + script.getAndIncrementInnerIndex() + "$RUBY$" + "__block__";

        ChildScopedBodyCompiler closureCompiler = new ChildScopedBodyCompiler(script, closureMethodName, inspector, scope);

        closureCompiler.beginMethod(args, scope);

        body.call(closureCompiler);

        closureCompiler.endBody();

        // Done with closure compilation

        loadThreadContext();
        loadSelf();
        script.getCacheCompiler().cacheClosure(this, closureMethodName, arity, scope, hasMultipleArgsHead, argsNodeId, inspector);

        invokeUtilityMethod("createBlock", sig(Block.class,
                params(ThreadContext.class, IRubyObject.class, BlockBody.class)));
    }

    public void runBeginBlock(StaticScope scope, CompilerCallback body) {
        String closureMethodName = "block_" + script.getAndIncrementInnerIndex() + "$RUBY$__begin__";

        ChildScopedBodyCompiler closureCompiler = new ChildScopedBodyCompiler(script, closureMethodName, null, scope);

        closureCompiler.beginMethod(null, scope);

        body.call(closureCompiler);

        closureCompiler.endBody();

        // Done with closure compilation
        loadThreadContext();
        loadSelf();

        StandardASMCompiler.buildStaticScopeNames(method, scope);

        script.getCacheCompiler().cacheClosureOld(this, closureMethodName);

        invokeUtilityMethod("runBeginBlock", sig(IRubyObject.class,
                params(ThreadContext.class, IRubyObject.class, String[].class, CompiledBlockCallback.class)));
    }

    public void createNewForLoop(int arity, CompilerCallback body, CompilerCallback args, boolean hasMultipleArgsHead, NodeType argsNodeId) {
        String closureMethodName = "block_" + script.getAndIncrementInnerIndex() + "$RUBY$__for__";

        ChildScopedBodyCompiler closureCompiler = new ChildScopedBodyCompiler(script, closureMethodName, null, scope);

        closureCompiler.beginMethod(args, null);

        body.call(closureCompiler);

        closureCompiler.endBody();

        // Done with closure compilation
        loadThreadContext();
        loadSelf();
        method.pushInt(arity);

        script.getCacheCompiler().cacheClosureOld(this, closureMethodName);

        method.ldc(Boolean.valueOf(hasMultipleArgsHead));
        method.ldc(BlockBody.asArgumentType(argsNodeId));

        invokeUtilityMethod("createSharedScopeBlock", sig(Block.class,
                params(ThreadContext.class, IRubyObject.class, Integer.TYPE, CompiledBlockCallback.class, Boolean.TYPE, Integer.TYPE)));
    }

    public void createNewEndBlock(CompilerCallback body) {
        String closureMethodName = "block_" + script.getAndIncrementInnerIndex() + "$RUBY$__end__";

        ChildScopedBodyCompiler closureCompiler = new ChildScopedBodyCompiler(script, closureMethodName, null, scope);

        closureCompiler.beginMethod(null, null);

        body.call(closureCompiler);

        closureCompiler.endBody();

        // Done with closure compilation
        loadThreadContext();
        loadSelf();
        method.iconst_0();

        script.getCacheCompiler().cacheClosureOld(this, closureMethodName);

        method.iconst_0(); // false
        method.iconst_0(); // zero

        invokeUtilityMethod("createSharedScopeBlock", sig(Block.class,
                params(ThreadContext.class, IRubyObject.class, Integer.TYPE, CompiledBlockCallback.class, Boolean.TYPE, Integer.TYPE)));

        loadRuntime();
        invokeUtilityMethod("registerEndBlock", sig(void.class, Block.class, Ruby.class));
        loadNil();
    }

    public void getCompiledClass() {
        method.aload(StandardASMCompiler.THIS);
        method.getfield(script.getClassname(), "$class", ci(Class.class));
    }

    public void println() {
        method.dup();
        method.getstatic(p(System.class), "out", ci(PrintStream.class));
        method.swap();

        method.invokevirtual(p(PrintStream.class), "println", sig(Void.TYPE, params(Object.class)));
    }

    public void defineAlias(String newName, String oldName) {
        loadThreadContext();
        method.ldc(newName);
        method.ldc(oldName);
        invokeUtilityMethod("defineAlias", sig(IRubyObject.class, ThreadContext.class, String.class, String.class));
    }

    public void loadFalse() {
        // TODO: cache?
        loadRuntime();
        invokeIRuby("getFalse", sig(RubyBoolean.class));
    }

    public void loadTrue() {
        // TODO: cache?
        loadRuntime();
        invokeIRuby("getTrue", sig(RubyBoolean.class));
    }

    public void loadCurrentModule() {
        loadThreadContext();
        invokeThreadContext("getCurrentScope", sig(DynamicScope.class));
        method.invokevirtual(p(DynamicScope.class), "getStaticScope", sig(StaticScope.class));
        method.invokevirtual(p(StaticScope.class), "getModule", sig(RubyModule.class));
    }

    public void retrieveInstanceVariable(String name) {
        loadSelf();
        loadRuntime();
        method.ldc(name);
        invokeUtilityMethod("getInstanceVariable", sig(IRubyObject.class, IRubyObject.class, Ruby.class, String.class));
    }

    public void assignInstanceVariable(String name) {
        loadSelf();
        method.ldc(name);
        invokeUtilityMethod("setInstanceVariable", sig(IRubyObject.class, IRubyObject.class, IRubyObject.class, String.class));
    }

    public void assignInstanceVariable(String name, CompilerCallback value) {
        value.call(this);
        loadSelf();
        method.ldc(name);
        invokeUtilityMethod("setInstanceVariable", sig(IRubyObject.class, IRubyObject.class, IRubyObject.class, String.class));
    }

    public void retrieveGlobalVariable(String name) {
        loadRuntime();
        method.ldc(name);
        invokeUtilityMethod("getGlobalVariable", sig(IRubyObject.class, Ruby.class, String.class));
    }

    public void assignGlobalVariable(String name) {
        loadRuntime();
        method.ldc(name);
        invokeUtilityMethod("setGlobalVariable", sig(IRubyObject.class, IRubyObject.class, Ruby.class, String.class));
    }

    public void assignGlobalVariable(String name, CompilerCallback value) {
        value.call(this);
        loadRuntime();
        method.ldc(name);
        invokeUtilityMethod("setGlobalVariable", sig(IRubyObject.class, IRubyObject.class, Ruby.class, String.class));
    }

    public void negateCurrentValue() {
        loadRuntime();
        invokeUtilityMethod("negate", sig(IRubyObject.class, IRubyObject.class, Ruby.class));
    }

    public void splatCurrentValue() {
        method.invokestatic(p(RuntimeHelpers.class), "splatValue", sig(RubyArray.class, params(IRubyObject.class)));
    }

    public void singlifySplattedValue() {
        method.invokestatic(p(RuntimeHelpers.class), "aValueSplat", sig(IRubyObject.class, params(IRubyObject.class)));
    }

    public void aryToAry() {
        method.invokestatic(p(RuntimeHelpers.class), "aryToAry", sig(IRubyObject.class, params(IRubyObject.class)));
    }

    public void ensureRubyArray() {
        invokeUtilityMethod("ensureRubyArray", sig(RubyArray.class, params(IRubyObject.class)));
    }

    public void ensureMultipleAssignableRubyArray(boolean masgnHasHead) {
        loadRuntime();
        method.pushBoolean(masgnHasHead);
        invokeUtilityMethod("ensureMultipleAssignableRubyArray", sig(RubyArray.class, params(IRubyObject.class, Ruby.class, boolean.class)));
    }

    public void forEachInValueArray(int start, int count, Object source, ArrayCallback callback, ArrayCallback nilCallback, CompilerCallback argsCallback) {
        // FIXME: This could probably be made more efficient
        for (; start < count; start++) {
            method.dup(); // dup the original array object
            loadNil();
            method.pushInt(start);
            invokeUtilityMethod("arrayEntryOrNil", sig(IRubyObject.class, RubyArray.class, IRubyObject.class, int.class));
            callback.nextValue(this, source, start);
            method.pop();
        }

        if (argsCallback != null) {
            method.dup(); // dup the original array object
            loadRuntime();
            method.pushInt(start);
            invokeUtilityMethod("subarrayOrEmpty", sig(RubyArray.class, RubyArray.class, Ruby.class, int.class));
            argsCallback.call(this);
            method.pop();
        }
    }

    public void asString() {
        method.invokeinterface(p(IRubyObject.class), "asString", sig(RubyString.class));
    }

    public void toJavaString() {
        method.invokevirtual(p(Object.class), "toString", sig(String.class));
    }

    public void nthRef(int match) {
        method.pushInt(match);
        backref();
        method.invokestatic(p(RubyRegexp.class), "nth_match", sig(IRubyObject.class, params(Integer.TYPE, IRubyObject.class)));
    }

    public void match() {
        loadThreadContext();
        method.invokevirtual(p(RubyRegexp.class), "op_match2", sig(IRubyObject.class, params(ThreadContext.class)));
    }

    public void match2() {
        loadThreadContext();
        method.swap();
        method.invokevirtual(p(RubyRegexp.class), "op_match", sig(IRubyObject.class, params(ThreadContext.class, IRubyObject.class)));
    }

    public void match3() {
        loadThreadContext();
        invokeUtilityMethod("match3", sig(IRubyObject.class, RubyRegexp.class, IRubyObject.class, ThreadContext.class));
    }

    public void createNewRegexp(final ByteList value, final int options) {
        String regexpField = script.getNewConstant(ci(RubyRegexp.class), "lit_reg_");

        // in current method, load the field to see if we've created a Pattern yet
        method.aload(StandardASMCompiler.THIS);
        method.getfield(script.getClassname(), regexpField, ci(RubyRegexp.class));

        Label alreadyCreated = new Label();
        method.ifnonnull(alreadyCreated); //[]

        // load string, for Regexp#source and Regexp#inspect
        String regexpString = value.toString();

        loadRuntime(); //[R]
        method.ldc(regexpString); //[R, rS]
        method.pushInt(options); //[R, rS, opts]

        method.invokestatic(p(RubyRegexp.class), "newRegexp", sig(RubyRegexp.class, params(Ruby.class, String.class, Integer.TYPE))); //[reg]

        method.aload(StandardASMCompiler.THIS); //[reg, T]
        method.swap(); //[T, reg]
        method.putfield(script.getClassname(), regexpField, ci(RubyRegexp.class)); //[]
        method.label(alreadyCreated);
        method.aload(StandardASMCompiler.THIS); //[T]
        method.getfield(script.getClassname(), regexpField, ci(RubyRegexp.class));
    }

    public void createNewRegexp(CompilerCallback createStringCallback, final int options) {
        boolean onceOnly = (options & ReOptions.RE_OPTION_ONCE) != 0;   // for regular expressions with the /o flag
        Label alreadyCreated = null;
        String regexpField = null;

        // only alter the code if the /o flag was present
        if (onceOnly) {
            regexpField = script.getNewConstant(ci(RubyRegexp.class), "lit_reg_");

            // in current method, load the field to see if we've created a Pattern yet
            method.aload(StandardASMCompiler.THIS);
            method.getfield(script.getClassname(), regexpField, ci(RubyRegexp.class));

            alreadyCreated = new Label();
            method.ifnonnull(alreadyCreated);
        }

        loadRuntime();

        createStringCallback.call(this);
        method.invokevirtual(p(RubyString.class), "getByteList", sig(ByteList.class));
        method.pushInt(options);

        method.invokestatic(p(RubyRegexp.class), "newRegexp", sig(RubyRegexp.class, params(Ruby.class, ByteList.class, Integer.TYPE))); //[reg]

        // only alter the code if the /o flag was present
        if (onceOnly) {
            method.aload(StandardASMCompiler.THIS);
            method.swap();
            method.putfield(script.getClassname(), regexpField, ci(RubyRegexp.class));
            method.label(alreadyCreated);
            method.aload(StandardASMCompiler.THIS);
            method.getfield(script.getClassname(), regexpField, ci(RubyRegexp.class));
        }
    }

    public void pollThreadEvents() {
        if (!RubyInstanceConfig.THREADLESS_COMPILE_ENABLED) {
            loadThreadContext();
            invokeThreadContext("pollThreadEvents", sig(Void.TYPE));
        }
    }

    public void nullToNil() {
        Label notNull = new Label();
        method.dup();
        method.ifnonnull(notNull);
        method.pop();
        loadNil();
        method.label(notNull);
    }

    public void isInstanceOf(Class clazz, BranchCallback trueBranch, BranchCallback falseBranch) {
        method.instance_of(p(clazz));

        Label falseJmp = new Label();
        Label afterJmp = new Label();

        method.ifeq(falseJmp); // EQ == 0 (i.e. false)
        trueBranch.branch(this);

        method.go_to(afterJmp);
        method.label(falseJmp);

        falseBranch.branch(this);

        method.label(afterJmp);
    }

    public void isCaptured(final int number, final BranchCallback trueBranch, final BranchCallback falseBranch) {
        backref();
        method.dup();
        isInstanceOf(RubyMatchData.class, new BranchCallback() {

            public void branch(BodyCompiler context) {
                method.visitTypeInsn(CHECKCAST, p(RubyMatchData.class));
                method.dup();
                method.invokevirtual(p(RubyMatchData.class), "use", sig(void.class));
                method.pushInt(number);
                method.invokevirtual(p(RubyMatchData.class), "group", sig(IRubyObject.class, params(int.class)));
                method.invokeinterface(p(IRubyObject.class), "isNil", sig(boolean.class));
                Label isNil = new Label();
                Label after = new Label();

                method.ifne(isNil);
                trueBranch.branch(context);
                method.go_to(after);

                method.label(isNil);
                falseBranch.branch(context);
                method.label(after);
            }
        }, new BranchCallback() {

            public void branch(BodyCompiler context) {
                method.pop();
                falseBranch.branch(context);
            }
        });
    }

    public void branchIfModule(CompilerCallback receiverCallback, BranchCallback moduleCallback, BranchCallback notModuleCallback, boolean mustBeModule) {
        receiverCallback.call(this);
        invokeUtilityMethod("checkIsModule", sig(RubyModule.class, IRubyObject.class));
        isInstanceOf(RubyModule.class, moduleCallback, notModuleCallback);
    }

    public void backref() {
        loadThreadContext();
        invokeThreadContext("getCurrentFrame", sig(Frame.class));
        method.invokevirtual(p(Frame.class), "getBackRef", sig(IRubyObject.class));
    }

    public void backrefMethod(String methodName) {
        backref();
        method.invokestatic(p(RubyRegexp.class), methodName, sig(IRubyObject.class, params(IRubyObject.class)));
    }

    public void issueLoopBreak() {
        // inside a loop, break out of it
        // go to end of loop, leaving break value on stack
        method.go_to(currentLoopLabels[2]);
    }

    public void issueLoopNext() {
        // inside a loop, jump to conditional
        method.go_to(currentLoopLabels[0]);
    }

    public void issueLoopRedo() {
        // inside a loop, jump to body
        method.go_to(currentLoopLabels[1]);
    }

    protected String getNewEnsureName() {
        return "ensure_" + (script.getAndIncrementEnsureNumber()) + "$RUBY$__ensure__";
    }

    public void protect(BranchCallback regularCode, BranchCallback protectedCode, Class ret) {
        String mname = getNewEnsureName();
        SkinnyMethodAdapter mv = new SkinnyMethodAdapter(script.getClassVisitor().visitMethod(ACC_PUBLIC | ACC_SYNTHETIC, mname, sig(ret, new Class[]{ThreadContext.class, IRubyObject.class, Block.class}), null, null));
        SkinnyMethodAdapter old_method = null;
        SkinnyMethodAdapter var_old_method = null;
        SkinnyMethodAdapter inv_old_method = null;
        boolean oldInNestedMethod = inNestedMethod;
        inNestedMethod = true;
        Label[] oldLoopLabels = currentLoopLabels;
        currentLoopLabels = null;
        int oldArgCount = argParamCount;
        argParamCount = 0; // synthetic methods always have zero arg parameters
        try {
            old_method = this.method;
            var_old_method = getVariableCompiler().getMethodAdapter();
            inv_old_method = getInvocationCompiler().getMethodAdapter();
            this.method = mv;
            getVariableCompiler().setMethodAdapter(mv);
            getInvocationCompiler().setMethodAdapter(mv);

            mv.visitCode();
            // set up a local IRuby variable

            mv.aload(StandardASMCompiler.THREADCONTEXT_INDEX);
            mv.dup();
            mv.invokevirtual(p(ThreadContext.class), "getRuntime", sig(Ruby.class));
            mv.dup();
            mv.astore(getRuntimeIndex());

            // grab nil for local variables
            mv.invokevirtual(p(Ruby.class), "getNil", sig(IRubyObject.class));
            mv.astore(getNilIndex());
            mv.invokevirtual(p(ThreadContext.class), "getCurrentScope", sig(DynamicScope.class));
            mv.dup();
            mv.astore(getDynamicScopeIndex());
            mv.invokevirtual(p(DynamicScope.class), "getValues", sig(IRubyObject[].class));
            mv.astore(getVarsArrayIndex());
            Label codeBegin = new Label();
            Label codeEnd = new Label();
            Label ensureBegin = new Label();
            Label ensureEnd = new Label();
            method.label(codeBegin);

            regularCode.branch(this);

            method.label(codeEnd);

            protectedCode.branch(this);
            mv.areturn();

            method.label(ensureBegin);
            method.astore(getExceptionIndex());
            method.label(ensureEnd);
            protectedCode.branch(this);

            method.aload(getExceptionIndex());
            method.athrow();

            method.trycatch(codeBegin, codeEnd, ensureBegin, null);
            method.trycatch(ensureBegin, ensureEnd, ensureBegin, null);
            mv.visitMaxs(1, 1);
            mv.visitEnd();
        } finally {
            this.method = old_method;
            getVariableCompiler().setMethodAdapter(var_old_method);
            getInvocationCompiler().setMethodAdapter(inv_old_method);
            inNestedMethod = oldInNestedMethod;
            currentLoopLabels = oldLoopLabels;
            argParamCount = oldArgCount;
        }

        method.aload(StandardASMCompiler.THIS);
        loadThreadContext();
        loadSelf();
        if (this instanceof ChildScopedBodyCompiler) {
            pushNull();
        } else {
            loadBlock();
        }
        method.invokevirtual(script.getClassname(), mname, sig(ret, new Class[]{ThreadContext.class, IRubyObject.class, Block.class}));
    }

    public void performEnsure(BranchCallback regularCode, BranchCallback protectedCode) {
        String mname = getNewEnsureName();
        BaseBodyCompiler ensure = outline(mname);
        ensure.performEnsureInner(regularCode, protectedCode);
    }

    private void performEnsureInner(BranchCallback regularCode, BranchCallback protectedCode) {
        Label codeBegin = new Label();
        Label codeEnd = new Label();
        Label ensureBegin = new Label();
        Label ensureEnd = new Label();
        method.label(codeBegin);

        regularCode.branch(this);

        method.label(codeEnd);

        protectedCode.branch(this);
        method.areturn();

        method.label(ensureBegin);
        method.astore(getExceptionIndex());
        method.label(ensureEnd);

        protectedCode.branch(this);

        method.aload(getExceptionIndex());
        method.athrow();

        method.trycatch(codeBegin, codeEnd, ensureBegin, null);
        method.trycatch(ensureBegin, ensureEnd, ensureBegin, null);

        loadNil();
        endBody();
    }

    protected String getNewRescueName() {
        return "rescue_" + (script.getAndIncrementRescueNumber()) + "$RUBY$__rescue__";
    }

    public void storeExceptionInErrorInfo() {
        loadException();
        loadThreadContext();
        invokeUtilityMethod("storeExceptionInErrorInfo", sig(void.class, Exception.class, ThreadContext.class));
    }

    public void clearErrorInfo() {
        loadThreadContext();
        invokeUtilityMethod("clearErrorInfo", sig(void.class, ThreadContext.class));
    }

    public void rescue(BranchCallback regularCode, Class exception, BranchCallback catchCode, Class ret) {
        String mname = getNewRescueName();
        SkinnyMethodAdapter mv = new SkinnyMethodAdapter(script.getClassVisitor().visitMethod(ACC_PUBLIC | ACC_SYNTHETIC, mname, sig(ret, new Class[]{ThreadContext.class, IRubyObject.class, Block.class}), null, null));
        SkinnyMethodAdapter old_method = null;
        SkinnyMethodAdapter var_old_method = null;
        SkinnyMethodAdapter inv_old_method = null;
        Label afterMethodBody = new Label();
        Label catchRetry = new Label();
        Label catchRaised = new Label();
        Label catchJumps = new Label();
        Label exitRescue = new Label();
        boolean oldWithinProtection = inNestedMethod;
        inNestedMethod = true;
        Label[] oldLoopLabels = currentLoopLabels;
        currentLoopLabels = null;
        int oldArgCount = argParamCount;
        argParamCount = 0; // synthetic methods always have zero arg parameters
        try {
            old_method = this.method;
            var_old_method = getVariableCompiler().getMethodAdapter();
            inv_old_method = getInvocationCompiler().getMethodAdapter();
            this.method = mv;
            getVariableCompiler().setMethodAdapter(mv);
            getInvocationCompiler().setMethodAdapter(mv);

            mv.start();

            // set up a local IRuby variable
            mv.aload(StandardASMCompiler.THREADCONTEXT_INDEX);
            mv.dup();
            mv.invokevirtual(p(ThreadContext.class), "getRuntime", sig(Ruby.class));
            mv.dup();
            mv.astore(getRuntimeIndex());

            // store previous exception for restoration if we rescue something
            loadRuntime();
            invokeUtilityMethod("getErrorInfo", sig(IRubyObject.class, Ruby.class));
            mv.astore(getPreviousExceptionIndex());

            // grab nil for local variables
            mv.invokevirtual(p(Ruby.class), "getNil", sig(IRubyObject.class));
            mv.astore(getNilIndex());

            mv.invokevirtual(p(ThreadContext.class), "getCurrentScope", sig(DynamicScope.class));
            mv.dup();
            mv.astore(getDynamicScopeIndex());
            mv.invokevirtual(p(DynamicScope.class), "getValues", sig(IRubyObject[].class));
            mv.astore(getVarsArrayIndex());

            Label beforeBody = new Label();
            Label afterBody = new Label();
            Label catchBlock = new Label();
            mv.trycatch(beforeBody, afterBody, catchBlock, p(exception));
            mv.label(beforeBody);

            regularCode.branch(this);

            mv.label(afterBody);
            mv.go_to(exitRescue);
            mv.label(catchBlock);
            mv.astore(getExceptionIndex());

            catchCode.branch(this);

            mv.label(afterMethodBody);
            mv.go_to(exitRescue);

            // retry handling in the rescue block
            mv.trycatch(catchBlock, afterMethodBody, catchRetry, p(JumpException.RetryJump.class));
            mv.label(catchRetry);
            mv.pop();
            mv.go_to(beforeBody);

            // any exceptions raised must continue to be raised, skipping $! restoration
            mv.trycatch(beforeBody, afterMethodBody, catchRaised, p(RaiseException.class));
            mv.label(catchRaised);
            mv.athrow();

            // and remaining jump exceptions should restore $!
            mv.trycatch(beforeBody, afterMethodBody, catchJumps, p(JumpException.class));
            mv.label(catchJumps);
            loadRuntime();
            mv.aload(getPreviousExceptionIndex());
            invokeUtilityMethod("setErrorInfo", sig(void.class, Ruby.class, IRubyObject.class));
            mv.athrow();

            mv.label(exitRescue);

            // restore the original exception
            loadRuntime();
            mv.aload(getPreviousExceptionIndex());
            invokeUtilityMethod("setErrorInfo", sig(void.class, Ruby.class, IRubyObject.class));

            mv.areturn();
            mv.end();
        } finally {
            inNestedMethod = oldWithinProtection;
            this.method = old_method;
            getVariableCompiler().setMethodAdapter(var_old_method);
            getInvocationCompiler().setMethodAdapter(inv_old_method);
            currentLoopLabels = oldLoopLabels;
            argParamCount = oldArgCount;
        }

        method.aload(StandardASMCompiler.THIS);
        loadThreadContext();
        loadSelf();
        if (this instanceof ChildScopedBodyCompiler) {
            pushNull();
        } else {
            loadBlock();
        }
        method.invokevirtual(script.getClassname(), mname, sig(ret, new Class[]{ThreadContext.class, IRubyObject.class, Block.class}));
    }

    public void performRescue(BranchCallback regularCode, BranchCallback rubyCatchCode, BranchCallback javaCatchCode) {
        String mname = getNewRescueName();
        BaseBodyCompiler rescueMethod = outline(mname);
        rescueMethod.performRescueInner(regularCode, rubyCatchCode, javaCatchCode);
    }

    public void performRescueInner(BranchCallback regularCode, BranchCallback rubyCatchCode, BranchCallback javaCatchCode) {
        Label afterRubyCatchBody = new Label();
        Label catchRetry = new Label();
        Label catchJumps = new Label();
        Label exitRescue = new Label();

        // store previous exception for restoration if we rescue something
        loadRuntime();
        invokeUtilityMethod("getErrorInfo", sig(IRubyObject.class, Ruby.class));
        method.astore(getPreviousExceptionIndex());

        Label beforeBody = new Label();
        Label afterBody = new Label();
        Label rubyCatchBlock = new Label();
        Label flowCatchBlock = new Label();
        method.visitTryCatchBlock(beforeBody, afterBody, flowCatchBlock, p(JumpException.FlowControlException.class));
        method.visitTryCatchBlock(beforeBody, afterBody, rubyCatchBlock, p(Exception.class));

        method.visitLabel(beforeBody);
        {
            regularCode.branch(this);
        }
        method.label(afterBody);
        method.go_to(exitRescue);

        // Handle Flow exceptions, just propagating them
        method.label(flowCatchBlock);
        {
            // restore the original exception
            loadRuntime();
            method.aload(getPreviousExceptionIndex());
            invokeUtilityMethod("setErrorInfo", sig(void.class, Ruby.class, IRubyObject.class));

            // rethrow
            method.athrow();
        }

        // Handle Ruby exceptions (RaiseException)
        method.label(rubyCatchBlock);
        {
            method.astore(getExceptionIndex());

            rubyCatchCode.branch(this);
            method.label(afterRubyCatchBody);
            method.go_to(exitRescue);
        }

        // retry handling in the rescue blocks
        method.trycatch(rubyCatchBlock, afterRubyCatchBody, catchRetry, p(JumpException.RetryJump.class));
        method.label(catchRetry);
        {
            method.pop();
        }
        method.go_to(beforeBody);

        // and remaining jump exceptions should restore $!
        method.trycatch(rubyCatchBlock, afterRubyCatchBody, catchJumps, p(JumpException.FlowControlException.class));
        method.label(catchJumps);
        {
            loadRuntime();
            method.aload(getPreviousExceptionIndex());
            invokeUtilityMethod("setErrorInfo", sig(void.class, Ruby.class, IRubyObject.class));
            method.athrow();
        }

        method.label(exitRescue);

        // restore the original exception
        loadRuntime();
        method.aload(getPreviousExceptionIndex());
        invokeUtilityMethod("setErrorInfo", sig(void.class, Ruby.class, IRubyObject.class));

        endBody();
    }

    public void wrapJavaException() {
        loadRuntime();
        loadException();
        wrapJavaObject();
    }

    public void wrapJavaObject() {
        method.invokestatic(p(JavaUtil.class), "convertJavaToUsableRubyObject", sig(IRubyObject.class, Ruby.class, Object.class));
    }

    public void inDefined() {
        method.aload(StandardASMCompiler.THREADCONTEXT_INDEX);
        method.iconst_1();
        invokeThreadContext("setWithinDefined", sig(void.class, params(boolean.class)));
    }

    public void outDefined() {
        method.aload(StandardASMCompiler.THREADCONTEXT_INDEX);
        method.iconst_0();
        invokeThreadContext("setWithinDefined", sig(void.class, params(boolean.class)));
    }

    public void stringOrNil() {
        loadRuntime();
        loadNil();
        invokeUtilityMethod("stringOrNil", sig(IRubyObject.class, String.class, Ruby.class, IRubyObject.class));
    }

    public void pushNull() {
        method.aconst_null();
    }

    public void pushString(String str) {
        method.ldc(str);
    }

    public void isMethodBound(String name, BranchCallback trueBranch, BranchCallback falseBranch) {
        metaclass();
        method.ldc(name);
        method.iconst_0(); // push false
        method.invokevirtual(p(RubyClass.class), "isMethodBound", sig(boolean.class, params(String.class, boolean.class)));
        Label falseLabel = new Label();
        Label exitLabel = new Label();
        method.ifeq(falseLabel); // EQ == 0 (i.e. false)
        trueBranch.branch(this);
        method.go_to(exitLabel);
        method.label(falseLabel);
        falseBranch.branch(this);
        method.label(exitLabel);
    }

    public void hasBlock(BranchCallback trueBranch, BranchCallback falseBranch) {
        loadBlock();
        method.invokevirtual(p(Block.class), "isGiven", sig(boolean.class));
        Label falseLabel = new Label();
        Label exitLabel = new Label();
        method.ifeq(falseLabel); // EQ == 0 (i.e. false)
        trueBranch.branch(this);
        method.go_to(exitLabel);
        method.label(falseLabel);
        falseBranch.branch(this);
        method.label(exitLabel);
    }

    public void isGlobalDefined(String name, BranchCallback trueBranch, BranchCallback falseBranch) {
        loadRuntime();
        invokeIRuby("getGlobalVariables", sig(GlobalVariables.class));
        method.ldc(name);
        method.invokevirtual(p(GlobalVariables.class), "isDefined", sig(boolean.class, params(String.class)));
        Label falseLabel = new Label();
        Label exitLabel = new Label();
        method.ifeq(falseLabel); // EQ == 0 (i.e. false)
        trueBranch.branch(this);
        method.go_to(exitLabel);
        method.label(falseLabel);
        falseBranch.branch(this);
        method.label(exitLabel);
    }

    public void isConstantDefined(String name, BranchCallback trueBranch, BranchCallback falseBranch) {
        loadThreadContext();
        method.ldc(name);
        invokeThreadContext("getConstantDefined", sig(boolean.class, params(String.class)));
        Label falseLabel = new Label();
        Label exitLabel = new Label();
        method.ifeq(falseLabel); // EQ == 0 (i.e. false)
        trueBranch.branch(this);
        method.go_to(exitLabel);
        method.label(falseLabel);
        falseBranch.branch(this);
        method.label(exitLabel);
    }

    public void isInstanceVariableDefined(String name, BranchCallback trueBranch, BranchCallback falseBranch) {
        loadSelf();
        invokeIRubyObject("getInstanceVariables", sig(InstanceVariables.class));
        method.ldc(name);
        //method.invokeinterface(p(IRubyObject.class), "getInstanceVariable", sig(IRubyObject.class, params(String.class)));
        method.invokeinterface(p(InstanceVariables.class), "fastHasInstanceVariable", sig(boolean.class, params(String.class)));
        Label trueLabel = new Label();
        Label exitLabel = new Label();
        //method.ifnonnull(trueLabel);
        method.ifne(trueLabel);
        falseBranch.branch(this);
        method.go_to(exitLabel);
        method.label(trueLabel);
        trueBranch.branch(this);
        method.label(exitLabel);
    }

    public void isClassVarDefined(String name, BranchCallback trueBranch, BranchCallback falseBranch) {
        method.ldc(name);
        method.invokevirtual(p(RubyModule.class), "fastIsClassVarDefined", sig(boolean.class, params(String.class)));
        Label trueLabel = new Label();
        Label exitLabel = new Label();
        method.ifne(trueLabel);
        falseBranch.branch(this);
        method.go_to(exitLabel);
        method.label(trueLabel);
        trueBranch.branch(this);
        method.label(exitLabel);
    }

    public Object getNewEnding() {
        return new Label();
    }

    public void isNil(BranchCallback trueBranch, BranchCallback falseBranch) {
        method.invokeinterface(p(IRubyObject.class), "isNil", sig(boolean.class));
        Label falseLabel = new Label();
        Label exitLabel = new Label();
        method.ifeq(falseLabel); // EQ == 0 (i.e. false)
        trueBranch.branch(this);
        method.go_to(exitLabel);
        method.label(falseLabel);
        falseBranch.branch(this);
        method.label(exitLabel);
    }

    public void isNull(BranchCallback trueBranch, BranchCallback falseBranch) {
        Label falseLabel = new Label();
        Label exitLabel = new Label();
        method.ifnonnull(falseLabel);
        trueBranch.branch(this);
        method.go_to(exitLabel);
        method.label(falseLabel);
        falseBranch.branch(this);
        method.label(exitLabel);
    }

    public void ifNull(Object gotoToken) {
        method.ifnull((Label) gotoToken);
    }

    public void ifNotNull(Object gotoToken) {
        method.ifnonnull((Label) gotoToken);
    }

    public void setEnding(Object endingToken) {
        method.label((Label) endingToken);
    }

    public void go(Object gotoToken) {
        method.go_to((Label) gotoToken);
    }

    public void isConstantBranch(final BranchCallback setup, final BranchCallback isConstant, final BranchCallback isMethod, final BranchCallback none, final String name) {
        rescue(new BranchCallback() {

            public void branch(BodyCompiler context) {
                setup.branch(BaseBodyCompiler.this);
                method.dup(); //[C,C]
                method.instance_of(p(RubyModule.class)); //[C, boolean]

                Label falseJmp = new Label();
                Label afterJmp = new Label();
                Label nextJmp = new Label();
                Label nextJmpPop = new Label();

                method.ifeq(nextJmp); // EQ == 0 (i.e. false)   //[C]
                method.visitTypeInsn(CHECKCAST, p(RubyModule.class));
                method.dup(); //[C, C]
                method.ldc(name); //[C, C, String]
                method.invokevirtual(p(RubyModule.class), "fastGetConstantAt", sig(IRubyObject.class, params(String.class))); //[C, null|C]
                method.dup();
                method.ifnull(nextJmpPop);
                method.pop();
                method.pop();

                isConstant.branch(BaseBodyCompiler.this);

                method.go_to(afterJmp);

                method.label(nextJmpPop);
                method.pop();

                method.label(nextJmp); //[C]

                metaclass();
                method.ldc(name);
                method.iconst_1(); // push true
                method.invokevirtual(p(RubyClass.class), "isMethodBound", sig(boolean.class, params(String.class, boolean.class)));
                method.ifeq(falseJmp); // EQ == 0 (i.e. false)

                isMethod.branch(BaseBodyCompiler.this);
                method.go_to(afterJmp);

                method.label(falseJmp);
                none.branch(BaseBodyCompiler.this);

                method.label(afterJmp);
            }
        }, JumpException.class, none, String.class);
    }

    public void metaclass() {
        invokeIRubyObject("getMetaClass", sig(RubyClass.class));
    }

    public void aprintln() {
        method.aprintln();
    }

    public void getVisibilityFor(String name) {
        method.ldc(name);
        method.invokevirtual(p(RubyClass.class), "searchMethod", sig(DynamicMethod.class, params(String.class)));
        method.invokevirtual(p(DynamicMethod.class), "getVisibility", sig(Visibility.class));
    }

    public void isPrivate(Object gotoToken, int toConsume) {
        method.getstatic(p(Visibility.class), "PRIVATE", ci(Visibility.class));
        Label temp = new Label();
        method.if_acmpne(temp);
        while ((toConsume--) > 0) {
            method.pop();
        }
        method.go_to((Label) gotoToken);
        method.label(temp);
    }

    public void isNotProtected(Object gotoToken, int toConsume) {
        method.getstatic(p(Visibility.class), "PROTECTED", ci(Visibility.class));
        Label temp = new Label();
        method.if_acmpeq(temp);
        while ((toConsume--) > 0) {
            method.pop();
        }
        method.go_to((Label) gotoToken);
        method.label(temp);
    }

    public void selfIsKindOf(Object gotoToken) {
        method.invokevirtual(p(RubyClass.class), "getRealClass", sig(RubyClass.class));
        loadSelf();
        method.invokevirtual(p(RubyModule.class), "isInstance", sig(boolean.class, params(IRubyObject.class)));
        method.ifne((Label) gotoToken); // EQ != 0 (i.e. true)
    }

    public void notIsModuleAndClassVarDefined(String name, Object gotoToken) {
        method.dup(); //[?, ?]
        method.instance_of(p(RubyModule.class)); //[?, boolean]
        Label falsePopJmp = new Label();
        Label successJmp = new Label();
        method.ifeq(falsePopJmp);

        method.visitTypeInsn(CHECKCAST, p(RubyModule.class)); //[RubyModule]
        method.ldc(name); //[RubyModule, String]

        method.invokevirtual(p(RubyModule.class), "fastIsClassVarDefined", sig(boolean.class, params(String.class))); //[boolean]
        method.ifeq((Label) gotoToken);
        method.go_to(successJmp);
        method.label(falsePopJmp);
        method.pop();
        method.go_to((Label) gotoToken);
        method.label(successJmp);
    }

    public void ifSingleton(Object gotoToken) {
        method.invokevirtual(p(RubyModule.class), "isSingleton", sig(boolean.class));
        method.ifne((Label) gotoToken); // EQ == 0 (i.e. false)
    }

    public void getInstanceVariable(String name) {
        method.ldc(name);
        invokeIRubyObject("getInstanceVariables", sig(InstanceVariables.class));
        method.invokeinterface(p(InstanceVariables.class), "fastGetInstanceVariable", sig(IRubyObject.class, params(String.class)));
    }

    public void getFrameName() {
        loadThreadContext();
        invokeThreadContext("getFrameName", sig(String.class));
    }

    public void getFrameKlazz() {
        loadThreadContext();
        invokeThreadContext("getFrameKlazz", sig(RubyModule.class));
    }

    public void superClass() {
        method.invokevirtual(p(RubyModule.class), "getSuperClass", sig(RubyClass.class));
    }

    public void attached() {
        method.visitTypeInsn(CHECKCAST, p(MetaClass.class));
        method.invokevirtual(p(MetaClass.class), "getAttached", sig(IRubyObject.class));
    }

    public void ifNotSuperMethodBound(Object token) {
        method.swap();
        method.iconst_0();
        method.invokevirtual(p(RubyModule.class), "isMethodBound", sig(boolean.class, params(String.class, boolean.class)));
        method.ifeq((Label) token);
    }

    public void concatArrays() {
        method.invokevirtual(p(RubyArray.class), "concat", sig(RubyArray.class, params(IRubyObject.class)));
    }

    public void concatObjectArrays() {
        invokeUtilityMethod("concatObjectArrays", sig(IRubyObject[].class, params(IRubyObject[].class, IRubyObject[].class)));
    }

    public void appendToArray() {
        method.invokevirtual(p(RubyArray.class), "append", sig(RubyArray.class, params(IRubyObject.class)));
    }

    public void appendToObjectArray() {
        invokeUtilityMethod("appendToObjectArray", sig(IRubyObject[].class, params(IRubyObject[].class, IRubyObject.class)));
    }

    public void convertToJavaArray() {
        method.invokestatic(p(ArgsUtil.class), "convertToJavaArray", sig(IRubyObject[].class, params(IRubyObject.class)));
    }

    public void aliasGlobal(String newName, String oldName) {
        loadRuntime();
        invokeIRuby("getGlobalVariables", sig(GlobalVariables.class));
        method.ldc(newName);
        method.ldc(oldName);
        method.invokevirtual(p(GlobalVariables.class), "alias", sig(Void.TYPE, params(String.class, String.class)));
        loadNil();
    }

    public void undefMethod(String name) {
        loadThreadContext();
        invokeThreadContext("getRubyClass", sig(RubyModule.class));

        Label notNull = new Label();
        method.dup();
        method.ifnonnull(notNull);
        method.pop();
        loadRuntime();
        method.ldc("No class to undef method '" + name + "'.");
        invokeIRuby("newTypeError", sig(RaiseException.class, params(String.class)));
        method.athrow();

        method.label(notNull);
        loadThreadContext();
        method.ldc(name);
        method.invokevirtual(p(RubyModule.class), "undef", sig(Void.TYPE, params(ThreadContext.class, String.class)));

        loadNil();
    }

    public void defineClass(
            final String name,
            final StaticScope staticScope,
            final CompilerCallback superCallback,
            final CompilerCallback pathCallback,
            final CompilerCallback bodyCallback,
            final CompilerCallback receiverCallback) {
        String classMethodName = null;
        if (receiverCallback == null) {
            String mangledName = JavaNameMangler.mangleStringForCleanJavaIdentifier(name);
            classMethodName = "class_" + script.getAndIncrementMethodIndex() + "$RUBY$" + mangledName;
        } else {
            classMethodName = "sclass_" + script.getAndIncrementMethodIndex() + "$RUBY$__singleton__";
        }

        final RootScopedBodyCompiler classBody = new ASMClassBodyCompiler(script, classMethodName, null, staticScope);

        CompilerCallback bodyPrep = new CompilerCallback() {

            public void call(BodyCompiler context) {
                if (receiverCallback == null) {
                    if (superCallback != null) {
                        classBody.loadRuntime();
                        superCallback.call(classBody);

                        classBody.invokeUtilityMethod("prepareSuperClass", sig(RubyClass.class, params(Ruby.class, IRubyObject.class)));
                    } else {
                        classBody.method.aconst_null();
                    }

                    classBody.loadThreadContext();

                    pathCallback.call(classBody);

                    classBody.invokeUtilityMethod("prepareClassNamespace", sig(RubyModule.class, params(ThreadContext.class, IRubyObject.class)));

                    classBody.method.swap();

                    classBody.method.ldc(name);

                    classBody.method.swap();

                    classBody.method.invokevirtual(p(RubyModule.class), "defineOrGetClassUnder", sig(RubyClass.class, params(String.class, RubyClass.class)));
                } else {
                    classBody.loadRuntime();

                    // we re-set self to the class, but store the old self in a temporary local variable
                    // this is to prevent it GCing in case the singleton is short-lived
                    classBody.method.aload(StandardASMCompiler.SELF_INDEX);
                    int selfTemp = classBody.getVariableCompiler().grabTempLocal();
                    classBody.getVariableCompiler().setTempLocal(selfTemp);
                    classBody.method.aload(StandardASMCompiler.SELF_INDEX);

                    classBody.invokeUtilityMethod("getSingletonClass", sig(RubyClass.class, params(Ruby.class, IRubyObject.class)));
                }

                // set self to the class
                classBody.method.dup();
                classBody.method.astore(StandardASMCompiler.SELF_INDEX);

                // CLASS BODY
                classBody.loadThreadContext();
                classBody.method.swap();

                // static scope
                StandardASMCompiler.buildStaticScopeNames(classBody.method, staticScope);
                classBody.invokeThreadContext("preCompiledClass", sig(Void.TYPE, params(RubyModule.class, String[].class)));
            }
        };

        // Here starts the logic for the class definition
        Label start = new Label();
        Label end = new Label();
        Label after = new Label();
        Label noException = new Label();
        classBody.method.trycatch(start, end, after, null);

        classBody.beginMethod(bodyPrep, staticScope);

        classBody.method.label(start);

        bodyCallback.call(classBody);
        classBody.method.label(end);
        // finally with no exception
        classBody.loadThreadContext();
        classBody.invokeThreadContext("postCompiledClass", sig(Void.TYPE, params()));

        classBody.method.go_to(noException);

        classBody.method.label(after);
        // finally with exception
        classBody.loadThreadContext();
        classBody.invokeThreadContext("postCompiledClass", sig(Void.TYPE, params()));
        classBody.method.athrow();

        classBody.method.label(noException);

        classBody.endBody();

        // prepare to call class definition method
        method.aload(StandardASMCompiler.THIS);
        loadThreadContext();
        if (receiverCallback == null) {
            // if there's no receiver, there could potentially be a superclass like class Foo << self
            // so we pass in self here
            method.aload(StandardASMCompiler.SELF_INDEX);
        } else {
            // otherwise, there's a receiver, so we pass that in directly for the sclass logic
            receiverCallback.call(this);
        }
        method.getstatic(p(Block.class), "NULL_BLOCK", ci(Block.class));

        method.invokevirtual(script.getClassname(), classMethodName, StandardASMCompiler.METHOD_SIGNATURES[0]);
    }

    public void defineModule(final String name, final StaticScope staticScope, final CompilerCallback pathCallback, final CompilerCallback bodyCallback) {
        String mangledName = JavaNameMangler.mangleStringForCleanJavaIdentifier(name);
        String moduleMethodName = "module__" + script.getAndIncrementMethodIndex() + "$RUBY$" + mangledName;

        final RootScopedBodyCompiler classBody = new ASMClassBodyCompiler(script, moduleMethodName, null, staticScope);

        CompilerCallback bodyPrep = new CompilerCallback() {

            public void call(BodyCompiler context) {
                classBody.loadThreadContext();

                pathCallback.call(classBody);

                classBody.invokeUtilityMethod("prepareClassNamespace", sig(RubyModule.class, params(ThreadContext.class, IRubyObject.class)));

                classBody.method.ldc(name);

                classBody.method.invokevirtual(p(RubyModule.class), "defineOrGetModuleUnder", sig(RubyModule.class, params(String.class)));

                // set self to the class
                classBody.method.dup();
                classBody.method.astore(StandardASMCompiler.SELF_INDEX);

                // CLASS BODY
                classBody.loadThreadContext();
                classBody.method.swap();

                // static scope
                StandardASMCompiler.buildStaticScopeNames(classBody.method, staticScope);

                classBody.invokeThreadContext("preCompiledClass", sig(Void.TYPE, params(RubyModule.class, String[].class)));
            }
        };

        // Here starts the logic for the class definition
        Label start = new Label();
        Label end = new Label();
        Label after = new Label();
        Label noException = new Label();
        classBody.method.trycatch(start, end, after, null);

        classBody.beginMethod(bodyPrep, staticScope);

        classBody.method.label(start);

        bodyCallback.call(classBody);
        classBody.method.label(end);

        classBody.method.go_to(noException);

        classBody.method.label(after);
        classBody.loadThreadContext();
        classBody.invokeThreadContext("postCompiledClass", sig(Void.TYPE, params()));
        classBody.method.athrow();

        classBody.method.label(noException);
        classBody.loadThreadContext();
        classBody.invokeThreadContext("postCompiledClass", sig(Void.TYPE, params()));

        classBody.endBody();

        // prepare to call class definition method
        method.aload(StandardASMCompiler.THIS);
        loadThreadContext();
        loadSelf();
        method.getstatic(p(IRubyObject.class), "NULL_ARRAY", ci(IRubyObject[].class));
        method.getstatic(p(Block.class), "NULL_BLOCK", ci(Block.class));

        method.invokevirtual(script.getClassname(), moduleMethodName, StandardASMCompiler.METHOD_SIGNATURES[4]);
    }

    public void unwrapPassedBlock() {
        loadBlock();
        invokeUtilityMethod("getBlockFromBlockPassBody", sig(Block.class, params(IRubyObject.class, Block.class)));
    }

    public void performBackref(char type) {
        loadThreadContext();
        switch (type) {
        case '~':
            invokeUtilityMethod("backref", sig(IRubyObject.class, params(ThreadContext.class)));
            break;
        case '&':
            invokeUtilityMethod("backrefLastMatch", sig(IRubyObject.class, params(ThreadContext.class)));
            break;
        case '`':
            invokeUtilityMethod("backrefMatchPre", sig(IRubyObject.class, params(ThreadContext.class)));
            break;
        case '\'':
            invokeUtilityMethod("backrefMatchPost", sig(IRubyObject.class, params(ThreadContext.class)));
            break;
        case '+':
            invokeUtilityMethod("backrefMatchLast", sig(IRubyObject.class, params(ThreadContext.class)));
            break;
        default:
            throw new NotCompilableException("ERROR: backref with invalid type");
        }
    }

    public void callZSuper(CompilerCallback closure) {
        loadRuntime();
        loadThreadContext();
        if (closure != null) {
            closure.call(this);
        } else {
            method.getstatic(p(Block.class), "NULL_BLOCK", ci(Block.class));
        }
        loadSelf();

        invokeUtilityMethod("callZSuper", sig(IRubyObject.class, params(Ruby.class, ThreadContext.class, Block.class, IRubyObject.class)));
    }

    public void checkIsExceptionHandled() {
        // ruby exception and list of exception types is on the stack
        loadRuntime();
        loadThreadContext();
        loadSelf();
        invokeUtilityMethod("isExceptionHandled", sig(IRubyObject.class, RubyException.class, IRubyObject[].class, Ruby.class, ThreadContext.class, IRubyObject.class));
    }

    public void checkIsJavaExceptionHandled() {
        // ruby exception and list of exception types is on the stack
        loadRuntime();
        loadThreadContext();
        loadSelf();
        invokeUtilityMethod("isJavaExceptionHandled", sig(IRubyObject.class, Exception.class, IRubyObject[].class, Ruby.class, ThreadContext.class, IRubyObject.class));
    }

    public void rethrowException() {
        loadException();
        method.athrow();
    }

    public void loadClass(String name) {
        loadRuntime();
        method.ldc(name);
        invokeIRuby("getClass", sig(RubyClass.class, String.class));
    }

    public void unwrapRaiseException() {
        // RaiseException is on stack, get RubyException out
        method.invokevirtual(p(RaiseException.class), "getException", sig(RubyException.class));
    }

    public void loadException() {
        method.aload(getExceptionIndex());
    }

    public void setFilePosition(ISourcePosition position) {
        if (!RubyInstanceConfig.POSITIONLESS_COMPILE_ENABLED) {
            loadThreadContext();
            method.ldc(position.getFile());
            invokeThreadContext("setFile", sig(void.class, params(String.class)));
        }
    }

    public void setLinePosition(ISourcePosition position) {
        if (!RubyInstanceConfig.POSITIONLESS_COMPILE_ENABLED) {
            if (lastPositionLine == position.getStartLine()) {
                // updating position for same line; skip
                return;
            } else {
                lastPositionLine = position.getStartLine();
                loadThreadContext();
                method.pushInt(position.getStartLine());
                method.invokestatic(script.getClassname(), "setPosition", sig(void.class, params(ThreadContext.class, int.class)));
            }
        }
    }

    public void checkWhenWithSplat() {
        loadThreadContext();
        invokeUtilityMethod("isWhenTriggered", sig(RubyBoolean.class, IRubyObject.class, IRubyObject.class, ThreadContext.class));
    }

    public void issueRetryEvent() {
        invokeUtilityMethod("retryJump", sig(IRubyObject.class));
    }

    public void defineNewMethod(String name, int methodArity, StaticScope scope,
            CompilerCallback body, CompilerCallback args,
            CompilerCallback receiver, ASTInspector inspector, boolean root) {
        // TODO: build arg list based on number of args, optionals, etc
        String newMethodName;
        if (root && Boolean.getBoolean("jruby.compile.toplevel")) {
            newMethodName = name;
        } else {
            String mangledName = JavaNameMangler.mangleStringForCleanJavaIdentifier(name);
            newMethodName = "method__" + script.getAndIncrementMethodIndex() + "$RUBY$" + mangledName;
        }

        BodyCompiler methodCompiler = script.startMethod(name, newMethodName, args, scope, inspector);

        // callbacks to fill in method body
        body.call(methodCompiler);

        methodCompiler.endBody();

        // prepare to call "def" utility method to handle def logic
        loadThreadContext();

        loadSelf();

        if (receiver != null) {
            receiver.call(this);        // script object
        }
        method.aload(StandardASMCompiler.THIS);

        method.ldc(name);

        method.ldc(newMethodName);

        StandardASMCompiler.buildStaticScopeNames(method, scope);

        method.pushInt(methodArity);

        // arities
        method.pushInt(scope.getRequiredArgs());
        method.pushInt(scope.getOptionalArgs());
        method.pushInt(scope.getRestArg());

        if (inspector.hasFrameAwareMethods() || !(inspector.noFrame() || RubyInstanceConfig.FRAMELESS_COMPILE_ENABLED)) {
            // We're doing normal framed compilation or the method needs a frame
            if (inspector.hasClosure() || inspector.hasScopeAwareMethods()) {
                // The method also needs a scope, do both
                method.getstatic(p(CallConfiguration.class), CallConfiguration.FRAME_AND_SCOPE.name(), ci(CallConfiguration.class));
            } else {
                if (inspector.hasConstant() || inspector.hasMethod() || inspector.hasClass() || inspector.hasClassVar()) {
                    // The method doesn't need a scope, but has static scope needs; use a dummy scope
                    method.getstatic(p(CallConfiguration.class), CallConfiguration.FRAME_AND_DUMMY_SCOPE.name(), ci(CallConfiguration.class));
                } else {
                    // The method doesn't need a scope or static scope; frame only
                    method.getstatic(p(CallConfiguration.class), CallConfiguration.FRAME_ONLY.name(), ci(CallConfiguration.class));
                }
            }
        } else {
            if (inspector.hasClosure() || inspector.hasScopeAwareMethods()) {
                // TODO: call config with scope but no frame
                if (RubyInstanceConfig.FASTEST_COMPILE_ENABLED) {
                    method.getstatic(p(CallConfiguration.class), CallConfiguration.SCOPE_ONLY.name(), ci(CallConfiguration.class));
                } else {
                    method.getstatic(p(CallConfiguration.class), CallConfiguration.BACKTRACE_AND_SCOPE.name(), ci(CallConfiguration.class));
                }
            } else {
                if (RubyInstanceConfig.FASTEST_COMPILE_ENABLED || inspector.noFrame()) {
                    method.getstatic(p(CallConfiguration.class), CallConfiguration.NO_FRAME_NO_SCOPE.name(), ci(CallConfiguration.class));
                } else {
                    method.getstatic(p(CallConfiguration.class), CallConfiguration.BACKTRACE_ONLY.name(), ci(CallConfiguration.class));
                }
            }
        }

        if (receiver != null) {
            invokeUtilityMethod("defs", sig(IRubyObject.class,
                    params(ThreadContext.class, IRubyObject.class, IRubyObject.class, Object.class, String.class, String.class, String[].class, int.class, int.class, int.class, int.class, CallConfiguration.class)));
        } else {
            invokeUtilityMethod("def", sig(IRubyObject.class,
                    params(ThreadContext.class, IRubyObject.class, Object.class, String.class, String.class, String[].class, int.class, int.class, int.class, int.class, CallConfiguration.class)));
        }
    }

    public void rethrowIfSystemExit() {
        loadRuntime();
        method.ldc("SystemExit");
        method.invokevirtual(p(Ruby.class), "fastGetClass", sig(RubyClass.class, String.class));
        method.swap();
        method.invokevirtual(p(RubyModule.class), "isInstance", sig(boolean.class, params(IRubyObject.class)));
        method.iconst_0();
        Label ifEnd = new Label();
        method.if_icmpeq(ifEnd);
        loadException();
        method.athrow();
        method.label(ifEnd);
    }

    public void literalSwitch(int[] cases, Object bodies, ArrayCallback arrayCallback, CompilerCallback defaultCallback) {
        // TODO assuming case is a fixnum
        method.checkcast(p(RubyFixnum.class));
        method.invokevirtual(p(RubyFixnum.class), "getLongValue", sig(long.class));
        method.l2i();

        Label[] labels = new Label[cases.length];
        for (int i = 0; i < labels.length; i++) {
            labels[i] = new Label();
        }
        Label defaultLabel = new Label();
        Label endLabel = new Label();

        method.lookupswitch(defaultLabel, cases, labels);
        for (int i = 0; i < cases.length; i++) {
            method.label(labels[i]);
            arrayCallback.nextValue(this, bodies, i);
            method.go_to(endLabel);
        }

        method.label(defaultLabel);
        defaultCallback.call(this);
        method.label(endLabel);
    }

    public void typeCheckBranch(Class type, BranchCallback trueCallback, BranchCallback falseCallback) {
        Label elseLabel = new Label();
        Label done = new Label();

        method.dup();
        method.instance_of(p(type));
        method.ifeq(elseLabel);

        trueCallback.branch(this);
        method.go_to(done);

        method.label(elseLabel);
        falseCallback.branch(this);

        method.label(done);
    }
   
    public void loadFilename() {
        loadRuntime();
        loadThis();
        method.getfield(getScriptCompiler().getClassname(), "filename", ci(String.class));
        method.invokestatic(p(RubyString.class), "newString", sig(RubyString.class, Ruby.class, CharSequence.class));
    }
}
TOP

Related Classes of org.jruby.compiler.impl.BaseBodyCompiler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.