RuntimeHelperCall.java

package org.jruby.ir.instructions;

import org.jruby.RubyModule;
import org.jruby.ir.IRScope;
import org.jruby.ir.IRVisitor;
import org.jruby.ir.Operation;
import org.jruby.ir.operands.Boolean;
import org.jruby.ir.operands.*;
import org.jruby.ir.runtime.IRRuntimeHelpers;
import org.jruby.ir.transformations.inlining.CloneInfo;
import org.jruby.parser.StaticScope;
import org.jruby.runtime.Block;
import org.jruby.runtime.DynamicScope;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;

import java.util.Arrays;
import java.util.Map;

import static org.jruby.ir.IRFlags.REQUIRES_FRAME;

public class RuntimeHelperCall extends Instr implements ResultInstr {
    public enum Methods {
        HANDLE_PROPAGATE_BREAK, HANDLE_NONLOCAL_RETURN, HANDLE_BREAK_AND_RETURNS_IN_LAMBDA,
        IS_DEFINED_BACKREF, IS_DEFINED_NTH_REF, IS_DEFINED_GLOBAL, IS_DEFINED_INSTANCE_VAR,
        IS_DEFINED_CLASS_VAR, IS_DEFINED_SUPER, IS_DEFINED_METHOD, IS_DEFINED_CALL,
        IS_DEFINED_CONSTANT_OR_METHOD, MERGE_KWARGS, CHECK_FOR_LJE
    };

    Variable  result;
    Methods    helperMethod;
    Operand[] args;

    public RuntimeHelperCall(Variable result, Methods helperMethod, Operand[] args) {
        super(Operation.RUNTIME_HELPER);
        this.result = result;
        this.helperMethod = helperMethod;
        this.args = args;
    }

    public Operand[] getArgs() {
        return args;
    }

    public Methods getHelperMethod() {
        return helperMethod;
    }

    @Override
    public Operand[] getOperands() {
        return getArgs();
    }

    @Override
    public Variable getResult() {
        return result;
    }

    @Override
    public void updateResult(Variable v) {
        this.result = v;
    }

    @Override
    public void simplifyOperands(Map<Operand, Operand> valueMap, boolean force) {
        for (int i = 0; i < args.length; i++) {
            args[i] = args[i].getSimplifiedOperand(valueMap, force);
        }
    }

    /**
     * Does this instruction do anything the scope is interested in?
     *
     * @param scope
     * @return true if it modified the scope.
     */
    @Override
    public boolean computeScopeFlags(IRScope scope) {
        boolean modifiedScope = false;

        // FIXME: Impl of this helper uses frame class.  Determine if we can do this another way.
        if (helperMethod == Methods.IS_DEFINED_SUPER) {
            modifiedScope = true;
            scope.getFlags().add(REQUIRES_FRAME);
        }

        return modifiedScope;
    }

    @Override
    public Instr clone(CloneInfo ii) {
        // SSS FIXME: array of args cloning should be part of utility class
        Operand[] clonedArgs = new Operand[args.length];
        for (int i = 0; i < args.length; i++) {
            clonedArgs[i] = args[i].cloneForInlining(ii);
        }
        Variable var = getResult();
        return new RuntimeHelperCall(var == null ? null : ii.getRenamedVariable(var), helperMethod, clonedArgs);
    }

    @Override
    public String toString() {
        return (getResult() == null ? "" : (getResult() + " = ")) + getOperation()  + "(" + helperMethod + ", " + Arrays.toString(args) + ")";
    }

    public IRubyObject callHelper(ThreadContext context, StaticScope currScope, DynamicScope currDynScope, IRubyObject self, Object[] temp, Block.Type blockType) {
        StaticScope scope = currDynScope.getStaticScope();

        switch (helperMethod) {
            case HANDLE_PROPAGATE_BREAK:
                return IRRuntimeHelpers.handlePropagatedBreak(context, currDynScope,
                        args[0].retrieve(context, self, currScope, currDynScope, temp), blockType);
            case HANDLE_NONLOCAL_RETURN:
                return IRRuntimeHelpers.handleNonlocalReturn(scope, currDynScope,
                        args[0].retrieve(context, self, currScope, currDynScope, temp), blockType);
            case HANDLE_BREAK_AND_RETURNS_IN_LAMBDA:
                return IRRuntimeHelpers.handleBreakAndReturnsInLambdas(context, scope, currDynScope,
                        args[0].retrieve(context, self, currScope, currDynScope, temp), blockType);
            case IS_DEFINED_BACKREF:
                return IRRuntimeHelpers.isDefinedBackref(context);
            case IS_DEFINED_CALL:
                return IRRuntimeHelpers.isDefinedCall(context, self,
                        (IRubyObject) args[0].retrieve(context, self, currScope, currDynScope, temp),
                        ((StringLiteral) args[1]).getString());
            case IS_DEFINED_CONSTANT_OR_METHOD:
                return IRRuntimeHelpers.isDefinedConstantOrMethod(context,
                        (IRubyObject) args[0].retrieve(context, self, currScope, currDynScope, temp),
                        ((StringLiteral) args[1]).getString());
            case IS_DEFINED_NTH_REF:
                return IRRuntimeHelpers.isDefinedNthRef(context, (int) ((Fixnum) args[0]).getValue());
            case IS_DEFINED_GLOBAL:
                return IRRuntimeHelpers.isDefinedGlobal(context, ((StringLiteral) args[0]).getString());
            case IS_DEFINED_INSTANCE_VAR:
                return IRRuntimeHelpers.isDefinedInstanceVar(context,
                        (IRubyObject) args[0].retrieve(context, self, currScope, currDynScope, temp),
                        ((StringLiteral) args[1]).getString());
            case IS_DEFINED_CLASS_VAR:
                return IRRuntimeHelpers.isDefinedClassVar(context,
                        (RubyModule) args[0].retrieve(context, self, currScope, currDynScope, temp),
                        ((StringLiteral) args[1]).getString());
            case IS_DEFINED_SUPER:
                return IRRuntimeHelpers.isDefinedSuper(context,
                        (IRubyObject) args[0].retrieve(context, self, currScope, currDynScope, temp));
            case IS_DEFINED_METHOD:
                return IRRuntimeHelpers.isDefinedMethod(context,
                        (IRubyObject) args[0].retrieve(context, self, currScope, currDynScope, temp),
                        ((StringLiteral) args[1]).getString(),
                        ((Boolean) args[2]).isTrue());
            case MERGE_KWARGS:
                return IRRuntimeHelpers.mergeKeywordArguments(context,
                        (IRubyObject) args[0].retrieve(context, self, currScope, currDynScope, temp),
                        (IRubyObject) args[1].retrieve(context, self, currScope, currDynScope, temp));
            case CHECK_FOR_LJE:
                IRRuntimeHelpers.checkForLJE(context, currDynScope, ((Boolean)args[0]).isTrue(), blockType);
                return null;
        }

        throw new RuntimeException("Unknown IR runtime helper method: " + helperMethod + "; INSTR: " + this);
    }

    @Override
    public void visit(IRVisitor visitor) {
        visitor.RuntimeHelperCall(this);
    }
}