Skip to content
Browse files

[IR] Breaks and non-local returns handled entirely via IR instrs.

* Removed last bits of special case handling of breaks and
  non-local returns from the interpreter by adding global exception
  handling in methods (those that receive nonlocal returns) and
  all closures (for stopping uncaught breaks in lambdas).  Since
  there is no syntactic/lexical way of distinguishing lambda closures
  from proc and other closures, we need this check in all closures.
* This patch does affect performance of intepreted code in some
  cases.  For example, most of the perf. benefits gained from
  recent patches are lost with this change.
* Worth investigating more closely and maybe selectively enabling
  this only for JIT-ted code, if necessary, for example.
  • Loading branch information...
1 parent 2846144 commit c73cc11118467815fe33f85a225bc15ed441d315 @subbuss subbuss committed Dec 30, 2012
View
59 src/org/jruby/ir/IRBuilder.java
@@ -795,6 +795,54 @@ public Operand buildBreak(BreakNode breakNode, IRScope s) {
return UnexecutableNil.U_NIL;
}
+ // These two methods could have been DRY-ed out if we had closures.
+ // For now, just duplicating code.
+ protected void catchUncaughtBreakInLambdas(IRClosure s) {
+ Label rBeginLabel = s.getNewLabel();
+ Label rEndLabel = s.getNewLabel();
+ Label rescueLabel = s.getNewLabel();
+
+ // protect the entire body as it exists now with the global ensure block
+ s.addInstrAtBeginning(new ExceptionRegionStartMarkerInstr(rBeginLabel, rEndLabel, null, rescueLabel));
+ 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, false)); // no type-checking
+
+ // Handle break using runtime helper
+ // --> IRRuntimeHelpers.catchUncaughtBreakInLambdas(context, scope, bj, blockType)
+ s.addInstr(new RuntimeHelperCall(null, "catchUncaughtBreakInLambdas", new Operand[]{exc} ));
+
+ // End
+ s.addInstr(new LabelInstr(rEndLabel));
+ }
+
+ 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
@@ -1641,6 +1689,11 @@ private IRMethod defineNewMethod(MethodDefNode defNode, IRScope s, boolean isIns
method.addInstr(new ReturnInstr(manager.getNil()));
}
+ // If the method can receive non-local returns
+ if (method.canReceiveNonlocalReturns()) {
+ handleNonlocalReturnInMethod(method);
+ }
+
return method;
}
@@ -2077,6 +2130,8 @@ public Operand buildForIter(final ForNode forNode, IRScope s) {
closure.addInstr(new ReturnInstr(closureRetVal));
}
+ // No need to add "catchUncaughtBreakInLambdas" since this closure cannot be a lambda!
+
return new WrappedIRClosure(closure);
}
@@ -2231,6 +2286,10 @@ public Operand buildIter(final IterNode iterNode, IRScope s) {
closure.addInstr(new ReturnInstr(closureRetVal));
}
+ // SSS FIXME: We can skip this if the closure has no calls
+ // since breaks can never get here in that case.
+ catchUncaughtBreakInLambdas(closure);
+
return new WrappedIRClosure(closure);
}
View
2 src/org/jruby/ir/IRBuilder19.java
@@ -395,6 +395,8 @@ public Operand buildLambda(LambdaNode node, IRScope s) {
// can be U_NIL if the node is an if node with returns in both branches.
if (closureRetVal != U_NIL) closure.addInstr(new ReturnInstr(closureRetVal));
+ catchUncaughtBreakInLambdas(closure);
+
Variable lambda = s.getNewTemporaryVariable();
s.addInstr(new BuildLambdaInstr(lambda, closure, node.getPosition()));
return lambda;
View
20 src/org/jruby/ir/IRScope.java
@@ -224,7 +224,7 @@ public final void putVariable(String name, LocalVariable var) {
protected boolean hasNonlocalReturns;
/** Can this scope receive a non-local return? */
- protected boolean canReceiveNonlocalReturns;
+ public boolean canReceiveNonlocalReturns;
/** Since backref ($~) and lastline ($_) vars are allocated space on the dynamic scope,
* this is an useful flag to compute. */
@@ -363,6 +363,10 @@ public Instr getLastInstr() {
return instrList.get(instrList.size() - 1);
}
+ public void addInstrAtBeginning(Instr i) {
+ instrList.add(0, i);
+ }
+
public void addInstr(Instr i) {
// SSS FIXME: If more instructions set these flags, there may be
// a better way to do this by encoding flags in its own object
@@ -543,6 +547,20 @@ public boolean canCaptureCallersBinding() {
return canCaptureCallersBinding;
}
+ public boolean canReceiveNonlocalReturns() {
+ if (this.canReceiveNonlocalReturns) {
+ return true;
+ }
+
+ boolean canReceiveNonlocalReturns = false;
+ for (IRClosure cl : getClosures()) {
+ if (cl.hasNonlocalReturns || cl.canReceiveNonlocalReturns()) {
+ canReceiveNonlocalReturns = true;
+ }
+ }
+ return canReceiveNonlocalReturns;
+ }
+
public CFG buildCFG() {
cfg = new CFG(this);
cfg.build(instrList);
View
15 src/org/jruby/ir/instructions/RuntimeHelperCall.java
@@ -9,12 +9,14 @@
import org.jruby.ir.operands.Operand;
import org.jruby.ir.operands.UndefinedValue;
import org.jruby.ir.operands.Variable;
+import org.jruby.ir.runtime.IRReturnJump;
import org.jruby.ir.runtime.IRRuntimeHelpers;
import org.jruby.ir.transformations.inlining.InlinerInfo;
import org.jruby.runtime.Block;
import org.jruby.runtime.DynamicScope;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
+import org.jruby.util.unsafe.UnsafeFactory;
import java.util.Map;
@@ -56,18 +58,25 @@ public Instr cloneForInlining(InlinerInfo ii) {
for (int i = 0; i < args.length; i++) {
clonedArgs[i++] = args[i].cloneForInlining(ii);
}
- return new RuntimeHelperCall(ii.getRenamedVariable(getResult()), helperMethod, clonedArgs);
+ Variable var = getResult();
+ return new RuntimeHelperCall(var == null ? null : ii.getRenamedVariable(var), helperMethod, clonedArgs);
}
@Override
public String toString() {
- return "" + getOperation() + "(" + helperMethod + ", " + Arrays.toString(args) + ")";
+ return (getResult() == null ? "" : (getResult() + " = ")) + getOperation() + "(" + helperMethod + ", " + Arrays.toString(args) + ")";
}
public IRubyObject callHelper(ThreadContext context, DynamicScope currDynScope, IRubyObject self, Object[] temp, IRScope scope, Block.Type blockType) {
+ Object exc = args[0].retrieve(context, self, currDynScope, temp);
if (helperMethod.equals("handlePropagatedBreak")) {
- Object exc = args[0].retrieve(context, self, currDynScope, temp);
return IRRuntimeHelpers.handlePropagatedBreak(context, scope, exc, blockType);
+ } else if (helperMethod.equals("handleNonlocalReturn")) {
+ return IRRuntimeHelpers.handleNonlocalReturn(scope, exc, blockType);
+ } else if (helperMethod.equals("catchUncaughtBreakInLambdas")) {
+ IRRuntimeHelpers.catchUncaughtBreakInLambdas(context, scope, exc, blockType);
+ // should not get here
+ return null;
} else {
// Unknown helper method!
return null;
View
15 src/org/jruby/ir/interpreter/Interpreter.java
@@ -64,6 +64,7 @@
import org.jruby.runtime.callsite.CacheEntry;
import org.jruby.internal.runtime.methods.InterpretedIRMethod;
import org.jruby.internal.runtime.methods.DynamicMethod;
+import org.jruby.util.unsafe.UnsafeFactory;
public class Interpreter {
private static final Logger LOG = LoggerFactory.getLogger("Interpreter");
@@ -557,19 +558,7 @@ private static IRubyObject interpret(ThreadContext context, IRubyObject self,
if (debug) LOG.info("ipc for rescuer/ensurer: " + ipc);
if (ipc == -1) {
- // SSS FIXME: Modify instruction stream to eliminate these two cases
- // of special handling of uncaught break-jumps and local-returns
- //
- // Add global-ensure-block to handle these scenarios:
- // -> for break jumps, needed in closures only
- // -> for nonlocal returns, needed in methods only
- if (t instanceof IRBreakJump) {
- // Should always throw an exception
- throw IRRuntimeHelpers.propagateBreak(context, scope, (IRBreakJump)t, blockType);
- } else {
- // No ensure block here, propagate the return, if necessary
- return IRRuntimeHelpers.handleNonlocalReturn(scope, t, blockType);
- }
+ UnsafeFactory.getUnsafe().throwException((Throwable)t);
} else {
exception = t;
}
View
8 src/org/jruby/ir/runtime/IRRuntimeHelpers.java
@@ -113,13 +113,13 @@ public static IRubyObject initiateBreak(ThreadContext context, IRScope scope, IR
}
}
- public static RuntimeException propagateBreak(ThreadContext context, IRScope scope, IRBreakJump bj, Block.Type blockType) {
- if (inNonMethodBodyLambda(scope, blockType)) {
+ public static void catchUncaughtBreakInLambdas(ThreadContext context, IRScope scope, Object exc, Block.Type blockType) throws RuntimeException {
+ if ((exc instanceof IRBreakJump) && inNonMethodBodyLambda(scope, blockType)) {
// We just unwound all the way up because of a non-local break
- return IRException.BREAK_LocalJumpError.getException(context.getRuntime());
+ throw IRException.BREAK_LocalJumpError.getException(context.getRuntime());
} else {
// Propagate
- return bj;
+ UnsafeFactory.getUnsafe().throwException((Throwable)exc);
}
}

0 comments on commit c73cc11

Please sign in to comment.
Something went wrong with that request. Please try again.