package org.jruby.ir.passes;
import org.jruby.ir.*;
import org.jruby.ir.instructions.*;
import org.jruby.runtime.Signature;
import org.jruby.ir.operands.ImmutableLiteral;
import org.jruby.ir.operands.Label;
import org.jruby.ir.operands.Operand;
import org.jruby.ir.operands.Self;
import org.jruby.ir.operands.TemporaryVariable;
import org.jruby.ir.operands.Variable;
import org.jruby.ir.representations.BasicBlock;
import org.jruby.ir.representations.CFG;
import org.jruby.runtime.Visibility;
import java.util.ListIterator;
public class AddCallProtocolInstructions extends CompilerPass {
@Override
public String getLabel() {
return "Add Call Protocol Instructions (push/pop of dyn-scope, frame, impl-class values)";
}
@Override
public String getShortLabel() {
return "Add Call Proto";
}
private boolean explicitCallProtocolSupported(IRScope scope) {
return scope instanceof IRMethod
|| (scope instanceof IRClosure && !(scope instanceof IREvalScript))
|| (scope instanceof IRModuleBody && !(scope instanceof IRMetaClassBody)
|| (scope instanceof IRScriptBody)
);
}
private void fixReturn(IRScope scope, ReturnBase i, ListIterator<Instr> instrs) {
Operand retVal = i.getReturnValue();
if (!(retVal instanceof ImmutableLiteral || retVal instanceof TemporaryVariable)) {
TemporaryVariable tmp = scope.createTemporaryVariable();
CopyInstr copy = new CopyInstr(tmp, retVal);
i.updateReturnValue(tmp);
instrs.previous();
instrs.add(copy);
instrs.next();
}
}
private void popSavedState(IRScope scope, boolean isGEB, boolean requireBinding, boolean requireFrame, Variable savedViz, Variable savedFrame, ListIterator<Instr> instrs) {
if (scope instanceof IRClosure && isGEB) {
instrs.previous();
}
if (requireBinding) instrs.add(new PopBindingInstr());
if (scope instanceof IRClosure) {
if (scope.needsFrame()) {
instrs.add(new RestoreBindingVisibilityInstr(savedViz));
instrs.add(new PopBlockFrameInstr(savedFrame));
}
} else {
if (requireFrame) {
if (scope.needsOnlyBackref()) {
instrs.add(new PopBackrefFrameInstr());
} else {
instrs.add(new PopMethodFrameInstr());
}
}
}
}
@Override
public Object execute(IRScope scope, Object... data) {
if (!explicitCallProtocolSupported(scope)) return null;
CFG cfg = scope.getCFG();
boolean requireFrame = scope.needsFrame();
boolean requireBinding = scope.needsBinding();
if (scope instanceof IRClosure || requireBinding || requireFrame) {
BasicBlock entryBB = cfg.getEntryBB();
Variable savedViz = null, savedFrame = null;
if (scope instanceof IRClosure) {
savedViz = scope.createTemporaryVariable();
savedFrame = scope.createTemporaryVariable();
int insertIndex = 0;
if (scope.needsFrame()) {
entryBB.insertInstr(insertIndex++, new SaveBindingVisibilityInstr(savedViz));
entryBB.insertInstr(insertIndex++, new PushBlockFrameInstr(savedFrame, scope.getName()));
}
if (requireBinding) {
entryBB.insertInstr(insertIndex++, new PushBlockBindingInstr());
}
entryBB.insertInstr(insertIndex++, new UpdateBlockExecutionStateInstr(Self.SELF));
BasicBlock prologueBB = createPrologueBlock(cfg);
Signature sig = ((IRClosure)scope).getSignature();
int arityValue = sig.arityValue();
if (arityValue == 0) {
prologueBB.addInstr(PrepareNoBlockArgsInstr.INSTANCE);
} else {
if (sig.isFixed()) {
if (arityValue == 1) {
prologueBB.addInstr(PrepareSingleBlockArgInstr.INSTANCE);
} else {
prologueBB.addInstr(PrepareBlockArgsInstr.INSTANCE);
}
} else {
prologueBB.addInstr(PrepareBlockArgsInstr.INSTANCE);
}
}
} else {
if (requireFrame) {
if (scope.needsOnlyBackref()) {
entryBB.addInstr(new PushBackrefFrameInstr());
} else {
entryBB.addInstr(new PushMethodFrameInstr(
scope.getName(),
scope.isScriptScope() ? Visibility.PRIVATE : Visibility.PUBLIC));
}
}
if (requireBinding) entryBB.addInstr(new PushMethodBindingInstr());
}
BasicBlock geb = cfg.getGlobalEnsureBB();
boolean gebProcessed = false;
if (geb == null) {
Variable exc = scope.createTemporaryVariable();
geb = new BasicBlock(cfg, Label.getGlobalEnsureBlockLabel());
geb.addInstr(new ReceiveJRubyExceptionInstr(exc));
geb.addInstr(new ThrowExceptionInstr(exc));
cfg.addGlobalEnsureBB(geb);
}
for (BasicBlock bb: cfg.getBasicBlocks()) {
Instr i = null;
ListIterator<Instr> instrs = bb.getInstrs().listIterator();
while (instrs.hasNext()) {
i = instrs.next();
if (!bb.isExitBB() && i instanceof ReturnInstr) {
if (requireFrame || requireBinding) fixReturn(scope, (ReturnInstr)i, instrs);
i = instrs.previous();
popSavedState(scope, bb == geb, requireBinding, requireFrame, savedViz, savedFrame, instrs);
if (bb == geb) gebProcessed = true;
break;
}
}
if (bb.isExitBB() && !bb.isEmpty()) {
if (i != null && i instanceof ReturnInstr) {
if (requireFrame || requireBinding) fixReturn(scope, (ReturnInstr)i, instrs);
instrs.previous();
}
popSavedState(scope, bb == geb, requireBinding, requireFrame, savedViz, savedFrame, instrs);
if (bb == geb) gebProcessed = true;
} else if (!gebProcessed && bb == geb) {
if (i != null) {
assert i.getOperation().transfersControl(): "Last instruction of GEB in scope: " + scope + " is " + i + ", not a control-xfer instruction";
instrs.previous();
}
popSavedState(scope, true, requireBinding, requireFrame, savedViz, savedFrame, instrs);
}
}
}
scope.setExplicitCallProtocolFlag();
(new LiveVariableAnalysis()).invalidate(scope);
return null;
}
private BasicBlock createPrologueBlock(CFG cfg) {
BasicBlock entryBB = cfg.getEntryBB();
BasicBlock oldStart = cfg.getOutgoingDestinationOfType(entryBB, CFG.EdgeType.FALL_THROUGH);
BasicBlock prologueBB = new BasicBlock(cfg, cfg.getScope().getNewLabel());
cfg.removeEdge(entryBB, oldStart);
cfg.addBasicBlock(prologueBB);
cfg.addEdge(entryBB, prologueBB, CFG.EdgeType.FALL_THROUGH);
cfg.addEdge(prologueBB, oldStart, CFG.EdgeType.FALL_THROUGH);
if (cfg.getGlobalEnsureBB() != null) {
BasicBlock geb = cfg.getGlobalEnsureBB();
cfg.addEdge(prologueBB, geb, CFG.EdgeType.EXCEPTION);
cfg.setRescuerBB(prologueBB, geb);
}
return prologueBB;
}
@Override
public boolean invalidate(IRScope scope) {
return false;
}
}