0% found this document useful (0 votes)
15 views10 pages

Continuation in Javascript PDF

Uploaded by

wangjun2012al
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
15 views10 pages

Continuation in Javascript PDF

Uploaded by

wangjun2012al
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 10

Exceptional Continuations in JavaScript

Florian Loitsch
Inria Sophia Antipolis
2004 route des Lucioles - BP 93
F-06902 Sophia Antipolis, Cedex,
France
http://www.inria.fr/mimosa/Florian.Loitsch

ABSTRACT JavaScript does not feature any construct similar to continua-


JavaScript, the main language for web-site development, does not tions, though. Most interpreters carry the necessary information
feature continuation. However, as part of client-server communi- to efficiently implement them, but as far as we know only Rhino
cation they would be useful as a means to suspend the currently (Mozilla Foundation) gives access to its continuations. In general
running execution. continuations need to be implemented on top of JavaScript’s high
In this paper we present our adaption of exception-based continua- level constructs.
tions to JavaScript. The enhanced technique deals with closures and Continuation Passing Style (CPS) lends itself for this task and
features improvements that reduce the cost of the work-around for with sufficiently high level features (in particular closures) CPS
the missing goto-instruction. We furthermore propose a practical can be implemented as a simple source code transformation (see
way of dealing with exception-based continuations in the context of for example (Steele 1976)). A program in CPS form, as the name
non-linear executions, which frequently happen due to callbacks. suggests, passes the current continuation directly as a parameter
Our benchmarks show that under certain conditions continuations to every function and the continuation is hence always available.
are still expensive, but are often viable. Especially compilers trans- This technique does indeed work in JavaScript, and some sys-
lating to JavaScript could benefit from static control flow analyses tems such as Links (Cooper et al. 2006) actually use it. In Links
to make continuations less costly. the continuations are not exposed to the developers either, but
are used internally for threading and transparent asynchronous
xml-http-requests.
1. Introduction CPS’s efficiency is however largely dependent on the speed of clo-
sure creation and tail call handling. Neither are fast in current main-
Xml-http-requests are an integral part of Ajax and the now called
stream JavaScript implementations and two handwritten bench-
“Web 2.0”. Basically they allow JavaScript (standardized as Ec-
marks (fib and nested) were 30 and 130 times slower than the
maScript (ECMA-262 1999)) programs to interactively communi-
native versions in our test setup. CPS transformed programs fur-
cate with the server: a request is sent to a given URL and once the
thermore change the call convention which makes it cumbersome
server returns, a callback is invoked. Both synchronous and asyn-
to interface with existing JavaScript code. We therefore looked for
chronous forms exist, but usually only the asynchronous form is
different techniques without these drawbacks. Our focus eventually
usable.1 concentrated on an exception-based technique similar to the one
Due to the asynchronous nature of xml-http-requests program- presented by Tao (Tao 2001) or Sekiguchi et al. (Sekiguchi et al.
mers need to determine the work that needs to be done after the re- 2001). Their work does not present full fledged continuations but
quests returns, and pack it into the callback function. Continuations only a way of suspending and resuming executions. In the first part
could free developers from this work. Some languages (eg. Scheme of the next section we will show how to adapt the technique to
(Kelsey et al. 1998)) feature first class continuations, but even sim- JavaScript. One of our motivations for this work was a Scheme-
ple one-shot continuations (suspend/resume) would be sufficient to-JavaScript compiler where we need full-fledged continuations to
for our task.2 support call/cc. In the second part we show how we extended
the given technique for this more general form of continuations.
1 When xml-http-requests are used synchronously they block the User Work in this direction on a minimal language without side-effects
Interface until the call is finished. A usually unacceptable behavior. has already been published (Pettyjohn et al. 2005).
2 An efficient suspend/resume implementation is also interesting for co-
operative threading (see for example Fair Threads (Serrano et al. 2004)).

1.1 Organization
Section 2 presents exception-based continuations and summarizes
the work that has been done in this area. In Section 2.2, 2.3 and 2.4
we show how to adapt these existing techniques to JavaScript. Sec-
tion 3 then shows how we can implement call/cc using similar
techniques. In Section 4 we discuss some optimizations. Ways to
Proceedings of the 2007 Workshop on Scheme and Functional Programming handle callbacks are proposed in Section 5. Section 6 presents the
Université Laval Technical Report DIUL-RT-0701 result of our benchmarks. Related work is discussed in Section 7
and we finally conclude in Section 8.

Scheme and Functional Programming 2007 37


