packages feed

pvar 0.1.1.0 → 0.2.0.0

raw patch · 6 files changed

+195/−65 lines, 6 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Data.Primitive.PVar: atomicFetchModifyIntPVar :: PrimMonad m => PVar m Int -> (Int -> Int) -> m Int
+ Data.Primitive.PVar: atomicModifyFetchIntPVar :: PrimMonad m => PVar m Int -> (Int -> Int) -> m Int
+ Data.Primitive.PVar: fetchModifyPVar :: (PrimMonad m, Prim a) => PVar m a -> (a -> a) -> m a
+ Data.Primitive.PVar: fetchModifyPVarM :: (PrimMonad m, Prim a) => PVar m a -> (a -> m a) -> m a
+ Data.Primitive.PVar: modifyFetchPVar :: (PrimMonad m, Prim a) => PVar m a -> (a -> a) -> m a
+ Data.Primitive.PVar: modifyFetchPVarM :: (PrimMonad m, Prim a) => PVar m a -> (a -> m a) -> m a
- Data.Primitive.PVar: modifyPVar :: (PrimMonad m, Prim a) => PVar m a -> (a -> a) -> m a
+ Data.Primitive.PVar: modifyPVar :: (PrimMonad m, Prim a) => PVar m a -> (a -> (a, b)) -> m b
- Data.Primitive.PVar: modifyPVarM :: (PrimMonad m, Prim a) => PVar m a -> (a -> m a) -> m a
+ Data.Primitive.PVar: modifyPVarM :: (PrimMonad m, Prim a) => PVar m a -> (a -> m (a, b)) -> m b

Files

