unsafePerformIO/NOINLINE tricks no longer work
Summary
Starting with GHC 9.0.1, the following program:
{-# OPTIONS_GHC -O1 #-}
import System.IO.Unsafe
import Data.IORef
{-# NOINLINE resourceId #-}
resourceId :: IO Int
resourceId = unsafePerformIO counter
counter :: IO (IO Int)
counter = do
ref <- newIORef 0
pure $ atomicModifyIORef' ref $ \i -> let j = i + 1 in (j, j)
main = do
print =<< resourceId
print =<< resourceId
Has gone from printing 1
then 2
, to printing 1
then 1
.
Whether it's a bug or GHC just getting better at optimisation is debatable. It definitely breaks code that works with 8.10, but I appreciate that with unsafePerformIO
that isn't evidence of anything. I'm raising a bug after looking at the core, which seems like it might be broken.
Steps to reproduce
Using GHC 9.0:
$ ghc --make Test.hs
$ ./Test
1
1
Expected behaviour
1
2
Environment
GHC 9.0.1. Reproduced on Windows, but happens on Linux/Mac CI machines too.
Core output
The reason I raised this as a bug is after looking at the output of -ddump-simpl
, available at https://gist.github.com/ndmitchell/a45d1db2eea8f8b2ee207e5d29475fce#file-core-hs-L67. Approximately, I think it has done the equivalent of:
resourceId = runRW $ \rw1 rw2 -> <all the work>
Namely, it pushed the creation of the IORef
inside the inner IO
, which I don't believe is allowed.