IO hack in demand analysis should apply in more situations
Consider
{-# LANGUAGE ScopedTypeVariables #-}
import Data.IORef
import Control.Exception
import Control.Monad
data Exc = Exc deriving Show
instance Exception Exc
-- Recursive instead of NOINLINE because of #17673
f :: Int -> Int -> IO ()
f 0 x = do
let true = sum [0..4] == 10
when true $ throwIO Exc
x `seq` return ()
f n x = f (n-1) (x+1)
main = f 1 (error "expensive computation") `catch` \(_ :: Exc) -> return ()
GHC will change its semantics from exiting normally to crashing when compiled with optimisations:
$ ghc -O0 io.hs
<compilation output>
$ ./io
$ ghc -O2 io.hs
<compilation output>
$ ./io
io: expensive computation
CallStack (from HasCallStack):
error, called at io.hs:18:13 in main:Main
This is because we are turning a precise exception (throwing Exc
) into an imprecise one (forcing the bottom in x
), the very thing we aim to avoid by applying the IO hack.
So, clearly not (isPrimOpId f)
is unsound. Checking for exprOkForSideEffects
would be sound, but is too imprecise (#17653 (closed)) because we also use it to make sure state threads are used linearly by read-only operations (#3207 (closed)).
Edit: While implementing !2525 (closed) I realised that it's not really a problem that not (isPrimOpId f)
is unsound, because raiseIO#
will never occur in a case scrutinee after simplification. There's !2956 (closed) which fixes this ticket.