Bob Rogers wrote:

   There are two broad solutions:

   1.  Restrict continuations to move only "outward", which makes it
unnecessary to restart inferior runloops.  This may not be as bad as it
seems, as most of the languages I can think of can be implemented using
only outward (returning) continuations.  And I believe the rest
(including Scheme) can be implemented without Parrot continuations, by
using the CP transformation, which is a standard Scheme compiler
technique in any case.  However, we'd probably have to abandon
coroutines, since telling people that they can't use coroutines with OO
programming is really lame.

Important to consider this option, but overall it's too restrictive and doesn't allow enough room for the future evolution of programming languages. (Yeah, we can't go out of our way to accommodate languages that don't exist yet. The YAGNI principle applies. But we can avoid making decisions along the lines of "No one could ever need more than 64K of RAM.")

   2.  Eliminate inferior runloops.  This is much harder, as it involves
rewriting much code, though the attached POC (see patch; more on this
below) shows that it is possible.  The essential strategy is to split
the C code into two or more portions, the first containing the part that
sets up the bytecode call, and the rest deals with the result.
Ironically, this is just the CP transformation mentioned earlier; we are
turning the latter half of the C code into a continuation, and then
arrange to call it after the bytecode returns.  Unfortunately, this is a
pain in C, as we have to fake a closure in order to retain state between
the C functions.

In general, this is the right direction to head. But that's about like saying you can get to Chicago from Portland by heading east. It is true, but we'll have to work out a few more details before we get there.

   The second solution is subject to a few variations:

   V1.  It may be possible to redesign delegation in a way that doesn't
involve vtable methods.  For instance, it's not clear that all uses of
VTABLE_get_string (there are ~250 of them) need to support delegation,
and some of them might be really hard to rewrite, being within deeply
nested C calls.  Of course, set_s_p would need to call a delegated
method, but that is easier to special-case (it is the subject of the
POC).  And that only covers some of the vtable-to-bytecode cases.

   V2.  Failing that, it may be possible to limit the number of vtable
methods that are subject to delegation.

In specific, both of these solutions are too specific. These two variations extend Parrot with C code that calls back into bytecode, but the general principle applies to any C code used to extend Parrot.

That doesn't mean we need to support CPS and exceptions in any arbitrary C code called from within Parrot (the native call interface handles more general C libraries), but it does mean we need to be a bit more general than limiting or eliminating vtable methods from delegation.

We can put some restrictions on the C code that avoids inferior runloops. These fall into the general category of calling conventions: we can require set-up and tear-down code wrapped around the C code; we can require any calls out of the C code (to bytecode, other C code, or by throwing an exception) to set up certain information before the call; we can probably even go so far as limiting what C features you can use in extension code; we can certainly provide macros to ease the pain of whatever constraints we put on C extension code. (This isn't a solution, it's just the tools we can use to reach a solution.)

What are the necessary and essential aspects of the Parrot calling conventions that a C routine would need to replicate or emulate in order to act as if it was a Parrot subroutine/method/opcode?

- accepting a return continuation as an argument?
- returning from the C code by invoking the return continuation passed in?
- creating a return continuation for any calls out of the C code (perhaps a special return continuation that externally acts just like an ordinary one, but internally takes special actions or stores special information for interfacing with the C code)?

Other suggestions? Leo?

   MMD doesn't worry me.  It has long been known that Parrot MMD needs
to be reimplemented more efficiently, and my own favorite candidate for
the job is the FSM-based dispatch that is standard technology for Common
Lisp systems.  The idea is that, instead of doing the type dispatch
directly, the MMD engine builds an FSM in PIR that does argument
discrimination, compiles it to bytecode, caches it for future use, then
calls it to handle the case at hand.  That neatly kills multiple birds
with one stone.

I can't say I'm entirely enamored with the idea of handling MMD with a Lispy FSM. OTOH, I do suspect that ultimately MMD will have to be extensible, to allow for different ways to do MMD. But that's a rabbit-trail, so let's save it for later.

   Nor does embedding.  In embedding, it is normal to exit the main
runloop and later start another main runloop.  As long as continuations
don't care which runloop they restart (i.e. there is only one setjmp
address that gets reset every time), this is not a problem.

Though embedding and extending both use the same C API for Parrot, their concerns are quite different. While extension is concerned with getting C code to act like native Parrot code, embedding is concerned with getting Parrot code (of all sorts) to act as a native part of some other system. All that to say: resolving the continuation barrier isn't necessary for embedding, but would still be handy for embedding in as much as it makes Parrot act as a more consistent system.

Allison

Reply via email to