package org.jruby.ir;

import org.jruby.Ruby;
import org.jruby.RubyInstanceConfig.CompileMode;
import org.jruby.ast.*;
import org.jruby.compiler.NotCompilableException;
import org.jruby.ir.instructions.*;
import org.jruby.ir.instructions.defined.BackrefIsMatchDataInstr;
import org.jruby.ir.instructions.defined.ClassVarIsDefinedInstr;
import org.jruby.ir.instructions.defined.GetDefinedConstantOrMethodInstr;
import org.jruby.ir.instructions.defined.GetErrorInfoInstr;
import org.jruby.ir.instructions.defined.GlobalIsDefinedInstr;
import org.jruby.ir.instructions.defined.HasInstanceVarInstr;
import org.jruby.ir.instructions.defined.IsMethodBoundInstr;
import org.jruby.ir.instructions.defined.MethodDefinedInstr;
import org.jruby.ir.instructions.defined.MethodIsPublicInstr;
import org.jruby.ir.instructions.defined.RestoreErrorInfoInstr;
import org.jruby.ir.instructions.defined.SuperMethodBoundInstr;
import org.jruby.ir.instructions.ReceiveRestArgInstr;
import org.jruby.ir.operands.Array;
import org.jruby.ir.operands.AsString;
import org.jruby.ir.operands.Backref;
import org.jruby.ir.operands.BacktickString;
import org.jruby.ir.operands.Bignum;
import org.jruby.ir.operands.CompoundArray;
import org.jruby.ir.operands.CompoundString;
import org.jruby.ir.operands.ScopeModule;
import org.jruby.ir.operands.CurrentScope;
import org.jruby.ir.operands.DynamicSymbol;
import org.jruby.ir.operands.Fixnum;
import org.jruby.ir.operands.Float;
import org.jruby.ir.operands.Hash;
import org.jruby.ir.operands.IRException;
import org.jruby.ir.operands.KeyValuePair;
import org.jruby.ir.operands.Label;
import org.jruby.ir.operands.LocalVariable;
import org.jruby.ir.operands.MethAddr;
import org.jruby.ir.operands.NthRef;
import org.jruby.ir.operands.ObjectClass;
import org.jruby.ir.operands.Operand;
import org.jruby.ir.operands.Range;
import org.jruby.ir.operands.Regexp;
import org.jruby.ir.operands.SValue;
import org.jruby.ir.operands.Splat;
import org.jruby.ir.operands.StringLiteral;
import org.jruby.ir.operands.Symbol;
import org.jruby.ir.operands.TemporaryVariable;
import org.jruby.ir.operands.UndefinedValue;
import org.jruby.ir.operands.UnexecutableNil;
import org.jruby.ir.operands.Variable;
import org.jruby.ir.operands.WrappedIRClosure;
import org.jruby.parser.StaticScope;
import org.jruby.runtime.Arity;
import org.jruby.runtime.BlockBody;
import org.jruby.runtime.CallType;
import org.jruby.util.ByteList;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import org.jruby.ir.listeners.IRScopeListener;

// This class converts an AST into a bunch of IR instructions

// IR Building Notes
// -----------------
//
// 1. More copy instructions added than necessary
// ----------------------------------------------
// Note that in general, there will be lots of a = b kind of copies
// introduced in the IR because the translation is entirely single-node focused.
// An example will make this clear
//
// RUBY:
//     v = @f
// will translate to
//
// AST:
//     LocalAsgnNode v
//       InstrVarNode f
// will translate to
//
// IR:
//     tmp = self.f [ GET_FIELD(tmp,self,f) ]
//     v = tmp      [ COPY(v, tmp) ]
//
// instead of
//     v = self.f   [ GET_FIELD(v, self, f) ]
//
// We could get smarter and pass in the variable into which this expression is going to get evaluated
// and use that to store the value of the expression (or not build the expression if the variable is null).
//
// But, that makes the code more complicated, and in any case, all this will get fixed in a single pass of
// copy propagation and dead-code elimination.
//
// Something to pay attention to and if this extra pass becomes a concern (not convinced that it is yet),
// this smart can be built in here.  Right now, the goal is to do something simple and straightforward that is going to be correct.
//
// 2. Returning null vs manager.getNil()
// ----------------------------
// - We should be returning null from the build methods where it is a normal "error" condition
// - We should be returning manager.getNil() where the actual return value of a build is the ruby nil operand
//   Look in buildIf for an example of this
//
// 3. Temporary variable reuse
// ---------------------------
// I am reusing variables a lot in places in this code.  Should I instead always get a new variable when I need it
// This introduces artificial data dependencies, but fewer variables.  But, if we are going to implement SSA pass
// this is not a big deal.  Think this through!

public class IRBuilder {
    protected static final Operand[] NO_ARGS = new Operand[]{};
    protected static final UnexecutableNil U_NIL = UnexecutableNil.U_NIL;

    private static String  rubyVersion = "1.8"; // default is 1.8

    public static void setRubyVersion(String rubyVersion) {
        IRBuilder.rubyVersion = rubyVersion;
    }

    public boolean is1_9() {
        return false;
    }

    public boolean is2_0() {
        return false;
    }

    private Operand buildOperand(Node node, IRScope s) throws NotCompilableException {
        switch (node.getNodeType()) {
            case ALIASNODE: return buildAlias((AliasNode) node, s);
            case ANDNODE: return buildAnd((AndNode) node, s);
            case ARGSCATNODE: return buildArgsCat((ArgsCatNode) node, s);
            case ARGSPUSHNODE: return buildArgsPush((ArgsPushNode) node, s);
            case ARRAYNODE: return buildArray(node, s);
            case ATTRASSIGNNODE: return buildAttrAssign((AttrAssignNode) node, s);
            case BACKREFNODE: return buildBackref((BackRefNode) node, s);
            case BEGINNODE: return buildBegin((BeginNode) node, s);
            case BIGNUMNODE: return buildBignum((BignumNode) node, s);
            case BLOCKNODE: return buildBlock((BlockNode) node, s);
            case BREAKNODE: return buildBreak((BreakNode) node, s);
            case CALLNODE: return buildCall((CallNode) node, s);
            case CASENODE: return buildCase((CaseNode) node, s);
            case CLASSNODE: return buildClass((ClassNode) node, s);
            case CLASSVARNODE: return buildClassVar((ClassVarNode) node, s);
            case CLASSVARASGNNODE: return buildClassVarAsgn((ClassVarAsgnNode) node, s);
            case CLASSVARDECLNODE: return buildClassVarDecl((ClassVarDeclNode) node, s);
            case COLON2NODE: return buildColon2((Colon2Node) node, s);
            case COLON3NODE: return buildColon3((Colon3Node) node, s);
            case CONSTDECLNODE: return buildConstDecl((ConstDeclNode) node, s);
            case CONSTNODE: return searchConst(s, s, ((ConstNode) node).getName());
            case DASGNNODE: return buildDAsgn((DAsgnNode) node, s);
            case DEFINEDNODE: return buildGetDefinitionBase(((DefinedNode) node).getExpressionNode(), s);
            case DEFNNODE: return buildDefn((MethodDefNode) node, s);
            case DEFSNODE: return buildDefs((DefsNode) node, s);
            case DOTNODE: return buildDot((DotNode) node, s);
            case DREGEXPNODE: return buildDRegexp((DRegexpNode) node, s);
            case DSTRNODE: return buildDStr((DStrNode) node, s);
            case DSYMBOLNODE: return buildDSymbol((DSymbolNode) node, s);
            case DVARNODE: return buildDVar((DVarNode) node, s);
            case DXSTRNODE: return buildDXStr((DXStrNode) node, s);
            case ENSURENODE: return buildEnsureNode((EnsureNode) node, s);
            case EVSTRNODE: return buildEvStr((EvStrNode) node, s);
            case FALSENODE: return buildFalse(node, s);
            case FCALLNODE: return buildFCall((FCallNode) node, s);
            case FIXNUMNODE: return buildFixnum((FixnumNode) node, s);
            case FLIPNODE: return buildFlip((FlipNode) node, s);
            case FLOATNODE: return buildFloat((FloatNode) node, s);
            case FORNODE: return buildFor((ForNode) node, s);
            case GLOBALASGNNODE: return buildGlobalAsgn((GlobalAsgnNode) node, s);
            case GLOBALVARNODE: return buildGlobalVar((GlobalVarNode) node, s);
            case HASHNODE: return buildHash((HashNode) node, s);
            case IFNODE: return buildIf((IfNode) node, s);
            case INSTASGNNODE: return buildInstAsgn((InstAsgnNode) node, s);
            case INSTVARNODE: return buildInstVar((InstVarNode) node, s);
            case ITERNODE: return buildIter((IterNode) node, s);
            case LITERALNODE: return buildLiteral((LiteralNode) node, s);
            case LOCALASGNNODE: return buildLocalAsgn((LocalAsgnNode) node, s);
            case LOCALVARNODE: return buildLocalVar((LocalVarNode) node, s);
            case MATCH2NODE: return buildMatch2((Match2Node) node, s);
            case MATCH3NODE: return buildMatch3((Match3Node) node, s);
            case MATCHNODE: return buildMatch((MatchNode) node, s);
            case MODULENODE: return buildModule((ModuleNode) node, s);
            case MULTIPLEASGNNODE: return buildMultipleAsgn((MultipleAsgnNode) node, s); // Only for 1.8
            case NEWLINENODE: return buildNewline((NewlineNode) node, s);
            case NEXTNODE: return buildNext((NextNode) node, s);
            case NTHREFNODE: return buildNthRef((NthRefNode) node, s);
            case NILNODE: return buildNil(node, s);
            case NOTNODE: return buildNot((NotNode) node, s);
            case OPASGNANDNODE: return buildOpAsgnAnd((OpAsgnAndNode) node, s);
            case OPASGNNODE: return buildOpAsgn((OpAsgnNode) node, s);
            case OPASGNORNODE: return buildOpAsgnOr((OpAsgnOrNode) node, s);
            case OPELEMENTASGNNODE: return buildOpElementAsgn((OpElementAsgnNode) node, s);
            case ORNODE: return buildOr((OrNode) node, s);
            case PREEXENODE: return buildPreExe((PreExeNode) node, s);
            case POSTEXENODE: return buildPostExe((PostExeNode) node, s);
            case REDONODE: return buildRedo(node, s);
            case REGEXPNODE: return buildRegexp((RegexpNode) node, s);
            case RESCUEBODYNODE:
                throw new NotCompilableException("rescue body is handled by rescue compilation at: " + node.getPosition());
            case RESCUENODE: return buildRescue((RescueNode) node, s);
            case RETRYNODE: return buildRetry(node, s);
            case RETURNNODE: return buildReturn((ReturnNode) node, s);
            case ROOTNODE:
                throw new NotCompilableException("Use buildRoot(); Root node at: " + node.getPosition());
            case SCLASSNODE: return buildSClass((SClassNode) node, s);
            case SELFNODE: return buildSelf((SelfNode) node, s);
            case SPLATNODE: return buildSplat((SplatNode) node, s);
            case STRNODE: return buildStr((StrNode) node, s);
            case SUPERNODE: return buildSuper((SuperNode) node, s);
            case SVALUENODE: return buildSValue((SValueNode) node, s);
            case SYMBOLNODE: return buildSymbol((SymbolNode) node, s);
            case TOARYNODE: return buildToAry((ToAryNode) node, s);
            case TRUENODE: return buildTrue(node, s);
            case UNDEFNODE: return buildUndef(node, s);
            case UNTILNODE: return buildUntil((UntilNode) node, s);
            case VALIASNODE: return buildVAlias(node, s);
            case VCALLNODE: return buildVCall((VCallNode) node, s);
            case WHILENODE: return buildWhile((WhileNode) node, s);
            case WHENNODE: assert false : "When nodes are handled by case node compilation."; return null;
            case XSTRNODE: return buildXStr((XStrNode) node, s);
            case YIELDNODE: return buildYield((YieldNode) node, s);
            case ZARRAYNODE: return buildZArray(node, s);
            case ZSUPERNODE: return buildZSuper((ZSuperNode) node, s);
            default: return buildVersionSpecificNodes(node, s);
        }
    }

    /* -----------------------------------------------------------------------------------
     * Every ensure block has a start label and end label, and at the end, it will jump
     * to an address stored in a return address variable.
     *
     * This ruby code will translate to the IR shown below
     * -----------------
     *   begin
     *       ... protected body ...
     *   ensure
     *       ... ensure block to run
     *   end
     * -----------------
     *  L_region_start
     *     IR instructions for the protected body
     *  L_start:
     *     .. ensure block IR ...
     *     jump %ret_addr
     *  L_end:
     * -----------------
     *
     * If N is a node in the protected body that might exit this scope (exception rethrows
     * and returns), N has to first jump to the ensure block and let the ensure block run.
     * In addition, N has to set up a return address label in the return address var of
     * this ensure block so that the ensure block can transfer control block to N.
     *
     * Since we can have a nesting of ensure blocks, we are maintaining a stack of these
     * well-nested ensure blocks.  Every node N that will exit this scope will have to
     * co-ordinate the jumps in-and-out of the ensure blocks in the top-to-bottom stacked
     * order.
     * ----------------------------------------------------------------------------------- */
    private static class EnsureBlockInfo {
        Label    regionStart;
        Label    start;
        Label    end;
        Label    dummyRescueBlockLabel;
        Variable returnAddr;
        Variable savedGlobalException;

        // Innermost loop within which this ensure block is nested, if any
        IRLoop   innermostLoop;

        // AST node for any associated rescue node in the case of begin-rescue-ensure-end block
        // Will be null in the case of begin-ensure-end block
        RescueNode matchingRescueNode;

        public EnsureBlockInfo(IRScope s, RescueNode n, IRLoop l) {
            regionStart = s.getNewLabel();
            start       = s.getNewLabel();
            end         = s.getNewLabel();
            returnAddr  = s.getNewTemporaryVariable();
            dummyRescueBlockLabel = s.getNewLabel();
            savedGlobalException = null;
            innermostLoop = l;
            matchingRescueNode = n;
        }

        // Emit jump chain by walking up the ensure block stack
        // If we have been passed a loop value, then emit values that are nested within that loop
        public static void emitJumpChain(IRScope s, Stack<EnsureBlockInfo> ebStack, IRLoop loop) {
            // SSS: There are 2 ways of encoding this:
            // 1. Jump to ensure block 1, return back here, jump ensure block 2, return back here, ...
            //    Generates 3*n instrs. where n is the # of ensure blocks to execute
            // 2. Jump to ensure block 1, then to block 2, then to 3, ...
            //    Generates n+1 instrs. where n is the # of ensure blocks to execute
            // Doesn't really matter all that much since we shouldn't have deep nesting of ensure blocks often
            // but is there a reason to go with technique 1 at all??
            int n = ebStack.size();
            EnsureBlockInfo[] ebArray = ebStack.toArray(new EnsureBlockInfo[n]);
            for (int i = n-1; i >= 0; i--) {
                EnsureBlockInfo ebi = ebArray[i];

                //
                if (ebi.innermostLoop != loop) break;

                Label retLabel = s.getNewLabel();
                if (ebi.savedGlobalException != null) {
                    s.addInstr(new PutGlobalVarInstr("$!", ebi.savedGlobalException));
                }
                s.addInstr(new SetReturnAddressInstr(ebi.returnAddr, retLabel));
                s.addInstr(new JumpInstr(ebi.start));
                s.addInstr(new LabelInstr(retLabel));
            }
        }
    }

    // Stack encoding nested ensure blocks
    private Stack<EnsureBlockInfo> _ensureBlockStack = new Stack<EnsureBlockInfo>();

    private static class RescueBlockInfo {
        RescueNode rescueNode;             // Rescue node for which we are tracking info
        Label      entryLabel;             // Entry of the rescue block
        Variable   savedExceptionVariable; // Variable that contains the saved $! variable
        IRLoop     innermostLoop;          // Innermost loop within which this ensure block is nested, if any

        public RescueBlockInfo(RescueNode n, Label l, Variable v, IRLoop loop) {
            rescueNode = n;
            entryLabel = l;
            savedExceptionVariable = v;
            innermostLoop = loop;
        }

        public void restoreException(IRScope s, IRLoop currLoop) {
            if (currLoop == innermostLoop) s.addInstr(new PutGlobalVarInstr("$!", savedExceptionVariable));
        }
    }

    // Stack encoding nested rescue blocks -- this just tracks the start label of the blocks
    private Stack<RescueBlockInfo> _rescueBlockStack = new Stack<RescueBlockInfo>();

    private int _lastProcessedLineNum = -1;

    // Since we are processing ASTs, loop bodies are processed in depth-first manner
    // with outer loops encountered before inner loops, and inner loops finished before outer ones.
    //
    // So, we can keep track of loops in a loop stack which  keeps track of loops as they are encountered.
    // This lets us implement next/redo/break/retry easily for the non-closure cases
    private Stack<IRLoop> loopStack = new Stack<IRLoop>();

    public IRLoop getCurrentLoop() {
        return loopStack.isEmpty() ? null : loopStack.peek();
    }

    protected IRManager manager;

    public IRBuilder(IRManager manager) {
        this.manager = manager;
    }

    public static Node buildAST(boolean isCommandLineScript, String arg) {
        Ruby ruby = Ruby.getGlobalRuntime();

        // set to IR mode, since we use different scopes, etc for IR
        ruby.getInstanceConfig().setCompileMode(CompileMode.OFFIR);

        // inline script
        if (isCommandLineScript) return ruby.parse(ByteList.create(arg), "-e", null, 0, false);

        // from file
        FileInputStream fis = null;
        try {
            File file = new File(arg);
            fis = new FileInputStream(file);
            long size = file.length();
            byte[] bytes = new byte[(int)size];
            fis.read(bytes);
            System.out.println("-- processing " + arg + " --");
            return ruby.parse(new ByteList(bytes), arg, null, 0, false);
        } catch (IOException ioe) {
            throw new RuntimeException(ioe);
        } finally {
            try { if (fis != null) fis.close(); } catch(Exception e) { }
        }
    }

