Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
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 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 _ensureBlockStack = new Stack();
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 _rescueBlockStack = new Stack();
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 loopStack = new Stack();
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 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 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 setupCallArgs(Node args, IRScope s) {
List argsList = new ArrayList();
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 elts = new ArrayList();
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 args = new ArrayList();
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 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 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
// when false
// 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