RE: Proposed change to ForeignPtr
I'm afraid George's questions have also rekindled my curiosity about whether implementing Haskell finalizers is really as hard as it sounds. Much has been written, but I still don't think we've got to the nub of the issue. On the face of it, if you can implement 'foreign import ccall safe', then you have a re-entrant runtime system. The times at which the program can make one of these foreign calls are limited, i.e. in the IO monad only - but I believe there's nothing particularly special about IO computations in the evaluation models used by nhc98 and Hugs (correct me if I'm wrong). Alastair writes:
The way GHC implements preemption is an optimized form of: set a bit when preemption is needed; and make sure that generated code will test that bit whenever it is in a position to perform a context switch.
What you're asking Hugs and NHC to do is: add a function to a list whenever you have a finalizer to run; make sure the interpreter will test that bit whenever it is in a position to perform a context switch.
It's basically the same. We don't have to mess around with signals to provide a regular timer interrupt but that's the easy bit of the code.
Ok so far.
We can probably avoid messing around with multiple C stacks. That's a significant saving but, it's the complexity of that is fairly self-contained - we could probably steal some code from some other language implementation.
The cost is going over all data structures in the system making sure that operations on them are suitably atomic. One of the issues I remember from old versions of GHC was that some of the primops would do some work, allocate some memory, then finish the job. The classic error to make in that code was for the second half of the code to assume almost anything about what happened in the first half of the code: how much space is on the stack, does a table have free space in it, is this pointer into the middle of an object ok?
You certainly can't keep local variables live across a heap check, everything has to be saved on the stack. Hugs is different because it has a conservative GC, so doesn't need to save everything on the stack for a GC. But how does it implement a safe foreign call? Presumably it must save away state on the stack in a way that the computation can be resumed safely, and that's all you need in order to be able to run a Haskell finalizer.
The problem is the scope: every single data structure and every bit of code that accesses it has to be vetted and we have to keep it in mind as we maintain the code. It's a high price to pay and I don't think it's necessary (because all you really need is for the runtime systems to talk to each other in very limited ways).
I don't quite understand this: could you give a concrete example of some extra invariant that has to be maintained? In GHC, context switches happen at very precise points (i.e. heap checks) so I don't think we have these kind of problems; certainly I don't remember vetting every single data structure. Can't Hugs use a similar approach? Cheers, Simon
participants (1)
-
Simon Marlow