    public static IRBuilder createIRBuilder(Ruby runtime, IRManager manager) {
        boolean is19 = runtime.is1_9();
        boolean is20 = runtime.is2_0();
        if (is20) {
            return new IRBuilder20(manager);
        } else if (is19) {
            return new IRBuilder19(manager);
        } else {
            return new IRBuilder(manager);
        }
    }

    private boolean hasListener() {
        return manager.getIRScopeListener() != null;
    }

    public IRBuilder newIRBuilder(IRManager manager) {
        if (is2_0()) {
            return new IRBuilder20(manager);
        } else if (is1_9()) {
            return new IRBuilder19(manager);
        } else {
            return new IRBuilder(manager);
        }
    }

    public Node skipOverNewlines(IRScope s, Node n) {
        if (n.getNodeType() == NodeType.NEWLINENODE) {
            // Do not emit multiple line number instrs for the same line
            int currLineNum = n.getPosition().getStartLine();
            if (currLineNum != _lastProcessedLineNum) {
               s.addInstr(new LineNumberInstr(s, currLineNum));
               _lastProcessedLineNum = currLineNum;
            }
        }

        while (n.getNodeType() == NodeType.NEWLINENODE)
            n = ((NewlineNode)n).getNextNode();

        return n;
    }

    public Operand build(Node node, IRScope s) {
        if (node == null) return null;

        if (s == null) {
            System.out.println("Got a null scope!");
            throw new NotCompilableException("Unknown node encountered in builder: " + node);
        }
        if (hasListener()) {
            IRScopeListener listener = manager.getIRScopeListener();
            listener.startBuildOperand(node, s);
        }
        Operand operand = buildOperand(node, s);
        if (hasListener()) {
            IRScopeListener listener = manager.getIRScopeListener();
            listener.endBuildOperand(node, s, operand);
        }
        return operand;
    }

    protected Operand buildVersionSpecificNodes(Node node, IRScope s) {
        throw new NotCompilableException("Unknown node encountered in builder: " + node.getClass());
    }

    protected Variable getSelf(IRScope s) {
        return s.getSelf();
    }

    protected Variable copyAndReturnValue(IRScope s, Operand val) {
        Variable v = s.getNewTemporaryVariable();
        s.addInstr(new CopyInstr(v, val));
        return v;
    }

    protected Variable getValueInTemporaryVariable(IRScope s, Operand val) {
        if (val != null && val instanceof TemporaryVariable) return (Variable) val;

        return copyAndReturnValue(s, val);
    }

    // Return the last argument in the list -- AttrAssign needs it
    protected Operand buildCallArgs(List<Operand> argsList, Node args, IRScope s) {
        // unwrap newline nodes to get their actual type
        args = skipOverNewlines(s, args);
        switch (args.getNodeType()) {
            case ARGSCATNODE: {
                CompoundArray a = (CompoundArray)build(args, s);
                argsList.add(new Splat(a));
                return a.getAppendedArg();
            }
            case ARGSPUSHNODE:  {
                ArgsPushNode ap = (ArgsPushNode)args;
                Operand v1 = build(ap.getFirstNode(), s);
                Operand v2 = build(ap.getSecondNode(), s);
                argsList.add(new Splat(new CompoundArray(v1, v2, true)));
                return v2;
            }
            case ARRAYNODE: {
                ArrayNode arrayNode = (ArrayNode)args;
                if (arrayNode.isLightweight()) {
                    List<Node> children = arrayNode.childNodes();
                    if (children.size() == 1) {
                        // skipOverNewlines is required because the parser inserts a NewLineNode in between!
                        Node child = skipOverNewlines(s, children.get(0));
                        if (child instanceof SplatNode) {
                            // SSS: If the only child is a splat, the splat is supposed to get through
                            // as an array without being expanded into the call arg list.
                            //
                            // The AST for the foo([*1]) is: ArrayNode(Splat19Node(..))
                            // The AST for the foo(*1) is: Splat19Node(..)
                            //
                            // Since a lone splat in call args is always expanded, we convert the splat
                            // into a compound array: *n --> args-cat([], *n)
                            SplatNode splat = (SplatNode)child;
                            Variable splatArray = getValueInTemporaryVariable(s, build(splat.getValue(), s));
                            argsList.add(new CompoundArray(new Array(), splatArray));
                            return new Splat(splatArray);
                        } else {
                            Operand childOperand = build(child, s);
                            argsList.add(childOperand);
                            return childOperand;
                        }
                    } else {
                        // explode array, it's an internal "args" array
                        for (Node n: children) {
                            argsList.add(build(n, s));
                        }
                    }
                } else {
                    // use array as-is, it's a literal array
                    argsList.add(build(arrayNode, s));
                }
                break;
            }
            default: {
                argsList.add(build(args, s));
                break;
            }
        }

        return argsList.isEmpty() ? manager.getNil() : argsList.get(argsList.size() - 1);
    }

    public List<Operand> setupCallArgs(Node args, IRScope s) {
        List<Operand> argsList = new ArrayList<Operand>();
        if (args != null) buildCallArgs(argsList, args, s);
        return argsList;
    }

    public void buildVersionSpecificAssignment(Node node, IRScope s, Variable v) {
        switch (node.getNodeType()) {
        case MULTIPLEASGNNODE: {
            Operand valuesArg;
            MultipleAsgnNode childNode = (MultipleAsgnNode) node;
            if (childNode.getHeadNode() != null && ((ListNode)childNode.getHeadNode()).childNodes().size() > 0) {
                // Invoke to_ary on the operand only if it is not an array already
                Variable result = s.getNewTemporaryVariable();
                s.addInstr(new ToAryInstr(result, v, manager.getTrue()));
                valuesArg = result;
            } else {
                s.addInstr(new EnsureRubyArrayInstr(v, v));
                valuesArg = v;
            }
            buildMultipleAsgnAssignment(childNode, s, null, valuesArg);
            break;
        }
        default:
            throw new NotCompilableException("Can't build assignment node: " + node);
        }
    }

    // This method is called to build assignments for a multiple-assignment instruction
    public void buildAssignment(Node node, IRScope s, Variable rhsVal) {
        switch (node.getNodeType()) {
            case ATTRASSIGNNODE:
                buildAttrAssignAssignment(node, s, rhsVal);
                break;
            case CLASSVARASGNNODE:
                s.addInstr(new PutClassVariableInstr(classVarDefinitionContainer(s), ((ClassVarAsgnNode)node).getName(), rhsVal));
                break;
            case CLASSVARDECLNODE:
                s.addInstr(new PutClassVariableInstr(classVarDeclarationContainer(s), ((ClassVarDeclNode)node).getName(), rhsVal));
                break;
            case CONSTDECLNODE:
                buildConstDeclAssignment((ConstDeclNode) node, s, rhsVal);
                break;
            case DASGNNODE: {
                DAsgnNode variable = (DAsgnNode) node;
                int depth = variable.getDepth();
                s.addInstr(new CopyInstr(s.getLocalVariable(variable.getName(), depth), rhsVal));
                break;
            }
            case GLOBALASGNNODE:
                s.addInstr(new PutGlobalVarInstr(((GlobalAsgnNode)node).getName(), rhsVal));
                break;
            case INSTASGNNODE:
                // NOTE: if 's' happens to the a class, this is effectively an assignment of a class instance variable
                s.addInstr(new PutFieldInstr(getSelf(s), ((InstAsgnNode)node).getName(), rhsVal));
                break;
            case LOCALASGNNODE: {
                LocalAsgnNode localVariable = (LocalAsgnNode) node;
                int depth = localVariable.getDepth();
                s.addInstr(new CopyInstr(s.getLocalVariable(localVariable.getName(), depth), rhsVal));
                break;
            }
            case ZEROARGNODE:
                throw new NotCompilableException("Shouldn't get here; zeroarg does not do assignment: " + node);
            default:
                buildVersionSpecificAssignment(node, s, rhsVal);
        }
    }

    protected LocalVariable getBlockArgVariable(IRScope cl, String name, int depth) {
        return cl.getLocalVariable(name, depth);
    }

    protected void receiveBlockArg(IRScope s, Variable v, Operand argsArray, int argIndex, boolean isClosureArg, boolean isSplat) {
        if (argsArray != null) {
            // We are in a nested receive situation -- when we are not at the root of a masgn tree
            // Ex: We are trying to receive (b,c) in this example: "|a, (b,c), d| = ..."
            if (isSplat) s.addInstr(new RestArgMultipleAsgnInstr(v, argsArray, argIndex));
            else s.addInstr(new ReqdArgMultipleAsgnInstr(v, argsArray, argIndex));
        } else {
            // argsArray can be null when the first node in the args-node-ast is a multiple-assignment
            // For example, for-nodes
            s.addInstr(isClosureArg ? new ReceiveClosureInstr(v) : (isSplat ? new ReceiveRestArgInstr(v, argIndex, argIndex) : new ReceivePreReqdArgInstr(v, argIndex)));
        }
    }

    public void buildVersionSpecificBlockArgsAssignment(Node node, IRScope s, Operand argsArray, int argIndex, boolean isMasgnRoot, boolean isClosureArg, boolean isSplat) {
        switch (node.getNodeType()) {
            case MULTIPLEASGNNODE: {
                Variable oldArgs = null;
                MultipleAsgnNode childNode = (MultipleAsgnNode) node;
                if (!isMasgnRoot) {
                    // Vars used to receive args should always be local-variables because
                    // these arg values may need to be accessed by some zsuper instruction.
                    // During interpretation, only local-vars are accessible (at least right now)
                    // outside the scope they are defined in.
                    Variable v = s.getLocalVariable("%_masgn_arg_" + argIndex, 0);
                    receiveBlockArg(s, v, argsArray, argIndex, isClosureArg, isSplat);
                    boolean runToAry = childNode.getHeadNode() != null && (((ListNode)childNode.getHeadNode()).childNodes().size() > 0);
                    if (runToAry) {
                        s.addInstr(new ToAryInstr(v, v, manager.getFalse()));
                    } else {
                        s.addInstr(new EnsureRubyArrayInstr(v, v));
                    }
                    argsArray = v;
                    // SSS FIXME: Are we guaranteed that splats dont head to multiple-assignment nodes!  i.e. |*(a,b)|?
                }
                // Build
                buildMultipleAsgnAssignment(childNode, s, argsArray, null);
                break;
            }
            default: throw new NotCompilableException("Can't build assignment node: " + node);
        }
    }

    // This method is called to build arguments for a block!
    public void buildBlockArgsAssignment(Node node, IRScope s, Operand argsArray, int argIndex, boolean isMasgnRoot, boolean isClosureArg, boolean isSplat) {
        Variable v;
        switch (node.getNodeType()) {
            case ATTRASSIGNNODE:
                v = s.getNewTemporaryVariable();
                receiveBlockArg(s, v, argsArray, argIndex, isClosureArg, isSplat);
                buildAttrAssignAssignment(node, s, v);
                break;
            case DASGNNODE: {
                DAsgnNode dynamicAsgn = (DAsgnNode) node;
                v = getBlockArgVariable((IRClosure)s, dynamicAsgn.getName(), dynamicAsgn.getDepth());
                receiveBlockArg(s, v, argsArray, argIndex, isClosureArg, isSplat);
                break;
            }
            case CLASSVARASGNNODE:
                v = s.getNewTemporaryVariable();
                receiveBlockArg(s, v, argsArray, argIndex, isClosureArg, isSplat);
                s.addInstr(new PutClassVariableInstr(classVarDefinitionContainer(s), ((ClassVarAsgnNode)node).getName(), v));
                break;
            case CLASSVARDECLNODE:
                v = s.getNewTemporaryVariable();
                receiveBlockArg(s, v, argsArray, argIndex, isClosureArg, isSplat);
                s.addInstr(new PutClassVariableInstr(classVarDeclarationContainer(s), ((ClassVarDeclNode)node).getName(), v));
                break;
            case CONSTDECLNODE:
                v = s.getNewTemporaryVariable();
                receiveBlockArg(s, v, argsArray, argIndex, isClosureArg, isSplat);
                buildConstDeclAssignment((ConstDeclNode) node, s, v);
                break;
            case GLOBALASGNNODE:
                v = s.getNewTemporaryVariable();
                receiveBlockArg(s, v, argsArray, argIndex, isClosureArg, isSplat);
                s.addInstr(new PutGlobalVarInstr(((GlobalAsgnNode)node).getName(), v));
                break;
            case INSTASGNNODE:
                v = s.getNewTemporaryVariable();
                receiveBlockArg(s, v, argsArray, argIndex, isClosureArg, isSplat);
                // NOTE: if 's' happens to the a class, this is effectively an assignment of a class instance variable
                s.addInstr(new PutFieldInstr(getSelf(s), ((InstAsgnNode)node).getName(), v));
                break;
            case LOCALASGNNODE: {
                LocalAsgnNode localVariable = (LocalAsgnNode) node;
                int depth = localVariable.getDepth();
                v = getBlockArgVariable((IRClosure)s, localVariable.getName(), depth);
                receiveBlockArg(s, v, argsArray, argIndex, isClosureArg, isSplat);
                break;
            }
            case ZEROARGNODE:
                throw new NotCompilableException("Shouldn't get here; zeroarg does not do assignment: " + node);
            default:
                buildVersionSpecificBlockArgsAssignment(node, s, argsArray, argIndex, isMasgnRoot, isClosureArg, isSplat);
        }
    }

    public Operand buildAlias(final AliasNode alias, IRScope s) {
        Operand newName = build(alias.getNewName(), s);
        Operand oldName = build(alias.getOldName(), s);
        s.addInstr(new AliasInstr(getSelf(s), newName, oldName));

        return manager.getNil();
    }

    // Translate "ret = (a && b)" --> "ret = (a ? b : false)" -->
    //
    //    v1 = -- build(a) --
    //       OPT: ret can be set to v1, but effectively v1 is false if we take the branch to L.
    //            while this info can be inferred by using attributes, why bother if we can do this?
    //    ret = v1
    //    beq(v1, false, L)
    //    v2 = -- build(b) --
    //    ret = v2
    // L:
    //
    public Operand buildAnd(final AndNode andNode, IRScope s) {
        if (andNode.getFirstNode().getNodeType().alwaysTrue()) {
            // build first node (and ignore its result) and then second node
            build(andNode.getFirstNode(), s);
            return build(andNode.getSecondNode(), s);
        } else if (andNode.getFirstNode().getNodeType().alwaysFalse()) {
            // build first node only and return its value
            return build(andNode.getFirstNode(), s);
        } else {
            Label    l   = s.getNewLabel();
            Operand  v1  = build(andNode.getFirstNode(), s);
            Variable ret = getValueInTemporaryVariable(s, v1);
            s.addInstr(BEQInstr.create(v1, manager.getFalse(), l));
            Operand  v2  = build(andNode.getSecondNode(), s);
            s.addInstr(new CopyInstr(ret, v2));
            s.addInstr(new LabelInstr(l));
            return ret;
        }
    }

    public Operand buildArray(Node node, IRScope s) {
        List<Operand> elts = new ArrayList<Operand>();
        for (Node e: node.childNodes())
            elts.add(build(e, s));

        return copyAndReturnValue(s, new Array(elts));
    }

    public Operand buildArgsCat(final ArgsCatNode argsCatNode, IRScope s) {
        Operand v1 = build(argsCatNode.getFirstNode(), s);
        Operand v2 = build(argsCatNode.getSecondNode(), s);
        return new CompoundArray(v1, v2);
    }

    public Operand buildArgsPush(final ArgsPushNode node, IRScope s) {
        throw new NotCompilableException("ArgsPush should never be encountered bare in 1.8" + node);
    }

    private Operand buildAttrAssign(final AttrAssignNode attrAssignNode, IRScope s) {
        Operand obj = build(attrAssignNode.getReceiverNode(), s);
        List<Operand> args = new ArrayList<Operand>();
        Node argsNode = attrAssignNode.getArgsNode();
        Operand lastArg = (argsNode == null) ? manager.getNil() : buildCallArgs(args, argsNode, s);
        s.addInstr(new AttrAssignInstr(obj, new MethAddr(attrAssignNode.getName()), args.toArray(new Operand[args.size()])));
        return lastArg;
    }

    public Operand buildAttrAssignAssignment(Node node, IRScope s, Operand value) {
        final AttrAssignNode attrAssignNode = (AttrAssignNode) node;
        Operand obj = build(attrAssignNode.getReceiverNode(), s);
        List<Operand> args = setupCallArgs(attrAssignNode.getArgsNode(), s);
        args.add(value);
        s.addInstr(new AttrAssignInstr(obj, new MethAddr(attrAssignNode.getName()), args.toArray(new Operand[args.size()])));
        return value;
    }

    public Operand buildBackref(BackRefNode node, IRScope s) {
        // SSS FIXME: Required? Verify with Tom/Charlie
        return copyAndReturnValue(s, new Backref(node.getType()));
    }

    public Operand buildBegin(BeginNode beginNode, IRScope s) {
        return build(beginNode.getBodyNode(), s);
    }

    public Operand buildBignum(BignumNode node, IRScope s) {
        // SSS: Since bignum literals are effectively interned objects, no need to copyAndReturnValue(...)
        // Or is this a premature optimization?
        return new Bignum(node.getValue());
    }

    public Operand buildBlock(BlockNode node, IRScope s) {
        Operand retVal = null;
        for (Node child : node.childNodes()) {
            retVal = build(child, s);
        }

        // Value of the last expression in the block
        return retVal;
    }