CHANGELOG.md view
@@ -1,5 +1,13 @@ # Changelog for pvar +## 0.2.0.0++* Rename `modifyPVar` to `fetchModifyPVar` and `modifyPVarM` to `fetchModifyPVarM`. This+  is a breaking change in favor of consistency with other librarries.+* New implementation for `modifyPVar` and `modifyPVarM` that can return some artifact.+* Addition of `modifyFetchPVar` and `modifyFetchPVarM`+* Addition of `atomicModifyFetchIntPVar` and `atomicFetchModifyIntPVar`+ ## 0.1.1.0  * Addition of backwards compatible:
README.md view
@@ -1,6 +1,6 @@ # pvar -Interface for a mutable veriable `PVar` that can hold values that have `Prim` instance.+Interface for a mutable variable `PVar` that can hold values that have `Prim` instance.  ## Status @@ -16,17 +16,17 @@  Main features include: -* Perfomance. There is practically no overhead when compared to operating on pure values,+* Performance. There is practically no overhead when compared to operating on pure values,   although there is a higher memory overhead, since `PVar` is backed by a   `MutableByteArray#` * Atomic operations for `PVar`s with `Int` values. This includes a unique function that is-  not availiable in `ghc-prim` out of the box:+  not available in `ghc-prim` out of the box:  ```haskell atomicModifyIntPVar :: PrimMonad m => PVar m Int -> (Int -> (Int, a)) -> m a ``` -* Works in `PrimMonad`, therfore usable with `ST`, `IO` and various transformer monads.+* Works in `PrimMonad`, therefore it is usable with `ST`, `IO` and various transformer monads. * Easy access to `PVar` contents with `Storable` * `isByteArrayPinned`, `isMutableByteArrayPinned` function that work on ghc-7.10 and   ghc-8.0 as well as all the newer ones.
pvar.cabal view
@@ -1,14 +1,15 @@ cabal-version: 1.12  name:           pvar-version:        0.1.1.0+version:        0.2.0.0 synopsis:       Mutable variable with primitive values-description:    Please see the README on GitHub at <https://github.com/lehins/pvar#readme>+description:    Mutable variable `PVar` that is backed by a single value `MutableByteArray` homepage:       https://github.com/lehins/pvar#readme bug-reports:    https://github.com/lehins/pvar/issues author:         Alexey Kuleshevich maintainer:     alexey@kuleshevi.ch-copyright:      Alexey Kuleshevich+copyright:      2020 Alexey Kuleshevich+category:       Data license:        BSD3 license-file:   LICENSE build-type:     Simple
src/Data/Primitive/PVar.hs view
@@ -14,24 +14,28 @@ -- Portability : non-portable -- module Data.Primitive.PVar-  ( -- | `PVar` has significantly better performance characterisitcs over-    -- `Data.IORef.IORef`, `Data.STRef.STRef` and `Data.Primtive.MutVar.MutVar`. This is+  ( -- | `PVar` has significantly better performance characteristics over+    -- `Data.IORef.IORef`, `Data.STRef.STRef` and `Data.Primitive.MutVar.MutVar`. This is     -- because value is mutated directly in memory instead of following an extra     -- pointer. Besides better performance there is another consequence of direct-    -- mutation, namely that values are always evaluated to normal form when being written+    -- mutation, namely the value is always evaluated to normal form when being written     -- into a `PVar` -  -- * Primitive variable     PVar+  -- * Creation   , newPVar   , withPVarST-  -- * Generic Operations+  -- * Mutable Operations   , readPVar   , writePVar-  , modifyPVar_   , modifyPVar-  , modifyPVarM_+  , modifyPVar_+  , fetchModifyPVar+  , modifyFetchPVar   , modifyPVarM+  , modifyPVarM_+  , fetchModifyPVarM+  , modifyFetchPVarM   , swapPVars_   , swapPVars   , copyPVar@@ -55,9 +59,11 @@   -- , (=*)   -- , (=/)   -- , (=%)-  -- ** Atomic operations+  -- * Atomic operations   , atomicModifyIntPVar   , atomicModifyIntPVar_+  , atomicFetchModifyIntPVar+  , atomicModifyFetchIntPVar   , atomicReadIntPVar   , atomicWriteIntPVar   , casIntPVar@@ -68,7 +74,7 @@   , atomicOrIntPVar   , atomicXorIntPVar   , atomicNotIntPVar-  -- ** Re-exports+  -- * Re-exports   , Prim   , PrimMonad(PrimState)   , RealWorld@@ -85,7 +91,6 @@ import Control.Monad.ST (ST, runST) import Data.Primitive.PVar.Internal import Data.Primitive.PVar.Unsafe-import Data.Primitive (sizeOf, alignment) import Data.Primitive.Types import qualified Foreign.Storable as S import GHC.Exts@@ -93,7 +98,7 @@  -- $pinned -- In theory it is unsafe to mix `S.Storable` and `Prim` operations on the same chunk of--- memory, because some instances can have differnet memory layouts for the same+-- memory, because some instances can have different memory layouts for the same -- type. This is highly uncommon in practice and if you are intermixing the two concepts -- together you probably already know what you are doing. @@ -153,10 +158,11 @@   primitive_ (copyMutableByteArrayToAddr# mbas# 0# addr# (sizeOfPVar# pvar)) {-# INLINE copyPVarToPtr #-} --- | Apply a pure function to the contents of a mutable variable. Returns the old value.+-- | Apply a pure function to the contents of a mutable variable. Returns the artifact of+-- computation. ----- @since 0.1.0-modifyPVar :: (PrimMonad m, Prim a) => PVar m a -> (a -> a) -> m a+-- @since 0.2.0+modifyPVar :: (PrimMonad m, Prim a) => PVar m a -> (a -> (a, b)) -> m b modifyPVar pvar f = modifyPVarM pvar (return . f) {-# INLINE modifyPVar #-} @@ -167,17 +173,54 @@ modifyPVar_ pvar f = modifyPVarM_ pvar (return . f) {-# INLINE modifyPVar_ #-} --- | Apply a monadic action to the contents of a mutable variable. Returns the old value.++-- | Apply a pure function to the contents of a mutable variable. Returns the old value. ----- @since 0.1.0-modifyPVarM :: (PrimMonad m, Prim a) => PVar m a -> (a -> m a) -> m a+-- @since 0.2.0+fetchModifyPVar :: (PrimMonad m, Prim a) => PVar m a -> (a -> a) -> m a+fetchModifyPVar pvar f = fetchModifyPVarM pvar (return . f)+{-# INLINE fetchModifyPVar #-}++-- | Apply a pure function to the contents of a mutable variable. Returns the new value.+--+-- @since 0.2.0+modifyFetchPVar :: (PrimMonad m, Prim a) => PVar m a -> (a -> a) -> m a+modifyFetchPVar pvar f = modifyFetchPVarM pvar (return . f)+{-# INLINE modifyFetchPVar #-}+++-- | Apply a monadic action to the contents of a mutable variable. Returns the artifact of+-- computation.+--+-- @since 0.2.0+modifyPVarM :: (PrimMonad m, Prim a) => PVar m a -> (a -> m (a, b)) -> m b modifyPVarM pvar f = do   a <- readPVar pvar-  a' <- f a-  writePVar pvar a'-  return a+  (a', b) <- f a+  b <$ writePVar pvar a' {-# INLINE modifyPVarM #-} +-- | Apply a monadic action to the contents of a mutable variable. Returns the old value.+--+-- @since 0.2.0+fetchModifyPVarM :: (PrimMonad m, Prim a) => PVar m a -> (a -> m a) -> m a+fetchModifyPVarM pvar f = do+  a <- readPVar pvar+  a <$ (writePVar pvar =<< f a)+{-# INLINE fetchModifyPVarM #-}+++-- | Apply a monadic action to the contents of a mutable variable. Returns the new value.+--+-- @since 0.2.0+modifyFetchPVarM :: (PrimMonad m, Prim a) => PVar m a -> (a -> m a) -> m a+modifyFetchPVarM pvar f = do+  a <- readPVar pvar+  a' <- f a+  a' <$ writePVar pvar a'+{-# INLINE modifyFetchPVarM #-}++ -- | Apply a monadic action to the contents of a mutable variable. -- -- @since 0.1.0@@ -191,9 +234,8 @@ swapPVars :: (PrimMonad m, Prim a) => PVar m a -> PVar m a -> m (a, a) swapPVars pvar1 pvar2 = do   a1 <- readPVar pvar1-  a2 <- modifyPVar pvar2 (const a1)-  writePVar pvar1 a2-  return (a1, a2)+  a2 <- fetchModifyPVar pvar2 (const a1)+  (a1, a2) <$ writePVar pvar1 a2 {-# INLINE swapPVars #-}  -- | Swap contents of two mutable variables.@@ -232,7 +274,7 @@ -- it. Memory allocated with number of bytes specified by @`S.sizeOf` a@ is allocated and -- pinned, therefore it is safe to operate directly with the pointer as well as over -- FFI. Returning the pointer from the supplied action would be very unsafe, therefore--- return the `PVar` if you still need it afterwards, garbage colelctor will cleanup the+-- return the `PVar` if you still need it afterwards, garbage collector will cleanup the -- memory when it is no longer needed. -- -- @since 0.1.0@@ -279,9 +321,34 @@ {-# INLINE atomicWriteIntPVar #-}  --- | Compare and swap. This is a function that is used to implement `modifyIntPVar`. Implies a--- full memory barrier.+-- | Apply a function to an integer element of a `PVar` atomically. Implies a full memory+-- barrier. Returns the new value. --+-- @since 0.2.0+atomicFetchModifyIntPVar ::+     PrimMonad m => PVar m Int -> (Int -> Int) -> m Int+atomicFetchModifyIntPVar pvar f =+  atomicModifyIntPVar pvar $ \a ->+    let a' = f a+     in a' `seq` (a', a)+{-# INLINE atomicFetchModifyIntPVar #-}++-- | Apply a function to an integer element of a `PVar` atomically. Implies a full memory+-- barrier. Returns the new value.+--+-- @since 0.2.0+atomicModifyFetchIntPVar ::+     PrimMonad m => PVar m Int -> (Int -> Int) -> m Int+atomicModifyFetchIntPVar pvar f =+  atomicModifyIntPVar pvar $ \a ->+    let a' = f a+     in a' `seq` (a', a')+{-# INLINE atomicModifyFetchIntPVar #-}+++-- | Compare and swap. This is also a function that is used to implement+-- `atomicModifyIntPVar`. Implies a full memory barrier.+-- -- @since 0.1.0 casIntPVar ::      PrimMonad m@@ -297,7 +364,7 @@   --- | Add two numbers, corresponds to @`+`@ done atomically. Returns the previous value of+-- | Add two numbers, corresponds to @(`+`)@ done atomically. Returns the previous value of -- the mutable variable. Implies a full memory barrier. -- -- @since 0.1.0@@ -308,7 +375,7 @@       (# s'#, p# #) -> (# s'#, I# p# #) {-# INLINE atomicAddIntPVar #-} --- | Subtract two numbers, corresponds to @`subtract`@ done atomically. Returns the+-- | Subtract two numbers, corresponds to @(`-`)@ done atomically. Returns the -- previous value of the mutable variable. Implies a full memory barrier. -- -- @since 0.1.0@@ -320,7 +387,7 @@ {-# INLINE atomicSubIntPVar #-}  --- | Binary conjuction (AND), corresponds to @`and`@ done atomically. Returns the previous+-- | Binary conjuction (AND), corresponds to @(`Data.Bits..&.`)@ done atomically. Returns the previous -- value of the mutable variable. Implies a full memory barrier. -- -- @since 0.1.0@@ -332,8 +399,8 @@ {-# INLINE atomicAndIntPVar #-}  --- | Binary negation of conjuction (Not AND), corresponds to @\\x y -> `complement` (x--- `and` y)@ done atomically. Returns the previous value of the mutable variable. Implies+-- | Binary negation of conjuction (NAND), corresponds to @\\x y -> `Data.Bits.complement` (x+-- `Data.Bits..&.` y)@ done atomically. Returns the previous value of the mutable variable. Implies -- a full memory barrier. -- -- @since 0.1.0@@ -345,7 +412,7 @@ {-# INLINE atomicNandIntPVar #-}  --- | Binary disjunction (OR), corresponds to `or` done atomically. Returns the previous+-- | Binary disjunction (OR), corresponds to @(`Data.Bits..|.`)@ done atomically. Returns the previous -- value of the mutable variable. Implies a full memory barrier. -- -- @since 0.1.0@@ -357,7 +424,7 @@ {-# INLINE atomicOrIntPVar #-}  --- | Binary exclusive disjunction (XOR), corresponds to `xor` done atomically. Returns the+-- | Binary exclusive disjunction (XOR), corresponds to @`Data.Bits.xor`@ done atomically. Returns the -- previous value of the mutable variable. Implies a full memory barrier. -- -- @since 0.1.0@@ -369,7 +436,7 @@ {-# INLINE atomicXorIntPVar #-}  --- | Binary negation (NOT), corresponds to ones' `complement` done atomically. Returns the+-- | Binary negation (NOT), corresponds to ones' @`Data.Bits.complement`@ done atomically. Returns the -- previous value of the mutable variable. Implies a full memory barrier. -- -- @since 0.1.0
src/Data/Primitive/PVar/Internal.hs view
@@ -44,17 +44,22 @@   -- * Re-exports   , isByteArrayPinned#   , isMutableByteArrayPinned#+  , sizeOf+  , alignment   )   where  import Control.DeepSeq import Control.Monad.Primitive (PrimMonad(primitive), PrimState, primitive_,                                 touch, unsafePrimToPrim)-import Data.Primitive (sizeOf, alignment) import Data.Primitive.Types import qualified Foreign.Storable as S import GHC.Exts +#if !MIN_VERSION_primitive(0,6,3)+import Data.Primitive (sizeOf, alignment)+#endif+ -- | Mutable variable with primitive value. -- -- @since 0.1.0@@ -269,7 +274,9 @@ alignmentPVar pvar = I# (alignmentPVar# pvar) {-# INLINE alignmentPVar #-} -+-- | Unwrap the primitive `Int`+--+-- @since 0.1.0 unI# :: Int -> Int# unI# (I# i#) = i# {-# INLINE unI# #-}
tests/Test/Primitive/PVarSpec.hs view
@@ -131,25 +131,43 @@     propPVarIO "newAlignedPinnedPVar" gen $ \a var -> do       pinnedVar <- newAlignedPinnedPVar a       (===) <$> readPVar var <*> readPVar pinnedVar-    propPVarIO "modifyPVar" gen $ \a pvar ->+    propPVarIO "modifyPVar_" gen $ \a pvar ->       return $       forAll arbitrary $ \f -> do-        modifyPVar pvar (apply f) `shouldReturn` a+        modifyPVar_ pvar (apply f)         readPVar pvar `shouldReturn` apply f a-    propPVarIO "modifyPVar_" gen $ \a pvar ->+    propPVarIO "modifyPVar" gen $ \a pvar ->       return $       forAll arbitrary $ \f -> do-        modifyPVar_ pvar (apply f)+        let (a', b :: Int) = apply f a+        modifyPVar pvar (apply f) `shouldReturn` b+        readPVar pvar `shouldReturn` a'+    propPVarIO "fetchModifyPVar" gen $ \a pvar ->+      return $+      forAll arbitrary $ \f -> do+        fetchModifyPVar pvar (apply f) `shouldReturn` a         readPVar pvar `shouldReturn` apply f a-    propPVarIO "modifyPVarM" gen $ \a pvar ->+    propPVarIO "fetchModifyPVarM" gen $ \a pvar ->       return $       forAllIO arbitrary $ \f -> do         a' <--          modifyPVarM pvar $ \a' -> do+          fetchModifyPVarM pvar $ \a' -> do             a' `shouldBe` a             pure $ apply f a'         a' `shouldBe` a         readPVar pvar `shouldReturn` apply f a+    propPVarIO "modifyFetchPVar" gen $ \a pvar ->+      return $+      forAll arbitrary $ \f ->+        modifyFetchPVar pvar (apply f) `shouldReturn` apply f a+    propPVarIO "modifyFetchPVarM" gen $ \a pvar ->+      return $+      forAllIO arbitrary $ \f -> do+        a' <-+          modifyFetchPVarM pvar $ \a' -> do+            a' `shouldBe` a+            pure $ apply f a'+        a' `shouldBe` apply f a     propPVarIO "modifyPVarM_" gen $ \a pvar ->       return $       forAllIO arbitrary $ \f -> do@@ -190,36 +208,59 @@       propPVarIO "copyFromByteArrayPVar" gen $ \_ var ->         return $         forAllIO (genByteArrayNonEmpty gen) $ \(ByteArrayNonEmpty i ba) -> do-            copyFromByteArrayPVar ba i var-            readPVar var `shouldReturn` indexByteArray ba i+          copyFromByteArrayPVar ba i var+          readPVar var `shouldReturn` indexByteArray ba i       propPVarIO "copyFromMutableByteArrayPVar" gen $ \_ var ->         return $         forAllIO (genByteArrayNonEmpty gen) $ \(ByteArrayNonEmpty i ba) -> do-            mba <- unsafeThawByteArray ba-            copyFromMutableByteArrayPVar mba i var-            readPVar var `shouldReturn` indexByteArray ba i+          mba <- unsafeThawByteArray ba+          copyFromMutableByteArrayPVar mba i var+          readPVar var `shouldReturn` indexByteArray ba i       propPVarST "sizeOf" gen $ \_ var -> pure (toPtrPVar var === Nothing)     describe "Reset Memory" $       propPVarIO "zeroPVar" gen $ \_ var -> do         zeroPVar var         readPVar var `shouldReturn` defZero     describe "Pinned Memory" $ do-      it "isByteArrayPinned - Unpinned" $-        forAllIO arbitrary $ \(Positive n) -> do-          mba <- newByteArray n+      let mostThreshold = 3248+          leastThreshold = 3277+      -- Experimentally found the threshold to be 3249:+      --     mostThreshold = 3248+      --     leastThreshold = 3249+      -- Documented to be 3277, but seems to be different in practice.+      -- https://gitlab.haskell.org/ghc/ghc/-/blob/feb852e67e166f752c783978f5fecc3c28c966f9/docs/users_guide/ffi-chap.rst#L1008+      it "Small - Unpinned" $ do+        mba <- newByteArray mostThreshold+        isMutableByteArrayPinned mba `shouldBe` False+        ba <- unsafeFreezeByteArray mba+        isByteArrayPinned ba `shouldBe` False+      it "Large - Pinned" $+        forAllIO arbitrary $ \(NonNegative n) -> do+          let n' = n + leastThreshold+          mba <- newByteArray n'+          isMutableByteArrayPinned mba `shouldBe` True           ba <- unsafeFreezeByteArray mba-          return $ not $ isByteArrayPinned ba+          isByteArrayPinned ba `shouldBe` True+      it "isByteArrayPinned - Unpinned" $+        forAll arbitrary $ \(NonNegative n) ->+          (n <= mostThreshold) ==> monadicIO $+          run $ do+            mba <- newByteArray n+            ba <- unsafeFreezeByteArray mba+            return $ not $ isByteArrayPinned ba       it "isByteArrayPinned - Pinned" $-        forAllIO arbitrary $ \(Positive n) -> do+        forAllIO arbitrary $ \(NonNegative n) -> do           mba <- newPinnedByteArray n           ba <- unsafeFreezeByteArray mba           return $ isByteArrayPinned ba       it "isMutableByteArrayPinned - Unpinned" $-        forAllIO arbitrary $ \(Positive n) -> do-          mba <- newByteArray n-          return $ not $ isMutableByteArrayPinned mba+        forAll arbitrary $ \(NonNegative n) ->+          n <= mostThreshold ==> monadicIO $+          run $ do+            mba <- newByteArray n+            return $ not $ isMutableByteArrayPinned mba       it "isMutableByteArrayPinned - Pinned" $-        forAllIO arbitrary $ \(Positive n) -> do+        forAllIO arbitrary $ \(NonNegative n) -> do           mba <- newPinnedByteArray n           return $ isMutableByteArrayPinned mba     extraSpec gen@@ -375,7 +416,7 @@           yvar <- newPVar x           ys' <-             mapConcurrently-              (\_ -> atomicModifyIntPVar yvar (\y -> (complement y, y)))+              (\_ -> atomicFetchModifyIntPVar yvar complement)               [1..n]           y' <- atomicReadIntPVar yvar           x' `shouldBe` y'@@ -406,12 +447,18 @@           yvar <- newPVar x           ys' <-             mapConcurrently-              (\y' -> atomicModifyIntPVar yvar (\y -> (f y y', y)))+              (\y' -> atomicFetchModifyIntPVar yvar (`f` y'))               xs           y' <- atomicReadIntPVar yvar+          atomicWriteIntPVar yvar x+          ys'' <-+            mapConcurrently+              (\y'' -> atomicModifyFetchIntPVar yvar (`f` y''))+              xs           x' `shouldBe` y'           F.foldl' f x' xs' `shouldBe` F.foldl' f x xs           F.foldl' f y' ys' `shouldBe` F.foldl' f x xs+          F.foldl' f x ys'' `shouldBe` F.foldl' f x xs  spec :: Spec spec = do