2-- Module : Streamly.Internal.Data.Array.Stream.Foreign
3-- Copyright : (c) 2019 Composewell Technologies
4-- License : BSD3-3-Clause
5-- Maintainer : streamly@composewell.com
6-- Stability : experimental
9-- Combinators to efficiently manipulate streams of immutable arrays.
11module Streamly.Internal.Data.Array.Stream.Foreign
16 -- * Flattening to elements
34#if !defined(mingw32_HOST_OS)
47import Data.Bifunctor (second)
48import Control.Exception (assert)
49import Control.Monad.Catch (MonadThrow, throwM)
50import Control.Monad.IO.Class (MonadIO(..))
51import Data.Word (Word8)
52import Foreign.ForeignPtr (withForeignPtr, touchForeignPtr)
53import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
54import Foreign.Ptr (minusPtr, plusPtr, castPtr)
55import Foreign.Storable (Storable(..))
56import Fusion.Plugin.Types (Fuse(..))
57import GHC.Exts (SpecConstrAnnotation(..))
58import GHC.ForeignPtr (ForeignPtr(..))
59import GHC.Ptr (Ptr(..))
60import GHC.Types (SPEC(..))
61import Prelude hiding (null, last, (!!), read, concat, unlines)
63#if !defined(mingw32_HOST_OS)
64import Streamly.Internal.FileSystem.FDIO (IOVec(..))
66import Streamly.Internal.Data.Array.Foreign.Type (Array(..))
67import Streamly.Internal.Data.Fold.Type (Fold(..))
68import Streamly.Internal.Data.Parser (ParseError(..))
69import Streamly.Internal.Data.Stream.Serial (SerialT)
70import Streamly.Internal.Data.Stream.StreamK.Type (IsStream)
71import Streamly.Internal.Data.SVar (adaptState, defState)
73import qualified Streamly.Internal.Data.Array.Foreign as A
74import qualified Streamly.Internal.Data.Array.Foreign as Array
75import qualified Streamly.Internal.Data.Array.Foreign.Type as A
76import qualified Streamly.Internal.Data.Array.Foreign.Mut.Type as MA
77import qualified Streamly.Internal.Data.Array.Stream.Mut.Foreign as AS
78import qualified Streamly.Internal.Data.Array.Stream.Fold.Foreign as ASF
79import qualified Streamly.Internal.Data.Fold.Type as FL
80import qualified Streamly.Internal.Data.Parser as PR
81import qualified Streamly.Internal.Data.Parser.ParserD as PRD
82import qualified Streamly.Internal.Data.Stream.IsStream as S
83import qualified Streamly.Internal.Data.Stream.StreamD as D
85-------------------------------------------------------------------------------
87-------------------------------------------------------------------------------
89-- | @arraysOf n stream@ groups the elements in the input stream into arrays of
92-- > arraysOf n = Stream.chunksOf n (Array.writeN n)
95{-# INLINE arraysOf #-}
96arraysOf :: (IsStream t, MonadIO m, Storable a)
97 => Int -> t m a -> t m (Array a)
98arraysOf n str = D.fromStreamD $ A.arraysOf n (D.toStreamD str)
100-------------------------------------------------------------------------------
102-------------------------------------------------------------------------------
104-- XXX efficiently compare two streams of arrays. Two streams can have chunks
105-- of different sizes, we can handle that in the stream comparison abstraction.
106-- This could be useful e.g. to fast compare whether two files differ.
108-- | Convert a stream of arrays into a stream of their elements.
110-- Same as the following but more efficient:
112-- > concat = Stream.unfoldMany Array.read
116concat :: (IsStream t, MonadIO m, Storable a) => t m (Array a) -> t m a
117-- concat m = D.fromStreamD $ A.flattenArrays (D.toStreamD m)
118-- concat m = D.fromStreamD $ D.concatMap A.toStreamD (D.toStreamD m)
119concat m = D.fromStreamD $ D.unfoldMany A.read (D.toStreamD m)
121-- | Convert a stream of arrays into a stream of their elements reversing the
122-- contents of each array before flattening.
124-- > concatRev = Stream.unfoldMany Array.readRev
127{-# INLINE concatRev #-}
128concatRev :: (IsStream t, MonadIO m, Storable a) => t m (Array a) -> t m a
129concatRev m = D.fromStreamD $ A.flattenArraysRev (D.toStreamD m)
131-------------------------------------------------------------------------------
132-- Intersperse and append
133-------------------------------------------------------------------------------
135-- | Flatten a stream of arrays after inserting the given element between
139{-# INLINE interpose #-}
140interpose :: (MonadIO m, IsStream t, Storable a) => a -> t m (Array a) -> t m a
141interpose x = S.interpose x A.read
143{-# INLINE intercalateSuffix #-}
144intercalateSuffix :: (MonadIO m, IsStream t, Storable a)
145 => Array a -> t m (Array a) -> t m a
146intercalateSuffix = S.intercalateSuffix A.read
148-- | Flatten a stream of arrays appending the given element after each
152{-# INLINE interposeSuffix #-}
153interposeSuffix :: (MonadIO m, IsStream t, Storable a)
154 => a -> t m (Array a) -> t m a
155-- interposeSuffix x = D.fromStreamD . A.unlines x . D.toStreamD
156interposeSuffix x = S.interposeSuffix x A.read
158data FlattenState s a =
160 | InnerLoop s !(ForeignPtr a) !(Ptr a) !(Ptr a)
162{-# INLINE_NORMAL unlines #-}
163unlines :: forall m a. (MonadIO m, Storable a)
164 => a -> D.Stream m (Array a) -> D.Stream m a
165unlines sep (D.Stream step state) = D.Stream step' (OuterLoop state)
167 {-# INLINE_LATE step' #-}
168 step' gst (OuterLoop st) = do
169 r <- step (adaptState gst) st
171 D.Yield Array{..} s ->
172 let p = unsafeForeignPtrToPtr aStart
173 in D.Skip (InnerLoop s aStart p aEnd)
174 D.Skip s -> D.Skip (OuterLoop s)
177 step' _ (InnerLoop st _ p end) | p == end =
178 return $ D.Yield sep $ OuterLoop st
180 step' _ (InnerLoop st startf p end) = do
183 touchForeignPtr startf
185 return $ D.Yield x (InnerLoop st startf
186 (p `plusPtr` sizeOf (undefined :: a)) end)
188-------------------------------------------------------------------------------
190-------------------------------------------------------------------------------
192{-# INLINE_NORMAL packArraysChunksOf #-}
193packArraysChunksOf :: (MonadIO m, Storable a)
194 => Int -> D.Stream m (Array a) -> D.Stream m (Array a)
195packArraysChunksOf n str =
196 D.map A.unsafeFreeze $ AS.packArraysChunksOf n $ D.map A.unsafeThaw str
198-- XXX instead of writing two different versions of this operation, we should
199-- write it as a pipe.
200{-# INLINE_NORMAL lpackArraysChunksOf #-}
201lpackArraysChunksOf :: (MonadIO m, Storable a)
202 => Int -> Fold m (Array a) () -> Fold m (Array a) ()
203lpackArraysChunksOf n fld =
204 FL.map A.unsafeThaw $ AS.lpackArraysChunksOf n (FL.map A.unsafeFreeze fld)
206#if !defined(mingw32_HOST_OS)
208-- | @groupIOVecsOf maxBytes maxEntries@ groups arrays in the incoming stream
209-- to create a stream of 'IOVec' arrays with a maximum of @maxBytes@ bytes in
210-- each array and a maximum of @maxEntries@ entries in each array.
213{-# INLINE_NORMAL groupIOVecsOf #-}
214groupIOVecsOf :: MonadIO m
215 => Int -> Int -> D.Stream m (Array a) -> D.Stream m (Array IOVec)
216groupIOVecsOf n maxIOVLen str =
218 $ AS.groupIOVecsOf n maxIOVLen
219 $ D.map A.unsafeThaw str
222-- | Coalesce adjacent arrays in incoming stream to form bigger arrays of a
223-- maximum specified size in bytes.
226{-# INLINE compact #-}
227compact :: (MonadIO m, Storable a)
228 => Int -> SerialT m (Array a) -> SerialT m (Array a)
229compact n xs = D.fromStreamD $ packArraysChunksOf n (D.toStreamD xs)
231-------------------------------------------------------------------------------
233-------------------------------------------------------------------------------
239 | Yielding arr (SplitState s arr)
242-- | Split a stream of arrays on a given separator byte, dropping the separator
243-- and coalescing all the arrays between two separators into a single array.
246{-# INLINE_NORMAL _splitOn #-}
250 -> D.Stream m (Array Word8)
251 -> D.Stream m (Array Word8)
252_splitOn byte (D.Stream step state) = D.Stream step' (Initial state)
256 {-# INLINE_LATE step' #-}
257 step' gst (Initial st) = do
261 (arr1, marr2) <- A.breakOn byte arr
262 return $ case marr2 of
263 Nothing -> D.Skip (Buffering s arr1)
264 Just arr2 -> D.Skip (Yielding arr1 (Splitting s arr2))
265 D.Skip s -> return $ D.Skip (Initial s)
266 D.Stop -> return D.Stop
268 step' gst (Buffering st buf) = do
272 (arr1, marr2) <- A.breakOn byte arr
273 buf' <- A.spliceTwo buf arr1
274 return $ case marr2 of
275 Nothing -> D.Skip (Buffering s buf')
276 Just x -> D.Skip (Yielding buf' (Splitting s x))
277 D.Skip s -> return $ D.Skip (Buffering s buf)
279 if A.byteLength buf == 0
281 else D.Skip (Yielding buf Finishing)
283 step' _ (Splitting st buf) = do
284 (arr1, marr2) <- A.breakOn byte buf
285 return $ case marr2 of
286 Nothing -> D.Skip $ Buffering st arr1
287 Just arr2 -> D.Skip $ Yielding arr1 (Splitting st arr2)
289 step' _ (Yielding arr next) = return $ D.Yield arr next
290 step' _ Finishing = return D.Stop
292-- | Split a stream of arrays on a given separator byte, dropping the separator
293-- and coalescing all the arrays between two separators into a single array.
296{-# INLINE splitOn #-}
298 :: (IsStream t, MonadIO m)
303 D.fromStreamD $ D.splitInnerBy (A.breakOn byte) A.spliceTwo $ D.toStreamD s
305{-# INLINE splitOnSuffix #-}
307 :: (IsStream t, MonadIO m)
311-- splitOn byte s = D.fromStreamD $ A.splitOn byte $ D.toStreamD s
312splitOnSuffix byte s =
313 D.fromStreamD $ D.splitInnerBySuffix (A.breakOn byte) A.spliceTwo $ D.toStreamD s
315-------------------------------------------------------------------------------
317-------------------------------------------------------------------------------
319-- When we have to take an array partially, take the last part of the array.
320{-# INLINE takeArrayListRev #-}
321takeArrayListRev :: forall a. Storable a => Int -> [Array a] -> [Array a]
329 let len = Array.length x
331 then x : go (n - len) xs
334 else let !(Array (ForeignPtr _ contents) end) = x
335 sz = sizeOf (undefined :: a)
336 !(Ptr start) = end `plusPtr` negate (n * sz)
337 in [Array (ForeignPtr start contents) end]
339-- When we have to take an array partially, take the last part of the array in
341{-# INLINE splitAtArrayListRev #-}
342splitAtArrayListRev :: forall a. Storable a =>
343 Int -> [Array a] -> ([Array a],[Array a])
344splitAtArrayListRev n ls
346 | otherwise = go n ls
348 go :: Int -> [Array a] -> ([Array a], [Array a])
351 let len = Array.length x
352 (xs', xs'') = go (m - len) xs
357 else let !(Array (ForeignPtr start contents) end) = x
358 sz = sizeOf (undefined :: a)
359 end1 = end `plusPtr` negate (m * sz)
360 arr2 = Array (ForeignPtr start contents) end1
361 !(Ptr addrEnd1) = end1
362 arr1 = Array (ForeignPtr addrEnd1 contents) end
365-------------------------------------------------------------------------------
366-- Fold to a single Array
367-------------------------------------------------------------------------------
369-- XXX Both of these implementations of splicing seem to perform equally well.
370-- We need to perform benchmarks over a range of sizes though.
372-- CAUTION! length must more than equal to lengths of all the arrays in the
374{-# INLINE spliceArraysLenUnsafe #-}
375spliceArraysLenUnsafe :: (MonadIO m, Storable a)
376 => Int -> SerialT m (MA.Array a) -> m (MA.Array a)
377spliceArraysLenUnsafe len buffered = do
378 arr <- liftIO $ MA.newArray len
379 end <- S.foldlM' writeArr (return $ MA.aEnd arr) buffered
380 return $ arr {MA.aEnd = end}
384 writeArr dst (MA.Array as ae _) =
385 liftIO $ withForeignPtr as $ \src -> do
386 let count = ae `minusPtr` src
387 A.memcpy (castPtr dst) (castPtr src) count
388 return $ dst `plusPtr` count
390{-# INLINE _spliceArrays #-}
391_spliceArrays :: (MonadIO m, Storable a)
392 => SerialT m (Array a) -> m (Array a)
394 buffered <- S.foldr S.cons S.nil s
395 len <- S.sum (S.map Array.length buffered)
396 arr <- liftIO $ MA.newArray len
397 end <- S.foldlM' writeArr (return $ MA.aEnd arr) s
398 return $ A.unsafeFreeze $ arr {MA.aEnd = end}
402 writeArr dst (Array as ae) =
403 liftIO $ withForeignPtr as $ \src -> do
404 let count = ae `minusPtr` src
405 A.memcpy (castPtr dst) (castPtr src) count
406 return $ dst `plusPtr` count
408{-# INLINE _spliceArraysBuffered #-}
409_spliceArraysBuffered :: (MonadIO m, Storable a)
410 => SerialT m (Array a) -> m (Array a)
411_spliceArraysBuffered s = do
412 buffered <- S.foldr S.cons S.nil s
413 len <- S.sum (S.map Array.length buffered)
414 A.unsafeFreeze <$> spliceArraysLenUnsafe len (S.map A.unsafeThaw s)
416{-# INLINE spliceArraysRealloced #-}
417spliceArraysRealloced :: forall m a. (MonadIO m, Storable a)
418 => SerialT m (Array a) -> m (Array a)
419spliceArraysRealloced s = do
420 let idst = liftIO $ MA.newArray (A.bytesToElemCount (undefined :: a)
423 arr <- S.foldlM' MA.spliceWithDoubling idst (S.map A.unsafeThaw s)
424 liftIO $ A.unsafeFreeze <$> MA.shrinkToFit arr
426-- XXX This should just be "fold A.write"
428-- | Given a stream of arrays, splice them all together to generate a single
429-- array. The stream must be /finite/.
432{-# INLINE toArray #-}
433toArray :: (MonadIO m, Storable a) => SerialT m (Array a) -> m (Array a)
434toArray = spliceArraysRealloced
435-- spliceArrays = _spliceArraysBuffered
437-- exponentially increasing sizes of the chunks upto the max limit.
438-- XXX this will be easier to implement with parsers/terminating folds
439-- With this we should be able to reduce the number of chunks/allocations.
440-- The reallocation/copy based toArray can also be implemented using this.
443{-# INLINE toArraysInRange #-}
444toArraysInRange :: (IsStream t, MonadIO m, Storable a)
445 => Int -> Int -> Fold m (Array a) b -> Fold m a b
446toArraysInRange low high (Fold step initial extract) =
450-- | Fold the input to a pure buffered stream (List) of arrays.
451{-# INLINE _toArraysOf #-}
452_toArraysOf :: (MonadIO m, Storable a)
453 => Int -> Fold m a (SerialT Identity (Array a))
454_toArraysOf n = FL.chunksOf n (A.writeNF n) FL.toStream
457-------------------------------------------------------------------------------
459-------------------------------------------------------------------------------
461-- GHC parser does not accept {-# ANN type [] NoSpecConstr #-}, so we need
463{-# ANN type List NoSpecConstr #-}
464newtype List a = List {getList :: [a]}
466{-# INLINE_NORMAL parseD #-}
468 forall m a b. (MonadIO m, MonadThrow m, Storable a)
469 => PRD.Parser m (Array a) b
470 -> D.Stream m (Array.Array a)
471 -> m (b, D.Stream m (Array.Array a))
472parseD (PRD.Parser pstep initial extract) stream@(D.Stream step state) = do
475 PRD.IPartial s -> go SPEC state (List []) s
476 PRD.IDone b -> return (b, stream)
477 PRD.IError err -> throwM $ ParseError err
481 -- "backBuf" contains last few items in the stream that we may have to
484 -- XXX currently we are using a dumb list based approach for backtracking
485 -- buffer. This can be replaced by a sliding/ring buffer using Data.Array.
486 -- That will allow us more efficient random back and forth movement.
488 go !_ st backBuf !pst = do
489 r <- step defState st
491 D.Yield x s -> gobuf SPEC [x] s backBuf pst
492 D.Skip s -> go SPEC s backBuf pst
497 gobuf !_ [] s backBuf !pst = go SPEC s backBuf pst
498 gobuf !_ (x:xs) s backBuf !pst = do
502 gobuf SPEC xs s (List []) pst1
503 PR.Partial n pst1 -> do
505 (n <= sum (map Array.length (x:getList backBuf)))
507 let src0 = takeArrayListRev n (x:getList backBuf)
508 src = Prelude.reverse src0 ++ xs
509 gobuf SPEC src s (List []) pst1
510 PR.Continue 0 pst1 ->
511 gobuf SPEC xs s (List (x:getList backBuf)) pst1
512 PR.Continue n pst1 -> do
514 (n <= sum (map Array.length (x:getList backBuf)))
516 let (src0, buf1) = splitAtArrayListRev n (x:getList backBuf)
517 src = Prelude.reverse src0 ++ xs
518 gobuf SPEC src s (List buf1) pst1
520 return (b, D.Stream step s)
523 (n <= sum (map Array.length (x:getList backBuf)))
525 let src0 = takeArrayListRev n (x:getList backBuf)
526 src = Prelude.reverse src0 ++ xs
527 return (b, D.append (D.fromList src) (D.Stream step s))
528 PR.Error err -> throwM $ ParseError err
531-- | Parse an array stream using the supplied 'Parser'. Returns the parse
532-- result and the unconsumed stream. Throws 'ParseError' if the parse fails.
538 (MonadIO m, MonadThrow m, Storable a)
540 -> SerialT m (A.Array a)
541 -> m (b, SerialT m (A.Array a))
542parse p s = fmap D.fromStreamD <$> parseD p (D.toStreamD s)
545-- | Fold an array stream using the supplied array stream 'Fold'.
550fold :: (MonadIO m, MonadThrow m, Storable a) =>
551 ASF.Fold m a b -> SerialT m (A.Array a) -> m b
552fold (ASF.Fold p) s = fst <$> parseD p (D.toStreamD s)
554-- | Like 'fold' but also returns the remaining stream.
559fold_ :: (MonadIO m, MonadThrow m, Storable a) =>
560 ASF.Fold m a b -> SerialT m (A.Array a) -> m (b, SerialT m (A.Array a))
561fold_ (ASF.Fold p) s = second D.fromStreamD <$> parseD p (D.toStreamD s)
563{-# ANN type ParseChunksState Fuse #-}
564data ParseChunksState x inpBuf st pst =
565 ParseChunksInit inpBuf st
566 | ParseChunksInitLeftOver inpBuf
567 | ParseChunksStream st inpBuf !pst
568 | ParseChunksBuf inpBuf st inpBuf !pst
569 | ParseChunksYield x (ParseChunksState x inpBuf st pst)
571{-# INLINE_NORMAL foldManyD #-}
573 :: (MonadThrow m, Storable a)
575 -> D.Stream m (Array a)
577foldManyD (ASF.Fold (PRD.Parser pstep initial extract)) (D.Stream step state) =
578 D.Stream stepOuter (ParseChunksInit [] state)
582 {-# INLINE_LATE stepOuter #-}
583 -- Buffer is empty, get the first element from the stream, initialize the
584 -- fold and then go to stream processing loop.
585 stepOuter gst (ParseChunksInit [] st) = do
586 r <- step (adaptState gst) st
592 return $ D.Skip $ ParseChunksBuf [x] s [] ps
594 let next = ParseChunksInit [x] s
595 in return $ D.Skip $ ParseChunksYield pb next
596 PRD.IError err -> throwM $ ParseError err
597 D.Skip s -> return $ D.Skip $ ParseChunksInit [] s
598 D.Stop -> return D.Stop
600 -- Buffer is not empty, go to buffered processing loop
601 stepOuter _ (ParseChunksInit src st) = do
605 return $ D.Skip $ ParseChunksBuf src st [] ps
607 let next = ParseChunksInit src st
608 in return $ D.Skip $ ParseChunksYield pb next
609 PRD.IError err -> throwM $ ParseError err
611 -- XXX we just discard any leftover input at the end
612 stepOuter _ (ParseChunksInitLeftOver _) = return D.Stop
614 -- Buffer is empty, process elements from the stream
615 stepOuter gst (ParseChunksStream st backBuf pst) = do
616 r <- step (adaptState gst) st
622 return $ D.Skip $ ParseChunksStream s [] pst1
623 PR.Partial n pst1 -> do
625 (n <= sum (map Array.length (x:backBuf)))
627 let src0 = takeArrayListRev n (x:backBuf)
628 src = Prelude.reverse src0
629 return $ D.Skip $ ParseChunksBuf src s [] pst1
630 PR.Continue 0 pst1 ->
631 return $ D.Skip $ ParseChunksStream s (x:backBuf) pst1
632 PR.Continue n pst1 -> do
634 (n <= sum (map Array.length (x:backBuf)))
636 let (src0, buf1) = splitAtArrayListRev n (x:backBuf)
637 src = Prelude.reverse src0
638 return $ D.Skip $ ParseChunksBuf src s buf1 pst1
641 ParseChunksYield b (ParseChunksInit [] s)
644 (n <= sum (map Array.length (x:backBuf)))
646 let src0 = takeArrayListRev n (x:backBuf)
647 let src = Prelude.reverse src0
649 ParseChunksYield b (ParseChunksInit src s)
650 PR.Error err -> throwM $ ParseError err
651 D.Skip s -> return $ D.Skip $ ParseChunksStream s backBuf pst
654 let src = Prelude.reverse backBuf
656 ParseChunksYield b (ParseChunksInitLeftOver src)
658 -- go back to stream processing mode
659 stepOuter _ (ParseChunksBuf [] s buf pst) =
660 return $ D.Skip $ ParseChunksStream s buf pst
662 -- buffered processing loop
663 stepOuter _ (ParseChunksBuf (x:xs) s backBuf pst) = do
667 return $ D.Skip $ ParseChunksBuf xs s [] pst1
668 PR.Partial n pst1 -> do
669 assert (n <= sum (map Array.length (x:backBuf))) (return ())
670 let src0 = takeArrayListRev n (x:backBuf)
671 src = Prelude.reverse src0 ++ xs
672 return $ D.Skip $ ParseChunksBuf src s [] pst1
673 PR.Continue 0 pst1 ->
674 return $ D.Skip $ ParseChunksBuf xs s (x:backBuf) pst1
675 PR.Continue n pst1 -> do
676 assert (n <= sum (map Array.length (x:backBuf))) (return ())
677 let (src0, buf1) = splitAtArrayListRev n (x:backBuf)
678 src = Prelude.reverse src0 ++ xs
679 return $ D.Skip $ ParseChunksBuf src s buf1 pst1
681 return $ D.Skip $ ParseChunksYield b (ParseChunksInit xs s)
683 assert (n <= sum (map Array.length (x:backBuf))) (return ())
684 let src0 = takeArrayListRev n (x:backBuf)
685 src = Prelude.reverse src0 ++ xs
686 return $ D.Skip $ ParseChunksYield b (ParseChunksInit src s)
687 PR.Error err -> throwM $ ParseError err
689 stepOuter _ (ParseChunksYield a next) = return $ D.Yield a next
691-- | Apply an array stream 'Fold' repeatedly on an array stream and emit the
692-- fold outputs in the output stream.
694-- See "Streamly.Prelude.foldMany" for more details.
697{-# INLINE foldMany #-}
699 :: (IsStream t, MonadThrow m, Storable a)
703foldMany p m = D.fromStreamD $ foldManyD p (D.toStreamD m)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list