    public Operand buildBreak(BreakNode breakNode, IRScope s) {
        IRLoop currLoop = getCurrentLoop();

        Operand rv = build(breakNode.getValueNode(), s);
        // If we have ensure blocks, have to run those first!
        if (!_ensureBlockStack.empty()) EnsureBlockInfo.emitJumpChain(s, _ensureBlockStack, currLoop);
        else if (!_rescueBlockStack.empty()) _rescueBlockStack.peek().restoreException(s, currLoop);

        if (currLoop != null) {
            s.addInstr(new CopyInstr(currLoop.loopResult, rv));
            s.addInstr(new JumpInstr(currLoop.loopEndLabel));
        } else {
            if (s instanceof IRClosure) {
                // This lexical scope value is only used (and valid) in regular block contexts.
                // If this instruction is executed in a Proc or Lambda context, the lexical scope value is useless.
                IRScope returnScope = s.getLexicalParent();
                if (is1_9() || is2_0()) {
                    // In 1.9 and later modes, no breaks from evals
                    if (s instanceof IREvalScript) s.addInstr(new ThrowExceptionInstr(IRException.BREAK_LocalJumpError));
                    else s.addInstr(new BreakInstr(rv, returnScope));
                } else {
                    // In pre-1.9 mode, breaks from evals are legitimate!
                    if (s instanceof IREvalScript) returnScope = returnScope.getLexicalParent();
                    s.addInstr(new BreakInstr(rv, returnScope));
                }
            } else {
                // We are not in a closure or a loop => bad break instr!
                s.addInstr(new ThrowExceptionInstr(IRException.BREAK_LocalJumpError));
            }
        }

        // Once the break instruction executes, control exits this scope
        return UnexecutableNil.U_NIL;
    }

    private void handleNonlocalReturnInMethod(IRScope s) {
        Label rBeginLabel = s.getNewLabel();
        Label rEndLabel   = s.getNewLabel();
        Label gebLabel    = s.getNewLabel();

        // protect the entire body as it exists now with the global ensure block
        s.addInstrAtBeginning(new ExceptionRegionStartMarkerInstr(rBeginLabel, rEndLabel, gebLabel, gebLabel));
        s.addInstr(new ExceptionRegionEndMarkerInstr());

        // Receive exceptions (could be anything, but the handler only processes IRReturnJumps)
        s.addInstr(new LabelInstr(gebLabel));
        Variable exc = s.getNewTemporaryVariable();
        s.addInstr(new ReceiveExceptionInstr(exc, false));  // no type-checking

        // Handle break using runtime helper
        // --> IRRuntimeHelpers.handleNonlocalReturn(scope, bj, blockType)
        Variable ret = s.getNewTemporaryVariable();
        s.addInstr(new RuntimeHelperCall(ret, "handleNonlocalReturn", new Operand[]{exc} ));
        s.addInstr(new ReturnInstr(ret));

        // End
        s.addInstr(new LabelInstr(rEndLabel));
    }

    // Wrap call in a rescue handler that catches the IRBreakJump
    private void receiveBreakException(IRScope s, Operand block, CallInstr callInstr) {
        // Check if we have to handle a break
        if (block != null && block instanceof WrappedIRClosure) {
            IRClosure closure = ((WrappedIRClosure)block).getClosure();
            if (!closure.hasBreakInstrs) {
                // No protection needed -- add the call and return
                s.addInstr(callInstr);
                return;
            }
        } else {
            // No protection needed -- add the call and return
            s.addInstr((Instr)callInstr);
            return;
        }

        Label rBeginLabel = s.getNewLabel();
        Label rEndLabel   = s.getNewLabel();
        Label rescueLabel = s.getNewLabel();

        // Protected region
        s.addInstr(new ExceptionRegionStartMarkerInstr(rBeginLabel, rEndLabel, null, rescueLabel));
        s.addInstr(callInstr);
        s.addInstr(new JumpInstr(rEndLabel));
        s.addInstr(new ExceptionRegionEndMarkerInstr());

        // Receive exceptions (could be anything, but the handler only processes IRBreakJumps)
        s.addInstr(new LabelInstr(rescueLabel));
        Variable exc = s.getNewTemporaryVariable();
        s.addInstr(new ReceiveExceptionInstr(exc));

        // Handle break using runtime helper
        // --> IRRuntimeHelpers.handlePropagatedBreak(context, scope, bj, blockType)
        s.addInstr(new RuntimeHelperCall(callInstr.getResult(), "handlePropagatedBreak", new Operand[]{exc} ));

        // End
        s.addInstr(new LabelInstr(rEndLabel));
    }

    public Operand buildCall(CallNode callNode, IRScope s) {
        Node          callArgsNode = callNode.getArgsNode();
        Node          receiverNode = callNode.getReceiverNode();
        // Though you might be tempted to move this build into the CallInstr as:
        //    new Callinstr( ... , build(receiverNode, s), ...)
        // that is incorrect IR because the receiver has to be built *before* call arguments are built
        // to preserve expected code execution order
        Operand       receiver     = build(receiverNode, s);
        List<Operand> args         = setupCallArgs(callArgsNode, s);
        Operand       block        = setupCallClosure(callNode.getIterNode(), s);
        Variable      callResult   = s.getNewTemporaryVariable();
        CallInstr     callInstr    = CallInstr.create(callResult, new MethAddr(callNode.getName()), receiver, args.toArray(new Operand[args.size()]), block);
        receiveBreakException(s, block, callInstr);
        return callResult;
    }

    public Operand buildCase(CaseNode caseNode, IRScope s) {
        // get the incoming case value
        Operand value = build(caseNode.getCaseNode(), s);

        // This is for handling case statements without a value (see example below)
        //   case
        //     when true <blah>
        //     when false <blah>
        //   end
        if (value == null) value = UndefinedValue.UNDEFINED;

        Label     endLabel  = s.getNewLabel();
        boolean   hasElse   = (caseNode.getElseNode() != null);
        Label     elseLabel = s.getNewLabel();
        Variable  result    = s.getNewTemporaryVariable();

        List<Label> labels = new ArrayList<Label>();
        Map<Label, Node> bodies = new HashMap<Label, Node>();

        // build each "when"
        for (Node aCase : caseNode.getCases().childNodes()) {
            WhenNode whenNode = (WhenNode)aCase;
            Label bodyLabel = s.getNewLabel();

            Variable eqqResult = s.getNewTemporaryVariable();
            labels.add(bodyLabel);
            Operand v1, v2;
            if (whenNode.getExpressionNodes() instanceof ListNode) {
                // SSS FIXME: Note about refactoring:
                // - BEQInstr has a quick implementation when the second operand is a boolean literal
                //   If it can be fixed to do this even on the first operand, we can switch around
                //   v1 and v2 in the UndefinedValue scenario and DRY out this code.
                // - Even with this asymmetric implementation of BEQInstr, you might be tempted to
                //   switch around v1 and v2 in the else case.  But, that is equivalent to this Ruby code change:
                //      (v1 == value) instead of (value == v1)
                //   It seems that they should be identical, but the first one is v1.==(value) and the second one is
                //   value.==(v1).  This is just fine *if* the Ruby programmer has implemented an algebraically
                //   symmetric "==" method on those objects.  If not, then, the results might be unexpected where the
                //   code (intentionally or otherwise) relies on this asymmetry of "==".  While it could be argued
                //   that this a Ruby code bug, we will just try to preserve the order of the == check as it appears
                //   in the Ruby code.
                if (value == UndefinedValue.UNDEFINED)  {
                    v1 = build(whenNode.getExpressionNodes(), s);
                    v2 = manager.getTrue();
                } else {
                    v1 = value;
                    v2 = build(whenNode.getExpressionNodes(), s);
                }
            } else {
                s.addInstr(new EQQInstr(eqqResult, build(whenNode.getExpressionNodes(), s), value));
                v1 = eqqResult;
                v2 = manager.getTrue();
            }
            s.addInstr(BEQInstr.create(v1, v2, bodyLabel));

            // SSS FIXME: This doesn't preserve original order of when clauses.  We could consider
            // preserving the order (or maybe not, since we would have to sort the constants first
            // in any case) for outputing jump tables in certain situations.
            //
            // add body to map for emitting later
            bodies.put(bodyLabel, whenNode.getBodyNode());
        }

        // Jump to else in case nothing matches!
        s.addInstr(new JumpInstr(elseLabel));

        // build "else" if it exists
        if (hasElse) {
            labels.add(elseLabel);
            bodies.put(elseLabel, caseNode.getElseNode());
        }

        // now emit bodies while preserving when clauses order
        for (Label whenLabel: labels) {
            s.addInstr(new LabelInstr(whenLabel));
            Operand bodyValue = build(bodies.get(whenLabel), s);
            // bodyValue can be null if the body ends with a return!
            if (bodyValue != null) {
               // SSS FIXME: Do local optimization of break results (followed by a copy & jump) to short-circuit the jump right away
               // rather than wait to do it during an optimization pass when a dead jump needs to be removed.  For this, you have
               // to look at what the last generated instruction was.
               Label tgt = endLabel;
               s.addInstr(new CopyInstr(result, bodyValue));
               s.addInstr(new JumpInstr(tgt));
            }
        }

        if (!hasElse) {
            s.addInstr(new LabelInstr(elseLabel));
            s.addInstr(new CopyInstr(result, manager.getNil()));
            s.addInstr(new JumpInstr(endLabel));
        }

        // close it out
        s.addInstr(new LabelInstr(endLabel));

        // SSS: Got rid of the marker case label instruction

        return result;
    }

    /**
     * Build a new class and add it to the current scope (s).
     */
    public Operand buildClass(ClassNode classNode, IRScope s) {
        Node superNode = classNode.getSuperNode();
        Colon3Node cpath = classNode.getCPath();
        Operand superClass = (superNode == null) ? null : build(superNode, s);
        String className = cpath.getName();
        Operand container = getContainerFromCPath(cpath, s);

        IRClassBody c = new IRClassBody(manager, s, className, classNode.getPosition().getLine(), classNode.getScope());
        Variable classBody = s.getNewTemporaryVariable();
        s.addInstr(new DefineClassInstr(classBody, c, container, superClass));
        Variable ret = s.getNewTemporaryVariable();
        s.addInstr(new ProcessModuleBodyInstr(ret, classBody));

        c.addInstr(new ReceiveSelfInstr(c.getSelf()));
        // Set %current_scope = <c>
        // Set %current_module = module<c>
        c.addInstr(new CopyInstr(c.getCurrentScopeVariable(), new CurrentScope(c)));
        c.addInstr(new CopyInstr(c.getCurrentModuleVariable(), new ScopeModule(c)));
        // Create a new nested builder to ensure this gets its own IR builder state
        Operand rv = newIRBuilder(manager).build(classNode.getBodyNode(), c);
        if (rv != null) c.addInstr(new ReturnInstr(rv));

        return ret;
    }

    public Operand buildSClass(SClassNode sclassNode, IRScope s) {
        //  class Foo
        //  ...
        //    class << self
        //    ...
        //    end
        //  ...
        //  end
        //
        // Here, the class << self declaration is in Foo's body.
        // Foo is the class in whose context this is being defined.
        Operand receiver = build(sclassNode.getReceiverNode(), s);

        // Create a dummy meta class and record it as being lexically defined in scope s
        IRModuleBody mc = new IRMetaClassBody(manager, s, manager.getMetaClassName(), sclassNode.getPosition().getLine(), sclassNode.getScope());
        Variable classBody = s.getNewTemporaryVariable();
        s.addInstr(new DefineMetaClassInstr(classBody, receiver, mc));
        Variable ret = s.getNewTemporaryVariable();
        s.addInstr(new ProcessModuleBodyInstr(ret, classBody));

        mc.addInstr(new ReceiveSelfInstr(mc.getSelf()));
        // Set %current_scope = <current-scope>
        // Set %current_module = <current-module>
        mc.addInstr(new ReceiveClosureInstr(mc.getImplicitBlockArg()));
        mc.addInstr(new CopyInstr(mc.getCurrentScopeVariable(), new CurrentScope(mc)));
        mc.addInstr(new CopyInstr(mc.getCurrentModuleVariable(), new ScopeModule(mc)));
        // Create a new nested builder to ensure this gets its own IR builder state
        Operand rv = newIRBuilder(manager).build(sclassNode.getBodyNode(), mc);
        if (rv != null) mc.addInstr(new ReturnInstr(rv));

        return ret;
    }

    // @@c
    public Operand buildClassVar(ClassVarNode node, IRScope s) {
        Variable ret = s.getNewTemporaryVariable();
        s.addInstr(new GetClassVariableInstr(ret, classVarDefinitionContainer(s), node.getName()));
        return ret;
    }

    // ClassVarAsgn node is assignment within a method/closure scope
    //
    // def foo
    //   @@c = 1
    // end
    public Operand buildClassVarAsgn(final ClassVarAsgnNode classVarAsgnNode, IRScope s) {
        Operand val = build(classVarAsgnNode.getValueNode(), s);
        s.addInstr(new PutClassVariableInstr(classVarDefinitionContainer(s), classVarAsgnNode.getName(), val));
        return val;
    }

    // ClassVarDecl node is assignment outside method/closure scope (top-level, class, module)
    //
    // class C
    //   @@c = 1
    // end
    public Operand buildClassVarDecl(final ClassVarDeclNode classVarDeclNode, IRScope s) {
        Operand val = build(classVarDeclNode.getValueNode(), s);
        s.addInstr(new PutClassVariableInstr(classVarDeclarationContainer(s), classVarDeclNode.getName(), val));
        return val;
    }

    public Operand classVarDeclarationContainer(IRScope s) {
        return classVarContainer(s, true);
    }

    public Operand classVarDefinitionContainer(IRScope s) {
        return classVarContainer(s, false);
    }

    // SSS FIXME: This feels a little ugly.  Is there a better way of representing this?
    public Operand classVarContainer(IRScope s, boolean declContext) {
        /* -------------------------------------------------------------------------------
         * We are looking for the nearest enclosing scope that is a non-singleton class body
         * without running into an eval-scope in between.
         *
         * Stop lexical scope walking at an eval script boundary.  Evals are essentially
         * a way for a programmer to splice an entire tree of lexical scopes at the point
         * where the eval happens.  So, when we hit an eval-script boundary at compile-time,
         * defer scope traversal to when we know where this scope has been spliced in.
         * ------------------------------------------------------------------------------- */
        IRScope cvarScope = s;
        while (cvarScope != null && !(cvarScope instanceof IREvalScript) && !cvarScope.isNonSingletonClassBody()) {
            cvarScope = cvarScope.getLexicalParent();
        }

        if ((cvarScope != null) && cvarScope.isNonSingletonClassBody()) {
            return new ScopeModule(cvarScope);
        } else {
            Variable tmp = s.getNewTemporaryVariable();
            s.addInstr(new GetClassVarContainerModuleInstr(tmp, s.getCurrentScopeVariable(), declContext ? null : getSelf(s)));
            return tmp;
        }
    }

    public Operand buildConstDecl(ConstDeclNode node, IRScope s) {
        Operand val = build(node.getValueNode(), s);
        return buildConstDeclAssignment(node, s, val);
    }

    private Operand findContainerModule(IRScope s) {
        IRScope nearestModuleBody = s.getNearestModuleReferencingScope();
        return (nearestModuleBody == null) ? s.getCurrentModuleVariable() : new ScopeModule(nearestModuleBody);
    }

    private Operand startingSearchScope(IRScope s) {
        IRScope nearestModuleBody = s.getNearestModuleReferencingScope();
        return nearestModuleBody == null ? s.getCurrentScopeVariable() : new CurrentScope(nearestModuleBody);
    }

    public Operand buildConstDeclAssignment(ConstDeclNode constDeclNode, IRScope s, Operand val) {
        Node constNode = constDeclNode.getConstNode();

        if (constNode == null) {
            s.addInstr(new PutConstInstr(findContainerModule(s), constDeclNode.getName(), val));
        } else if (constNode.getNodeType() == NodeType.COLON2NODE) {
            Operand module = build(((Colon2Node) constNode).getLeftNode(), s);
            s.addInstr(new PutConstInstr(module, constDeclNode.getName(), val));
        } else { // colon3, assign in Object
            ScopeModule object = new ScopeModule(manager.getObject());
            s.addInstr(new PutConstInstr(object, constDeclNode.getName(), val));
        }

        return val;
    }

    private void genInheritanceSearchInstrs(IRScope s, Operand startingModule, Variable constVal, Label foundLabel, boolean noPrivateConstants, String name) {
        s.addInstr(new InheritanceSearchConstInstr(constVal, startingModule, name, noPrivateConstants));
        s.addInstr(BNEInstr.create(constVal, UndefinedValue.UNDEFINED, foundLabel));
        s.addInstr(new ConstMissingInstr(constVal, startingModule, name));
        s.addInstr(new LabelInstr(foundLabel));
    }

    private Operand searchConstInInheritanceHierarchy(IRScope s, Operand startingModule, String name) {
        Variable constVal = s.getNewTemporaryVariable();
        genInheritanceSearchInstrs(s, startingModule, constVal, s.getNewLabel(), true, name);
        return constVal;
    }

    private Operand searchConst(IRScope s, IRScope startingScope, String name) {
        boolean noPrivateConstants = (s != startingScope);
        Variable v = s.getNewTemporaryVariable();
/**
 * SSS FIXME: Go back to a single instruction for now.
 *
 * Do not split search into lexical-search, inheritance-search, and const-missing instrs.
 *
        Label foundLabel = s.getNewLabel();
        s.addInstr(new LexicalSearchConstInstr(v, startingSearchScope(startingScope), name));
        s.addInstr(BNEInstr.create(v, UndefinedValue.UNDEFINED, foundLabel));
        genInheritanceSearchInstrs(s, findContainerModule(startingScope), v, foundLabel, noPrivateConstants, name);
**/
        s.addInstr(new SearchConstInstr(v, name, startingSearchScope(startingScope), noPrivateConstants));
        return v;
    }

