So is it just that there are a small number of places where it is unsafe to invoke IO from a primitive,
It is unsafe to invoke IO from a prim _in the middle of_ manipulating a data structure manipulated by IO operations unless the data structure is designed to allow that nested use. Trivial example: using a counter instead of a bool to record whether finalizers are blocked makes nested calls ok (at least as far as that particular data structure is concerned).
(It would be unsafe to invoke eval from prims which manipulate data structures in pure code except that we took care when writing the code to allow for that since most of the prims invoke eval.)
[...] Can we easily identify which are the unsafe places and fix them?
Look at all prims with types in IO. Look at what data structures they touch. Check if there are accesses to that data structure 'both sides' of a call to eval (there may be a few functions which invoke eval so watch for them). Check if any such accesses break data structure invariants.
Since this can affect Hugs as it stands (when you use unsafePerformIO), would you mind taking a look?
Now repeat for library code that we ship with Hugs. Data structures of interest will be those constructed with IORefs and things built from them (this includes MVars since Hugs implements MVars using IORefs).
The following libraries in fptools/libraries use IORefs and might be unsafe: System.Random and Data.Dynamic. Hugs has its own versions of these libraries that also use IORef in the same way. For GHC, we really ought to fix these to use MVars, since they're unsafe already. For Hugs, I don't think it would be a complete disaster if in the errata you say that Data.Dynamic and System.Random can't be used from finalizers. Cheers, Simon
SimonM:
[...] Can we easily identify which are the unsafe places and fix them?
Alastair:
Look at all prims with types in IO. Look at what data structures they touch. Check if there are accesses to that data structure 'both sides' of a call to eval (there may be a few functions which invoke eval so watch for them). Check if any such accesses break data structure invariants.
SimonM:
Since this can affect Hugs as it stands (when you use unsafePerformIO), would you mind taking a look?
There are over 200 calls to eval() in Hugs. Almost all of them look OK on a cursory scan. As Alastair said, most are at the start of primitives, and Hugs uses its stack and local variables a lot. But there's an unsafe use in evalName(), and I don't understand the mutual recursion between eval() and run(). There's another problem with Simon's patch I haven't been able to pin down: if you run the example, interrupt it at the right point and type another expression, the finalizers run, but the expression is lost.
Ross Paterson <ross@soi.city.ac.uk> writes:
there's an unsafe use in evalName(),
I think this is easily fixed by using malloc to allocate the buffer and then tracking down all uses and calling free.
and I don't understand the mutual recursion between eval() and run().
Not sure what you don't understand here so let me sketch my understanding: eval traverses the graph looking for the next redex and then calling an appropriate C function to apply the reduction. One of these reducers is the bytecode evaluator 'run'. run evaluates bytecode which both constructs fresh bits of graph and implements the evaluate/test part of case expressions. When evaluating case expressions, it has to trigger evaluation of the selector which it does by calling 'eval'. Without studying the code in detail, I believe that this recursion is safe because run is a critical part of non-monadic evaluation - if there were problems here, we'd have found them a long time ago. Of course, this naive belief might be shattered if I were to actually look at the code in detail. -- Alastair
On Thu, Oct 17, 2002 at 01:42:47PM +0100, Alastair Reid wrote:
Ross Paterson <ross@soi.city.ac.uk> writes:
there's an unsafe use in evalName(),
I think this is easily fixed by using malloc to allocate the buffer and then tracking down all uses and calling free.
OK -- there only 20 of those.
and I don't understand the mutual recursion between eval() and run().
[...] Without studying the code in detail, I believe that this recursion is safe because run is a critical part of non-monadic evaluation - if there were problems here, we'd have found them a long time ago. Of course, this naive belief might be shattered if I were to actually look at the code in detail.
It seems then that the invariant you proposed can be established without too much trouble. But Simon's bug is a whole other can of worms.
participants (3)
-
Alastair Reid -
Ross Paterson -
Simon Marlow