Implementation of transfers out of closures
Neal Gafter
neal at gafter.com
Sun Jul 13 17:11:44 PDT 2008
I'm now working on the implementation of transfers out of a closure. For
example, a "return" statement inside a closure returns from the enclosing
method (rather than from the closure itself). While specification changes
are outside the scope of this project, for those of you interested the
motivation for having a single meaning for "return" (return from the
enclosing method) rather than introducing a new meaning for "return" in a
closure, the issue is discussed in
http://gafter.blogspot.com/2006/08/tennents-correspondence-principle-and.html.
Similarly, "break" and "continue" can flow from within a closure to an
enclosing loop, switch, etc.
I have the following design constraints:
- The "normal" case should be relatively fast. Specifically, there should
be no capturing of a stack trace when the transfer is successful.
- The structure of the generated code should take advantage of existing
hotspot optimizations and enable new ones.
- Successful transfers should be transparent to intervening catch clauses
(this will be discussed in a later message).
- The failure case should capture a stack trace for debugging and
diagnosing the problem. This includes the case in which the transfer is
executed in the wrong thread, or when a transfer occurs too late.
- Transient object creation should be minimized.
- When a transfer fails because of being in the wrong thread, there
should be enough information for an underlying concurrent loop
implementation to shuttle the transfer to the correct thread to complete it.
See
http://gafter.blogspot.com/2006/10/concurrent-loops-using-java-closures.html,
where I suggested the proper treatment; the language should provide enough
information for a concurrent loop API to implement this:
What should happen if you use continue, break, or return within the body of
this loop, or throw an exception? The continue case is easy: it just
completes execution of that one iteration, and the other iterations proceed
on their merry way. The semantics of break are a bit subtle, but obvious
once you realize this is supposed to act like a loop: it completes the
current iteration and cancels any other iterations that have not completed,
and control returns to the caller of for eachConcurrently. Handling a
returnstatement is similar: it cancels any uncompleted iterations and
returns from
the enclosing method, which in this case would be getAttendees. Finally, any
exception that propagates out of a loop iteration cancels uncompleted
iterations and propagates from the loop.
I also carefully avoided the need for "volatile" in accurately (I think)
distinguishing the failure from success cases.
The planned implementation is as follows. First, we add two support classes:
/**
* A class used to implement nonlocal transfer of control. This
* should only be thrown when the target of the transfer is on
* the call stack of the current thread.
*/
public class Jump extends RuntimeException { // should really not be a
subtype of Throwable
/** Returns the thread in which the transfer should take place. */
public Thread thread() { return Thread.currentThread(); }
/** Causes the transfer to occur, typically by throwing this. */
public void transfer() { throw this; }
/** We suppress the stack trace for transfers. */
@Override public Throwable fillInStackTrace() {}
}
/**
* Exception thrown when a transfer from within a lambda doesn't have
* a matching frame on the stack of the current thread.
*/
class UnmatchedNonlocalTransfer extends RuntimeException {
/** The Jump that would cause the transfer of control. */
private Jump jump;
public UnmatchedNonlocalTransfer(Jump jump) {
this.jump = jump;
}
/** Returns the thread containing the target of the transfer. */
public Thread thread() { return jump.thread(); }
/** Attempts to cause the transfer to occur. May throw
UnmatchedNonlocalTransfer. */
public Unreachable transfer() { jump.transfer(); }
}
Now, to translate our source code, consider a method
int method(...) {
...
*return 3;* // original code inside some closure
...
}
this would be translated into something like this:
int method(...) {
static class Frame$ { // the promoted parts of the method call frame
Thread thread; // track the thread executing the call
boolean dead; // set to true when the method is not active
}
final Frame$ frame$ = new Frame$();
frame$.thread = Thread.currentThread();
// a synthetic class customized for handling nonlocal transfers from
this method
static class MyReturn extends Jump {
final Frame$ frame; // needed to detect correct frame in case there
is recursion
final int value; // the returned value (can't be stored in the frame
because of try-finally)
@Override public Unreachable transfer() {
throw (Thread.currentThread() == frame.thread && !frame.dead)
? this : new UnmatchedNonlocalTransfer(this);
}
@Override public void thread() { return frame.thread; }
MyReturn(Frame frame, int value) {
super();
this.frame = frame;
this.value = value;
}
}
try { // wraps the whole body of the translated method
...
*new MyReturn(frame$, 3).transfer();* // translated code inside
some closure
...
} catch (MyReturn ex) {
if (ex.frame == frame$) // allow support for recursion
return ex.value;
throw ex;
} finally {
frame$.dead = true; // allow detection of late return
}
}
It would be possible to eliminate the nested synthetic class MyReturn by
creating a fixed set of them in the language support libraries (one for each
possible non-object return type, and one generic one for object types), and
by adding an interface that Frame would implement which exposes the thread
and the "dead" flag. That improves the startup time but may hurt the
steady-state running speed.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://mail.openjdk.java.net/pipermail/closures-dev/attachments/20080713/3b1bd6ce/attachment.html
More information about the closures-dev
mailing list