    public Operand buildColon2(final Colon2Node iVisited, IRScope s) {
        Node leftNode = iVisited.getLeftNode();
        final String name = iVisited.getName();

        // ENEBO: Does this really happen?
        if (leftNode == null) return searchConst(s, s, name);

        if (iVisited instanceof Colon2ConstNode) {
            // 1. Load the module first (lhs of node)
            // 2. Then load the constant from the module
            Operand module = build(leftNode, s);
            return searchConstInInheritanceHierarchy(s, module, name);
        } else if (iVisited instanceof Colon2MethodNode) {
            Colon2MethodNode c2mNode = (Colon2MethodNode)iVisited;
            List<Operand> args       = setupCallArgs(null, s);
            Variable      callResult = s.getNewTemporaryVariable();
            Instr         callInstr  = CallInstr.create(callResult, new MethAddr(c2mNode.getName()),
                    null, args.toArray(new Operand[args.size()]), null);
            s.addInstr(callInstr);
            return callResult;
        } else {
            throw new NotCompilableException("Not compilable: " + iVisited);
        }
    }

    public Operand buildColon3(Colon3Node node, IRScope s) {
        return searchConstInInheritanceHierarchy(s, new ObjectClass(), node.getName());
    }

    interface CodeBlock {
        public Operand run(Object[] args);
    }

    private Operand protectCodeWithRescue(IRScope m, CodeBlock protectedCode, Object[] protectedCodeArgs, CodeBlock rescueBlock, Object[] rescueBlockArgs) {
        // This effectively mimics a begin-rescue-end code block
        // Except this catches all exceptions raised by the protected code

        Variable rv = m.getNewTemporaryVariable();
        Label rBeginLabel = m.getNewLabel();
        Label rEndLabel   = m.getNewLabel();
        Label rescueLabel = m.getNewLabel();

        // Protected region code
        m.addInstr(new LabelInstr(rBeginLabel));
        m.addInstr(new ExceptionRegionStartMarkerInstr(rBeginLabel, rEndLabel, null, rescueLabel));
        Object v1 = protectedCode.run(protectedCodeArgs); // YIELD: Run the protected code block
        m.addInstr(new CopyInstr(rv, (Operand)v1));
        m.addInstr(new JumpInstr(rEndLabel));
        m.addInstr(new ExceptionRegionEndMarkerInstr());

        // SSS FIXME: Create an 'Exception' operand type to eliminate the constant lookup below
        // We could preload a set of constant objects that are preloaded at boot time and use them
        // directly in IR when we know there is no lookup involved.
        //
        // new Operand type: CachedClass(String name)?
        //
        // Some candidates: Exception, StandardError, Fixnum, Object, Boolean, etc.
        // So, when they are referenced, they are fetched directly from the runtime object
        // which probably already has cached references to these constants.
        //
        // But, unsure if this caching is safe ... so, just an idea here for now.

        // Rescue code
        Label caughtLabel = m.getNewLabel();
        Variable exc = m.getNewTemporaryVariable();
        Variable excType = m.getNewTemporaryVariable();

        // Receive 'exc' and verify that 'exc' is of ruby-type 'Exception'
        m.addInstr(new LabelInstr(rescueLabel));
        m.addInstr(new ReceiveExceptionInstr(exc));
        m.addInstr(new InheritanceSearchConstInstr(excType, new ObjectClass(), "Exception", false));
        outputExceptionCheck(m, excType, exc, caughtLabel);

        // Fall-through when the exc !== Exception; rethrow 'exc'
        m.addInstr(new ThrowExceptionInstr(exc));

        // exc === Exception; Run the rescue block
        m.addInstr(new LabelInstr(caughtLabel));
        Object v2 = rescueBlock.run(rescueBlockArgs); // YIELD: Run the protected code block
        if (v2 != null) m.addInstr(new CopyInstr(rv, manager.getNil()));

        // End
        m.addInstr(new LabelInstr(rEndLabel));

        return rv;
    }

    protected Operand buildGenericGetDefinitionIR(Node node, IRScope s) {
        return buildGetDefinition(node, s);
    }

    protected Operand buildVersionSpecificGetDefinitionIR(Node node, IRScope s) {
        switch (node.getNodeType()) {
            case DVARNODE:
            case BACKREFNODE:
                return buildGetDefinition(node, s);
            default:
                return buildGenericGetDefinitionIR(node, s);
        }
    }

    public Operand buildGetDefinitionBase(Node node, IRScope s) {
        node = skipOverNewlines(s, node);
        switch (node.getNodeType()) {
        case CLASSVARASGNNODE:
        case CLASSVARDECLNODE:
        case CONSTDECLNODE:
        case DASGNNODE:
        case GLOBALASGNNODE:
        case LOCALASGNNODE:
        case MULTIPLEASGNNODE:
        case OPASGNNODE:
        case OPELEMENTASGNNODE:
        case FALSENODE:
        case TRUENODE:
        case LOCALVARNODE:
        case INSTVARNODE:
        case SELFNODE:
        case VCALLNODE:
        case YIELDNODE:
        case GLOBALVARNODE:
        case CONSTNODE:
        case FCALLNODE:
        case CLASSVARNODE:
            // these are all "simple" cases that don't require the heavier defined logic
            return buildGetDefinition(node, s);

        default:
            return buildVersionSpecificGetDefinitionIR(node, s);
        }
    }

    protected Variable buildDefnCheckIfThenPaths(IRScope s, Label undefLabel, Operand defVal) {
        Label defLabel = s.getNewLabel();
        Variable tmpVar = getValueInTemporaryVariable(s, defVal);
        s.addInstr(new JumpInstr(defLabel));
        s.addInstr(new LabelInstr(undefLabel));
        s.addInstr(new CopyInstr(tmpVar, manager.getNil()));
        s.addInstr(new LabelInstr(defLabel));
        return tmpVar;
    }

    protected Variable buildDefinitionCheck(IRScope s, ResultInstr definedInstr, String definedReturnValue) {
        Label undefLabel = s.getNewLabel();
        s.addInstr((Instr) definedInstr);
        s.addInstr(BEQInstr.create(definedInstr.getResult(), manager.getFalse(), undefLabel));
        return buildDefnCheckIfThenPaths(s, undefLabel, new StringLiteral(definedReturnValue));
    }

    public Operand buildGetArgumentDefinition(final Node node, IRScope s, String type) {
        if (node == null) return new StringLiteral(type);

        Operand rv = new StringLiteral(type);
        boolean failPathReqd = false;
        Label failLabel = s.getNewLabel();
        if (node instanceof ArrayNode) {
            for (int i = 0; i < ((ArrayNode) node).size(); i++) {
                Node iterNode = ((ArrayNode) node).get(i);
                Operand def = buildGetDefinition(iterNode, s);
                if (def == manager.getNil()) { // Optimization!
                    rv = manager.getNil();
                    break;
                } else if (!def.hasKnownValue()) { // Optimization!
                    failPathReqd = true;
                    s.addInstr(BEQInstr.create(def, manager.getNil(), failLabel));
                }
            }
        } else {
            Operand def = buildGetDefinition(node, s);
            if (def == manager.getNil()) { // Optimization!
                rv = manager.getNil();
            } else if (!def.hasKnownValue()) { // Optimization!
                failPathReqd = true;
                s.addInstr(BEQInstr.create(def, manager.getNil(), failLabel));
            }
        }

        // Optimization!
        return failPathReqd ? buildDefnCheckIfThenPaths(s, failLabel, rv) : rv;

    }

    public Operand buildGetDefinition(Node defnNode, IRScope s) {
        final Node node = skipOverNewlines(s, defnNode);
        switch (node.getNodeType()) {
            case CLASSVARASGNNODE:
            case CLASSVARDECLNODE:
            case CONSTDECLNODE:
            case DASGNNODE:
            case GLOBALASGNNODE:
            case LOCALASGNNODE:
            case MULTIPLEASGNNODE:
            case OPASGNNODE:
            case OPASGNANDNODE:
            case OPASGNORNODE:
            case OPELEMENTASGNNODE:
            case INSTASGNNODE: // simple assignment cases
                return new StringLiteral("assignment");
            case DVARNODE:
                return new StringLiteral("local-variable(in-block)");
            case FALSENODE:
                return new StringLiteral("false");
            case TRUENODE:
                return new StringLiteral("true");
            case LOCALVARNODE:
                return new StringLiteral("local-variable");
            case MATCH2NODE:
            case MATCH3NODE:
                return new StringLiteral("method");
            case NILNODE:
                return new StringLiteral("nil");
            case SELFNODE:
                return new StringLiteral("self");
            case CONSTNODE: {
                Label defLabel = s.getNewLabel();
                Label doneLabel = s.getNewLabel();
                Variable tmpVar  = s.getNewTemporaryVariable();
                String constName = ((ConstNode) node).getName();
                s.addInstr(new LexicalSearchConstInstr(tmpVar, startingSearchScope(s), constName));
                s.addInstr(BNEInstr.create(tmpVar, UndefinedValue.UNDEFINED, defLabel));
                s.addInstr(new InheritanceSearchConstInstr(tmpVar, findContainerModule(s), constName, false)); // SSS FIXME: should this be the current-module var or something else?
                s.addInstr(BNEInstr.create(tmpVar, UndefinedValue.UNDEFINED, defLabel));
                s.addInstr(new CopyInstr(tmpVar, manager.getNil()));
                s.addInstr(new JumpInstr(doneLabel));
                s.addInstr(new LabelInstr(defLabel));
                s.addInstr(new CopyInstr(tmpVar, new StringLiteral("constant")));
                s.addInstr(new LabelInstr(doneLabel));
                return tmpVar;
            }
            case GLOBALVARNODE:
                return buildDefinitionCheck(s, new GlobalIsDefinedInstr(s.getNewTemporaryVariable(), new StringLiteral(((GlobalVarNode) node).getName())), "global-variable");
            case INSTVARNODE:
                return buildDefinitionCheck(s, new HasInstanceVarInstr(s.getNewTemporaryVariable(), getSelf(s), new StringLiteral(((InstVarNode) node).getName())), "instance-variable");
            case YIELDNODE:
                return buildDefinitionCheck(s, new BlockGivenInstr(s.getNewTemporaryVariable(), s.getImplicitBlockArg()), "yield");
            case BACKREFNODE:
                return buildDefinitionCheck(s, new BackrefIsMatchDataInstr(s.getNewTemporaryVariable()), "$" + ((BackRefNode) node).getType());
            case NTHREFNODE: {
            // SSS FIXME: Is there a reason to do this all with low-level IR?
            // Can't this all be folded into a Java method that would be part
            // of the runtime library, which then can be used by buildDefinitionCheck method above?
            // This runtime library would be used both by the interpreter & the compiled code!

                /* -------------------------------------------------------------------------------------
                 * We have to generate IR for this:
                 *    v = backref; (!(v instanceof RubyMatchData) || v.group(n).nil?) ? nil : "$#{n}"
                 *
                 * which happens to be identical to: (where nthRef implicitly fetches backref again!)
                 *    v = backref; (!(v instanceof RubyMatchData) || nthRef(n).nil?) ? nil : "$#{n}"
                 *
                 * I am using the second form since it let us encode it in fewer IR instructions.
                 * But, note that this second form is not as clean as the first one plus it fetches backref twice!
                 * ------------------------------------------------------------------------------------- */
                int n = ((NthRefNode) node).getMatchNumber();
                Label undefLabel = s.getNewLabel();
                Variable tmpVar = s.getNewTemporaryVariable();
                s.addInstr(new BackrefIsMatchDataInstr(tmpVar));
                s.addInstr(BEQInstr.create(tmpVar, manager.getFalse(), undefLabel));
                // SSS FIXME:
                // - Can/should I use BEQInstr(new NthRef(n), manager.getNil(), undefLabel)? instead of .nil? & compare with flag?
                // - Or, even create a new IsNilInstr and NotNilInstr to represent optimized scenarios where
                //   the nil? method is not monkey-patched?
                // This matters because if String.nil? is monkey-patched, the two sequences can behave differently.
                s.addInstr(CallInstr.create(tmpVar, new MethAddr("nil?"), new NthRef(n), NO_ARGS, null));
                s.addInstr(BEQInstr.create(tmpVar, manager.getTrue(), undefLabel));
                return buildDefnCheckIfThenPaths(s, undefLabel, new StringLiteral("$" + n));
            }
            case COLON3NODE:
            case COLON2NODE: {
            // SSS FIXME: Is there a reason to do this all with low-level IR?
            // Can't this all be folded into a Java method that would be part
            // of the runtime library, which then can be used by buildDefinitionCheck method above?
            // This runtime library would be used both by the interpreter & the compiled code!

                final Colon3Node iVisited = (Colon3Node) node;
                final String name = iVisited.getName();

                // store previous exception for restoration if we rescue something
                Variable errInfo = s.getNewTemporaryVariable();
                s.addInstr(new GetErrorInfoInstr(errInfo));

                CodeBlock protectedCode = new CodeBlock() {
                    public Operand run(Object[] args) {
                        IRScope s    = (IRScope)args[0];
                        Node    n    = (Node)args[1];
                        String  name = (String)args[2];
                        Operand v    = (n instanceof Colon2Node) ? build(((Colon2Node)n).getLeftNode(), s) : new ObjectClass();

                        Variable tmpVar = s.getNewTemporaryVariable();
                        s.addInstr(new GetDefinedConstantOrMethodInstr(tmpVar, v, new StringLiteral(name)));
                        return tmpVar;
                    }
                };

                // rescue block
                CodeBlock rescueBlock = new CodeBlock() {
                    public Operand run(Object[] args) {
                        // Nothing to do -- ignore the exception, and restore stashed error info!
                        IRScope  m  = (IRScope)args[0];
                        m.addInstr(new RestoreErrorInfoInstr((Operand) args[1]));
                        return manager.getNil();
                    }
                };

                // Try verifying definition, and if we get an JumpException exception, process it with the rescue block above
                return protectCodeWithRescue(s, protectedCode, new Object[]{s, iVisited, name}, rescueBlock, new Object[] {s, errInfo});
            }
            case FCALLNODE: {
                /* ------------------------------------------------------------------
                 * Generate IR for:
                 *    r = self/receiver
                 *    mc = r.metaclass
                 *    return mc.methodBound(meth) ? buildGetArgumentDefn(..) : false
                 * ----------------------------------------------------------------- */
                Label undefLabel = s.getNewLabel();
                Variable tmpVar = s.getNewTemporaryVariable();
                StringLiteral mName = new StringLiteral(((FCallNode)node).getName());
                s.addInstr(new IsMethodBoundInstr(tmpVar, getSelf(s), mName));
                s.addInstr(BEQInstr.create(tmpVar, manager.getFalse(), undefLabel));
                Operand argsCheckDefn = buildGetArgumentDefinition(((FCallNode) node).getArgsNode(), s, "method");
                return buildDefnCheckIfThenPaths(s, undefLabel, argsCheckDefn);
            }
            case VCALLNODE:
                return buildDefinitionCheck(s, new IsMethodBoundInstr(s.getNewTemporaryVariable(), getSelf(s), new StringLiteral(((VCallNode) node).getName())), "method");
            case CALLNODE: {
            // SSS FIXME: Is there a reason to do this all with low-level IR?
            // Can't this all be folded into a Java method that would be part
            // of the runtime library?

                Label    undefLabel = s.getNewLabel();
                CallNode iVisited = (CallNode) node;
                Operand  receiverDefn = buildGetDefinition(iVisited.getReceiverNode(), s);
                s.addInstr(BEQInstr.create(receiverDefn, manager.getNil(), undefLabel));

                // protected main block
                CodeBlock protectedCode = new CodeBlock() {
                    public Operand run(Object[] args) {
                        IRScope  s          = (IRScope)args[0];
                        CallNode iVisited   = (CallNode)args[1];
                        String   methodName = iVisited.getName();
                        Variable tmpVar     = s.getNewTemporaryVariable();
                        Operand  receiver   = build(iVisited.getReceiverNode(), s);
                        s.addInstr(new MethodDefinedInstr(tmpVar, receiver, new StringLiteral(methodName)));
                        return buildDefnCheckIfThenPaths(s, (Label)args[2], tmpVar);
                    }
                };

                // rescue block
                CodeBlock rescueBlock = new CodeBlock() {
                    public Operand run(Object[] args) { return manager.getNil(); } // Nothing to do if we got an exception
                };

                // Try verifying definition, and if we get an exception, throw it out, and return nil
                return protectCodeWithRescue(s, protectedCode, new Object[]{s, iVisited, undefLabel}, rescueBlock, null);
            }
            case CLASSVARNODE: {
            // SSS FIXME: Is there a reason to do this all with low-level IR?
            // Can't this all be folded into a Java method that would be part
            // of the runtime library, which would be used both by the interpreter & the compiled code!

                /* --------------------------------------------------------------------------
                 * Generate IR for this ruby pseudo-code:
                 *   cm = tc.getCurrentScope.getStaticScope.getModule || self.metaclass
                 *   cm.isClassVarDefined ? "class variable" : nil
                 * ------------------------------------------------------------------------------ */
                ClassVarNode iVisited = (ClassVarNode) node;
                Operand cm = classVarDefinitionContainer(s);
                return buildDefinitionCheck(s, new ClassVarIsDefinedInstr(s.getNewTemporaryVariable(), cm, new StringLiteral(iVisited.getName())), "class variable");
            }
            case ATTRASSIGNNODE: {
                Label  undefLabel = s.getNewLabel();
                AttrAssignNode iVisited = (AttrAssignNode) node;
                Operand receiverDefn = buildGetDefinition(iVisited.getReceiverNode(), s);
                s.addInstr(BEQInstr.create(receiverDefn, manager.getNil(), undefLabel));

                // protected main block
                CodeBlock protectedCode = new CodeBlock() {
                    public Operand run(Object[] args) {
                        /* --------------------------------------------------------------------------
                         * This basically combines checks from CALLNODE and FCALLNODE
                         *
                         * Generate IR for this sequence
                         *
                         *    1. r  = receiver
                         *    2. mc = r.metaClass
                         *    3. v  = mc.getVisibility(methodName)
                         *    4. f  = !v || v.isPrivate? || (v.isProtected? && receiver/self?.kindof(mc.getRealClass))
                         *    5. return !f && mc.methodBound(attrmethod) ? buildGetArgumentDefn(..) : false
                         *
                         * Hide the complexity of instrs 2-4 into a verifyMethodIsPublicAccessible call
                         * which can executely entirely in Java-land.  No reason to expose the guts in IR.
                         * ------------------------------------------------------------------------------ */
                        IRScope s = (IRScope)args[0];
                        AttrAssignNode iVisited = (AttrAssignNode)args[1];
                        Label undefLabel = (Label)args[2];
                        StringLiteral attrMethodName = new StringLiteral(iVisited.getName());
                        Variable tmpVar     = s.getNewTemporaryVariable();
                        Operand  receiver   = build(iVisited.getReceiverNode(), s);
                        s.addInstr(new MethodIsPublicInstr(tmpVar, receiver, attrMethodName));
                        s.addInstr(BEQInstr.create(tmpVar, manager.getFalse(), undefLabel));
                        s.addInstr(new IsMethodBoundInstr(tmpVar, getSelf(s), attrMethodName));
                        s.addInstr(BEQInstr.create(tmpVar, manager.getFalse(), undefLabel));
                        Operand argsCheckDefn = buildGetArgumentDefinition(((AttrAssignNode) node).getArgsNode(), s, "assignment");
                        return buildDefnCheckIfThenPaths(s, undefLabel, argsCheckDefn);
                    }
                };

                // rescue block
                CodeBlock rescueBlock = new CodeBlock() {
                    public Operand run(Object[] args) { return manager.getNil(); } // Nothing to do if we got an exception
                };

                // Try verifying definition, and if we get an JumpException exception, process it with the rescue block above
                return protectCodeWithRescue(s, protectedCode, new Object[]{s, iVisited, undefLabel}, rescueBlock, null);
            }
            case ZSUPERNODE:
                return buildDefinitionCheck(s, new SuperMethodBoundInstr(s.getNewTemporaryVariable(), getSelf(s)), "super");
            case SUPERNODE: {
                Label undefLabel = s.getNewLabel();
                Variable tmpVar  = s.getNewTemporaryVariable();
                s.addInstr(new SuperMethodBoundInstr(tmpVar, getSelf(s)));
                s.addInstr(BEQInstr.create(tmpVar, manager.getFalse(), undefLabel));
                Operand superDefnVal = buildGetArgumentDefinition(((SuperNode) node).getArgsNode(), s, "super");
                return buildDefnCheckIfThenPaths(s, undefLabel, superDefnVal);
            }
            default: {
                // protected code
                CodeBlock protectedCode = new CodeBlock() {
                    public Operand run(Object[] args) {
                        build((Node)args[0], (IRScope)args[1]);
                        // always an expression as long as we get through here without an exception!
                        return new StringLiteral("expression");
                    }
                };
                // rescue block
                CodeBlock rescueBlock = new CodeBlock() {
                    public Operand run(Object[] args) { return manager.getNil(); } // Nothing to do if we got an exception
                };

                // Try verifying definition, and if we get an JumpException exception, process it with the rescue block above
                return protectCodeWithRescue(s, protectedCode, new Object[]{node, s}, rescueBlock, null);
            }
        }
    }

