(Moving to internals. Definitely not a language discussion.)

Nicholas Clark wrote:

> Sorry if this is a crack fuelled idea, and sorry that I don't 
> have a patch handy to implement it, but might the following 
> work:


With the same caveats (coke, no diff), try this strategy on for size:


The Setup:

 A. The easy part: Each function which made a speculative optimization
    (or a pessimization!) during compilation registers for a
    notification with the object upon which it based that decision.

 B. The hard part: At each invokecc, JIT compilers guarantee that the
    stack frame is in a state consistent with the PBC ISA. That is: Out-
    of-order optimizations over an invokecc op are illegal. Higher-level
    languages than PASM and IMCC themselves need to themselves offer
    higher-level guarantees of a similar nature, but may be able to
    nonetheless perform CSE hoisting, or some out-of-order
    optimizations, depending upon the language.

    At any of these points, the register file is consistent with the
    architected instruction stream (or the original program), and so the
    stack frame can, with sufficient metadata, be adjusted to resume
    execution with an interpreter, or with an alternative compiled
    version of the same routine.

So long as each world-changing event causes the caller to call from
parrot code with an invokecc, the entire call stack is guaranteed to be
in a rewritable state when one of these events occurs. Hard part solved.


The Algorithm:

 1. Change the world.

 2. Issue the world-changed notification(s): The notification handlers
    will reset the invalidated functions to their un-JITted state,
    releasing the (invalid) compiled forms to the garbage collector.

    Since it's not necessarily safe to execute JITted code between the
    world change and the COMPLETION of this notification phase, the
    notification handlers should all be warded against speculative
    optimizations: Written in C, run with an interpreter, or compiled
    without speculative optimizations.

 3. At this point, invalid compilations are unreachable except by the
    stack: parrot code can be safely executed again, right up to and
    including the perl6 compiler. Since the perl6 compiler is available,
    the invalidated compilations which are already on the stack can be
    recompiled using any necessary facilities. So:

    Recompile the invalidated frames currently on the stack.

Notes:

  - Exceptions (and non-local flow control in general) put this strategy
    at risk: If the compiler (or any notification) aborts, then an
    invalidated compilation could be entered. Very bad.

  - It's also wasteful to immediately recompile everything on the stack:
    If a lot of world-changing events occurred in rapid succession in a
    deep stack (e.g., loading a big batch of plugins), then a lot of
    needless compiler churn could happen.

  - Requires stack walking. Are coroutine stacks easy to find? They need
    to be rewritten, too.


Here's a lazier and safer (and probably simpler) strategy to solve all
of these problems (except multithreading):

  Change step 2: When a compilation is invalidated by its notification,
    before setting the stack free to the garbage collector, adjust all
    of its entry points: Have the first instruction at each entry point
    jump into the recompiler.

Now, even if the compiler throws an exception when rewriting a deeper
frame, the runtime behaves itself when entering exception handlers in
shallower frames--the mangled, drifting compilation now guarantees that
the frame referencing it will be rewritten.

If the compilation would be invalidated 30 times by 30 different library
loads deep in its call chain, then no big loss: It was never recompiled
in the first place. So it gets invalidated once, and then recompiled
once--when control actually returns to it.

Coroutine stacks are no longer a problem: The compilations registered
for notifications, and are thus findable through the notification
system. If the coroutine is never resumed, then no loss.


In a multithreaded environment, though, this class of problems has
significant complicatations. It strikes me that forcing parrot code to
take locks all the time is a very bad idea from a performance
standpoint. So the world-modifying thread would need to stall all the
other threads--at rewritable points. And it has to do so BEFORE it
changes the world, or at least before those changes become visible to
other threads. (.NET does this kind of stuff.) How? One idea:

The Setup:

    Guarantee that event-handling points offer ISA consistency similar
    to invokecc as above.

The Algorithm:

 1. Before changing the world, acquire a mutex.

 3. For each active thread, issue a high-priority event:

     -> The handler on each thread attempts to acquire the same mutex
        from step 1, and of course blocks the target thread.

 3. Are all other threads stalled? Continue.
    Otherwise, go back to 2. (A ward against newly-born threads.)

 4. Change the world, firing notifications as per above.

 5. Release the mutex from step 1.

 6. Done.

     -> Stalled threads acquire and release the mutex, exit the event
        handler, and jump into the frame rewriter if their compilation
        was invalidated.

Notes:

  - See any possibility of deadlocks? 3 could loop forever...

  - This has a only very slightly stronger requirements than those of a
    copying GC.

--
 
Gordon Henriksen
IT Manager
ICLUBcentral Inc.
[EMAIL PROTECTED]


Reply via email to