Skip to content

Commit

Permalink
[IR] First pass implementing an explicit call protocol via IR
Browse files Browse the repository at this point in the history
instructions -- for now, only bindings are explicitly managed for
non-closure and non-eval scopes. Frames, etc. will come later.
  • Loading branch information
subbuss committed May 7, 2012
1 parent fed071c commit 16b2963
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 5 deletions.
17 changes: 12 additions & 5 deletions src/org/jruby/internal/runtime/methods/InterpretedIRMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,16 +79,23 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
displayedCFG = true;
}

boolean hasExplicitCallProtocol = method.hasExplicitCallProtocol();
try {
// update call stacks (push: frame, class, scope, etc.)
RubyModule implementationClass = getImplementationClass();
context.preMethodFrameAndScope(implementationClass, name, self, block, method.getStaticScope());
RubyModule implClass = getImplementationClass();
StaticScope staticScope = method.getStaticScope();
if (hasExplicitCallProtocol) {
context.preMethodFrameAndClass(implClass, name, self, block, staticScope);
} else {
context.preMethodFrameAndScope(implClass, name, self, block, staticScope);
}
context.setCurrentVisibility(getVisibility());
return Interpreter.INTERPRET_METHOD(context, method, self, name, implementationClass, args, block, null, false);
return Interpreter.INTERPRET_METHOD(context, method, self, name, implClass, args, block, null, false);
} finally {
// update call stacks (pop: ..)
context.popFrame();
context.postMethodScopeOnly();
context.popRubyClass();
if (!hasExplicitCallProtocol) context.popScope();
}
}