    public Operand buildDAsgn(final DAsgnNode dasgnNode, IRScope s) {
        // SSS: Looks like we receive the arg in buildBlockArgsAssignment via the IterNode
        // We won't get here for argument receives!  So, buildDasgn is called for
        // assignments to block variables within a block.  As far as the IR is concerned,
        // this is just a simple copy
        int depth = dasgnNode.getDepth();
        Variable arg = s.getLocalVariable(dasgnNode.getName(), depth);
        Operand  value = build(dasgnNode.getValueNode(), s);
        s.addInstr(new CopyInstr(arg, value));
        return value;

        // IMPORTANT: The return value of this method is value, not arg!
        //
        // Consider this Ruby code: foo((a = 1), (a = 2))
        //
        // If we return 'value' this will get translated to:
        //    a = 1
        //    a = 2
        //    call("foo", [1,2]) <---- CORRECT
        //
        // If we return 'arg' this will get translated to:
        //    a = 1
        //    a = 2
        //    call("foo", [a,a]) <---- BUGGY
        //
        // This technique only works if 'value' is an immutable value (ex: fixnum) or a variable
        // So, for Ruby code like this:
        //     def foo(x); x << 5; end;
        //     foo(a=[1,2]);
        //     p a
        // we are guaranteed that the value passed into foo and 'a' point to the same object
        // because of the use of copyAndReturnValue method for literal objects.
    }

    private IRMethod defineNewMethod(MethodDefNode defNode, IRScope s, boolean isInstanceMethod) {
        IRMethod method = new IRMethod(manager, s, defNode.getName(), isInstanceMethod, defNode.getPosition().getLine(), defNode.getScope());

        method.addInstr(new ReceiveSelfInstr(getSelf(s)));

        // Set %current_scope = <current-scope>
        // Set %current_module = isInstanceMethod ? %self.metaclass : %self
        IRScope nearestScope = s.getNearestModuleReferencingScope();
        method.addInstr(new CopyInstr(method.getCurrentScopeVariable(), new CurrentScope(nearestScope == null ? s : nearestScope)));
        method.addInstr(new CopyInstr(method.getCurrentModuleVariable(), new ScopeModule(nearestScope == null ? s : nearestScope)));

        // Build IR for arguments (including the block arg)
        receiveMethodArgs(defNode.getArgsNode(), method);

        // Thread poll on entry to method
        method.addInstr(new ThreadPollInstr());

        // Build IR for body
        Node bodyNode = defNode.getBodyNode();
        if (bodyNode != null) {
            // Create a new nested builder to ensure this gets its own IR builder state
            Operand rv = newIRBuilder(manager).build(bodyNode, method);
            if (rv != null) method.addInstr(new ReturnInstr(rv));
        } else {
            method.addInstr(new ReturnInstr(manager.getNil()));
        }

        // If the method can receive non-local returns
        if (method.canReceiveNonlocalReturns()) {
            handleNonlocalReturnInMethod(method);
        }

        return method;
    }

    public Operand buildDefn(MethodDefNode node, IRScope s) { // Instance method
        IRMethod method = defineNewMethod(node, s, true);
        s.addInstr(new DefineInstanceMethodInstr(new StringLiteral("--unused--"), method));
        return manager.getNil();
    }

    public Operand buildDefs(DefsNode node, IRScope s) { // Class method
        Operand container =  build(node.getReceiverNode(), s);
        IRMethod method = defineNewMethod(node, s, false);

        s.addInstr(new DefineClassMethodInstr(container, method));
        return manager.getNil();
    }

    protected int receiveOptArgs(final ArgsNode argsNode, IRScope s, int opt, int argIndex) {
        ListNode optArgs = argsNode.getOptArgs();
        for (int j = 0; j < opt; j++, argIndex++) {
            // Jump to 'l' if this arg is not null.  If null, fall through and build the default value!
            Label l = s.getNewLabel();
            LocalAsgnNode n = (LocalAsgnNode)optArgs.get(j);
            String argName = n.getName();
            Variable av = s.getLocalVariable(argName, 0);
            if (s instanceof IRMethod) ((IRMethod)s).addArgDesc("opt", argName);
            s.addInstr(new ReceiveOptArgInstr(av, argIndex-j, argIndex-j, j));
            s.addInstr(BNEInstr.create(av, UndefinedValue.UNDEFINED, l)); // if 'av' is not undefined, go to default
            build(n, s);
            s.addInstr(new LabelInstr(l));
        }
        return argIndex;
    }

    public void receiveMethodArgs(final ArgsNode argsNode, IRScope s) {
        final int required = argsNode.getRequiredArgsCount();
        final int opt = argsNode.getOptionalArgsCount();
        final int rest = argsNode.getRestArg();

        s.getStaticScope().setArities(required, opt, rest);

        // FIXME: Expensive to do this explicitly?  But, two advantages:
        // (a) on inlining, we'll be able to get rid of these checks in almost every case.
        // (b) compiler to bytecode will anyway generate this and this is explicit.
        // For now, we are going explicit instruction route.  But later, perhaps can make this implicit in the method setup preamble?
        s.addInstr(new CheckArityInstr(required, opt, rest));

        // Other args begin at index 0
        int argIndex = 0;

        // Both for fixed arity and variable arity methods
        ListNode preArgs  = argsNode.getPre();
        for (int i = 0; i < required; i++, argIndex++) {
            ArgumentNode a = (ArgumentNode)preArgs.get(i);
            String argName = a.getName();
            s.addInstr(new ReceivePreReqdArgInstr(s.getLocalVariable(argName, 0), argIndex));
            if (s instanceof IRMethod) ((IRMethod)s).addArgDesc("req", argName);
        }

        if (opt > 0) {
            argIndex = receiveOptArgs(argsNode, s, opt, argIndex);
        }

        if (rest > -1) {
            // Consider: def foo(*); .. ; end
            // For this code, there is no argument name available from the ruby code.
            // So, we generate an implicit arg name
            String argName = argsNode.getRestArgNode().getName();
            if (s instanceof IRMethod) ((IRMethod)s).addArgDesc("rest", argName);
            argName = (argName.equals("")) ? "%_arg_array" : argName;
            s.addInstr(new ReceiveRestArgInstr(s.getLocalVariable(argName, 0), argIndex, argIndex));
        }

        // Receive block
        receiveMethodClosureArg(argsNode, s);
    }

    private void receiveMethodClosureArg(ArgsNode argsNode, IRScope s) {
        Variable blockVar = null;
        if (argsNode.getBlock() != null) {
            String blockArgName = argsNode.getBlock().getName();
            blockVar = s.getLocalVariable(blockArgName, 0);
            if (s instanceof IRMethod) ((IRMethod)s).addArgDesc("block", blockArgName);
            s.addInstr(new ReceiveClosureInstr(blockVar));
        }

        // SSS FIXME: This instruction is only needed if there is an yield instr somewhere!
        // In addition, store the block argument in an implicit block variable
        Variable implicitBlockArg = s.getImplicitBlockArg();
        if (blockVar == null) s.addInstr(new ReceiveClosureInstr(implicitBlockArg));
        else s.addInstr(new CopyInstr(implicitBlockArg, blockVar));
    }

    public void receiveBlockArgs(final IterNode node, IRScope s) {
        buildBlockArgsAssignment(node.getVarNode(), s, null, 0, true, false, false);
    }

    public void receiveBlockClosureArg(final Node node, IRScope s) {
        if (node != null) buildBlockArgsAssignment(node, s, null, 0, true, true, false);
    }

    public String buildType(Node typeNode) {
        switch (typeNode.getNodeType()) {
        case CONSTNODE:
            return ((ConstNode)typeNode).getName();
        case SYMBOLNODE:
            return ((SymbolNode)typeNode).getName();
        default:
            return "unknown_type";
        }
    }

    public Operand buildDot(final DotNode dotNode, IRScope s) {
        return copyAndReturnValue(s, new Range(build(dotNode.getBeginNode(), s), build(dotNode.getEndNode(), s), dotNode.isExclusive()));
    }

    private Operand dynamicPiece(Node pieceNode, IRScope s) {
        Operand piece = build(pieceNode, s);

        return piece == null ? manager.getNil() : piece;
    }

    public Operand buildDRegexp(DRegexpNode dregexpNode, IRScope s) {
        List<Operand> strPieces = new ArrayList<Operand>();
        for (Node n : dregexpNode.childNodes()) {
            strPieces.add(dynamicPiece(n, s));
        }

        return copyAndReturnValue(s, new Regexp(new CompoundString(strPieces), dregexpNode.getOptions()));
    }

    public Operand buildDStr(DStrNode dstrNode, IRScope s) {
        List<Operand> strPieces = new ArrayList<Operand>();
        for (Node n : dstrNode.childNodes()) {
            strPieces.add(dynamicPiece(n, s));
        }

        return copyAndReturnValue(s, new CompoundString(strPieces, dstrNode.getEncoding()));
    }

    public Operand buildDSymbol(DSymbolNode node, IRScope s) {
        List<Operand> strPieces = new ArrayList<Operand>();
        for (Node n : node.childNodes()) {
            strPieces.add(dynamicPiece(n, s));
        }

        return copyAndReturnValue(s, new DynamicSymbol(new CompoundString(strPieces, node.getEncoding())));
    }

    public Operand buildDVar(DVarNode node, IRScope s) {
        return s.getLocalVariable(node.getName(), node.getDepth());
    }

    public Operand buildDXStr(final DXStrNode dstrNode, IRScope s) {
        List<Operand> strPieces = new ArrayList<Operand>();
        for (Node nextNode : dstrNode.childNodes()) {
            strPieces.add(dynamicPiece(nextNode, s));
        }

        return copyAndReturnValue(s, new BacktickString(strPieces));
    }

    /* ****************************************************************
     * Consider the ensure-protected ruby code below:

           begin
             .. protected body ..
           ensure
             .. eb code
           end

       This ruby code is effectively rewritten into the following ruby code

          begin
            .. protected body ..
          rescue <any-exception-or-error> => e
            jump to eb-code, execute it, and come back here
            raise e
          end

      which in IR looks like this:

          L1:
            Exception region start marker
            ... IR for protected body ...
            Exception region end marker
            %v = L3       <--- skipped if the protected body had a return!
          L2:
            .. IR for ensure block ..
            jump_indirect %v
          L10:            <--- dummy rescue block
            e = recv_exception
            %v = L11
            jump L2
          L11:
            throw e
          L3:

     * ****************************************************************/
    public Operand buildEnsureNode(EnsureNode ensureNode, IRScope s) {
        Node bodyNode = ensureNode.getBodyNode();

        // Push a new ensure block info node onto the stack of ensure block
        EnsureBlockInfo ebi = new EnsureBlockInfo(s, (bodyNode instanceof RescueNode) ? (RescueNode)bodyNode : null, getCurrentLoop());
        _ensureBlockStack.push(ebi);

        Label rBeginLabel = ebi.regionStart;
        Label rEndLabel   = ebi.end;

        // start of protected region
        s.addInstr(new LabelInstr(rBeginLabel));
        s.addInstr(new ExceptionRegionStartMarkerInstr(rBeginLabel, rEndLabel, ebi.dummyRescueBlockLabel, ebi.dummyRescueBlockLabel));

        // Generate IR for code being protected
        Operand rv;
        if (bodyNode instanceof RescueNode) {
            // The rescue code will ensure that the region is ended
            rv = buildRescueInternal((RescueNode) bodyNode, s, ebi);
        } else {
            rv = build(bodyNode, s);

            // Jump to start of ensure block -- dont bother if we had a return in the protected body
            if (rv != U_NIL) s.addInstr(new SetReturnAddressInstr(ebi.returnAddr, rEndLabel));
        }

        // end of protected region
        s.addInstr(new ExceptionRegionEndMarkerInstr());

        // Pop the current ensure block info node *BEFORE* generating the ensure code for this block itself!
        _ensureBlockStack.pop();

        // Run the ensure block now
        s.addInstr(new JumpInstr(ebi.start));

        // Now build the dummy rescue block that:
        // * catches all exceptions thrown by the body
        // * jumps to the ensure block code
        // * returns back (via set_retaddr instr)
        Label rethrowExcLabel = s.getNewLabel();
        Variable exc = s.getNewTemporaryVariable();
        s.addInstr(new LabelInstr(ebi.dummyRescueBlockLabel));
        s.addInstr(new ReceiveExceptionInstr(exc, false)); // Dont check type since we are simply throwing it back
        s.addInstr(new SetReturnAddressInstr(ebi.returnAddr, rethrowExcLabel));

        // Generate the ensure block now
        s.addInstr(new LabelInstr(ebi.start));

        // Two cases:
        // 1. Ensure block has no explicit return => the result of the entire ensure expression is the result of the protected body.
        // 2. Ensure block has an explicit return => the result of the protected body is ignored.
        Operand ensureRetVal = (ensureNode.getEnsureNode() == null) ? manager.getNil() : build(ensureNode.getEnsureNode(), s);
        // U_NIL => there was a return from within the ensure block!
        if (ensureRetVal == U_NIL) rv = U_NIL;

        // Return (rethrow exception/end)
        s.addInstr(new JumpIndirectInstr(ebi.returnAddr));

        // rethrows the caught exception from the dummy ensure block
        s.addInstr(new LabelInstr(rethrowExcLabel));
        s.addInstr(new ThrowExceptionInstr(exc));

        // End label for the exception region
        s.addInstr(new LabelInstr(rEndLabel));

        return rv;
    }

    public Operand buildEvStr(EvStrNode node, IRScope s) {
        return new AsString(build(node.getBody(), s));
    }

    public Operand buildFalse(Node node, IRScope s) {
        return manager.getFalse();
    }

    public Operand buildFCall(FCallNode fcallNode, IRScope s) {
        Node          callArgsNode = fcallNode.getArgsNode();
        List<Operand> args         = setupCallArgs(callArgsNode, s);
        Operand       block        = setupCallClosure(fcallNode.getIterNode(), s);
        Variable      callResult   = s.getNewTemporaryVariable();
        CallInstr     callInstr    = CallInstr.create(CallType.FUNCTIONAL, callResult, new MethAddr(fcallNode.getName()), getSelf(s), args.toArray(new Operand[args.size()]), block);
        receiveBreakException(s, block, callInstr);
        return callResult;
    }

    private Operand setupCallClosure(Node node, IRScope s) {
        if (node == null) return null;

        switch (node.getNodeType()) {
            case ITERNODE:
                return build((IterNode)node, s);
            case BLOCKPASSNODE:
                return build(((BlockPassNode)node).getBodyNode(), s);
            default:
                throw new NotCompilableException("ERROR: Encountered a method with a non-block, non-blockpass iter node at: " + node);
        }
    }

    public Operand buildFixnum(FixnumNode node, IRScope s) {
        return new Fixnum(node.getValue());
    }

