Ok, just to give an example, under the proposed global+funobj scheme, a tail call to foo() will look like:
__MV_FLAGS = __MV_ARRAY = []; var funobj_gensym = foo; var result_gensym = funobj_gensym(); __MV_FLAGS = __MV_FLAGS === funobj_gensym? arguments.callee : null; return result_gensym;
funobj_gensym can be optimized out if we know foo is a symbol, and not a lambda or a function-valued expression.
This still leaves the following case broken:
(progn (defun foo (x) (try (if (= 0 x) (values 1 2 3) (bar)) (:catch (e) 27)))
(defun bar () (foo 0) (throw 13))
(multiple-value-bind (a b c) (foo 1) (list a b c)))
foo calls bar calls foo. The last call to foo return multiple values, which in the global variable solution get put in a global. bar never clears the global variable. the first call to foo catches the throw from bar, and returns 27. The problem is the global variable holding multiple values is tagged with foo's function object, so m-v-b thinks foo returned (values 27 2 3).
Right now that test case works with the original funobj-only scheme. I've pushed the code to the PS repository so people can play with it and it's in the history, but I won't be putting that code into the upcoming release.
To fix the problem above in the global var scheme, bar is going to have to clear multiple values not just on tail calls, but on all other kinds of returns.
If you want to play around with a complete multiple-value-bind implementation that does passthrough correctly, go to ea9044693d14c8 in HEAD. What I'm planning to do next is to roll back the complete solution to not do pass-through - it has improvements like multiple values from nested block returns, and not using arguments.callee.caller, that the original multiple value solution lacks.
If anyone has ideas about making passthrough work without lots of code overhead for tail calls and function prologues, I'd like to hear it. Otherwise I'm going to leave pass-through out of the upcoming release.
Vladimir
On Wed, Sep 12, 2012 at 3:12 AM, Vladimir Sedach vsedach@gmail.com wrote:
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.
With the function object approach, JS functions will always return exactly one value. IMO, having them pass through multiple values would be the wrong thing.
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.
Other exit points from a function are throws and control reaching the end of the function. So both are going to have to get instrumented.
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.
Yeah, I just finished up coding the function object approach, and the generated code gets ugly.
I don't see a way around instrumenting tail calls if you want to implement multiple value pass-through correctly.
I do see a way to combine the global variable and function object approach to simplify things:
Since there's only one way the values can travel up the stack, we can use a single global variable to store the multiple values with the function object that returned them. That way multiple-value-bind can check the array, and if it has values from any but the expected function object, we can just ignore them. To pass through multiple values, every tail call now has to check the global variable to see if multiple values got returned by the tail function. If so, it just replaces the function object they're tagged with with itself.
I can see that approach failing for a recursive function that sets multiple values in one of its sub-invocations, but doesn't return multiple values to whoever originally called it expecting multiple values.
Vladimir