Expand All @@ -103,5 +110,5 @@ public String getFile() {

public int getLine() {
return method.getLineNumber();
}
}
}
18 changes: 18 additions & 0 deletions src/org/jruby/ir/IRScope.java
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,13 @@ public final void putVariable(String name, LocalVariable var) {
/** # of thread poll instrs added to this scope */
private int threadPollInstrsCount;

/** Does this scope have explicit call protocol instructions?
* If yes, there are IR instructions for managing bindings/frames, etc.
* If not, this has to be managed implicitly as in the current runtime
* For now, only dyn-scopes are managed explicitly.
* Others will come in time */
private boolean hasExplicitCallProtocol;

/** Should we re-run compiler passes -- yes after we've inlined, for example */
private boolean relinearizeCFG;

Expand Down Expand Up @@ -249,6 +256,7 @@ protected IRScope(IRScope s, IRScope lexicalParent) {
this.usesEval = s.usesEval;
this.usesBackrefOrLastline = s.usesBackrefOrLastline;
this.usesZSuper = s.usesZSuper;
this.hasExplicitCallProtocol = s.hasExplicitCallProtocol;

this.localVars = new LocalVariableAllocator(); // SSS FIXME: clone!
this.localVars.nextSlot = s.localVars.nextSlot;
Expand Down Expand Up @@ -286,6 +294,8 @@ public IRScope(IRManager manager, IRScope lexicalParent, String name,
this.usesBackrefOrLastline = true;
this.usesZSuper = true;

this.hasExplicitCallProtocol = false;

this.localVars = new LocalVariableAllocator();
synchronized(globalScopeCount) { this.scopeId = globalScopeCount++; }
this.relinearizeCFG = false;
Expand Down Expand Up @@ -459,6 +469,14 @@ public boolean hasLoops() {
return hasLoops;
}

public boolean hasExplicitCallProtocol() {
return hasExplicitCallProtocol;
}

public void setExplicitCallProtocolFlag(boolean flag) {
this.hasExplicitCallProtocol = flag;
}

public void setCodeModificationFlag(boolean f) {
canModifyCode = f;
}
Expand Down
137 changes: 137 additions & 0 deletions src/org/jruby/ir/passes/AddCallProtocolInstructions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package org.jruby.ir.passes;

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

import org.jruby.ir.IRClosure;
import org.jruby.ir.IRMethod;
import org.jruby.ir.IRModuleBody;
import org.jruby.ir.IRScriptBody;
import org.jruby.ir.IRScope;
import org.jruby.ir.instructions.BreakInstr;
import org.jruby.ir.instructions.Instr;
import org.jruby.ir.instructions.PopBindingInstr;
import org.jruby.ir.instructions.PushBindingInstr;
import org.jruby.ir.instructions.ReceiveExceptionInstr;
import org.jruby.ir.instructions.ReturnBase;
import org.jruby.ir.instructions.ThrowExceptionInstr;
import org.jruby.ir.dataflow.analyses.StoreLocalVarPlacementProblem;
import org.jruby.ir.operands.Label;
import org.jruby.ir.operands.LocalVariable;
import org.jruby.ir.operands.Operand;
import org.jruby.ir.operands.Variable;
import org.jruby.ir.representations.BasicBlock;
import org.jruby.ir.representations.CFG;

public class AddCallProtocolInstructions extends CompilerPass {
boolean addedInstrs = false;

public String getLabel() {
return "Add Call Protocol Instructions (push/pop of dyn-scope, frame, impl-class values)";
}

public static List<Class<? extends CompilerPass>> DEPENDENCIES = new ArrayList<Class<? extends CompilerPass>>() {{
add(CFGBuilder.class);
}};

@Override
public List<Class<? extends CompilerPass>> getDependencies() {
return DEPENDENCIES;
}

public Object execute(IRScope scope, Object... data) {
StoreLocalVarPlacementProblem slvpp = (StoreLocalVarPlacementProblem)scope.getDataFlowSolution(StoreLocalVarPlacementProblem.NAME);

boolean scopeHasLocalVarStores = false;
boolean scopeHasUnrescuedExceptions = false;

CFG cfg = scope.cfg();
BasicBlock geb = cfg.getGlobalEnsureBB();

if (slvpp != null) {
scopeHasLocalVarStores = slvpp.scopeHasLocalVarStores();
scopeHasUnrescuedExceptions = slvpp.scopeHasUnrescuedExceptions();
} else {
// We dont require local-var load/stores to have been run.
// If it is not run, we go conservative and add push/pop binding instrs. everywhere
scopeHasLocalVarStores = true;
scopeHasUnrescuedExceptions = false;
for (BasicBlock bb: cfg.getBasicBlocks()) {
// SSS FIXME: This is highly conservative. If the bb has an exception raising instr.
// and if we dont have a rescuer, only then do we have unrescued exceptions.
if (cfg.getRescuerBBFor(bb) == null) {
scopeHasUnrescuedExceptions = true;
break;
}
}
}

// Add explicit binding push/pop instrs ONLY for methods -- we cannot handle this in closures and evals yet
// If the scope uses $_ or $~ family of vars, has local load/stores, or if its binding has escaped, we have
// to allocate a dynamic scope for it and add binding push/pop instructions.
if ((scope instanceof IRMethod) || (scope instanceof IRScriptBody) || (scope instanceof IRModuleBody)) {
if (scope.bindingHasEscaped() || scope.usesBackrefOrLastline() || scopeHasLocalVarStores) {
// Push
cfg.getEntryBB().addInstr(new PushBindingInstr(scope));

// Allocate GEB if necessary for popping binding
if (geb == null && scopeHasLocalVarStores && scopeHasUnrescuedExceptions) {
Variable exc = scope.getNewTemporaryVariable();
geb = new BasicBlock(cfg, new Label("_GLOBAL_ENSURE_BLOCK"));
geb.addInstr(new ReceiveExceptionInstr(exc));
geb.addInstr(new ThrowExceptionInstr(exc));
cfg.addGlobalEnsureBB(geb);
}

// Pop on all scope-exit paths
BasicBlock exitBB = cfg.getExitBB();
for (BasicBlock bb: cfg.getBasicBlocks()) {
ListIterator<Instr> instrs = bb.getInstrs().listIterator();
while (instrs.hasNext()) {
Instr i = instrs.next();
if ((bb != exitBB) && (i instanceof ReturnBase) || (i instanceof BreakInstr)) {
// Add before the break/return
instrs.previous();
instrs.add(new PopBindingInstr());
break;
}
}

if (bb == exitBB) {
// Last instr could be a return -- so, move iterator one position back
if (instrs.hasPrevious()) instrs.previous();
instrs.add(new PopBindingInstr());
}

if (bb == geb) {
// Add before throw-exception-instr which would be the last instr
instrs.previous();
instrs.add(new PopBindingInstr());
}
}
}

// This scope has an explicit call protocol flag now
scope.setExplicitCallProtocolFlag(true);
}

// Run on all nested closures.
for (IRClosure c: scope.getClosures()) execute(c);

// Mark as done
addedInstrs = true;

return null;
}

@Override
public Object previouslyRun(IRScope scope) {
return addedInstrs ? new Object() : null;
}

@Override
public void invalidate(IRScope scope) {
// Cannot add call protocol instructions after we've added them once.
}
}
8 changes: 8 additions & 0 deletions src/org/jruby/runtime/ThreadContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,14 @@ public void preBsfApply(String[] names) {
public void postBsfApply() {
popFrame();
}

public void preMethodFrameAndClass(RubyModule implClass, String name, IRubyObject self, Block block, StaticScope staticScope) {
RubyModule ssModule = staticScope.getModule();
// FIXME: This is currently only here because of some problems with IOOutputStream writing to a "bare" runtime without a proper scope
if (ssModule == null) ssModule = implClass;
pushRubyClass(ssModule);
pushCallFrame(implClass, name, self, block);
}

public void preMethodFrameAndScope(RubyModule clazz, String name, IRubyObject self, Block block,
StaticScope staticScope) {
Expand Down

0 comments on commit 16b2963

Please sign in to comment.