    public Operand buildFlip(FlipNode flipNode, IRScope s) {
        /* ----------------------------------------------------------------------
         * Consider a simple 2-state (s1, s2) FSM with the following transitions:
         *
         *     new_state(s1, F) = s1
         *     new_state(s1, T) = s2
         *     new_state(s2, F) = s2
         *     new_state(s2, T) = s1
         *
         * Here is the pseudo-code for evaluating the flip-node.
         * Let 'v' holds the value of the current state.
         *
         *    1. if (v == 's1') f1 = eval_condition(s1-condition); v = new_state(v, f1); ret = f1
         *    2. if (v == 's2') f2 = eval_condition(s2-condition); v = new_state(v, f2); ret = true
         *    3. return ret
         *
         * For exclusive flip conditions, line 2 changes to:
         *    2. if (!f1 && (v == 's2')) f2 = eval_condition(s2-condition); v = new_state(v, f2)
         *
         * In IR code below, we are representing the two states as 1 and 2.  Any
         * two values are good enough (even true and false), but 1 and 2 is simple
         * enough and also makes the IR output readable
         * ---------------------------------------------------------------------- */

        Fixnum s1 = new Fixnum((long)1);
        Fixnum s2 = new Fixnum((long)2);

        // Create a variable to hold the flip state
        IRScope nearestNonClosure = s.getNearestFlipVariableScope();
        Variable flipState = nearestNonClosure.getNewFlipStateVariable();
        nearestNonClosure.initFlipStateVariable(flipState, s1);
        if (s instanceof IRClosure) {
            // Clone the flip variable to be usable at the proper-depth.
            int n = 0;
            IRScope x = s;
            while (!x.isFlipScope()) {
                if (!x.isForLoopBody()) n++;
                x = x.getLexicalParent();
            }
            if (n > 0) flipState = ((LocalVariable)flipState).cloneForDepth(n);
        }

        // Variables and labels needed for the code
        Variable returnVal = s.getNewTemporaryVariable();
        Label    s2Label   = s.getNewLabel();
        Label    doneLabel = s.getNewLabel();

        // Init
        s.addInstr(new CopyInstr(returnVal, manager.getFalse()));

        // Are we in state 1?
        s.addInstr(BNEInstr.create(flipState, s1, s2Label));

        // ----- Code for when we are in state 1 -----
        Operand s1Val = build(flipNode.getBeginNode(), s);
        s.addInstr(BNEInstr.create(s1Val, manager.getTrue(), s2Label));

        // s1 condition is true => set returnVal to true & move to state 2
        s.addInstr(new CopyInstr(returnVal, manager.getTrue()));
        s.addInstr(new CopyInstr(flipState, s2));

        // Check for state 2
        s.addInstr(new LabelInstr(s2Label));

        // For exclusive ranges/flips, we dont evaluate s2's condition if s1's condition was satisfied
        if (flipNode.isExclusive()) s.addInstr(BEQInstr.create(returnVal, manager.getTrue(), doneLabel));

        // Are we in state 2?
        s.addInstr(BNEInstr.create(flipState, s2, doneLabel));

        // ----- Code for when we are in state 2 -----
        Operand s2Val = build(flipNode.getEndNode(), s);
        s.addInstr(new CopyInstr(returnVal, manager.getTrue()));
        s.addInstr(BNEInstr.create(s2Val, manager.getTrue(), doneLabel));

        // s2 condition is true => move to state 1
        s.addInstr(new CopyInstr(flipState, s1));

        // Done testing for s1's and s2's conditions.
        // returnVal will have the result of the flip condition
        s.addInstr(new LabelInstr(doneLabel));

        return returnVal;
    }

    public Operand buildFloat(FloatNode node, IRScope s) {
        // SSS: Since flaot literals are effectively interned objects, no need to copyAndReturnValue(...)
        // Or is this a premature optimization?
        return new Float(node.getValue());
    }

    public Operand buildFor(ForNode forNode, IRScope s) {
        Variable result = s.getNewTemporaryVariable();
        Operand  receiver = build(forNode.getIterNode(), s);
        Operand  forBlock = buildForIter(forNode, s);
        CallInstr callInstr = new CallInstr(CallType.NORMAL, result, new MethAddr("each"), receiver, NO_ARGS, forBlock);
        receiveBreakException(s, forBlock, callInstr);

        return result;
    }

    public Operand buildForIter(final ForNode forNode, IRScope s) {
            // Create a new closure context
        IRClosure closure = new IRClosure(manager, s, true, forNode.getPosition().getStartLine(), forNode.getScope(), Arity.procArityOf(forNode.getVarNode()), forNode.getArgumentType(), !is1_9() && !is2_0());
        s.addClosure(closure);

        // Create a new nested builder to ensure this gets its own IR builder state
        // like the ensure block stack
        IRBuilder forBuilder = newIRBuilder(manager);

            // Receive self
        closure.addInstr(new ReceiveSelfInstr(getSelf(closure)));

            // Build args
        Node varNode = forNode.getVarNode();
        if (varNode != null && varNode.getNodeType() != null) forBuilder.receiveBlockArgs(forNode, closure);

        // Set %current_scope = <current-scope>
        // Set %current_module = <current-module>
        closure.addInstr(new CopyInstr(closure.getCurrentScopeVariable(), new CurrentScope(closure)));
        closure.addInstr(new CopyInstr(closure.getCurrentModuleVariable(), new ScopeModule(closure)));

        // Thread poll on entry of closure
        closure.addInstr(new ThreadPollInstr());

            // Start label -- used by redo!
        closure.addInstr(new LabelInstr(closure.startLabel));

            // Build closure body and return the result of the closure
        Operand closureRetVal = forNode.getBodyNode() == null ? manager.getNil() : forBuilder.build(forNode.getBodyNode(), closure);
        if (closureRetVal != U_NIL) { // can be null if the node is an if node with returns in both branches.
            closure.addInstr(new ReturnInstr(closureRetVal));
        }

        return new WrappedIRClosure(closure);
    }

    public Operand buildGlobalAsgn(GlobalAsgnNode globalAsgnNode, IRScope s) {
        Operand value = build(globalAsgnNode.getValueNode(), s);
        s.addInstr(new PutGlobalVarInstr(globalAsgnNode.getName(), value));
        return value;
    }

    public Operand buildGlobalVar(GlobalVarNode node, IRScope s) {
        Variable rv  = s.getNewTemporaryVariable();
        s.addInstr(new GetGlobalVariableInstr(rv, node.getName()));
        return rv;
    }

    public Operand buildHash(HashNode hashNode, IRScope s) {
        if (hashNode.getListNode() == null || hashNode.getListNode().size() == 0) {
            return copyAndReturnValue(s, new Hash(new ArrayList<KeyValuePair>()));
        } else {
            int     i     = 0;
            Operand key   = null;
            Operand value = null;
            List<KeyValuePair> args = new ArrayList<KeyValuePair>();
            for (Node nextNode : hashNode.getListNode().childNodes()) {
                Operand v = build(nextNode, s);
                if (key == null) {
                    key = v;
                } else {
                    args.add(new KeyValuePair(key, v));
                    key = null;
                }
            }
            return copyAndReturnValue(s, new Hash(args));
        }
    }

    // Translate "r = if (cond); .. thenbody ..; else; .. elsebody ..; end" to
    //
    //     v = -- build(cond) --
    //     BEQ(v, FALSE, L1)
    //     r = -- build(thenbody) --
    //     jump L2
    // L1:
    //     r = -- build(elsebody) --
    // L2:
    //     --- r is the result of the if expression --
    //
    public Operand buildIf(final IfNode ifNode, IRScope s) {
        Node actualCondition = skipOverNewlines(s, ifNode.getCondition());

        Variable result;
        Label    falseLabel = s.getNewLabel();
        Label    doneLabel  = s.getNewLabel();
        Operand  thenResult;
        s.addInstr(BEQInstr.create(build(actualCondition, s), manager.getFalse(), falseLabel));

        boolean thenNull = false;
        boolean elseNull = false;
        boolean thenUnil = false;
        boolean elseUnil = false;

        // Build the then part of the if-statement
        if (ifNode.getThenBody() != null) {
            thenResult = build(ifNode.getThenBody(), s);
            if (thenResult != U_NIL) { // thenResult can be U_NIL if then-body ended with a return!
                // SSS FIXME: Can look at the last instr and short-circuit this jump if it is a break rather
                // than wait for dead code elimination to do it
                Label tgt = doneLabel;
                result = getValueInTemporaryVariable(s, thenResult);
                s.addInstr(new JumpInstr(tgt));
            } else {
                result = s.getNewTemporaryVariable();
                thenUnil = true;
            }
        } else {
            thenNull = true;
            result = s.getNewTemporaryVariable();
            s.addInstr(new CopyInstr(result, manager.getNil()));
            s.addInstr(new JumpInstr(doneLabel));
        }

        // Build the else part of the if-statement
        s.addInstr(new LabelInstr(falseLabel));
        if (ifNode.getElseBody() != null) {
            Operand elseResult = build(ifNode.getElseBody(), s);
            // elseResult can be U_NIL if then-body ended with a return!
            if (elseResult != U_NIL) {
                s.addInstr(new CopyInstr(result, elseResult));
            } else {
                elseUnil = true;
            }
        } else {
            elseNull = true;
            s.addInstr(new CopyInstr(result, manager.getNil()));
        }

        if (thenNull && elseNull) {
            s.addInstr(new LabelInstr(doneLabel));
            return manager.getNil();
        } else if (thenUnil && elseUnil) {
            return U_NIL;
        } else {
            s.addInstr(new LabelInstr(doneLabel));
            return result;
        }
    }

    public Operand buildInstAsgn(final InstAsgnNode instAsgnNode, IRScope s) {
        Operand val = build(instAsgnNode.getValueNode(), s);
        // NOTE: if 's' happens to the a class, this is effectively an assignment of a class instance variable
        s.addInstr(new PutFieldInstr(getSelf(s), instAsgnNode.getName(), val));
        return val;
    }

    public Operand buildInstVar(InstVarNode node, IRScope s) {
        Variable ret = s.getNewTemporaryVariable();
        s.addInstr(new GetFieldInstr(ret, getSelf(s), node.getName()));
        return ret;
    }

    public Operand buildIter(final IterNode iterNode, IRScope s) {
        IRClosure closure = new IRClosure(manager, s, false, iterNode.getPosition().getStartLine(), iterNode.getScope(), Arity.procArityOf(iterNode.getVarNode()), iterNode.getArgumentType(), !is1_9() && !is2_0());
        s.addClosure(closure);

        // Create a new nested builder to ensure this gets its own IR builder state
        // like the ensure block stack
        IRBuilder closureBuilder = newIRBuilder(manager);

        // Receive self
        closure.addInstr(new ReceiveSelfInstr(getSelf(closure)));

        // Build args
        NodeType argsNodeId = BlockBody.getArgumentTypeWackyHack(iterNode);
        if ((iterNode.getVarNode() != null) && (argsNodeId != null))
            closureBuilder.receiveBlockArgs(iterNode, closure);
        closureBuilder.receiveBlockClosureArg(iterNode.getBlockVarNode(), closure);

        // Set %current_scope = <current-scope>
        // Set %current_module = <current-module>
        closure.addInstr(new CopyInstr(closure.getCurrentScopeVariable(), new CurrentScope(closure)));
        closure.addInstr(new CopyInstr(closure.getCurrentModuleVariable(), new ScopeModule(closure)));

        // Thread poll on entry of closure
        closure.addInstr(new ThreadPollInstr());

        // start label -- used by redo!
        closure.addInstr(new LabelInstr(closure.startLabel));

        // Build closure body and return the result of the closure
        Operand closureRetVal = iterNode.getBodyNode() == null ? manager.getNil() : closureBuilder.build(iterNode.getBodyNode(), closure);
        if (closureRetVal != U_NIL) { // can be U_NIL if the node is an if node with returns in both branches.
            closure.addInstr(new ReturnInstr(closureRetVal));
        }

        return new WrappedIRClosure(closure);
    }

    public Operand buildLiteral(LiteralNode literalNode, IRScope s) {
        return copyAndReturnValue(s, new StringLiteral(literalNode.getName()));
    }

    public Operand buildLocalAsgn(LocalAsgnNode localAsgnNode, IRScope s) {
        Variable var  = s.getLocalVariable(localAsgnNode.getName(), localAsgnNode.getDepth());
        Operand value = build(localAsgnNode.getValueNode(), s);
        s.addInstr(new CopyInstr(var, value));
        return value;

        // IMPORTANT: The return value of this method is value, not var!
        //
        // Consider this Ruby code: foo((a = 1), (a = 2))
        //
        // If we return 'value' this will get translated to:
        //    a = 1
        //    a = 2
        //    call("foo", [1,2]) <---- CORRECT
        //
        // If we return 'var' this will get translated to:
        //    a = 1
        //    a = 2
        //    call("foo", [a,a]) <---- BUGGY
        //
        // This technique only works if 'value' is an immutable value (ex: fixnum) or a variable
        // So, for Ruby code like this:
        //     def foo(x); x << 5; end;
        //     foo(a=[1,2]);
        //     p a
        // we are guaranteed that the value passed into foo and 'a' point to the same object
        // because of the use of copyAndReturnValue method for literal objects.
    }

    public Operand buildLocalVar(LocalVarNode node, IRScope s) {
        return s.getLocalVariable(node.getName(), node.getDepth());
    }

    public Operand buildMatch(MatchNode matchNode, IRScope s) {
        Operand regexp = build(matchNode.getRegexpNode(), s);
        Variable result = s.getNewTemporaryVariable();
        s.addInstr(new MatchInstr(result, regexp));
        return result;
    }

    public Operand buildMatch2(Match2Node matchNode, IRScope s) {
        Operand receiver = build(matchNode.getReceiverNode(), s);
        Operand value    = build(matchNode.getValueNode(), s);
        Variable result = s.getNewTemporaryVariable();
        s.addInstr(new Match2Instr(result, receiver, value));
        return result;
    }

    public Operand buildMatch3(Match3Node matchNode, IRScope s) {
        Operand receiver = build(matchNode.getReceiverNode(), s);
        Operand value    = build(matchNode.getValueNode(), s);
        Variable result = s.getNewTemporaryVariable();
        s.addInstr(new Match3Instr(result, receiver, value));
        return result;
    }

    private Operand getContainerFromCPath(Colon3Node cpath, IRScope s) {
        Operand container;

        if (cpath instanceof Colon2Node) {
            Node leftNode = ((Colon2Node) cpath).getLeftNode();

            if (leftNode != null) { // Foo::Bar
                container = build(leftNode, s);
            } else { // Only name with no left-side Bar <- Note no :: on left
                container = findContainerModule(s);
            }
        } else { //::Bar
            container = new ScopeModule(manager.getObject());
        }

        return container;
    }

    public Operand buildModule(ModuleNode moduleNode, IRScope s) {
        Colon3Node cpath = moduleNode.getCPath();
        String moduleName = cpath.getName();
        Operand container = getContainerFromCPath(cpath, s);

        // Build the new module
        IRModuleBody m = new IRModuleBody(manager, s, moduleName, moduleNode.getPosition().getLine(), moduleNode.getScope());
        Variable moduleBody = s.getNewTemporaryVariable();
        s.addInstr(new DefineModuleInstr(moduleBody, m, container));
        Variable ret = s.getNewTemporaryVariable();
        s.addInstr(new ProcessModuleBodyInstr(ret, moduleBody));

        m.addInstr(new ReceiveSelfInstr(m.getSelf()));
        // Set %current_scope = <c>
        // Set %current_module = module<c>
        m.addInstr(new CopyInstr(m.getCurrentScopeVariable(), new CurrentScope(m)));
        m.addInstr(new CopyInstr(m.getCurrentModuleVariable(), new ScopeModule(m)));
        // Create a new nested builder to ensure this gets its own IR builder state
        Operand rv = newIRBuilder(manager).build(moduleNode.getBodyNode(), m);
        if (rv != null) m.addInstr(new ReturnInstr(rv));

        return ret;
    }

    public Operand buildMultipleAsgn(MultipleAsgnNode multipleAsgnNode, IRScope s) {
        Operand  values = build(multipleAsgnNode.getValueNode(), s);
        Variable ret = getValueInTemporaryVariable(s, values);
        buildMultipleAsgnAssignment(multipleAsgnNode, s, null, ret);
        return ret;
    }

    // SSS: This method is called both for regular multiple assignment as well as argument passing
    //
    // Ex: a,b,*c=v  is a regular assignment and in this case, the "values" operand will be non-null
    // Ex: { |a,b,*c| ..} is the argument passing case
    public void buildMultipleAsgnAssignment(final MultipleAsgnNode multipleAsgnNode, IRScope s, Operand argsArray, Operand values) {
        final ListNode sourceArray = multipleAsgnNode.getHeadNode();

        // First, build assignments for specific named arguments
        int i = 0;
        if (sourceArray != null) {
            for (Node an: sourceArray.childNodes()) {
                if (values == null) {
                    buildBlockArgsAssignment(an, s, argsArray, i, false, false, false);
                } else {
                    Variable rhsVal = s.getNewTemporaryVariable();
                    s.addInstr(new ReqdArgMultipleAsgnInstr(rhsVal, values, i));
                    buildAssignment(an, s, rhsVal);
                }
                i++;
            }
        }

        // First, build an assignment for a splat, if any, with the rest of the args!
        Node argsNode = multipleAsgnNode.getArgsNode();
        if (argsNode == null) {
            if (sourceArray == null)
                throw new NotCompilableException("Something's wrong, multiple assignment with no head or args at: " + multipleAsgnNode.getPosition());
        } else if (argsNode instanceof StarNode) {
            // do nothing
        } else if (values != null) {
            Variable rhsVal = s.getNewTemporaryVariable();
            s.addInstr(new RestArgMultipleAsgnInstr(rhsVal, values, i));
            buildAssignment(argsNode, s, rhsVal); // rest of the argument array!
        } else {
            buildBlockArgsAssignment(argsNode, s, argsArray, i, false, false, true); // rest of the argument array!
        }

    }

