I don't understand this bit. Can you give an example?
So this wouldn't work if you call a JS function expecting multiple values, and that JS function happens to call a PS function that wants to return multiple values.
In any case, interoperability with non-PS programs is a separate issue- as far as I can tell, neither the GV nor foo.mv designs can achieve it.
I see a problem in that you'd have to instrument not just return statments, but any kind of control flow that goes up the stack - so you'd have to put the suppression code at the end of every function definition, and also wrap throws.
I haven't come up with any examples of this yet and would like to see one. It seems the question is, how might the GV design fail to clean up dangling MV state? There are three ways for MV state to flow in GV:
(1) non-tail calls that clear MV; (2) non-tail calls that bind MV and then clear it; (3) tail calls that let MV pass through.
The only instrumentation that GV adds is to clear MV in cases (1) and (2). It doesn't instrument return statements. I think one can show that (1) and (2) can't fail in a way that leaves any dangling MV. Consider (2). The JS looks something like:
var temp = firstResult(); MV = [secondResult()]; return temp;
// in caller var blah = undefined; if (MV) { blah = MV[0]; } MV = null;
There's no way for any of this code to fail except in firstResult() or secondResult(), in which case MV never gets assigned and no state can dangle. That answers case (2). For case (1) just remove the code that binds MV.
That leaves (3), tail call passthrough. Can this case fail? I was going to say it could, but now I'm not sure. Consider this example:
(defun foo (x y) (blah1) (values x y))
(defun baz () (blah2) (foo))
(defun bar () (blah3) (multiple-value-bind (a b) (baz) (blah4 a b)))
If an error is thrown from blah1, blah2, or blah3, no MV is assigned. If an error is thrown from blah4, MV has already been cleared.
The argument is that the tail-call-ness of (FOO) inside BAZ doesn't leave any space for failures to "squeeze in". One example isn't the general case, obviously, but having gotten this far I'd be intrigued to see an example of where GV can leave dangling state (apart from interop with non-PS code, which as I said I think both designs fail at, and which I suspect is impossible to do perfectly).
It's worth noting that foo.mv might have a problem with errors too:
foo.mv = arguments.callee.mv; var result = foo(); delete foo.mv; return result;
If foo() throws an error, there is a dangling foo.mv. Might that be harmless? If it isn't, you need something like:
foo.mv = arguments.callee.mv; try { return foo(); } finally { delete foo.mv; }
... around every tail call in the program. The possible performance hit is worrisome. That's on top of the possible performance hit of arguments.callee.mv, which is the kind of thing that causes V8 to turn off optimizations.
Daniel
On Fri, Sep 7, 2012 at 7:00 PM, Vladimir Sedach vsedach@gmail.com wrote:
You'd want "EXTRAS = null" after each MULTIPLE-VALUE-BIND as well, but maybe you get this for free since the function call in a MULTIPLE-VALUE-BIND is by definition not a tail call.
We can summarize this proposal similarly to the other one:
- Pass multiple values using a global variable
- Instrument every non-tail call.
It may still be too naive but it at least fixes the examples that broke GV earlier in the thread. So, is there an example that works in foo.mv but fails in GV as described above?
I see a problem in that you'd have to instrument not just return statments, but any kind of control flow that goes up the stack - so you'd have to put the suppression code at the end of every function definition, and also wrap throws. So this wouldn't work if you call a JS function expecting multiple values, and that JS function happens to call a PS function that wants to return multiple values.
p.s. I also wonder if the recursion difficulty disappears now that we understand passthrough better. The example was:
(defun foo (x) (if (= x 1) (values 1 2) (1+ (foo (1- x)))))
In both the GV and foo.mv designs, shouldn't foo(2) do the right thing now? That recursion is a non-tail call, so GV would suppress MV passthrough and foo.mv wouldn't enable it.
You're right. Good point.
Vladimir
parenscript-devel mailing list parenscript-devel@common-lisp.net http://lists.common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel