[GHC] #15349: fixST is a bit wrong

GHC ghc-devs at haskell.org
Wed Jul 25 02:28:24 UTC 2018


#15349: fixST is a bit wrong
-------------------------------------+-------------------------------------
        Reporter:  dfeuer            |                Owner:  dfeuer
            Type:  bug               |               Status:  merge
        Priority:  normal            |            Milestone:  8.6.1
       Component:  Core Libraries    |              Version:  8.5
      Resolution:                    |             Keywords:
Operating System:  Unknown/Multiple  |         Architecture:
 Type of failure:  Incorrect result  |  Unknown/Multiple
  at runtime                         |            Test Case:
      Blocked By:                    |             Blocking:
 Related Tickets:                    |  Differential Rev(s):  Phab:D4948
       Wiki Page:                    |
-------------------------------------+-------------------------------------

Comment (by dfeuer):

 I took a deep dive into lazy `ST` and came up with an absurdly inefficient
 "reference implementation" that I believe should be extremely correct. How
 inefficient? The monadic bind creates three `MVar`s and two green threads!
 I wonder if someone has a good idea about how to turn that into something
 both correct and efficient. The idea here is to turn each suspended
 computation into its very own green thread, and to use `MVar`s to
 communicate between them. One `MVar` requests a state token, while another
 is used to transfer one.

 {{{#!hs
 {-# language MagicHash, UnboxedTuples, GADTs, RankNTypes, BangPatterns #-}
 module Control.Monad.ST.Lazy.Imp where
 import qualified Control.Monad.ST as ST
 import qualified GHC.ST as ST

 import GHC.IO
 import GHC.Exts
 import Control.Concurrent.MVar
 import Control.Monad
 import Control.Applicative
 import Control.Concurrent

 infixl 1 :>>=

 data ST s a where
   Pure :: a -> ST s a
   StrictToLazyST :: ST.ST s a -> ST s a
   (:>>=) :: ST s a -> (a -> ST s b) -> ST s b
   FixST :: (a -> ST s a) -> ST s a

 strictToLazyST :: ST.ST s a -> ST s a
 strictToLazyST = StrictToLazyST

 instance Functor (ST s) where
   fmap = liftM
 instance Applicative (ST s) where
   pure = Pure
   (<*>) = ap
   liftA2 = liftM2
 instance Monad (ST s) where
   (>>=) = (:>>=)

 data State s = State (State# s)

 -- We don't care about thread IDs
 forkIO_ :: IO () -> IO ()
 forkIO_ m = void (forkIO m)

 run
      -- Request and receive a state token
   :: MVar () -> MVar (State RealWorld)
      -- Wait for a request and provide a state token
   -> MVar () -> MVar (State RealWorld)
   -> ST RealWorld a -> IO a

 run !s_in !m_in !s_out !m_out (Pure a) = do
   forkIO_ $ do
     readMVar s_out                  -- If we need the state,
     _ <- tryPutMVar s_in ()         -- request the state
     takeMVar m_in >>= putMVar m_out -- and transfer it
     pure ()
   pure a

 run s_in m_in _s_out m_out (StrictToLazyST (ST.ST m)) = do
   putMVar s_in () -- Request the state
   State s <- takeMVar m_in -- Get the state
   case m s of
     (# s', a #) -> do
       putMVar m_out (State s') -- Put the new state
       pure a

 -- This is the hard case. We have to 'run' @n@ if we need
 -- *either* its state token *or* its value.
 run s_in m_in s_out m_out (n :>>= f) = do
   sn_out <- newEmptyMVar
   n_out <- newEmptyMVar
   resv <- newEmptyMVar
   -- run_it gets filled if we need to run @n@, either for its
   -- value or for its state.
   run_it <- newEmptyMVar
   forkIO_ $ readMVar sn_out >> tryPutMVar run_it () >> return ()
   forkIO_ $ do
     readMVar run_it
     res <- run s_in m_in sn_out n_out n
     putMVar resv res
   run sn_out n_out s_out m_out
     (f $ unsafeDupablePerformIO $
            tryPutMVar run_it () >> readMVar resv)

 run s_in m_in s_out m_out (FixST f) = do
   resv <- newEmptyMVar
   res <- run s_in m_in s_out m_out (f $ unsafeDupablePerformIO $ readMVar
 resv)
   putMVar resv res
   pure res

 runST :: (forall s. ST s a) -> a
 runST st = runRW# $ \s ->
   let ss = State s
   in case unIO
     (do
        s_in <- newEmptyMVar
        m_in <- newMVar ss
        s_out <- newEmptyMVar
        m_out <- newEmptyMVar
        run s_in m_in s_out m_out st) s of
     (# _, a #) -> a
 }}}

-- 
Ticket URL: <http://ghc.haskell.org/trac/ghc/ticket/15349#comment:13>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler


More information about the ghc-tickets mailing list