{-# LANGUAGE BangPatterns, MagicHash, UnboxedTuples, CPP #-} -- | Integer counters providing thread-safe, lock-free mutation functions. -- -- Atomic counters are represented by a single memory location, such that -- built-in processor instructions are sufficient to perform fetch-and-add or -- compare-and-swap. -- -- Remember, contention on such counters should still be minimized! module Data.Atomics.Counter -- Reexport to get all the docs. ( -- * Type of counters of counters and tickets AtomicCounter, -- * Creating counters newCounter, -- * Tickets, used for compare-and-swap -- | See the documentation for "Data.Atomics" for more explanation of the -- ticket abstraction. The same ideas apply here for counters as for -- general mutable locations (IORefs). CTicket, peekCTicket, -- * Atomic memory operations casCounter, incrCounter, incrCounter_, -- * Non-atomic operations readCounter, readCounterForCAS, writeCounter ) where import Data.Atomics.Internal #if MIN_VERSION_base(4,7,0) import GHC.Base hiding ((==#)) import qualified GHC.PrimopWrappers as GPW #else import GHC.Base #endif -- GHC 7.8 changed some primops #if MIN_VERSION_base(4,7,0) (==#) :: Int# -> Int# -> Bool (==#) x y = case x GPW.==# y of { 0# -> False; _ -> True } #endif #ifndef __GLASGOW_HASKELL__ #error "Counter: this library is not portable to other Haskell's" #endif #include "MachDeps.h" #ifndef SIZEOF_HSINT #define SIZEOF_HSINT INT_SIZE_IN_BYTES #endif -- | The type of mutable atomic counters. data AtomicCounter = AtomicCounter (MutableByteArray# RealWorld) -- | You should not depend on this type. It varies between different implementations -- of atomic counters. type CTicket = Int -- TODO: Could newtype this. -- | Create a new counter initialized to the given value. {-# INLINE newCounter #-} newCounter :: Int -> IO AtomicCounter newCounter n = do c <- newRawCounter writeCounter c n -- Non-atomic is ok; it hasn't been released into the wild. return c -- | Create a new, uninitialized counter. {-# INLINE newRawCounter #-} newRawCounter :: IO AtomicCounter newRawCounter = IO $ \s -> case newByteArray# size s of { (# s', arr #) -> (# s', AtomicCounter arr #) } where !(I# size) = SIZEOF_HSINT {-# INLINE readCounter #-} -- | Equivalent to `readCounterForCAS` followed by `peekCTicket`. readCounter :: AtomicCounter -> IO Int readCounter (AtomicCounter arr) = IO $ \s -> case readIntArray# arr 0# s of { (# s', i #) -> (# s', I# i #) } {-# INLINE writeCounter #-} -- | Make a non-atomic write to the counter. No memory-barrier. writeCounter :: AtomicCounter -> Int -> IO () writeCounter (AtomicCounter arr) (I# i) = IO $ \s -> case writeIntArray# arr 0# i s of { s' -> (# s', () #) } {-# INLINE readCounterForCAS #-} -- | Just like the "Data.Atomics" CAS interface, this routine returns an opaque -- ticket that can be used in CAS operations. Except for the difference in return -- type, the semantics of this are the same as `readCounter`. readCounterForCAS :: AtomicCounter -> IO CTicket readCounterForCAS = readCounter {-# INLINE peekCTicket #-} -- | Opaque tickets cannot be constructed, but they can be destructed into values. peekCTicket :: CTicket -> Int peekCTicket !x = x {-# INLINE casCounter #-} -- | Compare and swap for the counter ADT. Similar behavior to -- `Data.Atomics.casIORef`, in particular, in both success and failure cases it -- returns a ticket that you should use for the next attempt. (That is, in the -- success case, it actually returns the new value that you provided as input, but in -- ticket form.) casCounter :: AtomicCounter -> CTicket -> Int -> IO (Bool, CTicket) -- casCounter (AtomicCounter barr) !old !new = casCounter (AtomicCounter mba#) (I# old#) newBox@(I# new#) = IO$ \s1# -> let (# s2#, res# #) = casIntArray# mba# 0# old# new# s1# in case res# ==# old# of False -> (# s2#, (False, I# res# ) #) -- Failure True -> (# s2#, (True , newBox ) #) -- Success -- {-# INLINE sameCTicket #-} -- sameCTicket :: CTicket -> CTicket -> Bool -- sameCTicket = (==) {-# INLINE incrCounter #-} -- | Increment the counter by a given amount. Returns the value AFTER the increment -- (in contrast with the behavior of the underlying instruction on architectures -- like x86.) -- -- Note that UNLIKE with boxed implementations of counters, where increment is -- based on CAS, this increment is /O(1)/. Fetch-and-add does not require a retry -- loop like CAS. incrCounter :: Int -> AtomicCounter -> IO Int incrCounter (I# incr#) (AtomicCounter mba#) = IO $ \ s1# -> let (# s2#, res #) = fetchAddIntArray# mba# 0# incr# s1# in -- fetchAddIntArray# changed behavior in 7.10 to return the OLD value, so we -- need this to maintain forwards compatibility: #if MIN_VERSION_base(4,8,0) (# s2#, (I# (res +# incr#)) #) #else (# s2#, (I# res) #) #endif {-# INLINE incrCounter_ #-} -- | An alternate version for when you don't care about the old value. incrCounter_ :: Int -> AtomicCounter -> IO () incrCounter_ (I# incr#) (AtomicCounter mba#) = IO $ \ s1# -> -- NOTE: either old or new behavior of fetchAddIntArray# is fine here, since -- we don't inspect the return value: let (# s2#, _ #) = fetchAddIntArray# mba# 0# incr# s1# in (# s2#, () #)