2. Suspend/Resume with Exceptions
This section summarizes existing work on exception-based contin-
uations by Tao (Tao 2001) and Sekiguchi et al. (Sekiguchi et al.
2001). In both cases the technique has been developed for transpar-
ent migration and checkpointing, and uses Java/JVM and/or C++
as target-language. As our work is targetted at JavaScript we will
use JavaScript for all code samples, though. Also, we are going to Figure 2: A global view of saving mechanism.
ignore JavaScript’s higher-order functions during the initial sum-
mary. A discussion of this property is delayed to Section 2.4 where
we evolve the summarized technique for higher order languages. are instrumented so they can save their activation frame. The sum
of all these activation frames represents the complete call-stack.
2.1 Idea To simplify the description (and implementation) we wrap the top-
level into a function. The new top-level hence consists of a simple
call to a function which contains the original top-level.
1: function sleep(ms) {
Saving is initiated by the suspend function when it throws a special
2: suspend(function(resume) {
3: setTimeout(resume, ms); exception. The exception triggers the saving code in each function.
4: }); Figure 2 presents a high level view of the suspension mechanism.
5: } When suspend is called with parameter h, it stores the given pro-
cedure in a special exception. It subsequently throws the exception
which is intercepted by the first (youngest) function on the stack.
Figure 1: Sleep implemented through suspend/resume
After having saved its activation frame the function then rethrows
the exception. The same exception is then intercepted by the next
The goal of suspend/resume is to save the current state of an ex- function, until finally at the bottom a global exception handler cap-
ecution and to be able to resume it later on. We propose a library tures the exception. The handler builds a proper continuation-object
function suspend which, similarly to Scheme’s call/cc takes a out of the saved data, and passes it to the h-function which had been
function as parameter. suspend executes the given procedure in given to suspend. When h returns the execution is halted.
turn with a reified version of the current continuation as parame-
ter. Independent of the outcome of this call (in particular excep- 1: function sequence(f, g) {
tions are ignored), it then halts the execution. The program is ef- 2: print(’1: ’ + f());
fectively stopped, until an external event invokes the continuation 3: return g();
to resume execution. Once resumed the continuation has served 4: }
its purpose and becomes invalid, so it can not be invoked multi-
ple times. Figure 1 shows how suspend would be used to create
Figure 3: Running example
the missing sleep function in JavaScript. suspend starts by ex-
ecuting the anonymous function of line 2. This procedure passes
the given continuation resume to JavaScript’s setTimeout, which
prepares a timeout with the continuation as callback. The anony- 1: function sequence(f, g) {
mous function then returns, and suspend halts the execution. After 2: var tmp1;
ms milliseconds JavaScript triggers the timeout-event and invokes 3: var index = 0;
4: try {
the stored callback (the resume-continuation), which resumes the
5: index = 1; tmp1 = f();
execution.
6: print(’1: ’ + tmp1);
The implementation of the suspend function is not local and its 7: index = 2; return g();
presence requires the instrumentation of all other functions. Each 8: } catch(e) {
function needs to be able to save its current activation frame (lo- 9: if (e instanceof ContinuationException) {
cal variables, and current position within the code) and to restore 10: var frame = new Object();
it from this data. Contrary to CPS where the continuation is al- 11: frame.index = index; // save position
ready given as parameter, we create the continuation only when 12: frame.f = f; frame.g = g;
the suspend function is called from inside the program: suspend 13: frame.tmp1 = tmp1;
raises a special exception3 which triggers the saving in each live 14: e.pushFrame(frame);
15: }
function. The important work during suspension is hence done by
16: throw e;
each function separately. 17: }
As expected, restoration rebuilds the call-stack by asking each 18: }
function to rebuild its activation frame. Once the call-stack has been
rebuilt the continuation continues normally.
Although suspend and resume are tightly coupled we present Figure 4: with suspension code
them in separate sections. This makes sense because the suspend
code might be used independently (see Section 4.3). Figure 4 takes a closer look at a single instrumented function. The
original non-instrumented version can be found in Figure 3. In ad-
2.2 Suspend dition to the previously mentioned try/catch, an index variable
During suspension the whole call-stack needs to be saved. As has been introduced which represents the instruction pointer. Note
JavaScript does not give access to the stack itself, all functions that only calls potentially leading to a suspend (dubbed “unsafe
calls”) need to be indexed. The call to print is safe, and there-
3 The exception starting the saving gives the whole technique its name, but fore doesn’t update the index variable. The correct identification
is itself not essential. Another convention could use a special return-value of safe function calls needs be determined by static analyses (see
to trigger the saving. Depending on the interpreter (or the JIT-compiler) this Section 4.3). The catch part responsible for saving starts at line 8.
could even be faster. A new frame object is constructed and filled with the local variables

38 Scheme and Functional Programming 2007


