Make unsafePerformIO lazy
authorsimonpj@microsoft <unknown>
Mon, 8 May 2006 14:25:07 +0000 (14:25 +0000)
committersimonpj@microsoft <unknown>
Mon, 8 May 2006 14:25:07 +0000 (14:25 +0000)
The stricteness analyser used to have a HACK which ensured that NOINLNE things
were not strictness-analysed.  The reason was unsafePerformIO. Left to itself,
the strictness analyser would discover this strictness for unsafePerformIO:
unsafePerformIO:  C(U(AV))
But then consider this sub-expression
unsafePerformIO (\s -> let r = f x in
       case writeIORef v r s of (# s1, _ #) ->
       (# s1, r #)
The strictness analyser will now find that r is sure to be eval'd,
and may then hoist it out.  This makes tests/lib/should_run/memo002
deadlock.

Solving this by making all NOINLINE things have no strictness info is overkill.
In particular, it's overkill for runST, which is perfectly respectable.
Consider
f x = runST (return x)
This should be strict in x.

So the new plan is to define unsafePerformIO using the 'lazy' combinator:

unsafePerformIO (IO m) = lazy (case m realWorld# of (# _, r #) -> r)

Remember, 'lazy' is a wired-in identity-function Id, of type a->a, which is
magically NON-STRICT, and is inlined after strictness analysis.  So
unsafePerformIO will look non-strict, and that's what we want.

GHC/IOBase.lhs

index 27f7dab..499899a 100644 (file)
@@ -215,7 +215,7 @@ help of 'unsafePerformIO'.  So be careful!
 -}
 {-# NOINLINE unsafePerformIO #-}
 unsafePerformIO        :: IO a -> a
-unsafePerformIO (IO m) = case m realWorld# of (# _, r #)   -> r
+unsafePerformIO (IO m) = lazy (case m realWorld# of (# _, r #) -> r)
 
 -- Why do we NOINLINE unsafePerformIO?  See the comment with
 -- GHC.ST.runST.  Essentially the issue is that the IO computation
@@ -223,6 +223,22 @@ unsafePerformIO (IO m) = case m realWorld# of (# _, r #)   -> r
 -- not at all.  If we let the compiler see the application of the IO
 -- to realWorld#, it might float out part of the IO.
 
+-- Why is there a call to 'lazy' in unsafePerformIO?
+-- If we don't have it, the demand analyser discovers the following strictness
+-- for unsafePerformIO:  C(U(AV))
+-- But then consider
+--     unsafePerformIO (\s -> let r = f x in 
+--                            case writeIORef v r s of (# s1, _ #) ->
+--                            (# s1, r #)
+-- The strictness analyser will find that the binding for r is strict,
+-- (becuase of uPIO's strictness sig), and so it'll evaluate it before 
+-- doing the writeIORef.  This actually makes tests/lib/should_run/memo002
+-- get a deadlock!  
+--
+-- Solution: don't expose the strictness of unsafePerformIO,
+--          by hiding it with 'lazy'
+
+
 {-|
 'unsafeInterleaveIO' allows 'IO' computation to be deferred lazily.
 When passed a value of type @IO a@, the 'IO' will only be performed