    public Operand buildNewline(NewlineNode node, IRScope s) {
        return build(skipOverNewlines(s, node), s);
    }

    public Operand buildNext(final NextNode nextNode, IRScope s) {
        IRLoop currLoop = getCurrentLoop();

        Operand rv = (nextNode.getValueNode() == null) ? manager.getNil() : build(nextNode.getValueNode(), s);

        // If we have ensure blocks, have to run those first!
        if (!_ensureBlockStack.empty()) EnsureBlockInfo.emitJumpChain(s, _ensureBlockStack, currLoop);
        else if (!_rescueBlockStack.empty()) _rescueBlockStack.peek().restoreException(s, currLoop);

        if (currLoop != null) {
            // If a regular loop, the next is simply a jump to the end of the iteration
            s.addInstr(new JumpInstr(currLoop.iterEndLabel));
        } else {
            s.addInstr(new ThreadPollInstr(true));
            // If a closure, the next is simply a return from the closure!
            if (s instanceof IRClosure) s.addInstr(new ReturnInstr(rv));
            else s.addInstr(new ThrowExceptionInstr(IRException.NEXT_LocalJumpError));
        }

        // Once the "next instruction" (closure-return) executes, control exits this scope
        return UnexecutableNil.U_NIL;
    }

    public Operand buildNthRef(NthRefNode nthRefNode, IRScope s) {
        return copyAndReturnValue(s, new NthRef(nthRefNode.getMatchNumber()));
    }

    public Operand buildNil(Node node, IRScope s) {
        return manager.getNil();
    }

    public Operand buildNot(NotNode node, IRScope s) {
        Variable ret = s.getNewTemporaryVariable();
        s.addInstr(new NotInstr(ret, build(node.getConditionNode(), s)));
        return ret;
    }

    public Operand buildOpAsgn(OpAsgnNode opAsgnNode, IRScope s) {
        Label l;
        Variable readerValue = s.getNewTemporaryVariable();
        Variable writerValue = s.getNewTemporaryVariable();

        // get attr
        Operand  v1 = build(opAsgnNode.getReceiverNode(), s);
        s.addInstr(CallInstr.create(readerValue, new MethAddr(opAsgnNode.getVariableName()), v1, NO_ARGS, null));

        // Ex: e.val ||= n
        //     e.val &&= n
        String opName = opAsgnNode.getOperatorName();
        if (opName.equals("||") || opName.equals("&&")) {
            l = s.getNewLabel();
            s.addInstr(BEQInstr.create(readerValue, opName.equals("||") ? manager.getTrue() : manager.getFalse(), l));

            // compute value and set it
            Operand  v2 = build(opAsgnNode.getValueNode(), s);
            s.addInstr(CallInstr.create(writerValue, new MethAddr(opAsgnNode.getVariableNameAsgn()), v1, new Operand[] {v2}, null));
            // It is readerValue = v2.
            // readerValue = writerValue is incorrect because the assignment method
            // might return something else other than the value being set!
            s.addInstr(new CopyInstr(readerValue, v2));
            s.addInstr(new LabelInstr(l));

            return readerValue;
        }
        // Ex: e.val = e.val.f(n)
        else {
            // call operator
            Operand  v2 = build(opAsgnNode.getValueNode(), s);
            Variable setValue = s.getNewTemporaryVariable();
            s.addInstr(CallInstr.create(setValue, new MethAddr(opAsgnNode.getOperatorName()), readerValue, new Operand[]{v2}, null));

            // set attr
            s.addInstr(CallInstr.create(writerValue, new MethAddr(opAsgnNode.getVariableNameAsgn()), v1, new Operand[] {setValue}, null));
            // Returning writerValue is incorrect becuase the assignment method
            // might return something else other than the value being set!
            return setValue;
        }
    }

    // Translate "x &&= y" --> "x = y if is_true(x)" -->
    //
    //    x = -- build(x) should return a variable! --
    //    f = is_true(x)
    //    beq(f, false, L)
    //    x = -- build(y) --
    // L:
    //
    public Operand buildOpAsgnAnd(OpAsgnAndNode andNode, IRScope s) {
        Label    l  = s.getNewLabel();
        Operand  v1 = build(andNode.getFirstNode(), s);
        Variable result = getValueInTemporaryVariable(s, v1);
        s.addInstr(BEQInstr.create(v1, manager.getFalse(), l));
        Operand v2 = build(andNode.getSecondNode(), s);  // This does the assignment!
        s.addInstr(new CopyInstr(result, v2));
        s.addInstr(new LabelInstr(l));
        return result;
    }

    // FIXME: This logic is not quite right....marked extra branch checks
    // to make sure the value is not defined but nil.  Nil will trigger ||=
    // rhs expression.
    //
    // Translate "x ||= y" --> "x = (is_defined(x) && is_true(x) ? x : y)" -->
    //
    //    v = -- build(x) should return a variable! --
    //    f = is_true(v)
    //    beq(f, true, L)
    //    -- build(x = y) --
    // L:
    //
    public Operand buildOpAsgnOr(final OpAsgnOrNode orNode, IRScope s) {
        Label    l1 = s.getNewLabel();
        Label    l2 = null;
        Variable flag = s.getNewTemporaryVariable();
        Operand  v1;
        boolean  needsDefnCheck = needsDefinitionCheck(orNode.getFirstNode());
        if (needsDefnCheck) {
            l2 = s.getNewLabel();
            v1 = buildGetDefinitionBase(orNode.getFirstNode(), s);
            s.addInstr(new CopyInstr(flag, v1));
            s.addInstr(BEQInstr.create(flag, manager.getNil(), l2)); // if v1 is undefined, go to v2's computation
        }
        v1 = build(orNode.getFirstNode(), s); // build of 'x'
        s.addInstr(new CopyInstr(flag, v1));
        Variable result = getValueInTemporaryVariable(s, v1);
        if (needsDefnCheck) {
            s.addInstr(new LabelInstr(l2));
        }
        s.addInstr(BEQInstr.create(flag, manager.getTrue(), l1));  // if v1 is defined and true, we are done!
        Operand v2 = build(orNode.getSecondNode(), s); // This is an AST node that sets x = y, so nothing special to do here.
        s.addInstr(new CopyInstr(result, v2));
        s.addInstr(new LabelInstr(l1));

        // Return value of x ||= y is always 'x'
        return result;
    }

    /**
     * Check whether the given node is considered always "defined" or whether it
     * has some form of definition check.
     *
     * @param node Then node to check
     * @return Whether the type of node represents a possibly undefined construct
     */
    private boolean needsDefinitionCheck(Node node) {
        switch (node.getNodeType()) {
        case CLASSVARASGNNODE:
        case CLASSVARDECLNODE:
        case CONSTDECLNODE:
        case DASGNNODE:
        case GLOBALASGNNODE:
        case LOCALASGNNODE:
        case MULTIPLEASGNNODE:
        case OPASGNNODE:
        case OPELEMENTASGNNODE:
        case DVARNODE:
        case FALSENODE:
        case TRUENODE:
        case LOCALVARNODE:
        case MATCH2NODE:
        case MATCH3NODE:
        case NILNODE:
        case SELFNODE:
            // all these types are immediately considered "defined"
            return false;
        default:
            return true;
        }
    }

    public Operand buildOpElementAsgn(OpElementAsgnNode node, IRScope s) {
        if (node.isOr()) return buildOpElementAsgnWithOr(node, s);
        if (node.isAnd()) return buildOpElementAsgnWithAnd(node, s);

        return buildOpElementAsgnWithMethod(node, s);
    }

    // Translate "a[x] ||= n" --> "a[x] = n if !is_true(a[x])"
    //
    //    tmp = build(a) <-- receiver
    //    arg = build(x) <-- args
    //    val = buildCall([], tmp, arg)
    //    f = is_true(val)
    //    beq(f, true, L)
    //    val = build(n) <-- val
    //    buildCall([]= tmp, arg, val)
    // L:
    //
    public Operand buildOpElementAsgnWithOr(OpElementAsgnNode opElementAsgnNode, IRScope s) {
        Operand array = build(opElementAsgnNode.getReceiverNode(), s);
        Label    l     = s.getNewLabel();
        Variable elt   = s.getNewTemporaryVariable();
        List<Operand> argList = setupCallArgs(opElementAsgnNode.getArgsNode(), s);
        s.addInstr(CallInstr.create(elt, new MethAddr("[]"), array, argList.toArray(new Operand[argList.size()]), null));
        s.addInstr(BEQInstr.create(elt, manager.getTrue(), l));
        Operand value = build(opElementAsgnNode.getValueNode(), s);
        argList.add(value);
        s.addInstr(CallInstr.create(elt, new MethAddr("[]="), array, argList.toArray(new Operand[argList.size()]), null));
        s.addInstr(new CopyInstr(elt, value));
        s.addInstr(new LabelInstr(l));
        return elt;
    }

    // Translate "a[x] &&= n" --> "a[x] = n if is_true(a[x])"
    public Operand buildOpElementAsgnWithAnd(OpElementAsgnNode opElementAsgnNode, IRScope s) {
        Operand array = build(opElementAsgnNode.getReceiverNode(), s);
        Label    l     = s.getNewLabel();
        Variable elt   = s.getNewTemporaryVariable();
        List<Operand> argList = setupCallArgs(opElementAsgnNode.getArgsNode(), s);
        s.addInstr(CallInstr.create(elt, new MethAddr("[]"), array, argList.toArray(new Operand[argList.size()]), null));
        s.addInstr(BEQInstr.create(elt, manager.getFalse(), l));
        Operand value = build(opElementAsgnNode.getValueNode(), s);
        argList.add(value);
        s.addInstr(CallInstr.create(elt, new MethAddr("[]="), array, argList.toArray(new Operand[argList.size()]), null));
        s.addInstr(new CopyInstr(elt, value));
        s.addInstr(new LabelInstr(l));
        return elt;
    }

    // a[i] *= n, etc.  anything that is not "a[i] &&= .. or a[i] ||= .."
    //    arr = build(a) <-- receiver
    //    arg = build(x) <-- args
    //    elt = buildCall([], arr, arg)
    //    val = build(n) <-- val
    //    val = buildCall(METH, elt, val)
    //    val = buildCall([]=, arr, arg, val)
    public Operand buildOpElementAsgnWithMethod(OpElementAsgnNode opElementAsgnNode, IRScope s) {
        Operand array = build(opElementAsgnNode.getReceiverNode(), s);
        List<Operand> argList = setupCallArgs(opElementAsgnNode.getArgsNode(), s);
        Variable elt = s.getNewTemporaryVariable();
        s.addInstr(CallInstr.create(elt, new MethAddr("[]"), array, argList.toArray(new Operand[argList.size()]), null)); // elt = a[args]
        Operand value = build(opElementAsgnNode.getValueNode(), s);                                       // Load 'value'
        String  operation = opElementAsgnNode.getOperatorName();
        s.addInstr(CallInstr.create(elt, new MethAddr(operation), elt, new Operand[] { value }, null)); // elt = elt.OPERATION(value)
        // SSS: do not load the call result into 'elt' to eliminate the RAW dependency on the call
        // We already know what the result is going be .. we are just storing it back into the array
        Variable tmp = s.getNewTemporaryVariable();
        argList.add(elt);
        s.addInstr(CallInstr.create(tmp, new MethAddr("[]="), array, argList.toArray(new Operand[argList.size()]), null));   // a[args] = elt
        return elt;
    }

    // Translate ret = (a || b) to ret = (a ? true : b) as follows
    //
    //    v1 = -- build(a) --
    //       OPT: ret can be set to v1, but effectively v1 is true if we take the branch to L.
    //            while this info can be inferred by using attributes, why bother if we can do this?
    //    ret = v1
    //    beq(v1, true, L)
    //    v2 = -- build(b) --
    //    ret = v2
    // L:
    //
    public Operand buildOr(final OrNode orNode, IRScope s) {
        if (orNode.getFirstNode().getNodeType().alwaysTrue()) {
            // build first node only and return true
            return build(orNode.getFirstNode(), s);
        } else if (orNode.getFirstNode().getNodeType().alwaysFalse()) {
            // build first node as non-expr and build second node
            build(orNode.getFirstNode(), s);
            return build(orNode.getSecondNode(), s);
        } else {
            Label    l   = s.getNewLabel();
            Operand  v1  = build(orNode.getFirstNode(), s);
            Variable ret = getValueInTemporaryVariable(s, v1);
            s.addInstr(BEQInstr.create(v1, manager.getTrue(), l));
            Operand  v2  = build(orNode.getSecondNode(), s);
            s.addInstr(new CopyInstr(ret, v2));
            s.addInstr(new LabelInstr(l));
            return ret;
        }
    }

    public Operand buildPostExe(PostExeNode postExeNode, IRScope s) {
        IRClosure endClosure = new IRClosure(manager, s, false, postExeNode.getPosition().getStartLine(), postExeNode.getScope(), Arity.procArityOf(postExeNode.getVarNode()), postExeNode.getArgumentType(), !is1_9() && !is2_0());
        // Set up %current_scope and %current_module
        endClosure.addInstr(new CopyInstr(endClosure.getCurrentScopeVariable(), new CurrentScope(endClosure)));
        endClosure.addInstr(new CopyInstr(endClosure.getCurrentModuleVariable(), new ScopeModule(endClosure)));
        build(postExeNode.getBodyNode(), endClosure);

        // Add an instruction to record the end block at runtime
        s.addInstr(new RecordEndBlockInstr(s, endClosure));
        return manager.getNil();
    }

    public Operand buildPreExe(PreExeNode preExeNode, IRScope s) {
        IRClosure beginClosure = new IRClosure(manager, s, false, preExeNode.getPosition().getStartLine(), preExeNode.getScope(), Arity.procArityOf(preExeNode.getVarNode()), preExeNode.getArgumentType(), !is1_9() && !is2_0());
        // Set up %current_scope and %current_module
        beginClosure.addInstr(new CopyInstr(beginClosure.getCurrentScopeVariable(), new CurrentScope(beginClosure)));
        beginClosure.addInstr(new CopyInstr(beginClosure.getCurrentModuleVariable(), new ScopeModule(beginClosure)));
        build(preExeNode.getBodyNode(), beginClosure);

        // Record the begin block at IR build time
        s.getTopLevelScope().recordBeginBlock(beginClosure);
        return manager.getNil();
    }

    public Operand buildRedo(Node node, IRScope s) {
        // If in a loop, a redo is a jump to the beginning of the loop.
        // If not, for closures, a redo is a jump to the beginning of the closure.
        // If not in a loop or a closure, it is a local jump error
        IRLoop currLoop = getCurrentLoop();
        if (currLoop != null) {
             s.addInstr(new JumpInstr(currLoop.iterStartLabel));
        } else {
            if (s instanceof IRClosure) {
                s.addInstr(new ThreadPollInstr(true));
                s.addInstr(new JumpInstr(((IRClosure)s).startLabel));
            } else {
                s.addInstr(new ThrowExceptionInstr(IRException.REDO_LocalJumpError));
            }
        }
        return manager.getNil();
    }

    public Operand buildRegexp(RegexpNode reNode, IRScope s) {
        return copyAndReturnValue(s, new Regexp(new StringLiteral(reNode.getValue()), reNode.getOptions()));
    }

    public Operand buildRescue(RescueNode node, IRScope s) {
        return buildRescueInternal(node, s, null);
    }

    private Operand buildRescueInternal(RescueNode rescueNode, IRScope s, EnsureBlockInfo ensure) {
        // Labels marking start, else, end of the begin-rescue(-ensure)-end block
        Label rBeginLabel = ensure == null ? s.getNewLabel() : ensure.regionStart;
        Label rEndLabel   = ensure == null ? s.getNewLabel() : ensure.end;
        Label rescueLabel = s.getNewLabel(); // Label marking start of the first rescue code.

        if (ensure == null) s.addInstr(new LabelInstr(rBeginLabel));

        // Placeholder rescue instruction that tells rest of the compiler passes the boundaries of the rescue block.
        s.addInstr(new ExceptionRegionStartMarkerInstr(rBeginLabel, rEndLabel, ensure == null ? null : ensure.dummyRescueBlockLabel, rescueLabel));

        // Save $! in a temp var so it can be restored when the exception gets handled.
        // SSS FIXME: Dont yet understand why an exception needs to be saved/restored.
        Variable savedGlobalException = s.getNewTemporaryVariable();
        s.addInstr(new GetGlobalVariableInstr(savedGlobalException, "$!"));
        if (ensure != null) ensure.savedGlobalException = savedGlobalException;

        // Body
        Operand tmp = manager.getNil();  // default return value if for some strange reason, we neither have the body node or the else node!
        Variable rv = s.getNewTemporaryVariable();
        if (rescueNode.getBodyNode() != null) tmp = build(rescueNode.getBodyNode(), s);

        // Push rescue block *after* body has been built.
        // If not, this messes up generation of retry in these scenarios like this:
        //
        //     begin    -- 1
        //       ...
        //     rescue
        //       begin  -- 2
        //         ...
        //         retry
        //       rescue
        //         ...
        //       end
        //     end
        //
        // The retry should jump to 1, not 2.
        // If we push the rescue block before building the body, we will jump to 2.
        _rescueBlockStack.push(new RescueBlockInfo(rescueNode, rBeginLabel, savedGlobalException, getCurrentLoop()));

        // Since rescued regions are well nested within Ruby, this bare marker is sufficient to
        // let us discover the edge of the region during linear traversal of instructions during cfg construction.
        ExceptionRegionEndMarkerInstr rbEndInstr = new ExceptionRegionEndMarkerInstr();
        s.addInstr(rbEndInstr);

        // Else part of the body -- we simply fall through from the main body if there were no exceptions
        Label elseLabel = rescueNode.getElseNode() == null ? null : s.getNewLabel();
        if (elseLabel != null) {
            s.addInstr(new LabelInstr(elseLabel));
            tmp = build(rescueNode.getElseNode(), s);
        }

        if (tmp != U_NIL) {
            s.addInstr(new CopyInstr(rv, tmp));

            // No explicit return from the protected body
            // - If we dont have any ensure blocks, simply jump to the end of the rescue block
            // - If we do, get the innermost ensure block, set up the return address to the end of the ensure block, and go execute the ensure code.
            if (ensure == null) {
                s.addInstr(new JumpInstr(rEndLabel));
            } else {
                // NOTE: rEndLabel is identical to ensure.end, but less confusing to use rEndLabel since that makes more semantic sense
                s.addInstr(new SetReturnAddressInstr(ensure.returnAddr, rEndLabel));
                s.addInstr(new JumpInstr(ensure.start));
            }
        } else {
            // If the body had an explicit return, the return instruction IR build takes care of setting
            // up execution of all necessary ensure blocks.  So, nothing to do here!
            //
            // Additionally, the value in 'rv' will never be used, so need to set it to any specific value.
            // So, we can leave it undefined.  If on the other hand, there was an exception in that block,
            // 'rv' will get set in the rescue handler -- see the 'rv' being passed into
            // buildRescueBodyInternal below.  So, in either case, we are good!
        }

        // Build the actual rescue block(s)
        s.addInstr(new LabelInstr(rescueLabel));
        buildRescueBodyInternal(s, rescueNode.getRescueNode(), rv, rEndLabel);

        // End label -- only if there is no ensure block!  With an ensure block, you end at ensureEndLabel.
        if (ensure == null) s.addInstr(new LabelInstr(rEndLabel));

        _rescueBlockStack.pop();
        return rv;
    }