and the index variable. In this example the exception itself serves
as container for the continuation data, and the frame object is hence 1: { 1: switch (goto) {
stored in the exception. Finally the exception is rethrown. 2: safe1; 2: case false: // default mode.
3: safe2; 3: // no restoration
4: unsafe(); 4: safe1; safe2;
2.3 Resume 5: safe3; 5: // jump to unsafe statement
At the end of suspension the global exception handler invokes the 6: } 6: case 1: goto = false;
function h which had been given to suspend (see Section 2.2), 7: unsafe();
8: safe3;
but then halts the execution. The program can only resume, if an
9: }
external event triggers the invocation of the continuation. Usually
h registers the continuation (which has been given as parameter) as
call-back. In the example of Figure 1 the continuation was stored 1: if (test) { 1: if ((goto && goto <= 2) ||
as call-back for the timeout-event. 2: unsafe1(); 2: (!goto && test)) {
3: unsafe2(); 3: switch (goto) {
4: } else 4: case 0: case 1:
1: function sequence(f, g) { 5: unsafe3(); 5: goto = false;
2: var tmp1, goto = false; 6: unsafe1();
3: if (RESTORE.doRestore) { 7: case 2: goto = false;
4: var frame = RESTORE.popFrame(); 8: unsafe2();
5: index = frame.index; 9: } else {
6: f = frame.f; g = frame.g; 10: goto = false;
7: tmp1 = frame.tmp1; 11: unsafe3();
8: goto = index; // emulate a jump 12: }
9: }
10: ... <suspension-code omitted> ... Figure 6: Goto examples
11: switch (goto) {
12: case false:
13: case 1: goto = false;
14: tmp1 = f(); 1: function sequence(f, g) {
15: print(’1: ’ + tmp1); 2: var tmp1;
16: // fall-through 3: var index = 0;
17: case 2: goto = false; 4: var goto = false;
18: return g(); 5: if (RESTORE.doRestore) {
19: } 6: var frame = RESTORE.popFrame();
20: ... <suspension-code omitted> ... 7: index = frame.index;
21: } 8: f = frame.f; g = frame.g;
9: tmp1 = frame.tmp1;
Figure 5: with restoration code 10: goto = index;
11: }
12: try {
13: switch (goto) {
When the continuation is invoked it starts by setting a global 14: case false:
restoration flag RESTORE.doRestore. Subsequently it calls the 15: case 1: goto = false;
last (oldest) function of the saved stack (in our case the top-level). 16: index = 1; tmp1 = f();
Each function is then responsible for restoring its original activa- 17: print(’1: ’ + tmp1);
tion frame, and calling the next function of the initial stack. The 18: case 2: goto = false;
global flag serves as switch for restoration mode. The saved ac- 19: index = 2; return g();
tivation frame itself too is accessible through a global variable. 20: }
Figure 5 shows the instrumented version of our running example 21: } catch(e) {
(Figure 3). A test first checks if the program is in restoration or 22: if (e instanceof ContinuationException) {
23: var frame = new Object();
normal execution mode. In the first case it restores the values of the 24: frame.index = index; // save position
local variables, and jumps to the saved position. Eventually the ex- 25: frame.f = f; frame.g = g;
ecution arrives at the saved location and invokes the next function 26: frame.tmp1 = tmp1;
(still in restoration mode), which in turn restores itself and calls the 27: e.pushFrame(frame);
next function. The restoration is finished when the suspend func- 28: }
tion is reached: suspend clears the restoration flag and returns. 29: throw e;
The execution then continues normally. 30: }
In our example a switch-statement was introduced to emulate the 31: }
jump to the target given by the index variable. In general blocks
are converted into switch statements and branching constructions Figure 7: with suspension and restoration code
are modified so they reenter the saved branch. Function calls are
transformed into A-normal form (Flanagan et al. 1993), so the al-
ready calculated parameters are not executed multiple times. Fig-
ure 6 gives some examples of these transformations. Additional 2.4 Suspend/Resume in JavaScript
material can be found in (Tao 2001) and (Sekiguchi et al. 2001). The previous sections give an overview of suspend/resume in
The goto emulation makes code examples more difficult to read first-order languages like C++ or Java/JVM. JavaScript, however,
and we will from now use an informal “goto index;” form, too. is a higher order language and hence features functions as first
Figure 7 shows the complete sequence-function with suspension class citizens. In this section we will discuss the implications of
and restoration instrumentation. this property.

Scheme and Functional Programming 2007 39


JavaScript’s functions have semantics similar to Scheme proce- Another subtle difficulty is introduced by pointers to functions
dures. That is, free variables are lexically scoped and during the (which could appear in C++ too). If the variable that holds the call-
creation of functions the current environment is saved in the clo- target is modified the call will not work as expected. Figure 9a con-
sure. Closures contain hence references to variables of activation tains an example which demonstrates how this can be a problem.
frames.4 This however poses problems when the original call-stack
is destroyed by a call to suspend. A similar call-stack is rebuilt 1: function() {
during the resume, but the closure’s references are still referenc- 2: var g = function() {
ing variables of the old stack. The following example demonstrates 3: g = false;
4: suspendCall();
such a case:
5: };
1: function f() { 6: g();
2: var x = 1; var y = 2; 7: }
3: var g = function() { print(x, y); };
(a) call-target g is modified
4: suspendCall();
5: x = 3;
6: g(); // should print 3, 2 1: function() {
7: } 2: var g = function() {
3: g = false;
When the program reaches line 4 the stack structure resembles the 4: suspendCall();
diagram of Figure 8a. The call-stack contains a list of activation 5: };
frames with f’s activation frame on top. The frame contains f’s 6: var tmp = g;
three local variables x, y and g. The variable g points to a function 7: tmp();
8: }
which in turn captures f’s x and y. For explanatory purposes we
have marked locations of this original call-stack with stars. (b) call-target is constant
During the call to suspendCall the continuation mechanism
throws an exception and saves the variables of all stack-frames. Figure 9: modified call-target
When the execution is resumed a similar stack is reconstructed.
This new stack can be seen in Figure 8b. The restored call-stack is The call at line 6 depends on the local variable g which is modified
(as intended) similar to the original call-stack, but the closure g still after the invocation. During restoration g is correctly restored to
references variables of the old call-stack. This does not pose any false and the program then jumps to the call location. Just calling
problem for constant variables like y, but is incorrect for all others. g again is however not possible anymore. The solution to this
In our example the x of the new frame is changed, but g will still problem is simple. One just needs to introduce additional local
reference the unchanged x and hence incorrectly print 1. variables so that calls do not depend on variables that are changed
outside their scope. Figure 9b shows the corrected version.

3. Call/cc
Suspend/Resume is sufficient for asynchronous communication
and cooperative threading. In the context of Scheme (and other lan-
guages) full fledged continuations are however needed. This section
presents the changes to evolve suspend/resume to call/cc.
Suspend/resume basically pauses the control flow. Instead of
returning, suspend aborts the execution until an event invokes
(a) before (b) after the resume-continuations. With the exception of event-handling
code, the program continues semantically as if no instruction had
been executed between the end of the suspend-function and its
continuation.
Call/cc-continuations, on the other hand, are more flexible. They
can be invoked at any time and multiple times. In particular users
are free to execute code between the return of call/cc and the
invocation of the captured continuation. This raises an important
question: what happens to (stack-)variables that are modified after
(c) boxed the continuation has been captured? Semantically there are two
possibilities: - either these variables are restored to the value they
Figure 8: Call-stacks before and after callcc-call. had when the continuation was captured; - or they should be left
at their new value. Whereas the first choice could be useful for
Our solution is to box all non-constant escaping variables (a con- checkpointing, etc. it is the latter one which is generally adopted.
servative super set of the concerned variables). The closure will Similar to Scheme we want hence modifications to variables remain
still reference an outdated variable, but the referenced box of the when continuations are executed.
escaped variables will be in sync with the equivalent variables of The function in Figure 10a, for instance, would yield different re-
the new stack-frame (see Figure 8c).5 sults depending on the chosen semantics. After the first invocation
of the continuation the print in line 4 should obviously print 1,
4 In fact, most JavaScript implementations currently just store the call-stack but more importantly (due to the assignment in the following line)
itself in the closure. other invocations could then either continue printing 1 (value at
5 Scheme (and other) compiler writers will not be surprised by the solution. time of suspension) or could then print 2, 3, etc. We would like
Boxing of escaping variables is a common practice in Scheme compilers our technique to print the incrementing sequence, but our previ-
(Kranz et al. 1986), but usually for entirely different reasons. ous suspension technique ignores modifications that happened to

40 Scheme and Functional Programming 2007


reducing the modifications to the original source code thus making
1: function () { 1: function () { the code lighter and faster. In the remainder of the chapter we will
2: var x = 1; 2: // restoration code use call/cc and suspend/resume indifferently as all optimiza-
3: callccCall(); 3: // producing ’frame’ tions apply for both scenarios.
4: print(x); 4: ... We will first present our hoisting- and tail-call optimizations in
5: x = x + 1; 5: callccCall(); separate sections and then discuss miscellaneous optimizations in
6: } 6: print(x); the following section.
7: x = x + 1;
(a) call/cc example 8: if (frame)
9: frame.x = x;
10: }

(b) update at the end 4.1 Hoisting Instructions


During the restoration of the call-stack the program needs to exe-
1: function () {
2: var x;
cute a jump to the saved target which is an expensive operation in
3: var frame = false; JavaScript. The following optimization moves the targets to later
4: if (RESTORE.doRestore) { locations (thereby skipping instructions) which generally reduces
5: frame = RESTORE.popFrame(); the cost of the jumps. The skipped instructions are duplicated at the
6: index = frame.index; jump-origin, and are executed before the jump.
7: x = frame.x; As discussed in Section 2.3 JavaScript does not feature any goto-
8: goto index; instruction, and the body of functions has to be transformed to
9: } emulate jumps. Most constructs surrounding a jump-target need to
10: try { test if they are executing an emulated goto or if they are executed
11: x = 1; normally. We therefore define the cost of a jump-target to be its
12: gotoTarget1: callccCall();
13: print(x);
nesting-level. The more nested a target is, the more it costs (even in
14: x = x + 1; normal operation, as the tests have to be done all the time).
15: } catch (e) { The goal of this optimization is to reduce the cost of jump-targets. It
16: /* save-code */ basically copies code from the jump-target to the jump-origin. The
17: } finally { copied instructions are then already executed before the jump, and
18: if (frame) { frame.x = x; } the jump-target can be advanced so it skips the copied code. The
19: } moved jump-target might leave constructs, thereby reducing the
20: } nesting-level, and as a result one could avoid its instrumentation.
(c) complete version with update in finally
In Figure 11 we demonstrate on an example the impact this opti-
mization can have. In the first code-snippet we informally state the
need for a jump to the unsafe call (labeled with “target:”). The sec-
Figure 10: Call/cc with side-effects
ond code-sample shows the expensive transformations needed to
emulate this goto-instruction. As the unsafe call is embedded in a
stack-variables after the continuation has been saved. It would have while-loop and an if, both constructs need to be transformed for
printed 1 all the time: as x does not escape it is not boxed (see Sec- the emulated jump. In Figure 11c we copied the targets statement
tion 2.4) and during the construction of the continuation x is saved to the jump-origin in line 4. The call of line 15 is therefore already
with value 1. Every restoration of the original call-stack would sub- executed before the jump, and the jump-target has been advanced
sequently restore this value. There are (at least) two ways to ob- to the instruction following the call. The if-statement is finished,
tain the chosen behavior. One can either box all muted variables or and the next instruction would thus be the test of the while-loop
track the changes and update the continuation. Boxing is easier to (line 9) which is equivalent to the while-construct itself. The new
implement but for efficiency reasons we use the second technique. jump-target is hence just before the while-statement. Both the loop
Suppose frame is the name of the continuation structure that holds and the if-statement do not contain any jump-targets anymore and
all local variables. We could then just add a new line updating the can hence be left untransformed (Figure 11d).
continuation at the end of the function as in Figure 10b. The suspension code is left nearly untouched. The sole change
If the function has already been suspended once, then the frame rectifies the scope of the suspension try/catch. As the restoration
variable is not false and the value for x is updated in line 9. The code now contains calls to unsafe locations too, it is necessary to
program now correctly outputs 1, 2, etc. In general just updating enlarge the exception-handler so that the try-keyword is before the
at the end of the function is however incorrect. There are many if-statement in the beginning of the function.
means to exit a function, and only few go through the last line Due to implementation-specific reasons we currently restrict the
of a function. We therefore use a finally clause of a try/- copied code to be at most the targeted call and a potential assign-
catch (which incidentally has to be used for continuation support ment of its return-value. In the future we would like to remove this
anyway). This way variables are always updated before leaving the limit and experiment with bigger copies.
function. The complete version (still without suspension code) can Concluding this section we would like to point out that this opti-
be found in Figure 10c. mization is not always beneficial. Blocks containing jump-targets
are generally transformed to switch-statements, with one excep-
tion: when there is only one jump-target and the target is (part of)
4. Suspend/Resume and Call/cc Optimizations the first statement. In this case the block can be left untouched. The
This section presents some optimizations to the previously pre- presented optimization however advances jump-targets, and could
sented implementation. All important technical aspects have al- move the target from the first statement to the second statement.
ready been discussed in previous sections, and we will hence only In this case the previously untouched block would then be trans-
focus on implementation and efficiency issues. The techniques formed into a switch-statement. Our implementation does not yet
shown in this section do not add any functionality but succeed in take into account this special case.

Scheme and Functional Programming 2007 41


done when it was suspended. Function f would hence restore itself
if (RESTORE.doRestore) { if (RESTORE.doRestore) { and then reexecute the call to g. In the optimized version f should
... ... call h (a function which might not even be visible to f) directly to
goto target; goto = 1; skip g.
} } In this section we continue evolving our continuation technique
print(’before’); switch (goto) { so that a tail-call optimization becomes possible. In the new ver-
while (test1) { case false: sion functions save a pointer to themselves in addition to their
print(’loop’); print(’before’);
if (test2) { case 1:
activation-frame data. This pointer is then looked up during restora-
doSomething; while (goto == 1 || test1) { tion to retrieve the next function. Tail-calling functions are simply
} else { switch (goto) { skipped during saving and are hence removed in the restored stack.
print(’if’); case false: print(’loop’); In our running example, the tail-calling g would not save its frame
target: unsafeCall(); // fall through and would hence not appear in the saved continuation data. During
} case 1: restoration functions must not just call the same next function as
} if (goto == 0 && test2) before, but have to retrieve the function pointer in the next frame.
print(’after’); doSomething; The function f would retrieve h’s frame (as g did not register its
else frame), and therefore call h as next function.
(a) goto-example switch (goto) {
case false:
The benefits of this optimization are twofold: the number of acti-
print(’if’); vation frames is reduced, and the instrumentation for tail-call lo-
// fall through cations is simplified. Indeed, tail-calling functions will not be re-
case 1: goto = false; stored, and it is hence unnecessary to add jump-emulations to their
unsafeCall(); tail-call locations.
}
}
1: function sequence(f, g) {
} 2: var tmp1, index = 0, isTail = false;
print(’after’);
3: if (RESTORE.doRestore) {
}
4: var frame = RESTORE.popFrame();
(b) jump to the call itself 5: index = frame.index;
6: f = frame.f; g = frame.g;
7: tmp1 = frame.tmp1;
1: if (RESTORE.doRestore) if (RESTORE.doRestore) { 8: // restore remaining stack:
2: { ... 9: var callCcTmp = RESTORE.callNext();
3: ... goto = 1; 10: switch (index) {
4: unsafeCall(); switch (goto) { 11: case 1: tmp1 = callCcTmp; break;
5: goto target; 1: unsafeCall(); break; 12: }
6: } } 13: goto index;
7: print(’before’); } 14: }
8: target: switch (goto) { 15: try {
9: while (test1) { case false: 16: index = 1; tmp1 = f();
10: print(’loop’); print(’before’); 17: gotoTarget1: print(’1: ’ + tmp1);
11: if (test2) { case 1: goto = false; 18: isTail = true; return g();
12: doSomething; while (test1) { 19: } catch(e) {
13: } else { print(’loop’); 20: if (e instanceof ContinuationException &&
14: print(’if’); if (test2) { 21: !isTail) {
15: unsafeCall(); doSomething; 22: var frame = new Object();
16: } } else { 23: frame.index = index; // save position
17: } print(’if’); 24: frame.f = f; frame.g = g;
18: print(’after’); unsafeCall(); 25: frame.tmp1 = tmp1;
} 26: e.pushFrame(frame, this, arguments.callee);
(c) unsafe call copied to jump-origin } 27: }
print(’after’); 28: throw e;
} 29: }
(d) with goto-emulation
Figure 12: complete version with assignment in restoration code.
Figure 11: jump to before and after the call.
Figure 12 shows the new (suspension and restoration) code of
the sequence-example (Figure 3). We will first focus on the sus-
4.2 Tail-call Optimization pension code, and hence skip the restoration-if for now. As al-
The continuation-technique which has been presented until now ready mentioned in the summary a pointer to the currently run-
ensures that a restored call-stack is similar to the original one. Like ning function is saved during suspension. In JavaScript this pointer
the original stack the restored stack would have the same number is readily available as a combination of the this-keyword and
of activation frames and each activation frame would have the same the arguments.callee (line 26). The tail-call optimization itself
values as the original stack. Often not all of these frames are still can be seen in line 18 and line 21. Tail-calls are now specially
needed, though. Suppose f calls g which in turn tail-calls h. When marked (the isTail-variable is set to true), and if a function is
the continuation has been captured during g’s tail-call, then the tail-calling then it skips itself during saving (due to the test in line
restoration could skip g if f called h directly. In practice changing 21). Tail-calling functions are hence ignored during saving, and it
the f’s call target is however not that easy. Each function restores is the restoration part’s responsibility to determine and execute the
itself and is then responsible to reexecute the same call as it had next non-skipped function. The necessary information is inside the

42 Scheme and Functional Programming 2007


saved continuation through the means of the function-pointers. In- already extensive and we therefore have not yet implemented this
stead of calling the same previous call as before one just has to technique. Initial tests on tak (one of our benchmarks) showed
retrieve the next function of the continuation-state and invoke it in- potential, though. The new version was about twice as fast as the
stead. In order to clarify the code we hide this operation and use a old one.
method-call (RESTORE.callNext) in line 9 instead.
The result is then assigned to their respective variables (if any).
Thanks to the hoisting-optimization this task is simplified. The 5. Callbacks
original call in the body is left untouched, and the copied call is Using our call/cc-framework the top-level is responsible for
simply replaced by the temporary variable callCcTmp which holds catching the suspension-exception. In web-browsers, callbacks oc-
the result of the callNext-call: whereas we previously would have cur however outside the dynamic extent of the original top-level.
had tmp1=f(); in line 11, we now have tmp1=callCcTmp;. A call/cc inside a callback would hence fail. In this section we
We want to point out that this optimization is not a substitute review the importance of callbacks, and discuss our solution to this
for (expensive) proper tail-recursion handling (as presented in issue.
(Loitsch and Serrano 2007)). Frames are only discarded when con- JavaScript is usually used in web-browsers where it is responsible
tinuations are taken or invoked, which is clearly not sufficient for for the user-interface. Web-browsers provide a (mostly) standard-
proper tail-recursion (as required for Scheme). The main-benefit is ized way, the Document Object Model (DOM) (Hors et al. 2000),
hence not the removal of the frames but the removal of instrumen- for accessing visual elements through JavaScript. A recurring pat-
tation for tail-calls. In some cases the optimization can avoid the tern involves the use of callbacks to react to events. Callbacks are
complete instrumentation for functions: if a function’s unsafe calls functions stored in the DOM which are then invoked when an event
are only at tail-call-locations, then it does not need any instrumen- occurs. Another form, not involving the DOM, can be found in Fig-
tation at all. ure 1 where the timeout-callback had been used to resume the ex-
ecution of the suspended program. As already mentioned in Sec-
4.3 Miscellaneous Optimizations tion 2.2 suspended programs can only be awaken through external
callbacks. Due to the ubiquity of callbacks it is hence important to
This section groups several optimizations that are either too small allow call/cc to work inside callbacks with minimal effort for the
to merit a separate section, or are not yet sufficiently explored (and programmer.
hence subject for future work). The solution we adopted is completely transparent. The call/cc-
As call/cc is usually only present in few locations, most calls exception handler signals through a global flag CALLCC.handler
do not (and often even can not) reach any call/cc. An optimiz- its presence (or absence). Every function starts by testing this flag.
ing compiler should hence use standard compilation techniques If the handler is present the execution continues normally. If the flag
(Muchnick 1997) to reduce the number of unsafe call-locations. is not set, though, then the function is not inside the dynamic extent
We dub “unsafe” call-location calls that might eventually reach a of a call/cc exception handler. In the latter case the function
call/cc. In JavaScript one can modify global variables through creates the exception handler itself before continuing.
several ways, most of which are difficult to detect (amongst others
the eval-function, and the global this-object). JavaScript itself is
hence difficult to optimize in this area. Even though it is gener- 1: function callCcHandler(f, f_this, args) {
ally possible to mark most local functions as safe, calls to global 2: try {
functions need to be considered unsafe. However, when JavaScript 3: CALLCC.handler = ’present’;
is used as compilation target for a different language, then such 4: f.apply(f_this, args);
5: } catch (e) {
an analysis can be often much more effective. We have imple-
6: ...
mented an ad-hoc analysis in our Scheme-to-JavaScript compiler 7: } finally {
(S CM 2J S). Even though Scheme is highly dynamic this analysis 8: CALLCC.handler = ’absent’;
was able to detect the absence of continuations in 10 out of 11 9: }
benchmarks. The remaining benchmark (a meta circular interpreter 10: }
making heavy use of higher-order functions) had about 75% of its
functions instrumented.
Figure 13: callCcHandler creates a call/cc-exception handler.
One should also consider handling functions by hand. Especially
libraries are possible candidates for this special treatment. If the
library itself does not use continuations then only exported higher- To avoid code duplication we have implemented the function
order functions need to be instrumented. To keep libraries generic callCcHandler (Figure 13) which contains the try/catch orig-
we usually export both versions of these functions (one unin- inally found in the top-level. It takes a function as parameter,
stremented and one instrumented). As a bonus even continuation- and invokes it inside the try/catch. Functions that are invoked
heavy programs might prefer calling the uninstremented func- through the handler, are hence inside a dynamic extent of a call/-
tion when they can prove that the sent parameter does not invoke cc-exception handler.
call/cc. In S CM 2J S important functions like for-each, map and Initially CALLCC.handler is set to ’absent’. The first function
others have been implemented this way. The closures sent to these that is executed will hence encounter the absent-state and there-
functions are often small anonymous lambdas that can be easily fore execute the callCcHandler function. callCcHandler cre-
analyzed. ates a try/catch and sets CALLCC.handler to ’present’. The
In a similar vein it can be beneficial to create versions without following functions are then protected and do not need to call the
restoration-code (and hence without goto-emulation). This version handler again. When the top-level is left, callCcHandler sets
is sufficient in all but one context: during stack restoration the CALLCC.handler to absent again. Callbacks that occur afterward
full version is needed. The switch to full version can happen at encounter the ’absent’-state again and will hence reinvoke the
several occasions: during saving a function might save the full callCcHandler.
version instead of itself; or the callNext method of the RESTORE- Figure 14 shows the few lines that are added to each function.
object could translate the original version to the full version and If the function has been invoked outside the dynamic extent of a
call the latter. Even without this optimization the code growth is continuation-try/catch (as it happens for the top-level or in the case

Scheme and Functional Programming 2007 43


1: function f(...) { Exception-based continuations instrument the original code and
2: if (CALLCC.handler === ’absent’) { thus slow down the program even when continuations are never
3: return callCcHandler(arguments.callee, used. The impact however is largely dependent on the given pro-
4: this, gram. A sequential program without function calls is nearly un-
5: arguments); affected, whereas small functions with many function calls are
6: } significantly slowed down. A static analysis (like that of Shivers
7: ...
(1991)) is usually able to reduce the number of instrumented func-
tions, but if continuations are heavily used then such an analy-
Figure 14: Try/catch is triggered on demand. sis does not help either. Links (Cooper et al. 2006), for instance,
uses continuations to implement threading. Due to the huge num-
ber of possible suspension points, nearly all functions must be in-
of callbacks) then the function invokes callCcHandler which cre- strumented. When, on the other hand, continuations are used to
ates an exception-handler. The function callCcHandler would set simulate synchronous client-server communication on top of asyn-
CALLCC.handler to ’present’ and invoke the given function. chronous xml-http-requests then only functions reaching these
The variables arguments.callee (a pointer to the running func- requests need to be modified. In this case the penalties due to con-
tion), this and arguments contain enough information to restart tinuations are furthermore usually insignificant compared to the
the function. time spent on the communication itself.
Our benchmarks are intended to measure realistic worst-case sce-
6. Benchmarks narios for the latter use-case. In particular we are not interested by
the cost of the actual continuation-construction and -invocation but
Firefox trampolines & call/cc
we want to determine the slow-down due to the instrumentation
Suspend/Resume2 Call/cc2
(even when not reaching any call/cc).
We have added continuation support to S CM 2J S, our Scheme-to-
1 2 3 4 5 JavaScript compiler (Loitsch and Serrano 2007). As a typing pass
1.8
Bague 1.8
3.5
eliminated all instrumentation for all but one (ewal) benchmark we
Fib 3.5
Mb100 1.0
1.1
modified the original benchmarks to disturb the typing algorithm.
Mbrot 1.0
1.1
1.9
The benchmarks still make no use of the continuation support, but
Nested
Quicksort 1.4
1.4
3.9
the typing pass is not able to prove this anymore. As a result most
Sieve 1.1
1.4 (but not all) functions are now instrumented. We left our inlining
Tak
3.2
3.4
4.1 pass activated too, which reduces the stress on very small functions.
Towers 3.2
Even/Odd 1.5
1.5
To evaluate the impact of continuation instrumentation we ran our
Ewal 1.6
1.5 benchmarks under three Internet browsers:
(a) Firefox • Firefox 2.0.0.3,
• Opera 9.20 build 638, and
Opera suspend & call/cc
• Konqueror 3.5.7
Suspend/Resume2 Call/cc2

1 2 3 4 5 6 7 8 9 10
All benchmarks were run on an Intel Pentium 4 3.40GHz, 1GB,
Bague 2.0
2.4 running Linux 2.6.21. Each program was run 5 times, and the
Fib
3.8
5.8
6.2 minimum time was collected. The time measurement was done by a
Mb100
Mbrot 3.5
4.2
3.9
small JavaScript program itself. Any time spent on the preparation
Nested 7.2
7.7 (parsing, precompiling, etc.) was hence not measured. The results
2.7
Quicksort
2.2
3.3 are shown in Figure 15.
Sieve 3.8
Tak 7.8
16.9
We have noticed tremendous differences between the three browsers.
Towers 10.4
11.1 Konqueror seems to be the least affected, but as it was not very fast
1.1
Even/Odd 1.1
4.4
in the beginning, the time penalties are important. Opera’s behavior
Ewal 4.7
largely depends on the benchmarks, but one can see that continu-
(b) Opera ation support can be expensive. Even though Firefox has worse
values than Konqueror one should note that Firefox was up to
Konqueror trampolines & call/cc ten times faster than Konqueror. Compared to the uninstrumented
Suspend/Resume2 Call/cc2 version continuation-enabled code was however up to 4.1 times
slower.
1
1.3
2 3 4 5
Despite these apparently bad results we think that continuations are
Bague 1.4
Fib 2.9
2.9
viable, as most benchmarks have been modified to exhibit worst
Mb100 1.0
1.0 case scenarios. Even the most realistic benchmark (ewal) repre-
1.0
Mbrot 1.0
1.9
sents a non-optimal example for exception-based continuations. Its
Nested 2.2
Quicksort 1.1
1.3
high number of anonymous functions and closures makes it diffi-
Sieve 1.1
1.7
2.0
cult to analyze.
Tak 3.1
2.5
Towers 2.5
1.0
Even/Odd 1.0
1.8
Ewal 1.7
7. Related Work
(c) Konqueror Our work is an adaption and evolution of the suspension and migra-
tion techniques presented in Tao’s thesis (Tao 2001) and Sekiguchi
Figure 15: Impact of suspend/resume and call/cc instrumentation. et al.’s paper (Sekiguchi et al. 2001). Pettyjohn et al. later extended
Raw code is the 1.0 mark. Lower is better. this technique for call/cc (Pettyjohn et al. 2005) and formally

44 Scheme and Functional Programming 2007


showed the correctness of their approach on a minimal language Greg Pettyjohn, John Clements, Joe Marshall, Shriram Krishna-
without side-effects. murthi, and Matthias Felleisen. Continuations from general-
Several other projects implemented continuations in JavaScript ized stack inspection. In International Conference on Functional
using different techniques: Narrative JavaScript (Mix) and djax Programming, ICFP 2005, September 2005.
(Friedlander) both unnest all constructs and explicitly handle the Tatsurou Sekiguchi, Takahiro Sakamoto, and Akinori Yonezawa.
control-flow. Code is within a while(true)-loop and a switch- Portable implementation of continuation operators in imperative
statement. Narrative JavaScript stores local variables in an object, languages by exception handling. Lecture Notes in Computer
whereas djax creates a closure at each invocation. In the latter case Science, 2022:217+, 2001.
all local variables are declared outside the scope of the invoked Manuel Serrano, Frédéric Boussinot, and Bernard Serpette.
function, and are thereby captured. Scheme fair threads. In PPDP ’04: Proceedings of the 6th ACM
jwacs (Wright) and Links (Cooper et al. 2006) both use CPS to SIGPLAN international conference on Principles and practice of
implement continuations. declarative programming, pages 203–214, New York, NY, USA,
2004. ACM Press. ISBN 1-58113-819-9.
8. Conclusion Olin Grigsby Shivers. Control-Flow Analysis of Higher-Order
We have presented exception-based continuations for JavaScript. Languages or Taming Lambda. PhD thesis, Carnegie Mellon
Starting with an implementation of suspend/resume for C++, University, May 1991.
Java or JVM we have adapted the technique to JavaScript. We have Guy L Steele. Lambda: The ultimate declarative. Technical report,
then extended the technique to call/cc. We have presented several Massachusetts Institute of Technology, Cambridge, MA, USA,
optimizations that, most of which reduce the cost of the goto- 1976.
emulation. Finally we have discussed our implementation to deal Wei Tao. A portable mechanism for thread persistence and mi-
with non-linear execution as happens with callbacks. gration. PhD thesis, University of Utah, 2001. Adviser-Gary
Our benchmarks show that full fledged continuations can still be Lindstrom.
expensive, but are now usable in many scenarios. Especially when James Wright. jwacs. URL http://chumsley.org/jwacs/-
using JavaScript as target-language, static analyses can help im- index.html.
proving the speed of exception-based continuations.

References
Ezra Cooper, Sam Lindley, Philip Wadler, and Jeremy Yallop.
Links: Web programming without tiers. submitted to ICFP 2006,
URL http://groups.inf.ed.ac.uk/links/papers/-
links-icfp06/links-icfp06.pdf, 2006.
ECMA. ECMA-262: ECMAScript Language Specification. Third
edition, 1999.
Cormac Flanagan, Amr Sabry, Bruce F. Duba, and Matthias
Felleisen. The essence of compiling with continuations. In
Proceedings ACM SIGPLAN 1993 Conf. on Programming Lan-
guage Design and Implementation, PLDI’93, Albuquerque, NM,
USA, 23–25 June 1993, volume 28(6), pages 237–247. ACM
Press, New York, 1993.
Hamish Friedlander. djax. URL http://djax.mindcontrol-
dogs.com/.
A. Le Hors, P. Le Hegaret, G. Nicol, J. Robie, M. Champion, and
S. Byrne (Eds). “Document Object Model (DOM) Level 2 Core
Specification Version 1.0”. W3C Recommendation, 2000.
R. Kelsey, W. Klinger, and J. Rees. Revised5 report on the algo-
rithmic language Scheme. Higher-Order and Symbolic Compu-
tation, 11(1), August 1998.
David Kranz, Richard Kelsey, Jonathan Rees, Paul Hudak, James
Philbin, and Norman Adams. Orbit: an optimizing compiler
for scheme. In SIGPLAN ’86: Proceedings of the 1986 SIG-
PLAN symposium on Compiler construction, pages 219–233,
New York, NY, USA, 1986. ACM Press. ISBN 0-89791-197-
0.
Florian Loitsch and Manuel Serrano. Hop client-side compilation.
In TFP 2007: Draft Proceedings of the 8th Symposium on Trends
in Functional Programming, April 2007.
Neil Mix. Narrative javascript. URL http://neilmix.com/-
narrativejs/.
Mozilla Foundation. Rhino. URL http://www.mozilla.org/-
rhino/.
S. Muchnick. Advanced Compiler Design & Implementation. Mor-
gan Kaufmann, 1997. ISBN 1-55860-320-4.

Scheme and Functional Programming 2007 45


46 Scheme and Functional Programming 2007

You might also like