On Nov 15, 2004, at 10:29 AM, Leopold Toetsch wrote:

Jeff Clites <[EMAIL PROTECTED]> wrote:

Picture this call stack:

main --> A --> B --> C --> D --> E

The call from D to E uses the "RESUMEABLE" label, and E stores the
resulting continuation in a global, and everything up to main returns.
Then, main invokes the continuation, and we find ourselves back in D.
Now, C, B, and A will return "again", without any compile-time hint.

That's fine. The return is an expected operation. I don't think that's the problem. The error in gc_13 is a call path like:

   choose() -> try (with_cc) -> fail() ->
                                        |
                     (choose again)  <- +     <--+
                                                 |
   choose() -> try (with_cc) -> fail() ->        |
                                        |        |
                     (choose again)  <- +        |
                                                 |
   fail()  --------------------------------------+


The problem now is not per se the path in main from the two choose()
calls down to fail is executed more then once (as it's the case with
multiple returns). The problem is the loop in main. By denoting the loop
from the call to fail() to the first choose() with some kind of syntax,
the register allocator does the right thing.

But consider even this simple function:

sub B
{
        a = 1
        foo()
        print a
        b = 2
        return b
}

If something called by foo() captures a continuation, and something invokes it after B() returns, then there's a hidden branch, in effect, from the return to the print, isn't there? The register allocator could decide to use the same register for a and b, but then the "second" return from foo() would print 2 instead of 1, which is wrong. And the author of B(), of course, may have no idea such a thing would happen, so wouldn't be able to supply any syntax to tell the compiler.

I'm just trying to come up with a simpler example, since it seems to me that there's a problem any time a function returns, and the last thing that executed in that frame wasn't a call to that function. (There's a lot going on in the gc_13 example, and I think some of it is distracting from the main point.)

But a RESUMABLE label seems like the information that's needed by the compiler. But on the other hand in an example like the above, the function B() may not be written to expect foo() to be resumed. So, what should happen at runtime, if the label is absent? We could declare undefined behavior, but that would mean that for defined behavior, you'd need the RESUMABLE label all the way up the stack, and Ruby and Scheme don't have that syntactic constraint. With Scheme, it's only clear from the syntax what's going on locally--but you can invoke a continuation far from any call/cc, if that continuation was stored away into a variable.

JEff



Reply via email to