on master help sign in
Fix Data.Array.Generic.writeLastN bug (#2567)

Co-authored-by: Harendra Kumar <harendra@composewell.com>

commit313d1786a03cd2f9aae052b2314cc9b3a96daf2d authorRanjeet Ranjan <ranjeetr@composewell.com> dateThu 24 Aug 2023, 15:34 (3 years ago) parentfe5a036
2 files changed+58−26
core/src/Streamly/Internal/Data/Array/Generic.hs+12−1 core/src/Streamly/Internal/Data/Ring/Generic.hs+46−25
core/src/Streamly/Internal/Data/Array/Generic.hs+12−1
@@ -242,6 +242,17 @@ getIndex i arr@Array {..} =
242242 then Just $ getIndexUnsafe i arr
243243 else Nothing
244244
245+-- >>> import qualified Streamly.Data.Stream as Stream
246+-- >>> import qualified Streamly.Data.Fold as Fold
247+-- >>> import qualified Streamly.Internal.Data.Array.Generic as Array
248+-- >>> import Data.Function ((&))
249+-- >>> :{
250+-- Stream.fromList [1,2,3,4,5::Int]
251+-- & Stream.scan (Array.writeLastN 2)
252+-- & Stream.fold Fold.toList
253+-- :}
254+-- [fromList [],fromList [1],fromList [1,2],fromList [2,3],fromList [3,4],fromList [4,5]]
255+--
245256{-# INLINE writeLastN #-}
246257writeLastN :: MonadIO m => Int -> Fold m a (Array a)
247258writeLastN n = FL.rmapM f (RB.writeLastN n)
@@ -249,7 +260,7 @@ writeLastN n = FL.rmapM f (RB.writeLastN n)
249260 where
250261
251262 f rb = do
252− arr <- RB.toMutArray 0 n rb
263+ arr <- RB.copyToMutArray 0 n rb
253264 return $ unsafeFreeze arr
254265
255266{-# INLINE getSliceUnsafe #-}
core/src/Streamly/Internal/Data/Ring/Generic.hs+46−25
@@ -20,6 +20,7 @@ module Streamly.Internal.Data.Ring.Generic
2020
2121 -- * Conversion
2222 , toMutArray
23+ , copyToMutArray
2324 , toStreamWith
2425 ) where
2526
@@ -29,15 +30,11 @@ import Control.Monad.IO.Class (liftIO, MonadIO)
2930import Streamly.Internal.Data.Stream.Type (Stream)
3031import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))
3132import Streamly.Internal.Data.Fold.Type (Fold(..))
32−import Streamly.Internal.Data.MutArray.Generic
33− ( MutArray(..)
34− , new
35− , uninit
36− , putIndexUnsafe
37− , putSliceUnsafe
38− )
33+import Streamly.Internal.Data.MutArray.Generic (MutArray(..))
34+
3935-- import qualified Streamly.Internal.Data.Stream.Type as Stream
4036import qualified Streamly.Internal.Data.Fold.Type as Fold
37+import qualified Streamly.Internal.Data.MutArray.Generic as MutArray
4138
4239-- XXX Use MutableArray rather than keeping a MutArray here.
4340data Ring a = Ring
@@ -58,8 +55,8 @@ data Ring a = Ring
5855{-# INLINE createRing #-}
5956createRing :: MonadIO m => Int -> m (Ring a)
6057createRing count = liftIO $ do
61− arr <- new count
62− arr1 <- uninit arr count
58+ arr <- MutArray.new count
59+ arr1 <- MutArray.uninit arr count
6360 return (Ring
6461 { ringArr = arr1
6562 , ringHead = 0
@@ -67,6 +64,9 @@ createRing count = liftIO $ do
6764 })
6865
6966
67+-- | Note that it is not safe to return a reference to the mutable Ring using a
68+-- scan as the Ring is continuously getting mutated. You could however copy out
69+-- the Ring.
7070{-# INLINE writeLastN #-}
7171writeLastN :: MonadIO m => Int -> Fold m a (Ring a)
7272writeLastN n = Fold step initial extract
@@ -101,7 +101,7 @@ unsafeInsertRingWith :: Ring a -> a -> IO Int
101101unsafeInsertRingWith Ring{..} x = do
102102 assertM(ringMax >= 1)
103103 assertM(ringHead < ringMax)
104− putIndexUnsafe ringHead ringArr x
104+ MutArray.putIndexUnsafe ringHead ringArr x
105105 let rh1 = ringHead + 1
106106 next = if rh1 == ringMax then 0 else rh1
107107 return next
@@ -124,24 +124,45 @@ seek adj rng@Ring{..}
124124
125125-- | @toMutArray rignHeadAdjustment lengthToRead ring@.
126126-- Convert the ring into a boxed mutable array. Note that the returned MutArray
127−-- may share the same underlying memory as the Ring.
127+-- shares the same underlying memory as the Ring, the user of this API needs to
128+-- ensure that the ring is not mutated during and after the conversion.
129+--
128130{-# INLINE toMutArray #-}
129131toMutArray :: MonadIO m => Int -> Int -> Ring a -> m (MutArray a)
130−toMutArray adj n Ring{..} = do
131− let len = min ringMax n
132− let idx = mod (ringHead + adj) ringMax
133− end = idx + len
134− if end <= ringMax
135− then
136− -- putSliceUnsafe ringArr idx arr1 0 len
137− return $ ringArr { arrStart = idx, arrLen = len }
132+toMutArray adj n Ring{..} =
133+ -- XXX for empty Ring it will raise an Exception: divide by zero
134+ if ringMax <= 0
135+ then MutArray.nil
136+ else do
137+ let len = min ringMax n
138+ let idx = mod (ringHead + adj) ringMax
139+ end = idx + len
140+ if end <= ringMax
141+ then
142+ return $ ringArr { arrStart = idx, arrLen = len }
143+ else do
144+ -- XXX Just swap the elements in the existing ring and return the
145+ -- same array without reallocation.
146+ arr <- liftIO $ MutArray.new len
147+ arr1 <- MutArray.uninit arr len
148+ MutArray.putSliceUnsafe ringArr idx arr1 0 (ringMax - idx)
149+ MutArray.putSliceUnsafe ringArr 0 arr1 (ringMax - idx) (end - ringMax)
150+ return arr1
151+
152+-- | Copy out the mutable ring to a mutable Array.
153+{-# INLINE copyToMutArray #-}
154+copyToMutArray :: MonadIO m => Int -> Int -> Ring a -> m (MutArray a)
155+copyToMutArray adj n Ring{..} = do
156+ if ringMax <= 0
157+ then MutArray.nil
138158 else do
139− -- XXX Just swap the elements in the existing ring and return the
140− -- same array without reallocation.
141− arr <- liftIO $ new len
142− arr1 <- uninit arr len
143− putSliceUnsafe ringArr idx arr1 0 (ringMax - idx)
144− putSliceUnsafe ringArr 0 arr1 (ringMax - idx) (end - ringMax)
159+ let len = min ringMax n
160+ let idx = mod (ringHead + adj) ringMax
161+ end = idx + len
162+ arr <- MutArray.new len
163+ arr1 <- MutArray.uninit arr len
164+ MutArray.putSliceUnsafe ringArr idx arr1 0 (ringMax - idx)
165+ MutArray.putSliceUnsafe ringArr 0 arr1 (ringMax - idx) (end - ringMax)
145166 return arr1
146167
147168-- This would be theoretically slower than toMutArray because of a branch