packages feed

atomic-modify-general-0.1.0.0: src/Data/IORef/AtomicModify.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE Trustworthy #-}
{-# LANGUAGE UnboxedTuples #-}

-- | Atomic 'IORef' and array modification operations for more general result
-- types.
module Data.IORef.AtomicModify
  ( atomicModifyIORef3General
  , atomicModifyArray3General
  , atomicModifySmallArray3General
  ) where

import GHC.IORef (IORef (..), newIORef, readIORef)
import GHC.STRef (STRef (..))
import GHC.Exts ( casMutVar#, MutVar#, RealWorld, readMutVar#, lazy, State#
                , writeMutVar#, Int (..), Int#, MutableArray#, readArray#
                , casArray#, SmallMutableArray#, readSmallArray#, casSmallArray#
                )
import Data.IORef.AtomicModify.SmallArraySize
                ( getSizeofSmallMutableArray# )
import GHC.IO (IO (..))
import System.IO.Unsafe (unsafeDupablePerformIO)
import Data.Primitive.Array
import Data.Primitive.SmallArray
import Control.Monad (when)
import Control.Exception (ArrayException (..), throwIO)

-- | A version of 'GHC.IORef.atomicModifyIORef2' that takes an arbitrary pair
-- of functions. This function will allocate more than 'atomicModifyIORef2',
-- and will tend to take longer to succeed when there is a lot of contention
-- for the 'IORef'.
--
-- @
-- atomicModifyIORef2 ref f = do
--   (old, _new, r) <- atomicModifyIORef2General ref fst f
--   pure (old, r)
-- @
--
-- If the first function (the \"extraction function\") is a record field
-- selector (e.g., 'snd'), we do our best to make sure the thunk placed in the
-- 'IORef' is a selector thunk, so the garbage collector can drop the rest of
-- the record once the record is forced. In other cases, callers should
-- generally force the returned @new@ value in order to avoid a potential space
-- leak.
--
-- Conceptually:
--
-- @
-- atomicModifyIORef3General ref extract f = do
--   -- Begin atomic block
--   old <- 'readIORef' ref
--   let r = f old
--       new = extract r
--   'writeIORef' ref new
--   -- End atomic block
--   r `seq` pure (old, new, r)
-- @
--
-- where other threads cannot interfere with the operations in the \"atomic block\".
-- In particular, no other thread can write to the 'IORef' between the 'readIORef'
-- and the 'writeIORef' operations.
atomicModifyIORef3General
  :: IORef a -> (t -> a) -> (a -> t) -> IO (a, a, t)
atomicModifyIORef3General (IORef (STRef ref)) extract = \f -> do
  -- atomicModifyMutVar2# creates a thunk for the result of applying the user
  -- function 'f' to the "old" value read from the IORef, and then edits that thunk
  -- in a CAS loop. In Haskell land, thunks are immutable, so we can't exactly
  -- do that. Instead, we make an IORef, 'holder', to hold the "old" value, and
  -- use 'unsafeDupablePerformIO' to create a thunk that will read it and apply
  -- 'f' to the result. We /can/ edit the holder IORef in the CAS loop.
  -- Note: since casMutVar# introduces a full memory barrier, any thread reading
  -- the 'r' thunk from 'ref' will have "seen" the preceding 'writeMutVar' to 'holder',
  -- so it won't get the uninitialized value or anything similarly stale.
  holder@(IORef (STRef holder#)) <- newIORef uninitialized
  let r = unsafeDupablePerformIO (f <$> readIORef holder)
      -- I really don't think r is going to inline anyway, but if it does, we
      -- could produce an unnecessary space leak.
      {-# NOINLINE r #-}
  let new = extract r
  IO (\s -> case atomicModifyIORef3General' ref holder# new r s of
               (# s', old, new', !res #) -> (# s', (old, new', res) #))
{-# INLINE atomicModifyIORef3General #-}

atomicModifyIORef3General'
  :: MutVar# RealWorld a -> MutVar# RealWorld a -> a -> t -> State# RealWorld -> (# State# RealWorld, a, a, t #)
atomicModifyIORef3General' ref holder new r s1 =
  case readMutVar# ref s1 of { (# s2, old #) ->
  case writeMutVar# holder old s2 of { s3 ->
  case casMutVar# ref old new s3 of { (# s4, flag, _ #) ->
  case flag of
    -- Why the lazy invocations?
    --
    -- In the event that 'old' gets forced, unboxed, and reboxed between
    -- 'readMutVar#' and 'casMutVar#', the CAS will never succeed. I doubt
    -- that could happen anyway, but let's be sure.
    --
    -- If 'r' is forced before the holder is written, it will read an
    -- uninitialized value and throw an exception. Ouch. Let's make sure that
    -- doesn't happen either.
    0# -> (# s4, lazy old, lazy new, lazy r #)
    _ -> atomicModifyIORef3General' ref holder new r s4 }}}
{-# NOINLINE atomicModifyIORef3General' #-}

uninitialized :: a
uninitialized = error "Uninitialized. This is a bug in atomic-modify-generics."
{-# NOINLINE uninitialized #-}

-- | A version of 'atomicModifyIORef3General' for 'Array's. See the
-- documentation there. Indexing is performed safely.
atomicModifyArray3General
  :: MutableArray RealWorld a -> Int -> (t -> a) -> (a -> t) -> IO (a, a, t)
-- See atomicModifyIORef3General for implementation comments.
-- Why do we perform safe indexing? This operation is expensive enough
-- that I don't think we really have to worry about the cost of a single
-- bounds check.
atomicModifyArray3General mary@(MutableArray mary#) ix@(I# ix#) extract = \f -> do
  -- We use unsigned comparison to make sure ix is non-negative
  -- and less than the array size with just one comparison. The LLVM
  -- backend is clever enough to produce this from the obvious two-sided
  -- check, but last I looked the native code generator wasn't.
  let !sz = sizeofMutableArray mary
  when ((fromIntegral ix :: Word) >= fromIntegral sz) $ outOfBoundsArray ix sz
  holder@(IORef (STRef holder#)) <- newIORef uninitialized
  let r = unsafeDupablePerformIO (f <$> readIORef holder)
      {-# NOINLINE r #-}
  let new = extract r
  IO (\s -> case atomicModifyArray3General' mary# ix# holder# new r s of
               (# s', old, new', !res #) -> (# s', (old, new', res) #))
{-# INLINE atomicModifyArray3General #-}

outOfBoundsArray :: Int -> Int -> IO a
outOfBoundsArray ix sz
  | ix < 0 = throwIO . IndexOutOfBounds $
      "\natomicModifyArray3General was passed a negative array index of " ++
        show ix ++ "."
  | otherwise = throwIO . IndexOutOfBounds $
      "\natomicModifyArray3General was passed an array index of " ++
        show ix ++ ",\nbut an array of only " ++ show sz ++ " elements."
{-# NOINLINE outOfBoundsArray #-}

atomicModifyArray3General'
  :: MutableArray# RealWorld a -> Int# -> MutVar# RealWorld a -> a -> t -> State# RealWorld -> (# State# RealWorld, a, a, t #)
atomicModifyArray3General' mary ix holder new r s1 =
  case readArray# mary ix s1 of { (# s2, old #) ->
  case writeMutVar# holder old s2 of { s3 ->
  case casArray# mary ix old new s3 of { (# s4, flag, _ #) ->
  case flag of
    0# -> (# s4, lazy old, lazy new, lazy r #)
    _ -> atomicModifyArray3General' mary ix holder new r s4 }}}
{-# NOINLINE atomicModifyArray3General' #-}

-- | A version of 'atomicModifyIORef3General' for 'SmallArray's. See the
-- documentation there. Indexing is performed safely.
atomicModifySmallArray3General
  :: SmallMutableArray RealWorld a -> Int -> (t -> a) -> (a -> t) -> IO (a, a, t)
-- See atomicModifyIORef3General for implementation comments.
atomicModifySmallArray3General (SmallMutableArray mary#) ix@(I# ix#) extract = \f -> do
  IO (\s -> case getSizeofSmallMutableArray# mary# s of (# s', sz# #) -> (# s', I# sz# #))
    >>= \sz -> when ((fromIntegral ix :: Word) >= fromIntegral sz) (outOfBoundsSmallArray ix sz)
  holder@(IORef (STRef holder#)) <- newIORef uninitialized
  let r = unsafeDupablePerformIO (f <$> readIORef holder)
      {-# NOINLINE r #-}
  let new = extract r
  IO (\s -> case atomicModifySmallArray3General' mary# ix# holder# new r s of
               (# s', old, new', !res #) -> (# s', (old, new', res) #))
{-# INLINE atomicModifySmallArray3General #-}

atomicModifySmallArray3General'
  :: SmallMutableArray# RealWorld a -> Int# -> MutVar# RealWorld a -> a -> t -> State# RealWorld -> (# State# RealWorld, a, a, t #)
atomicModifySmallArray3General' mary ix holder new r s1 =
  case readSmallArray# mary ix s1 of { (# s2, old #) ->
  case writeMutVar# holder old s2 of { s3 ->
  case casSmallArray# mary ix old new s3 of { (# s4, flag, _ #) ->
  case flag of
    0# -> (# s4, lazy old, lazy new, lazy r #)
    _ -> atomicModifySmallArray3General' mary ix holder new r s4 }}}
{-# NOINLINE atomicModifySmallArray3General' #-}

outOfBoundsSmallArray :: Int -> Int -> IO a
outOfBoundsSmallArray ix sz
  | ix < 0 = throwIO . IndexOutOfBounds $
      "\natomicModifySmallArray3General was passed a negative array index of " ++
        show ix ++ "."
  | otherwise = throwIO . IndexOutOfBounds $
      "\natomicModifySmallArray3General was passed an array index of " ++
        show ix ++ ",\nbut an array of only " ++ show sz ++ " elements."
{-# NOINLINE outOfBoundsSmallArray #-}