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:
>
>   1. Pass multiple values using a global variable
>   2. 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