on 364af08 help sign in
1090 lines 39 kB history raw
1{-# LANGUAGE CPP #-}
2{-# LANGUAGE BangPatterns #-}
3{-# LANGUAGE ExistentialQuantification #-}
4{-# LANGUAGE MagicHash #-}
5{-# LANGUAGE RecordWildCards #-}
6{-# LANGUAGE ScopedTypeVariables #-}
7{-# LANGUAGE TypeFamilies #-}
8{-# LANGUAGE UnboxedTuples #-}
9{-# LANGUAGE FlexibleContexts #-}
10#include "inline.hs"
12-- |
13-- Module : Streamly.Mem.Array.Types
14-- Copyright : (c) 2019 Composewell Technologies
15--
16-- License : BSD3
17-- Maintainer : harendra.kumar@gmail.com
18-- Stability : experimental
19-- Portability : GHC
20--
21module Streamly.Mem.Array.Types
22 (
23 Array (..)
25 -- * Construction
26 , withNewArray
27 , newArray
28 , unsafeSnoc
29 , snoc
30 , spliceWithDoubling
32 , fromList
33 , fromListN
34 , fromStreamDN
35 -- , fromStreamD
37 -- * Streams of arrays
38 , fromStreamDArraysOf
39 , flattenArrays
40 , flattenArraysRev
41 , packArraysChunksOf
42 , groupIOVecsOf
43 , splitOn
45 -- * Elimination
46 , unsafeIndexIO
47 , unsafeIndex
48 , length
49 , byteLength
50 , byteCapacity
51 , foldl'
52 , foldr
53 , splitAt
55 , toStreamD
56 , toStreamDRev
57 , toStreamK
58 , toStreamKRev
59 , toList
60 , toArrayN
61 , read
63 -- * Utilities
64 , defaultChunkSize
65 , mkChunkSize
66 , mkChunkSizeKB
67 , unsafeInlineIO
68 , realloc
69 , shrinkToFit
70 , memcpy
71 , memcmp
73 , unlines
74 )
75where
77import Control.Exception (assert)
78import Control.DeepSeq (NFData(..))
79import Control.Monad (when)
80import Control.Monad.IO.Class (MonadIO(..))
81import Data.Functor.Identity (runIdentity)
82import Data.Word (Word8)
83import Foreign.C.String (CString)
84import Foreign.C.Types (CSize(..), CInt(..))
85import Foreign.ForeignPtr
86 (ForeignPtr, withForeignPtr, touchForeignPtr, plusForeignPtr)
87import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
88import Foreign.Ptr (plusPtr, minusPtr, castPtr, nullPtr)
89import Foreign.Storable (Storable(..))
90import Prelude hiding (length, foldr, read, unlines, splitAt)
91import Text.Read (readPrec, readListPrec, readListPrecDefault)
93import GHC.Base (Addr#, realWorld#)
94import GHC.Exts (IsList, IsString(..))
95import GHC.ForeignPtr (mallocPlainForeignPtrAlignedBytes, newForeignPtr_)
96import GHC.IO (IO(IO), unsafePerformIO)
97import GHC.Ptr (Ptr(..))
99import Streamly.Fold.Types (Fold(..))
100import Streamly.SVar (adaptState)
101import Streamly.FileSystem.FDIO (IOVec(..))
103import qualified Streamly.Streams.StreamD.Type as D
104import qualified Streamly.Streams.StreamK as K
105import qualified GHC.Exts as Exts
107#ifdef DEVBUILD
108import qualified Data.Foldable as F
109#endif
111-------------------------------------------------------------------------------
112-- Design Notes
113-------------------------------------------------------------------------------
115-- There are two goals that we want to fulfill with arrays. One, holding large
116-- amounts of data in non-GC memory and the ability to pass this data to and
117-- from the operating system without an extra copy overhead. Two, allow random
118-- access to elements based on their indices. The first one falls in the
119-- category of storage buffers while the second one falls in the category of
120-- maps/multisets/hashmaps.
121--
122-- For the first requirement we use an array of Storables and store it in a
123-- ForeignPtr. We can have both immutable and mutable variants of this array
124-- using wrappers over the same underlying type.
125--
126-- For the second requirement, we need a separate type for arrays of
127-- polymorphic values, for example vectors of handler functions, lookup tables.
128-- We can call this type a "vector" in contrast to arrays. It should not
129-- require Storable instance for the type. In that case we need to use an
130-- Array# instead of a ForeignPtr. This type of array would not reduce the GC
131-- overhead much because each element of the array still needs to be scanned by
132-- the GC. However, it can store polymorphic elements and allow random access
133-- to those. But in most cases random access means storage, and it means we
134-- need to avoid GC scanning except in cases of trivially small storage. One
135-- way to achieve that would be to put the array in a Compact region. However,
136-- if and when we mutate this, we will have to use a manual GC copying out to
137-- another CR and freeing the old one.
139-------------------------------------------------------------------------------
140-- Array Data Type
141-------------------------------------------------------------------------------
143-- We require that an array stores only Storable. Arrays are used for buffering
144-- data while streams are used for processing. If you want something to be
145-- buffered it better be Storable so that we can store it in non-GC memory.
146--
147-- We can use a "Storable" constraint in the Array type and the Constraint can
148-- be automatically provided to a function that pattern matches on the Array
149-- type. However, it has huge performance cost, so we do not use it.
150-- XXX Can this cost be alleviated in GHC-8.10 specialization fix?
151--
152-- XXX Another way to not require the Storable constraint in array operations
153-- is to store the elemSize in the array at construction and use that instead
154-- of using sizeOf. Need to charaterize perf cost of this.
155--
156-- XXX rename the fields to "start, next, end".
157--
158data Array a =
159#ifdef DEVBUILD
160 Storable a =>
161#endif
162 Array
163 { aStart :: {-# UNPACK #-} !(ForeignPtr a) -- first address
164 , aEnd :: {-# UNPACK #-} !(Ptr a) -- first unused address
165 , aBound :: {-# UNPACK #-} !(Ptr a) -- first address beyond allocated memory
166 }
168-------------------------------------------------------------------------------
169-- Utility functions
170-------------------------------------------------------------------------------
172foreign import ccall unsafe "string.h memcpy" c_memcpy
173 :: Ptr Word8 -> Ptr Word8 -> CSize -> IO (Ptr Word8)
175foreign import ccall unsafe "string.h strlen" c_strlen
176 :: CString -> IO CSize
178foreign import ccall unsafe "string.h memchr" c_memchr
179 :: Ptr Word8 -> Word8 -> CSize -> IO (Ptr Word8)
181-- XXX we are converting Int to CSize
182memcpy :: Ptr Word8 -> Ptr Word8 -> Int -> IO ()
183memcpy dst src len = c_memcpy dst src (fromIntegral len) >> return ()
185foreign import ccall unsafe "string.h memcmp" c_memcmp
186 :: Ptr Word8 -> Ptr Word8 -> CSize -> IO CInt
188-- XXX we are converting Int to CSize
189-- return True if the memory locations have identical contents
190{-# INLINE memcmp #-}
191memcmp :: Ptr Word8 -> Ptr Word8 -> Int -> IO Bool
192memcmp p1 p2 len = do
193 r <- c_memcmp p1 p2 (fromIntegral len)
194 return $ r == 0
196{-# INLINE unsafeInlineIO #-}
197unsafeInlineIO :: IO a -> a
198unsafeInlineIO (IO m) = case m realWorld# of (# _, r #) -> r
200-------------------------------------------------------------------------------
201-- Construction
202-------------------------------------------------------------------------------
204-- | Allocate an array that can hold 'count' items. The memory of the array is
205-- uninitialized.
206--
207-- Note that this is internal routine, the reference to this array cannot be
208-- given out until the array has been written to and frozen.
209{-# INLINE newArray #-}
210newArray :: forall a. Storable a => Int -> IO (Array a)
211newArray count = do
212 let size = count * sizeOf (undefined :: a)
213 fptr <- mallocPlainForeignPtrAlignedBytes size (alignment (undefined :: a))
214 let p = unsafeForeignPtrToPtr fptr
215 return $ Array
216 { aStart = fptr
217 , aEnd = p
218 , aBound = p `plusPtr` size
219 }
221-- | Allocate an Array of the given size and run an IO action passing the array
222-- start pointer.
223{-# INLINE withNewArray #-}
224withNewArray :: forall a. Storable a => Int -> (Ptr a -> IO ()) -> IO (Array a)
225withNewArray count f = do
226 arr <- newArray count
227 withForeignPtr (aStart arr) $ \p -> f p >> return arr
229-- XXX grow the array when we are beyond bound.
230--
231-- Internal routine for when the array is being created. Appends one item at
232-- the end of the array. Useful when sequentially writing a stream to the
233-- array. DOES NOT CHECK THE ARRAY BOUNDS.
234{-# INLINE unsafeSnoc #-}
235unsafeSnoc :: forall a. Storable a => Array a -> a -> IO (Array a)
236unsafeSnoc arr@Array{..} x = do
237 when (aEnd == aBound) $
238 error "BUG: unsafeSnoc: writing beyond array bounds"
239 poke aEnd x
240 touchForeignPtr aStart
241 return $ arr {aEnd = aEnd `plusPtr` (sizeOf (undefined :: a))}
243{-# INLINE snoc #-}
244snoc :: forall a. Storable a => Array a -> a -> IO (Array a)
245snoc arr@Array {..} x = do
246 if (aEnd == aBound)
247 then do
248 let oldStart = unsafeForeignPtrToPtr aStart
249 size = aEnd `minusPtr` oldStart
250 newSize = (size + (sizeOf (undefined :: a)))
251 newPtr <- mallocPlainForeignPtrAlignedBytes newSize (alignment (undefined :: a))
252 withForeignPtr newPtr $ \pNew -> do
253 memcpy (castPtr pNew) (castPtr oldStart) size
254 poke (pNew `plusPtr` size) x
255 touchForeignPtr aStart
256 return $ Array
257 { aStart = newPtr
258 , aEnd = pNew `plusPtr` (size + sizeOf (undefined :: a))
259 , aBound = pNew `plusPtr` newSize
260 }
261 else do
262 poke aEnd x
263 touchForeignPtr aStart
264 return $ arr {aEnd = aEnd `plusPtr` (sizeOf (undefined :: a))}
266-- | Reallocate the array to the specified size in bytes. If the size is less
267-- than the original array the array gets truncated.
268{-# NOINLINE realloc #-}
269realloc :: forall a. Storable a => Int -> Array a -> IO (Array a)
270realloc newSize Array{..} = do
271 assert (aEnd <= aBound) (return ())
272 let oldStart = unsafeForeignPtrToPtr aStart
273 let size = aEnd `minusPtr` oldStart
274 newPtr <- mallocPlainForeignPtrAlignedBytes
275 newSize (alignment (undefined :: a))
276 withForeignPtr newPtr $ \pNew -> do
277 memcpy (castPtr pNew) (castPtr oldStart) size
278 touchForeignPtr aStart
279 return $ Array
280 { aStart = newPtr
281 , aEnd = pNew `plusPtr` size
282 , aBound = pNew `plusPtr` newSize
283 }
285-- | Remove the free space from an Array.
286shrinkToFit :: forall a. Storable a => Array a -> IO (Array a)
287shrinkToFit arr@Array{..} = do
288 assert (aEnd <= aBound) (return ())
289 if aEnd /= aBound
290 then do
291 let oldStart = unsafeForeignPtrToPtr aStart
292 let size = aEnd `minusPtr` oldStart
293 realloc size arr
294 else return arr
296-- XXX when converting an array of Word8 from a literal string we can simply
297-- refer to the literal string. Is it possible to write rules such that
298-- fromList Word8 can be rewritten so that GHC does not first convert the
299-- literal to [Char] and then we convert it back to an Array Word8?
300--
301-- Note that the address must be a read-only address (meant to be used for
302-- read-only string literals) because we are sharing it, any modification to
303-- the original address would change our array. That's why this function is
304-- unsafe.
305{-# INLINE _fromCStringAddrUnsafe #-}
306_fromCStringAddrUnsafe :: Addr# -> IO (Array Word8)
307_fromCStringAddrUnsafe addr# = do
308 ptr <- newForeignPtr_ (castPtr cstr)
309 len <- c_strlen cstr
310 let n = fromIntegral len
311 let p = unsafeForeignPtrToPtr ptr
312 let end = p `plusPtr` n
313 return $ Array
314 { aStart = ptr
315 , aEnd = end
316 , aBound = end
317 }
318 where
319 cstr :: CString
320 cstr = Ptr addr#
322-------------------------------------------------------------------------------
323-- Elimination
324-------------------------------------------------------------------------------
326-- | Return element at the specified index without checking the bounds.
327--
328-- Unsafe because it does not check the bounds of the array.
329{-# INLINE_NORMAL unsafeIndexIO #-}
330unsafeIndexIO :: forall a. Storable a => Array a -> Int -> IO a
331unsafeIndexIO Array {..} i =
332 withForeignPtr aStart $ \p -> do
333 let elemSize = sizeOf (undefined :: a)
334 elemOff = p `plusPtr` (elemSize * i)
335 assert (i >= 0 && elemOff `plusPtr` elemSize <= aEnd)
336 (return ())
337 peek elemOff
339-- | Return element at the specified index without checking the bounds.
340{-# INLINE_NORMAL unsafeIndex #-}
341unsafeIndex :: forall a. Storable a => Array a -> Int -> a
342unsafeIndex arr i = let !r = unsafeInlineIO $ unsafeIndexIO arr i in r
344-- | /O(1)/ Get the byte length of the array.
345--
346-- @since 0.7.0
347{-# INLINE byteLength #-}
348byteLength :: Array a -> Int
349byteLength Array{..} =
350 let p = unsafeForeignPtrToPtr aStart
351 len = aEnd `minusPtr` p
352 in assert (len >= 0) len
354-- | /O(1)/ Get the length of the array i.e. the number of elements in the
355-- array.
356--
357-- @since 0.7.0
358{-# INLINE length #-}
359length :: forall a. Storable a => Array a -> Int
360length arr = byteLength arr `div` sizeOf (undefined :: a)
362{-# INLINE byteCapacity #-}
363byteCapacity :: Array a -> Int
364byteCapacity Array{..} =
365 let p = unsafeForeignPtrToPtr aStart
366 len = aBound `minusPtr` p
367 in assert (len >= 0) len
369{-# INLINE_NORMAL toStreamD #-}
370toStreamD :: forall m a. (Monad m, Storable a) => Array a -> D.Stream m a
371toStreamD Array{..} =
372 let p = unsafeForeignPtrToPtr aStart
373 in D.Stream step p
375 where
377 {-# INLINE_LATE step #-}
378 step _ p | p == aEnd = return D.Stop
379 step _ p = do
380 -- unsafeInlineIO allows us to run this in Identity monad for pure
381 -- toList/foldr case which makes them much faster due to not
382 -- accumulating the list and fusing better with the pure consumers.
383 --
384 -- This should be safe as the array contents are guaranteed to be
385 -- evaluated/written to before we peek at them.
386 let !x = unsafeInlineIO $ do
387 r <- peek p
388 touchForeignPtr aStart
389 return r
390 return $ D.Yield x (p `plusPtr` (sizeOf (undefined :: a)))
392{-# INLINE toStreamK #-}
393toStreamK :: forall t m a. (K.IsStream t, Storable a) => Array a -> t m a
394toStreamK Array{..} =
395 let p = unsafeForeignPtrToPtr aStart
396 in go p
398 where
400 go p | p == aEnd = K.nil
401 | otherwise =
402 -- See Note in toStreamD.
403 let !x = unsafeInlineIO $ do
404 r <- peek p
405 touchForeignPtr aStart
406 return r
407 in x `K.cons` go (p `plusPtr` (sizeOf (undefined :: a)))
409{-# INLINE_NORMAL toStreamDRev #-}
410toStreamDRev :: forall m a. (Monad m, Storable a) => Array a -> D.Stream m a
411toStreamDRev Array{..} =
412 let p = aEnd `plusPtr` negate (sizeOf (undefined :: a))
413 in D.Stream step p
415 where
417 {-# INLINE_LATE step #-}
418 step _ p | p < unsafeForeignPtrToPtr aStart = return D.Stop
419 step _ p = do
420 -- See comments in toStreamD for why we use unsafeInlineIO
421 let !x = unsafeInlineIO $ do
422 r <- peek p
423 touchForeignPtr aStart
424 return r
425 return $ D.Yield x (p `plusPtr` negate (sizeOf (undefined :: a)))
427{-# INLINE toStreamKRev #-}
428toStreamKRev :: forall t m a. (K.IsStream t, Storable a) => Array a -> t m a
429toStreamKRev Array {..} =
430 let p = aEnd `plusPtr` negate (sizeOf (undefined :: a))
431 in go p
433 where
435 go p | p < unsafeForeignPtrToPtr aStart = K.nil
436 | otherwise =
437 let !x = unsafeInlineIO $ do
438 r <- peek p
439 touchForeignPtr aStart
440 return r
441 in x `K.cons` go (p `plusPtr` negate (sizeOf (undefined :: a)))
443{-# INLINE_NORMAL foldl' #-}
444foldl' :: forall a b. Storable a => (b -> a -> b) -> b -> Array a -> b
445foldl' f z arr = runIdentity $ D.foldl' f z $ toStreamD arr
447{-# INLINE_NORMAL foldr #-}
448foldr :: Storable a => (a -> b -> b) -> b -> Array a -> b
449foldr f z arr = runIdentity $ D.foldr f z $ toStreamD arr
451-------------------------------------------------------------------------------
452-- Instances
453-------------------------------------------------------------------------------
455-- | @toArrayN n@ folds a maximum of @n@ elements from the input stream to an
456-- 'Array'.
457--
458-- @since 0.7.0
459{-# INLINE_NORMAL toArrayN #-}
460toArrayN :: forall m a. (MonadIO m, Storable a) => Int -> Fold m a (Array a)
461toArrayN n = Fold step initial extract
463 where
465 initial = do
466 if n < 0 then error "toArrayN: negative count specified" else return ()
467 liftIO $ newArray n
468 step arr@(Array _ end bound) _ | end == bound = return arr
469 step (Array start end bound) x = do
470 liftIO $ poke end x
471 return $ Array start (end `plusPtr` sizeOf (undefined :: a)) bound
472 extract = liftIO . shrinkToFit
474{-# INLINE_NORMAL fromStreamDN #-}
475fromStreamDN :: forall m a. (MonadIO m, Storable a)
476 => Int -> D.Stream m a -> m (Array a)
477fromStreamDN limit str = do
478 arr <- liftIO $ newArray limit
479 end <- D.foldlM' write (aEnd arr) $ D.take limit str
480 return $ arr {aEnd = end}
482 where
484 write ptr x = do
485 liftIO $ poke ptr x
486 return $ ptr `plusPtr` sizeOf (undefined :: a)
488data GroupState s start end bound
489 = GroupStart s
490 | GroupBuffer s start end bound
491 | GroupYield start end bound (GroupState s start end bound)
492 | GroupFinish
494-- | @fromStreamArraysOf n stream@ groups the input stream into a stream of
495-- arrays of size n.
496{-# INLINE_NORMAL fromStreamDArraysOf #-}
497fromStreamDArraysOf :: (MonadIO m, Storable a)
498 => Int -> D.Stream m a -> D.Stream m (Array a)
499-- fromStreamDArraysOf n str = D.groupsOf n (toArrayN n) str
500fromStreamDArraysOf n (D.Stream step state) =
501 D.Stream step' (GroupStart state)
503 where
505 {-# INLINE_LATE step' #-}
506 step' _ (GroupStart st) = do
507 when (n <= 0) $
508 -- XXX we can pass the module string from the higher level API
509 error $ "Streamly.Mem.Array.Types.fromStreamDArraysOf: the size of "
510 ++ "arrays [" ++ show n ++ "] must be a natural number"
511 Array start end bound <- liftIO $ newArray n
512 return $ D.Skip (GroupBuffer st start end bound)
514 step' gst (GroupBuffer st start end bound) = do
515 r <- step (adaptState gst) st
516 case r of
517 D.Yield x s -> do
518 liftIO $ poke end x
519 let end' = end `plusPtr` 1
520 return $
521 if end' >= bound
522 then D.Skip (GroupYield start end' bound (GroupStart s))
523 else D.Skip (GroupBuffer s start end' bound)
524 D.Skip s -> return $ D.Skip (GroupBuffer s start end bound)
525 D.Stop -> return $ D.Skip (GroupYield start end bound GroupFinish)
527 step' _ (GroupYield start end bound next) =
528 return $ D.Yield (Array start end bound) next
530 step' _ GroupFinish = return D.Stop
532-- XXX concatMap does not seem to have the best possible performance so we have
533-- a custom way to concat arrays.
534data FlattenState s a =
535 OuterLoop s
536 | InnerLoop s (ForeignPtr a) (Ptr a) (Ptr a)
538{-# INLINE_NORMAL flattenArrays #-}
539flattenArrays :: forall m a. (MonadIO m, Storable a)
540 => D.Stream m (Array a) -> D.Stream m a
541flattenArrays (D.Stream step state) = D.Stream step' (OuterLoop state)
543 where
545 {-# INLINE_LATE step' #-}
546 step' gst (OuterLoop st) = do
547 r <- step (adaptState gst) st
548 return $ case r of
549 D.Yield Array{..} s ->
550 let p = unsafeForeignPtrToPtr aStart
551 in D.Skip (InnerLoop s aStart p aEnd)
552 D.Skip s -> D.Skip (OuterLoop s)
553 D.Stop -> D.Stop
555 step' _ (InnerLoop st _ p end) | p == end =
556 return $ D.Skip $ OuterLoop st
558 step' _ (InnerLoop st startf p end) = do
559 x <- liftIO $ do
560 r <- peek p
561 touchForeignPtr startf
562 return r
563 return $ D.Yield x (InnerLoop st startf
564 (p `plusPtr` (sizeOf (undefined :: a))) end)
566{-# INLINE_NORMAL flattenArraysRev #-}
567flattenArraysRev :: forall m a. (MonadIO m, Storable a)
568 => D.Stream m (Array a) -> D.Stream m a
569flattenArraysRev (D.Stream step state) = D.Stream step' (OuterLoop state)
571 where
573 {-# INLINE_LATE step' #-}
574 step' gst (OuterLoop st) = do
575 r <- step (adaptState gst) st
576 return $ case r of
577 D.Yield Array{..} s ->
578 let p = aEnd `plusPtr` negate (sizeOf (undefined :: a))
579 -- XXX we do not need aEnd
580 in D.Skip (InnerLoop s aStart p aEnd)
581 D.Skip s -> D.Skip (OuterLoop s)
582 D.Stop -> D.Stop
584 step' _ (InnerLoop st start p _) | p < unsafeForeignPtrToPtr start =
585 return $ D.Skip $ OuterLoop st
587 step' _ (InnerLoop st startf p end) = do
588 x <- liftIO $ do
589 r <- peek p
590 touchForeignPtr startf
591 return r
592 return $ D.Yield x (InnerLoop st startf
593 (p `plusPtr` negate (sizeOf (undefined :: a))) end)
595-- CAUTION: a very large number (millions) of arrays can degrade performance
596-- due to GC overhead because we need to buffer the arrays before we flatten
597-- all the arrays.
598--
599-- We could take the approach of doubling the memory allocation on each
600-- overflow. This would result in more or less the same amount of copying as in
601-- the chunking approach. However, if we have to shrink in the end then it may
602-- result in an extra copy of the entire data.
603--
604{-# INLINE fromStreamD #-}
605fromStreamD :: (MonadIO m, Storable a) => D.Stream m a -> m (Array a)
606fromStreamD m = do
607 let s = fromStreamDArraysOf defaultChunkSize m
608 buffered <- D.foldr K.cons K.nil s
609 len <- K.foldl' (+) 0 (K.map length buffered)
610 fromStreamDN len $ flattenArrays $ D.fromStreamK buffered
612-- | Convert an 'Array' into a list.
613--
614-- @since 0.7.0
615{-# INLINABLE toList #-}
616toList :: Storable a => Array a -> [a]
617toList = foldr (:) []
619instance (Show a, Storable a) => Show (Array a) where
620 {-# INLINE showsPrec #-}
621 showsPrec _ = shows . toList
623-- | Create an 'Array' from the first N elements of a list. The array is
624-- allocated to size N, if the list terminates before N elements then the
625-- array may hold less than N elements.
626--
627-- @since 0.7.0
628{-# INLINABLE fromListN #-}
629fromListN :: Storable a => Int -> [a] -> Array a
630fromListN n xs = unsafePerformIO $ fromStreamDN n $ D.fromList xs
632-- | Create an 'Array' from a list. The list must be of finite size.
633--
634-- @since 0.7.0
635{-# INLINABLE fromList #-}
636fromList :: Storable a => [a] -> Array a
637fromList xs = unsafePerformIO $ fromStreamD $ D.fromList xs
639instance (Storable a, Read a, Show a) => Read (Array a) where
640 {-# INLINE readPrec #-}
641 readPrec = do
642 xs <- readPrec
643 return (fromList xs)
644 readListPrec = readListPrecDefault
646instance (a ~ Char) => IsString (Array a) where
647 {-# INLINE fromString #-}
648 fromString = fromList
650-- GHC versions 8.0 and below cannot derive IsList
651instance Storable a => IsList (Array a) where
652 type (Item (Array a)) = a
653 {-# INLINE fromList #-}
654 fromList = fromList
655 {-# INLINE fromListN #-}
656 fromListN = fromListN
657 {-# INLINE toList #-}
658 toList = toList
660{-# INLINE arrcmp #-}
661arrcmp :: Array a -> Array a -> Bool
662arrcmp arr1 arr2 =
663 let !res = unsafeInlineIO $ do
664 let ptr1 = unsafeForeignPtrToPtr $ aStart arr1
665 let ptr2 = unsafeForeignPtrToPtr $ aStart arr2
666 let len1 = aEnd arr1 `minusPtr` ptr1
667 let len2 = aEnd arr2 `minusPtr` ptr2
669 if len1 == len2
670 then do
671 r <- memcmp (castPtr ptr1) (castPtr ptr2) len1
672 touchForeignPtr $ aStart arr1
673 touchForeignPtr $ aStart arr2
674 return r
675 else return False
676 in res
678-- XXX we are assuming that Storable equality means element equality. This may
679-- or may not be correct? arrcmp is 40% faster compared to stream equality.
680instance (Storable a, Eq a) => Eq (Array a) where
681 {-# INLINE (==) #-}
682 (==) = arrcmp
683 -- arr1 == arr2 = runIdentity $ D.eqBy (==) (toStreamD arr1) (toStreamD arr2)
685instance (Storable a, NFData a) => NFData (Array a) where
686 {-# INLINE rnf #-}
687 rnf = foldl' (\_ x -> rnf x) ()
689instance (Storable a, Ord a) => Ord (Array a) where
690 {-# INLINE compare #-}
691 compare arr1 arr2 = unsafePerformIO $
692 D.cmpBy compare (toStreamD arr1) (toStreamD arr2)
694 -- Default definitions defined in base do not have an INLINE on them, so we
695 -- replicate them here with an INLINE.
696 {-# INLINE (<) #-}
697 x < y = case compare x y of { LT -> True; _ -> False }
699 {-# INLINE (<=) #-}
700 x <= y = case compare x y of { GT -> False; _ -> True }
702 {-# INLINE (>) #-}
703 x > y = case compare x y of { GT -> True; _ -> False }
705 {-# INLINE (>=) #-}
706 x >= y = case compare x y of { LT -> False; _ -> True }
708 -- These two default methods use '<=' rather than 'compare'
709 -- because the latter is often more expensive
710 {-# INLINE max #-}
711 max x y = if x <= y then y else x
713 {-# INLINE min #-}
714 min x y = if x <= y then x else y
716#ifdef DEVBUILD
717-- Definitions using the Storable constraint from the Array type. These are to
718-- make the Foldable instance possible though it is much slower (7x slower).
719--
720{-# INLINE_NORMAL toStreamD_ #-}
721toStreamD_ :: forall m a. MonadIO m => Int -> Array a -> D.Stream m a
722toStreamD_ size Array{..} =
723 let p = unsafeForeignPtrToPtr aStart
724 in D.Stream step p
726 where
728 {-# INLINE_LATE step #-}
729 step _ p | p == aEnd = return D.Stop
730 step _ p = do
731 x <- liftIO $ do
732 r <- peek p
733 touchForeignPtr aStart
734 return r
735 return $ D.Yield x (p `plusPtr` size)
737{-# INLINE_NORMAL _foldr #-}
738_foldr :: forall a b. (a -> b -> b) -> b -> Array a -> b
739_foldr f z arr@Array {..} =
740 let !n = sizeOf (undefined :: a)
741 in unsafePerformIO $ D.foldr f z $ toStreamD_ n arr
743-- | Note that the 'Foldable' instance is 7x slower than the direct
744-- operations.
745instance Foldable Array where
746 foldr = _foldr
747#endif
749-- | Convert an 'Array' into a stream.
750--
751-- @since 0.7.0
752{-# INLINE_EARLY read #-}
753read :: (Monad m, K.IsStream t, Storable a) => Array a -> t m a
754read = D.fromStreamD . toStreamD
755-- XXX add fallback to StreamK rule
756-- {-# RULES "Streamly.Array.read fallback to StreamK" [1]
757-- forall a. S.readK (read a) = K.fromArray a #-}
759-------------------------------------------------------------------------------
760-- Semigroup and Monoid
761-------------------------------------------------------------------------------
763-------------------------------------------------------------------------------
764-- IO
765-------------------------------------------------------------------------------
767-- | GHC memory management allocation header overhead
768allocOverhead :: Int
769allocOverhead = 2 * sizeOf (undefined :: Int)
771mkChunkSize :: Int -> Int
772mkChunkSize n = let size = n - allocOverhead in max size 0
774mkChunkSizeKB :: Int -> Int
775mkChunkSizeKB n = mkChunkSize (n * k)
776 where k = 1024
778-- | Default maximum buffer size in bytes, for reading from and writing to IO
779-- devices, the value is 32KB minus GHC allocation overhead, which is a few
780-- bytes, so that the actual allocation is 32KB.
781defaultChunkSize :: Int
782defaultChunkSize = mkChunkSizeKB 32
784{-# INLINE_NORMAL unlines #-}
785unlines :: forall m a. (MonadIO m, Storable a)
786 => a -> D.Stream m (Array a) -> D.Stream m a
787unlines sep (D.Stream step state) = D.Stream step' (OuterLoop state)
788 where
789 {-# INLINE_LATE step' #-}
790 step' gst (OuterLoop st) = do
791 r <- step (adaptState gst) st
792 return $ case r of
793 D.Yield Array{..} s ->
794 let p = unsafeForeignPtrToPtr aStart
795 in D.Skip (InnerLoop s aStart p aEnd)
796 D.Skip s -> D.Skip (OuterLoop s)
797 D.Stop -> D.Stop
799 step' _ (InnerLoop st _ p end) | p == end =
800 return $ D.Yield sep $ OuterLoop st
802 step' _ (InnerLoop st startf p end) = do
803 x <- liftIO $ do
804 r <- peek p
805 touchForeignPtr startf
806 return r
807 return $ D.Yield x (InnerLoop st startf
808 (p `plusPtr` (sizeOf (undefined :: a))) end)
810{-# INLINE spliceTwo #-}
811spliceTwo :: (MonadIO m, Storable a) => Array a -> Array a -> m (Array a)
812spliceTwo arr1 arr2 = do
813 let src1 = unsafeForeignPtrToPtr (aStart arr1)
814 src2 = unsafeForeignPtrToPtr (aStart arr2)
815 len1 = aEnd arr1 `minusPtr` src1
816 len2 = aEnd arr2 `minusPtr` src2
818 arr <- liftIO $ newArray (len1 + len2)
819 let dst = unsafeForeignPtrToPtr (aStart arr)
821 liftIO $ do
822 memcpy (castPtr dst) (castPtr src1) len1
823 touchForeignPtr (aStart arr1)
824 memcpy (castPtr (dst `plusPtr` len1)) (castPtr src2) len2
825 touchForeignPtr (aStart arr2)
826 return arr { aEnd = dst `plusPtr` (len1 + len2) }
828-- Splice a new array into a pre-reserved array. The user must ensure that
829-- there is enough space in the array.
830{-# INLINE spliceWith #-}
831spliceWith :: (MonadIO m) => Array a -> Array a -> m (Array a)
832spliceWith dst@(Array _ end bound) src = liftIO $ do
833 let srcLen = byteLength src
834 if end `plusPtr` srcLen > bound
835 then error "Bug: spliceIntoUnsafe: Not enough space in the target array"
836 else
837 withForeignPtr (aStart dst) $ \_ -> do
838 withForeignPtr (aStart src) $ \psrc -> do
839 let pdst = aEnd dst
840 memcpy (castPtr pdst) (castPtr psrc) srcLen
841 return $ dst { aEnd = pdst `plusPtr` srcLen }
843-- Splice a new array into a preallocated array, doubling the space if there is
844-- no space in the target array.
845{-# INLINE spliceWithDoubling #-}
846spliceWithDoubling :: (MonadIO m, Storable a)
847 => Array a -> Array a -> m (Array a)
848spliceWithDoubling dst@(Array start end bound) src = do
849 assert (end <= bound) (return ())
850 let srcLen = aEnd src `minusPtr` unsafeForeignPtrToPtr (aStart src)
852 dst1 <-
853 if end `plusPtr` srcLen >= bound
854 then do
855 let oldStart = unsafeForeignPtrToPtr start
856 oldSize = end `minusPtr` oldStart
857 newSize = max (oldSize * 2) (oldSize + srcLen)
858 liftIO $ realloc newSize dst
859 else return dst
860 spliceWith dst1 src
862data SpliceState s arr
863 = SpliceInitial s
864 | SpliceBuffering s arr
865 | SpliceYielding arr (SpliceState s arr)
866 | SpliceFinish
868-- XXX can use general grouping combinators to achieve this?
869-- | Coalesce adajcent arrays in incoming stream to form bigger arrays of a
870-- maximum specified size.
871--
872-- @since 0.7.0
873{-# INLINE_NORMAL packArraysChunksOf #-}
874packArraysChunksOf :: (MonadIO m, Storable a)
875 => Int -> D.Stream m (Array a) -> D.Stream m (Array a)
876packArraysChunksOf n (D.Stream step state) =
877 D.Stream step' (SpliceInitial state)
879 where
881 {-# INLINE_LATE step' #-}
882 step' gst (SpliceInitial st) = do
883 when (n <= 0) $
884 -- XXX we can pass the module string from the higher level API
885 error $ "Streamly.Mem.Array.Types.packArraysChunksOf: the size of "
886 ++ "arrays [" ++ show n ++ "] must be a natural number"
887 r <- step gst st
888 case r of
889 D.Yield arr s -> return $
890 let len = byteLength arr
891 in if len >= n
892 then D.Skip (SpliceYielding arr (SpliceInitial s))
893 else D.Skip (SpliceBuffering s arr)
894 D.Skip s -> return $ D.Skip (SpliceInitial s)
895 D.Stop -> return $ D.Stop
897 step' gst (SpliceBuffering st buf) = do
898 r <- step gst st
899 case r of
900 D.Yield arr s -> do
901 let len = byteLength buf + byteLength arr
902 if len > n
903 then return $
904 D.Skip (SpliceYielding buf (SpliceBuffering s arr))
905 else do
906 buf' <- if byteCapacity buf < n
907 then liftIO $ realloc n buf
908 else return buf
909 buf'' <- spliceWith buf' arr
910 return $ D.Skip (SpliceBuffering s buf'')
911 D.Skip s -> return $ D.Skip (SpliceBuffering s buf)
912 D.Stop -> return $ D.Skip (SpliceYielding buf SpliceFinish)
914 step' _ SpliceFinish = return D.Stop
916 step' _ (SpliceYielding arr next) = return $ D.Yield arr next
918data GatherState s arr
919 = GatherInitial s
920 | GatherBuffering s arr Int
921 | GatherYielding arr (GatherState s arr)
922 | GatherFinish
924-- | @groupIOVecsOf maxBytes maxEntries@ groups arrays in the incoming stream
925-- to create a stream of 'IOVec' arrays with a maximum of @maxBytes@ bytes in
926-- each array and a maximum of @maxEntries@ entries in each array.
927--
928-- @since 0.7.0
929{-# INLINE_NORMAL groupIOVecsOf #-}
930groupIOVecsOf :: MonadIO m
931 => Int -> Int -> D.Stream m (Array a) -> D.Stream m (Array IOVec)
932groupIOVecsOf n maxIOVLen (D.Stream step state) =
933 D.Stream step' (GatherInitial state)
935 where
937 {-# INLINE_LATE step' #-}
938 step' gst (GatherInitial st) = do
939 when (n <= 0) $
940 -- XXX we can pass the module string from the higher level API
941 error $ "Streamly.Mem.Array.Types.groupIOVecsOf: the size of "
942 ++ "groups [" ++ show n ++ "] must be a natural number"
943 when (maxIOVLen <= 0) $
944 -- XXX we can pass the module string from the higher level API
945 error $ "Streamly.Mem.Array.Types.groupIOVecsOf: the number of "
946 ++ "IOVec entries [" ++ show n ++ "] must be a natural number"
947 r <- step (adaptState gst) st
948 case r of
949 D.Yield arr s -> do
950 let p = unsafeForeignPtrToPtr (aStart arr)
951 len = byteLength arr
952 iov <- liftIO $ newArray maxIOVLen
953 iov' <- liftIO $ unsafeSnoc iov (IOVec (castPtr p)
954 (fromIntegral len))
955 if len >= n
956 then return $ D.Skip (GatherYielding iov' (GatherInitial s))
957 else return $ D.Skip (GatherBuffering s iov' len)
958 D.Skip s -> return $ D.Skip (GatherInitial s)
959 D.Stop -> return $ D.Stop
961 step' gst (GatherBuffering st iov len) = do
962 r <- step (adaptState gst) st
963 case r of
964 D.Yield arr s -> do
965 let p = unsafeForeignPtrToPtr (aStart arr)
966 alen = byteLength arr
967 len' = len + alen
968 if len' > n || length iov >= maxIOVLen
969 then do
970 iov' <- liftIO $ newArray maxIOVLen
971 iov'' <- liftIO $ unsafeSnoc iov' (IOVec (castPtr p)
972 (fromIntegral alen))
973 return $ D.Skip (GatherYielding iov
974 (GatherBuffering s iov'' alen))
975 else do
976 iov' <- liftIO $ unsafeSnoc iov (IOVec (castPtr p)
977 (fromIntegral alen))
978 return $ D.Skip (GatherBuffering s iov' len')
979 D.Skip s -> return $ D.Skip (GatherBuffering s iov len)
980 D.Stop -> return $ D.Skip (GatherYielding iov GatherFinish)
982 step' _ GatherFinish = return D.Stop
984 step' _ (GatherYielding iov next) = return $ D.Yield iov next
986-- | Create two slices of an array without copying the original array. The
987-- specified index @i@ is the first index of the second slice.
988--
989-- @since 0.7.0
990splitAt :: forall a. Storable a => Int -> Array a -> (Array a, Array a)
991splitAt i arr@Array{..} =
992 let maxIndex = length arr - 1
993 in if i < 0
994 then error "sliceAt: negative array index"
995 else if i > maxIndex
996 then error $ "sliceAt: specified array index " ++ show i
997 ++ " is beyond the maximum index " ++ show maxIndex
998 else let off = i * sizeOf (undefined :: a)
999 p = unsafeForeignPtrToPtr aStart `plusPtr` off
1000 in ( Array
1001 { aStart = aStart
1002 , aEnd = p
1003 , aBound = p
1005 , Array
1006 { aStart = aStart `plusForeignPtr` off
1007 , aEnd = aEnd
1008 , aBound = aBound
1012-- Drops the separator byte
1013{-# INLINE breakOn #-}
1014breakOn :: MonadIO m
1015 => Word8 -> Array Word8 -> m (Array Word8, Maybe (Array Word8))
1016breakOn sep arr@Array{..} = liftIO $ do
1017 let p = unsafeForeignPtrToPtr aStart
1018 loc <- c_memchr p sep (fromIntegral $ aEnd `minusPtr` p)
1019 return $
1020 if loc == nullPtr
1021 then (arr, Nothing)
1022 else
1023 ( Array
1024 { aStart = aStart
1025 , aEnd = loc
1026 , aBound = loc
1028 , Just $ Array
1029 { aStart = aStart `plusForeignPtr` (loc `minusPtr` p + 1)
1030 , aEnd = aEnd
1031 , aBound = aBound
1035data SplitState s arr
1036 = Initial s
1037 | Buffering s arr
1038 | Splitting s arr
1039 | Yielding arr (SplitState s arr)
1040 | Finishing
1042-- | Split a stream of arrays on a given separator byte, dropping the separator
1043-- and coalescing all the arrays between two separators into a single array.
1045-- @since 0.7.0
1046{-# INLINE_NORMAL splitOn #-}
1047splitOn
1048 :: MonadIO m
1049 => Word8
1050 -> D.Stream m (Array Word8)
1051 -> D.Stream m (Array Word8)
1052splitOn byte (D.Stream step state) = D.Stream step' (Initial state)
1054 where
1056 {-# INLINE_LATE step' #-}
1057 step' gst (Initial st) = do
1058 r <- step gst st
1059 case r of
1060 D.Yield arr s -> do
1061 (arr1, marr2) <- breakOn byte arr
1062 return $ case marr2 of
1063 Nothing -> D.Skip (Buffering s arr1)
1064 Just arr2 -> D.Skip (Yielding arr1 (Splitting s arr2))
1065 D.Skip s -> return $ D.Skip (Initial s)
1066 D.Stop -> return $ D.Stop
1068 step' gst (Buffering st buf) = do
1069 r <- step gst st
1070 case r of
1071 D.Yield arr s -> do
1072 (arr1, marr2) <- breakOn byte arr
1073 buf' <- spliceTwo buf arr1
1074 return $ case marr2 of
1075 Nothing -> D.Skip (Buffering s buf')
1076 Just x -> D.Skip (Yielding buf' (Splitting s x))
1077 D.Skip s -> return $ D.Skip (Buffering s buf)
1078 D.Stop -> return $
1079 if byteLength buf == 0
1080 then D.Stop
1081 else D.Skip (Yielding buf Finishing)
1083 step' _ (Splitting st buf) = do
1084 (arr1, marr2) <- breakOn byte buf
1085 return $ case marr2 of
1086 Nothing -> D.Skip $ Buffering st arr1
1087 Just arr2 -> D.Skip $ Yielding arr1 (Splitting st arr2)
1089 step' _ (Yielding arr next) = return $ D.Yield arr next
1090 step' _ Finishing = return $ D.Stop