EvEff icon indicating copy to clipboard operation
EvEff copied to clipboard

Local state is not safe

Open noughtmare opened this issue 3 years ago • 3 comments

E.g. the program:

import Control.Ev.Eff

main :: IO ()
main = print $ runEff $ local 0 $ do
  localModify (+ 1)
  localGet

Prints (when compiled with -O):

0

I think that this is due to common sub-expression elimination. First the code above gets turned into something like:

import Control.Ev.Eff

main :: IO ()
main =
  let !r = unsafePerformIO (newIORef 0)
      !x1 = unsafePerformIO (readIORef r)
      !() = unsafePerformIO (writeIORef r (x1 + 1)
      !x2 = unsafePerformIO (readIORef r)
  in pure x2

And common sub-expression elimination transforms that to:

import Control.Ev.Eff

main :: IO ()
main =
  let !r = unsafePerformIO (newIORef 0)
      !x1andx2 = unsafePerformIO (readIORef r)
      !() = unsafePerformIO (writeIORef r (x1andx2 + 1)
  in pure x1andx2

A simple solution seems to be to mark perform NOINLINE, but that probably affects performance.

noughtmare avatar May 05 '21 13:05 noughtmare

I have a better solution, we can rewrite Ctl into a monad transformer CtlM which can run IO actions properly. Then you only need one unsafePerformIO at the top level in runCtl.

I have been able to make it work, but I have to clean up my code a bit. Expect a pull request soon.

Update: the change significantly affects some benchmarks, so I am investigating further. But maybe the microbenchmarks are also not representative of real-world scenarios w.r.t. inlining and specialization as Alexis King's talk "Effects for Less" showed.

noughtmare avatar May 05 '21 19:05 noughtmare

@noughtmare I tried to reproduce this with the current HEAD and it works as intended. Can your confirm that?

mmhat avatar Jul 01 '21 09:07 mmhat

If I compile with optimizations then I can still reproduce it.

noughtmare avatar Jul 01 '21 09:07 noughtmare