(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]