    private void outputExceptionCheck(IRScope s, Operand excType, Operand excObj, Label caughtLabel) {
        Variable eqqResult = s.getNewTemporaryVariable();
        s.addInstr(new RescueEQQInstr(eqqResult, excType, excObj));
        s.addInstr(BEQInstr.create(eqqResult, manager.getTrue(), caughtLabel));
    }

    private void buildRescueBodyInternal(IRScope s, Node node, Variable rv, Label endLabel) {
        final RescueBodyNode rescueBodyNode = (RescueBodyNode) node;
        final Node exceptionList = rescueBodyNode.getExceptionNodes();

        // Load exception & exception comparison type
        Variable exc = s.getNewTemporaryVariable();
        s.addInstr(new ReceiveExceptionInstr(exc));

        // Compare and branch as necessary!
        Label uncaughtLabel = s.getNewLabel();
        Label caughtLabel = s.getNewLabel();
        if (exceptionList != null) {
            if (exceptionList instanceof ListNode) {
               for (Node excType : ((ListNode) exceptionList).childNodes()) {
                   outputExceptionCheck(s, build(excType, s), exc, caughtLabel);
               }
            } else { // splatnode, catch
                outputExceptionCheck(s, build(((SplatNode)exceptionList).getValue(), s), exc, caughtLabel);
            }
        } else {
            // FIXME:
            // rescue => e AND rescue implicitly EQQ the exception object with StandardError
            // We generate explicit IR for this test here.  But, this can lead to inconsistent
            // behavior (when compared to MRI) in certain scenarios.  See example:
            //
            //   self.class.const_set(:StandardError, 1)
            //   begin; raise TypeError.new; rescue; puts "AHA"; end
            //
            // MRI rescues the error, but we will raise an exception because of reassignment
            // of StandardError.  I am ignoring this for now and treating this as undefined behavior.
            //
            // SSS FIXME: Create a 'StandardError' operand type to eliminate this.
            Variable v = s.getNewTemporaryVariable();
            s.addInstr(new InheritanceSearchConstInstr(v, s.getCurrentModuleVariable(), "StandardError", false));
            outputExceptionCheck(s, v, exc, caughtLabel);
        }

        // Uncaught exception -- build other rescue nodes or rethrow!
        s.addInstr(new LabelInstr(uncaughtLabel));
        if (rescueBodyNode.getOptRescueNode() != null) {
            buildRescueBodyInternal(s, rescueBodyNode.getOptRescueNode(), rv, endLabel);
        } else {
            s.addInstr(new ThrowExceptionInstr(exc));
        }

        // Caught exception case -- build rescue body
        s.addInstr(new LabelInstr(caughtLabel));
        Node realBody = skipOverNewlines(s, rescueBodyNode.getBodyNode());
        Operand x = build(realBody, s);
        if (x != U_NIL) { // can be U_NIL if the rescue block has an explicit return
            // Restore "$!"
            RescueBlockInfo rbi = _rescueBlockStack.peek();
            s.addInstr(new PutGlobalVarInstr("$!", rbi.savedExceptionVariable));

            // Set up node return value 'rv'
            s.addInstr(new CopyInstr(rv, x));

            // If we dont have a matching ensure block, jump to the end of the rescue block.
            // If we have a match, jump to that ensure block.  On return, jump to the end of the rescue block.
            if (_ensureBlockStack.empty()) {
                s.addInstr(new JumpInstr(endLabel));
            } else {
                EnsureBlockInfo ebi = _ensureBlockStack.peek();
                if (rbi.rescueNode == ebi.matchingRescueNode) {
                    s.addInstr(new SetReturnAddressInstr(ebi.returnAddr, endLabel));
                    s.addInstr(new JumpInstr(ebi.start));
                } else {
                    s.addInstr(new JumpInstr(endLabel));
                }
            }
        }
    }

    public Operand buildRetry(Node node, IRScope s) {
        // JRuby only supports retry when present in rescue blocks!
        // 1.9 doesn't support retry anywhere else.

        // Jump back to the innermost rescue block
        // We either find it, or we add code to throw a runtime exception
        if (_rescueBlockStack.empty()) {
            s.addInstr(new ThrowExceptionInstr(IRException.RETRY_LocalJumpError));
        } else {
            s.addInstr(new ThreadPollInstr(true));
            // Restore $! and jump back to the entry of the rescue block
            RescueBlockInfo rbi = _rescueBlockStack.peek();
            s.addInstr(new PutGlobalVarInstr("$!", rbi.savedExceptionVariable));
            s.addInstr(new JumpInstr(rbi.entryLabel));
            // Retries effectively create a loop
            s.setHasLoopsFlag(true);
        }
        return manager.getNil();
    }

    public Operand buildReturn(ReturnNode returnNode, IRScope s) {
        Operand retVal = (returnNode.getValueNode() == null) ? manager.getNil() : build(returnNode.getValueNode(), s);

        // Before we return,
        // - have to go execute all the ensure blocks if there are any.
        //   this code also takes care of resetting "$!"
        // - if we dont have any ensure blocks, we have to clear "$!"
        if (!_ensureBlockStack.empty()) EnsureBlockInfo.emitJumpChain(s, _ensureBlockStack, null);
        else if (!_rescueBlockStack.empty()) s.addInstr(new PutGlobalVarInstr("$!", manager.getNil()));

        if (s instanceof IRClosure) {
            // If 'm' is a block scope, a return returns from the closest enclosing method.
            // If this happens to be a module body, the runtime throws a local jump error if
            // the closure is a proc.  If the closure is a lambda, then this is just a normal
            // return and the static methodToReturnFrom value is ignored
            s.addInstr(new NonlocalReturnInstr(retVal, s.getNearestMethod()));
        } else if (s.isModuleBody()) {
            IRMethod sm = s.getNearestMethod();

            // Cannot return from top-level module bodies!
            if (sm == null) s.addInstr(new ThrowExceptionInstr(IRException.RETURN_LocalJumpError));
            else s.addInstr(new NonlocalReturnInstr(retVal, sm));
        } else {
            s.addInstr(new ReturnInstr(retVal));
        }

        // The value of the return itself in the containing expression can never be used because of control-flow reasons.
        // The expression that uses this result can never be executed beyond the return and hence the value itself is just
        // a placeholder operand.
        return UnexecutableNil.U_NIL;
    }

    public IREvalScript buildEvalRoot(StaticScope staticScope, IRScope containingScope, String file, int lineNumber, RootNode rootNode) {
        // Top-level script!
        IREvalScript script = new IREvalScript(manager, containingScope, file, lineNumber, staticScope);

        // Debug info: record line number
        script.addInstr(new LineNumberInstr(script, lineNumber));

        // Set %current_scope = <current-scope>
        // Set %current_module = <current-module>
        script.addInstr(new CopyInstr(script.getCurrentScopeVariable(), new CurrentScope(script)));
        script.addInstr(new CopyInstr(script.getCurrentModuleVariable(), new ScopeModule(script)));
        // Build IR for the tree and return the result of the expression tree
        Operand rval = rootNode.getBodyNode() == null ? manager.getNil() : build(rootNode.getBodyNode(), script);
        script.addInstr(new ReturnInstr(rval));

        return script;
    }

    public IRScope buildRoot(RootNode rootNode) {
        String file = rootNode.getPosition().getFile();
        StaticScope staticScope = rootNode.getStaticScope();

        // Top-level script!
        IRScriptBody script = new IRScriptBody(manager, "__file__", file, staticScope);
        script.addInstr(new ReceiveSelfInstr(script.getSelf()));
        // Set %current_scope = <current-scope>
        // Set %current_module = <current-module>
        script.addInstr(new CopyInstr(script.getCurrentScopeVariable(), new CurrentScope(script)));
        script.addInstr(new CopyInstr(script.getCurrentModuleVariable(), new ScopeModule(script)));

        // Build IR for the tree and return the result of the expression tree
        script.addInstr(new ReturnInstr(build(rootNode.getBodyNode(), script)));

        return script;
    }

    public Operand buildSelf(Node node, IRScope s) {
        return getSelf(s);
    }

    public Operand buildSplat(SplatNode splatNode, IRScope s) {
        // SSS: Since splats can only occur in call argument lists, no need to copyAndReturnValue(...)
        // Verify with Tom / Charlie
        return new Splat(build(splatNode.getValue(), s));
    }

    public Operand buildStr(StrNode strNode, IRScope s) {
        return copyAndReturnValue(s, new StringLiteral(strNode.getValue()));
    }

    private Operand buildSuperInstr(IRScope s, Operand block, Operand[] args) {
        CallInstr superInstr;
        Variable ret = s.getNewTemporaryVariable();
        if ((s instanceof IRMethod) && (s.getLexicalParent() instanceof IRClassBody)) {
            IRMethod m = (IRMethod)s;
            if (m.isInstanceMethod) {
                superInstr = new InstanceSuperInstr(ret, s.getCurrentModuleVariable(), new MethAddr(s.getName()), args, block);
            } else {
                superInstr = new ClassSuperInstr(ret, s.getCurrentModuleVariable(), new MethAddr(s.getName()), args, block);
            }
        } else {
            // We dont always know the method name we are going to be invoking if the super occurs in a closure.
            // This is because the super can be part of a block that will be used by 'define_method' to define
            // a new method.  In that case, the method called by super will be determined by the 'name' argument
            // to 'define_method'.
            superInstr = new UnresolvedSuperInstr(ret, getSelf(s), args, block);
        }

        receiveBreakException(s, block, superInstr);
        return ret;
    }

    public Operand buildSuper(SuperNode superNode, IRScope s) {
        if (s.isModuleBody()) return buildSuperInScriptBody(s);

        List<Operand> args = setupCallArgs(superNode.getArgsNode(), s);
        Operand block = setupCallClosure(superNode.getIterNode(), s);
        if (block == null) block = s.getImplicitBlockArg();
        return buildSuperInstr(s, block, args.toArray(new Operand[args.size()]));
    }

    private Operand buildSuperInScriptBody(IRScope s) {
        Variable ret = s.getNewTemporaryVariable();
        s.addInstr(new UnresolvedSuperInstr(ret, getSelf(s), NO_ARGS, null));
        return ret;
    }

    public Operand buildSValue(SValueNode node, IRScope s) {
        // SSS FIXME: Required? Verify with Tom/Charlie
        return copyAndReturnValue(s, new SValue(build(node.getValue(), s)));
    }

    public Operand buildSymbol(SymbolNode node, IRScope s) {
        // SSS: Since symbols are interned objects, no need to copyAndReturnValue(...)
        return new Symbol(node.getName());
    }

    // ENEBO: This is it's own instruction, but an older note pointed out we
    // could make this an ordinary method and then depend on inlining.
    public Operand buildToAry(ToAryNode node, IRScope s) {
        Operand array = build(node.getValue(), s);
        Variable result = s.getNewTemporaryVariable();
        s.addInstr(new ToAryInstr(result, array, manager.getFalse()));
        return result;
    }

    public Operand buildTrue(Node node, IRScope s) {
        return manager.getTrue();
    }

    public Operand buildUndef(Node node, IRScope s) {
        Operand methName = build(((UndefNode) node).getName(), s);
        Variable result = s.getNewTemporaryVariable();
        s.addInstr(new UndefMethodInstr(result, methName));
        return result;
    }

    private Operand buildConditionalLoop(IRScope s, Node conditionNode,
            Node bodyNode, boolean isWhile, boolean isLoopHeadCondition) {
        if (isLoopHeadCondition &&
                ((isWhile && conditionNode.getNodeType().alwaysFalse()) ||
                (!isWhile && conditionNode.getNodeType().alwaysTrue()))) {
            // we won't enter the loop -- just build the condition node
            build(conditionNode, s);
            return manager.getNil();
        } else {
            IRLoop loop = new IRLoop(s, getCurrentLoop());
            Variable loopResult = loop.loopResult;
            Label setupResultLabel = s.getNewLabel();

            // Push new loop
            loopStack.push(loop);

            // End of iteration jumps here
            s.addInstr(new LabelInstr(loop.loopStartLabel));
            if (isLoopHeadCondition) {
                Operand cv = build(conditionNode, s);
                s.addInstr(BEQInstr.create(cv, isWhile ? manager.getFalse() : manager.getTrue(), setupResultLabel));
            }

            // Redo jumps here
            s.addInstr(new LabelInstr(loop.iterStartLabel));

            // Thread poll at start of iteration -- ensures that redos and nexts run one thread-poll per iteration
            s.addInstr(new ThreadPollInstr(true));

            // Build body
            if (bodyNode != null) build(bodyNode, s);

            // Next jumps here
            s.addInstr(new LabelInstr(loop.iterEndLabel));
            if (isLoopHeadCondition) {
                s.addInstr(new JumpInstr(loop.loopStartLabel));
            } else {
                Operand cv = build(conditionNode, s);
                s.addInstr(BEQInstr.create(cv, isWhile ? manager.getTrue() : manager.getFalse(), loop.iterStartLabel));
            }

            // Loop result -- nil always
            s.addInstr(new LabelInstr(setupResultLabel));
            s.addInstr(new CopyInstr(loopResult, manager.getNil()));

            // Loop end -- breaks jump here bypassing the result set up above
            s.addInstr(new LabelInstr(loop.loopEndLabel));

            // Done with loop
            loopStack.pop();

            return loopResult;
        }
    }

    public Operand buildUntil(final UntilNode untilNode, IRScope s) {
        return buildConditionalLoop(s, untilNode.getConditionNode(), untilNode.getBodyNode(), false, untilNode.evaluateAtStart());
    }

    public Operand buildVAlias(Node node, IRScope s) {
        VAliasNode valiasNode = (VAliasNode) node;
        s.addInstr(new GVarAliasInstr(new StringLiteral(valiasNode.getNewName()), new StringLiteral(valiasNode.getOldName())));
        return manager.getNil();
    }

    public Operand buildVCall(VCallNode node, IRScope s) {
        Variable callResult = s.getNewTemporaryVariable();
        Instr    callInstr  = CallInstr.create(CallType.VARIABLE, callResult, new MethAddr(node.getName()), getSelf(s), NO_ARGS, null);
        s.addInstr(callInstr);
        return callResult;
    }

    public Operand buildWhile(final WhileNode whileNode, IRScope s) {
        return buildConditionalLoop(s, whileNode.getConditionNode(), whileNode.getBodyNode(), true, whileNode.evaluateAtStart());
    }

    public Operand buildXStr(XStrNode node, IRScope s) {
        return copyAndReturnValue(s, new BacktickString(new StringLiteral(node.getValue())));
    }

    public Operand buildYield(YieldNode node, IRScope s) {
        Variable ret = s.getNewTemporaryVariable();
        s.addInstr(new YieldInstr(ret, s.getImplicitBlockArg(), build(node.getArgsNode(), s), node.getExpandArguments()));
        return ret;
    }

    public Operand buildZArray(Node node, IRScope s) {
       return copyAndReturnValue(s, new Array());
    }

/**
    private Operand[] getZSuperArgs(IRScope s) {
        if (s instanceof IRMethod) return ((IRMethod)s).getCallArgs();

        Operand[] sArgs = s.getNearestMethod().getCallArgs();

        // Update args to make them accessible at a different depth
        int n = ((IRClosure)s).getNestingDepth();
        for (int i = 0; i < sArgs.length; i++) {
            Operand arg = sArgs[i];
            sArgs[i] = (arg instanceof Splat) ? new Splat(((LocalVariable)((Splat)arg).getArray()).cloneForDepth(n)) : ((LocalVariable)arg).cloneForDepth(n);
        }

        return sArgs;
    }
**/

    public Operand buildZSuper(ZSuperNode zsuperNode, IRScope s) {
        if (s.isModuleBody()) return buildSuperInScriptBody(s);

        Operand block = setupCallClosure(zsuperNode.getIterNode(), s);
        if (block == null) block = s.getImplicitBlockArg();

        if (s instanceof IRMethod) {
            Operand[] args = ((IRMethod)s).getCallArgs();
            return buildSuperInstr(s, block, args);
        } else {
            // If we are in a block, we cannot make any assumptions about what args
            // the super instr is going to get -- if there were no 'define_method'
            // for defining methods, we could guarantee that the super is going to
            // receive args from the nearest method the block is embedded in.  But,
            // in the presence of 'define_method', all bets are off.
            Variable ret = s.getNewTemporaryVariable();
            receiveBreakException(s, block, new ZSuperInstr(ret, getSelf(s), block));
            return ret;
        }
    }
}
