Right now, Parrot's support for NCI callbacks (C code calling back into
PIR code) is relatively limited.  In particular, there are at least the
following limitations:

1. The return type must be void (C library does not expect a response).

2. The callback must have exactly two arguments (from both the C and PIR
   perspectives).

3. One of these arguments must be a "user data" pointer, provided by
   the PIR code when it registers the callback with the C library, and
   handed back by the C code to the PIR code during every callback.

4. The C code must treat the user data pointer as entirely opaque (no
   modification or assumed structure).

5. The other argument's type can vary, but is much more limited than an
   arbitrary ("normal") NCI argument.

I am starting to implement a GLUT and OpenGL binding for Parrot.  GLUT
is extremely callback-oriented.  You essentially can't do anything
beyond open the top-level window until callbacks are functional.  You
can't even draw in that window -- the rendered image never actually
appears, and in fact the window itself is simply transparent with just
the chrome showing.

Unfortunately, none of the GLUT callbacks fall within the current
limitations on Parrot NCI callbacks.  I was thus beginning to look at
improving the Parrot NCI code accordingly, but soon realized I didn't
know how to get around some pretty fundamental problems (at least, not
in pure C code).

It turns out there's a good reason that limitations #3 and #4 exist.
What actually gets registered with the external C library is a pointer
to a C function in src/inter_cb.c.  This function calls a chain of other
functions.  First, they check that the "user data" argument has not been
obviously corrupted, and that the interpreter state is still sane.
Next, they pull the Sub PMC and signature for the PIR function that
should be called *from properties of the user data argument*.  A little
more sanity checking and a switch statement later, and this PIR Sub is
called using Parrot_runops_fromc_args_event().

And therein lies the problem.  The callback entry point is compiled C
code.  There could be dozens of callbacks registered by the PIR code,
but only *one* set of C functions that handles all of them.  In order to
have a single compiled Parrot function that brokers all these different
callbacks from C to PIR, Parrot cheats.  Parrot adds several properties
to the "user data" parameter before handing it off to the C library, and
when the C library calls back, Parrot reads these properties to figure
out which PIR function should get called.

But this cheat doesn't work if the C library's callbacks don't all
support a completely opaque user data parameter.  And guess what?  Not a
single one of the GLUT callbacks is of this form.  In fact, several of
the most important GLUT callbacks have *no parameters at all*, and all
of the callback parameters have defined meanings on the C side.

So ... how can Parrot support callbacks of the types that GLUT uses?
The trick of putting magical properties on a special user data parameter
won't work anymore.  I've been tanking on this for a while, and come up
with several possible schemes.

My first idea was that each time a new callback was registered, Parrot
would copy a tiny shim function and then poke the address(es) of the
data it needed to figure out what PIR routine to call directly into the
copied shim code.  The shim copy could then be handed off to the C
library callback registration.  When a callback came, the shim copy
would then magically call the right PIR function, since it had all the
information it needed "hardcoded".

A variant of this involved having a global registry of callbacks, so
that instead of poking a pile of data into the copied shim, Parrot would
simply need to poke in the callback number (one integer).  When the
callback came in, the shim copy would then use this single integer to
look up the info it needed in the global registry.

But I don't know how to portably do either kind of compiled code poking
in pure C.  (Not that I know it can't be done -- I just personally don't
know how.)

So my next idea was to have a shim function that did nothing but call
another function, thus putting its own address on the stack.  The other
function would then reach up above its own stack frame and grab that
return address, using *that address* to look up the needed information
in a global callback registry.  By making copies of this simple shim
every time a new callback was registered, each copy would get its own
address, and everything would magically work.

This was certainly better than the previous ideas, because it didn't
involve poking anything into the shim code, just being able to copy it.

But I still don't know how to do that in pure portable C.  My guess is
that you could declare another function (which need not do anything) in
some special way so that it is guaranteed to be compiled into the object
code directly after the magic shim.  Subtracting the address of the shim
function from the dummy function would then tell you exactly how many
bytes long the compiled shim is (including perhaps some padding, which
doesn't matter).  This would allow you to copy the shim at will.

But this still leaves the problems of:

A. Being able to portably force two functions two be compiled
   sequentially in memory.  I would hope this is easy, but I don't
   recall which set of declarations makes exactly this guarantee.
 
B. Being able to execute a copy of the shim on architectures that have
   non-executable stack and/or heap.  I assume this had to be solved
   before JIT could work, so I'm guessing this isn't a real problem.

C. Being able to reach up above one's own stack frame and grab the
   return address from the shim function's dummy call.  Is there a
   trick to make this work in pure portable C?

Anyway, there's my brain dump.  Any help greatly appreciated, because
right now the GLUT/OpenGL binding is just dead in the water.


-'f


Reply via email to