package org.jruby.ir;
import java.util.List;
import java.util.ArrayList;
// Closures are contexts/scopes for the purpose of IR building. They are self-contained and accumulate instructions
// that don't merge into the flow of the containing scope. They are manipulated as an unit.
// Their parents are always execution scopes.
import org.jruby.ir.operands.Label;
import org.jruby.ir.operands.Operand;
import org.jruby.ir.operands.Splat;
import org.jruby.ir.operands.ClosureLocalVariable;
import org.jruby.ir.operands.LocalVariable;
import org.jruby.ir.operands.TemporaryVariable;
import org.jruby.ir.operands.TemporaryClosureVariable;
import org.jruby.ir.operands.Variable;
import org.jruby.ir.instructions.Instr;
import org.jruby.ir.instructions.ReceiveArgBase;
import org.jruby.ir.instructions.ReceiveExceptionInstr;
import org.jruby.ir.instructions.ReceiveRestArgBase;
import org.jruby.ir.instructions.RuntimeHelperCall;
import org.jruby.ir.representations.BasicBlock;
import org.jruby.ir.representations.CFG;
import org.jruby.ir.transformations.inlining.InlinerInfo;
import org.jruby.parser.StaticScope;
import org.jruby.parser.IRStaticScope;
import org.jruby.runtime.Arity;
import org.jruby.runtime.BlockBody;
import org.jruby.runtime.InterpretedIRBlockBody;
import org.jruby.runtime.InterpretedIRBlockBody19;
public class IRClosure extends IRScope {
public final Label startLabel; // Label for the start of the closure (used to implement redo)
public final Label endLabel; // Label for the end of the closure (used to implement retry)
public final int closureId; // Unique id for this closure within the nearest ancestor method.
private int nestingDepth; // How many nesting levels within a method is this closure nested in?
private BlockBody body;
// for-loop body closures are special in that they dont really define a new variable scope.
// They just silently reuse the parent scope. This changes how variables are allocated (see IRMethod.java).
private boolean isForLoopBody;
// Block parameters
private List<Operand> blockArgs;
/** The parameter names, for Proc#parameters */
private String[] parameterList;
public boolean addedGEBForUncaughtBreaks;
/** Used by cloning code */
private IRClosure(IRClosure c, IRScope lexicalParent) {
super(c, lexicalParent);
this.closureId = lexicalParent.getNextClosureId();
setName("_CLOSURE_CLONE_" + closureId);
this.startLabel = getNewLabel(getName() + "_START");
this.endLabel = getNewLabel(getName() + "_END");
this.body = (c.body instanceof InterpretedIRBlockBody19) ? new InterpretedIRBlockBody19(this, c.body.arity(), c.body.getArgumentType())
: new InterpretedIRBlockBody(this, c.body.arity(), c.body.getArgumentType());
this.addedGEBForUncaughtBreaks = false;
}
public IRClosure(IRManager manager, IRScope lexicalParent, boolean isForLoopBody,
int lineNumber, StaticScope staticScope, Arity arity, int argumentType, boolean is1_9) {
this(manager, lexicalParent, lexicalParent.getFileName(), lineNumber, staticScope, isForLoopBody ? "_FOR_LOOP_" : "_CLOSURE_");
this.isForLoopBody = isForLoopBody;
this.blockArgs = new ArrayList<Operand>();
if (getManager().isDryRun()) {
this.body = null;
} else {
this.body = is1_9 ? new InterpretedIRBlockBody19(this, arity, argumentType)
: new InterpretedIRBlockBody(this, arity, argumentType);
if ((staticScope != null) && !isForLoopBody) ((IRStaticScope)staticScope).setIRScope(this);
}
// set nesting depth -- after isForLoopBody value is set
int n = 0;
IRScope s = this;
while (s instanceof IRClosure) {
if (!s.isForLoopBody()) n++;
s = s.getLexicalParent();
}
this.nestingDepth = n;
}
// Used by IREvalScript
protected IRClosure(IRManager manager, IRScope lexicalParent, String fileName, int lineNumber, StaticScope staticScope, String prefix) {
super(manager, lexicalParent, null, fileName, lineNumber, staticScope);
this.isForLoopBody = false;
this.startLabel = getNewLabel(prefix + "START");
this.endLabel = getNewLabel(prefix + "END");
this.closureId = lexicalParent.getNextClosureId();
setName(prefix + closureId);
this.body = null;
this.parameterList = new String[] {};
// set nesting depth
int n = 0;
IRScope s = this;
while (s instanceof IRClosure) {
if (!s.isForLoopBody()) n++;
s = s.getLexicalParent();
}
this.nestingDepth = n;
}
public void setParameterList(String[] parameterList) {
this.parameterList = parameterList;
}
public String[] getParameterList() {
return this.parameterList;
}
@Override
public int getNextClosureId() {
return getLexicalParent().getNextClosureId();
}
@Override
public LocalVariable getNewFlipStateVariable() {
throw new RuntimeException("Cannot get flip variables from closures.");
}
@Override
public TemporaryVariable getNewTemporaryVariable() {
temporaryVariableIndex++;
return new TemporaryClosureVariable(closureId, temporaryVariableIndex);
}
public TemporaryVariable getNewTemporaryVariable(String name) {
temporaryVariableIndex++;
return new TemporaryClosureVariable(name, temporaryVariableIndex);
}
@Override
public Label getNewLabel() {
return getNewLabel("CL" + closureId + "_LBL");
}
public String getScopeName() {
return "Closure";
}
@Override
public boolean isForLoopBody() {
return isForLoopBody;
}
@Override
public boolean isTopLocalVariableScope() {
return false;
}
@Override
public boolean isFlipScope() {
return false;
}
@Override
public void addInstr(Instr i) {
// Accumulate block arguments
if (i instanceof ReceiveRestArgBase) blockArgs.add(new Splat(((ReceiveRestArgBase)i).getResult()));
else if (i instanceof ReceiveArgBase) blockArgs.add(((ReceiveArgBase) i).getResult());
super.addInstr(i);
}
public Operand[] getBlockArgs() {
return blockArgs.toArray(new Operand[blockArgs.size()]);
}
public String toStringBody() {
StringBuilder buf = new StringBuilder();
buf.append(getName()).append(" = { \n");
CFG c = getCFG();
if (c != null) {
buf.append("\nCFG:\n").append(c.toStringGraph()).append("\nInstructions:\n").append(c.toStringInstrs());
} else {
buf.append(toStringInstrs());
}
buf.append("\n}\n\n");
return buf.toString();
}
public BlockBody getBlockBody() {
return body;
}
@Override
public LocalVariable findExistingLocalVariable(String name, int scopeDepth) {
LocalVariable lvar = localVars.getVariable(name);
if (lvar != null) return lvar;
int newDepth = isForLoopBody ? scopeDepth : scopeDepth - 1;
return newDepth >= 0 ? getLexicalParent().findExistingLocalVariable(name, newDepth) : null;
}
public LocalVariable getNewLocalVariable(String name, int depth) {
if (isForLoopBody) return getLexicalParent().getNewLocalVariable(name, depth);
if (depth == 0) {
LocalVariable lvar = new ClosureLocalVariable(this, name, 0, localVars.nextSlot);
localVars.putVariable(name, lvar);
return lvar;
} else {
return getLexicalParent().getNewLocalVariable(name, depth-1);
}
}
@Override
public LocalVariable getLocalVariable(String name, int scopeDepth) {
if (isForLoopBody) return getLexicalParent().getLocalVariable(name, scopeDepth);
LocalVariable lvar = findExistingLocalVariable(name, scopeDepth);
if (lvar == null) lvar = getNewLocalVariable(name, scopeDepth);
// Create a copy of the variable usable at the right depth
if (lvar.getScopeDepth() != scopeDepth) lvar = lvar.cloneForDepth(scopeDepth);
return lvar;
}
public int getNestingDepth() {
return nestingDepth;
}
public LocalVariable getImplicitBlockArg() {
// SSS: FIXME: Ugly! We cannot use 'getLocalVariable(Variable.BLOCK, getNestingDepth())' because
// of scenario 3. below. Can we clean up this code?
//
// 1. If the variable has previously been defined, return a copy usable at the closure's nesting depth.
// 2. If not, and if the closure is ultimately nested within a method, build a local variable that will
// be defined in that method.
// 3. If not, and if the closure is not nested within a method, the closure can never receive a block.
// So, we could return 'null', but it creates problems for IR generation. So, for this scenario,
// we simply create a dummy var at depth 0 (meaning, it is local to the closure itself) and return it.
LocalVariable blockVar = findExistingLocalVariable(Variable.BLOCK, getNestingDepth());
if (blockVar != null) {
// Create a copy of the variable usable at the right depth
if (blockVar.getScopeDepth() != getNestingDepth()) blockVar = blockVar.cloneForDepth(getNestingDepth());
} else {
IRScope s = this;
while (s instanceof IRClosure) s = s.getLexicalParent();
if (s instanceof IRMethod) {
blockVar = s.getNewLocalVariable(Variable.BLOCK, 0);
// Create a copy of the variable usable at the right depth
if (getNestingDepth() != 0) blockVar = blockVar.cloneForDepth(getNestingDepth());
} else {
// Dummy var
blockVar = getNewLocalVariable(Variable.BLOCK, 0);
}
}
return blockVar;
}
public IRClosure cloneForClonedInstr(InlinerInfo ii) {
IRClosure clonedClosure = new IRClosure(this, ii.getNewLexicalParentForClosure());
clonedClosure.isForLoopBody = this.isForLoopBody;
clonedClosure.nestingDepth = this.nestingDepth;
clonedClosure.parameterList = this.parameterList;
// Create a new inliner info object
ii = ii.cloneForCloningClosure(clonedClosure);
// clone the cfg, and all instructions
clonedClosure.setCFG(getCFG().cloneForCloningClosure(clonedClosure, ii));
return clonedClosure;
}
// Add a global-ensure-block to catch uncaught breaks
// This is usually required only if this closure is being
// used as a lambda, but it is safe to add this for any closure
protected boolean addGEBForUncaughtBreaks() {
// Nothing to do if already done
if (addedGEBForUncaughtBreaks) {
return false;
}
CFG cfg = cfg();
BasicBlock geb = cfg.getGlobalEnsureBB();
if (geb == null) {
geb = new BasicBlock(cfg, new Label("_GLOBAL_ENSURE_BLOCK"));
Variable exc = getNewTemporaryVariable();
geb.addInstr(new ReceiveExceptionInstr(exc, false)); // No need to check type since it is not used before rethrowing
// Handle uncaught break using runtime helper
// --> IRRuntimeHelpers.catchUncaughtBreakInLambdas(context, scope, bj, blockType)
geb.addInstr(new RuntimeHelperCall(null, "catchUncaughtBreakInLambdas", new Operand[]{exc} ));
cfg.addGlobalEnsureBB(geb);
} else {
// SSS FIXME: Assumptions:
//
// First instr is a 'ReceiveExceptionInstr'
// Last instr is a 'ThrowExceptionInstr'
List<Instr> instrs = geb.getInstrs();
Variable exc = ((ReceiveExceptionInstr)instrs.get(0)).getResult();
instrs.set(instrs.size(), new RuntimeHelperCall(null, "catchUncaughtBreakInLambdas", new Operand[]{exc} ));
}
// Update scope
addedGEBForUncaughtBreaks = true;
return true;
}
}