[parenscript-devel] A simpler way to do multiple value return? (Was: PS test failures in 7be9b45)

Vladimir Sedach vsedach at gmail.com
Wed Sep 12 18:55:15 UTC 2012


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 at 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




More information about the parenscript-devel mailing list