on master help sign in
Split the current Array into Array and Mutable.Array

* Add unsafeFreezeWithShrink * Cleanup + Reformat code + Respect hlint

commit12c672ef1dce9be9c4c455c313eb960d1b899f7c authoradithyaov <adi.obilisetty@gmail.com> dateWed 5 Feb 2020, 18:50 (7 years ago) parent27c3aeb
11 files changed+1708−1087
src/Streamly/FileSystem/FD.hs+6−8 src/Streamly/Internal/Data/Stream/StreamD.hs+4−3 src/Streamly/Internal/Data/Unicode/Stream.hs+5−5 src/Streamly/Internal/FileSystem/Handle.hs+6−8 src/Streamly/Internal/Memory/Array.hs+4−12 src/Streamly/Internal/Memory/Array/Types.hs+202−1030 src/Streamly/Internal/Memory/ArrayStream.hs+33−14 src/Streamly/Internal/Memory/Mutable/Array/Types.hs+1440 src/Streamly/Internal/Network/Socket.hs+5−5 src/Streamly/Memory/Ring.hs+2−2 streamly.cabal+1
src/Streamly/FileSystem/FD.hs+6−8
@@ -130,7 +130,9 @@ import Prelude hiding (read)
130130import qualified GHC.IO.FD as FD
131131import qualified GHC.IO.Device as RawIO
132132
133−import Streamly.Internal.Memory.Array.Types (Array(..), byteLength, defaultChunkSize)
133+import Streamly.Internal.Memory.Array.Types (Array(..), byteLength, defaultChunkSize, unsafeFreeze)
134+import Streamly.Internal.Memory.Mutable.Array.Types (mutableArray)
135+
134136import Streamly.Internal.Data.Stream.Serial (SerialT)
135137import Streamly.Internal.Data.Stream.StreamK.Type (IsStream, mkStream)
136138
@@ -216,14 +218,10 @@ readArrayUpto size (Handle fd) = do
216218 withForeignPtr ptr $ \p -> do
217219 -- n <- hGetBufSome h p size
218220 n <- RawIO.read fd p size
219− let v = Array
220− { aStart = ptr
221− , aEnd = p `plusPtr` n
222− , aBound = p `plusPtr` size
223− }
224221 -- XXX shrink only if the diff is significant
225− -- A.shrinkToFit v
226− return v
222+ -- Use unsafeFreezeWithShrink
223+ return $
224+ unsafeFreeze $ mutableArray ptr (p `plusPtr` n) (p `plusPtr` size)
227225
228226-------------------------------------------------------------------------------
229227-- Array IO (output)
src/Streamly/Internal/Data/Stream/StreamD.hs+4−3
@@ -363,6 +363,7 @@ import Streamly.Internal.Data.Stream.SVar (fromConsumer, pushToFold)
363363
364364import qualified Streamly.Internal.Data.Pipe.Types as Pipe
365365import qualified Streamly.Internal.Memory.Array.Types as A
366+import qualified Streamly.Internal.Memory.Mutable.Array.Types as MA
366367import qualified Streamly.Internal.Data.Fold as FL
367368import qualified Streamly.Memory.Ring as RB
368369import qualified Streamly.Internal.Data.Stream.StreamK as K
@@ -4516,16 +4517,16 @@ tapAsync f (Stream step1 state1) = Stream step TapInit
45164517lastN :: (Storable a, MonadIO m) => Int -> Fold m a (Array a)
45174518lastN n
45184519 | n <= 0 = fmap (const mempty) FL.drain
4519− | otherwise = Fold step initial done
4520+ | otherwise = A.unsafeFreeze <$> Fold step initial done
45204521 where
45214522 step (Tuple3' rb rh i) a = do
45224523 rh1 <- liftIO $ RB.unsafeInsert rb rh a
45234524 return $ Tuple3' rb rh1 (i + 1)
45244525 initial = fmap (\(a, b) -> Tuple3' a b (0 :: Int)) $ liftIO $ RB.new n
45254526 done (Tuple3' rb rh i) = do
4526− arr <- liftIO $ A.newArray n
4527+ arr <- liftIO $ MA.newArray n
45274528 foldFunc i rh snoc' arr rb
4528− snoc' b a = liftIO $ A.unsafeSnoc b a
4529+ snoc' b a = liftIO $ MA.unsafeSnoc b a
45294530 foldFunc i
45304531 | i < n = RB.unsafeFoldRingM
45314532 | otherwise = RB.unsafeFoldRingFullM
src/Streamly/Internal/Data/Unicode/Stream.hs+5−5
@@ -192,7 +192,7 @@ decode0 table byte =
192192 utf8table =
193193 let !(Ptr addr) = table
194194 end = table `plusPtr` 364
195− in A.Array (ForeignPtr addr undefined) end end :: A.Array Word8
195+ in A.Array (ForeignPtr addr undefined) end :: A.Array Word8
196196 showByte = "Streamly: decode0: byte: " ++ show byte
197197 showTable = " table: " ++ show utf8table
198198
@@ -219,7 +219,7 @@ decode1 table state codep byte =
219219 utf8table =
220220 let !(Ptr addr) = table
221221 end = table `plusPtr` 364
222− in A.Array (ForeignPtr addr undefined) end end :: A.Array Word8
222+ in A.Array (ForeignPtr addr undefined) end :: A.Array Word8
223223 showByte = "Streamly: decode1: byte: " ++ show byte
224224 showState st cp =
225225 " state: " ++ show st ++
@@ -246,7 +246,7 @@ data FreshPoint s a
246246{-# INLINE_NORMAL decodeUtf8WithD #-}
247247decodeUtf8WithD :: Monad m => CodingFailureMode -> Stream m Word8 -> Stream m Char
248248decodeUtf8WithD cfm (Stream step state) =
249− let A.Array p _ _ = utf8d
249+ let A.Array p _ = utf8d
250250 !ptr = unsafeForeignPtrToPtr p
251251 in Stream (step' ptr) (FreshPointDecodeInit state)
252252 where
@@ -337,7 +337,7 @@ resumeDecodeUtf8EitherD
337337 -> Stream m Word8
338338 -> Stream m (Either DecodeError Char)
339339resumeDecodeUtf8EitherD dst codep (Stream step state) =
340− let A.Array p _ _ = utf8d
340+ let A.Array p _ = utf8d
341341 !ptr = unsafeForeignPtrToPtr p
342342 stt =
343343 if dst == 0
@@ -431,7 +431,7 @@ decodeUtf8ArraysWithD ::
431431 -> Stream m (A.Array Word8)
432432 -> Stream m Char
433433decodeUtf8ArraysWithD cfm (Stream step state) =
434− let A.Array p _ _ = utf8d
434+ let A.Array p _ = utf8d
435435 !ptr = unsafeForeignPtrToPtr p
436436 in Stream (step' ptr) (OuterLoop state Nothing)
437437 where
src/Streamly/Internal/FileSystem/Handle.hs+6−8
@@ -114,8 +114,9 @@ import Streamly.Data.Fold (Fold)
114114import Streamly.Internal.Data.Fold.Types (Fold2(..))
115115import Streamly.Internal.Data.Unfold.Types (Unfold(..))
116116import Streamly.Internal.Memory.Array.Types
117− (Array(..), writeNUnsafe, defaultChunkSize, shrinkToFit,
118− lpackArraysChunksOf)
117+ (Array(..), writeNUnsafe, defaultChunkSize, lpackArraysChunksOf
118+ , unsafeFreezeWithShrink)
119+import Streamly.Internal.Memory.Mutable.Array.Types (mutableArray)
119120import Streamly.Internal.Data.Stream.Serial (SerialT)
120121import Streamly.Internal.Data.Stream.StreamK.Type (IsStream, mkStream)
121122-- import Streamly.String (encodeUtf8, decodeUtf8, foldLines)
@@ -157,13 +158,10 @@ readArrayUpto size h = do
157158 -- ptr <- mallocPlainForeignPtrAlignedBytes size (alignment (undefined :: Word8))
158159 withForeignPtr ptr $ \p -> do
159160 n <- hGetBufSome h p size
160− let v = Array
161− { aStart = ptr
162− , aEnd = p `plusPtr` n
163− , aBound = p `plusPtr` size
164− }
165161 -- XXX shrink only if the diff is significant
166− shrinkToFit v
162+ return $
163+ unsafeFreezeWithShrink $
164+ mutableArray ptr (p `plusPtr` n) (p `plusPtr` size)
167165
168166-------------------------------------------------------------------------------
169167-- Stream of Arrays IO
src/Streamly/Internal/Memory/Array.hs+4−12
@@ -154,14 +154,6 @@ import qualified Streamly.Internal.Data.Stream.StreamK as K
154154-- Construction
155155-------------------------------------------------------------------------------
156156
157−{-
158−-- | Create a new uninitialized array of given length.
159−--
160−-- @since 0.7.0
161−newArray :: (MonadIO m, Storable a) => Int -> m (Array a)
162−newArray len = undefined
163−-}
164−
165157-- | Create an 'Array' from the first N elements of a stream. The array is
166158-- allocated to size N, if the stream terminates before N elements then the
167159-- array may hold less than N elements.
@@ -223,7 +215,7 @@ read :: forall m a. (Monad m, Storable a) => Unfold m (Array a) a
223215read = Unfold step inject
224216 where
225217
226− inject (Array (ForeignPtr start contents) (Ptr end) _) =
218+ inject (Array (ForeignPtr start contents) (Ptr end)) =
227219 return $ ReadUState (ForeignPtr end contents) (Ptr start)
228220
229221 {-# INLINE_LATE step #-}
@@ -257,7 +249,7 @@ unsafeRead :: forall m a. (Monad m, Storable a) => Unfold m (Array a) a
257249unsafeRead = Unfold step inject
258250 where
259251
260− inject (Array fp _ _) = return fp
252+ inject (Array fp _) = return fp
261253
262254 {-# INLINE_LATE step #-}
263255 step (ForeignPtr p contents) = do
@@ -448,7 +440,7 @@ writeIndex arr i a = do
448440-- @since 0.7.0
449441{-# INLINE writeSlice #-}
450442writeSlice :: (IsStream t, Monad m, Storable a)
451− => Array a -> Int -> Int -> t m a -> m ()
443+ => Array a -> Int -> Int -> t m a -> m (Array a)
452444writeSlice arr i len s = undefined
453445
454446-- | @writeSliceRev arr i count stream@ writes a stream to the array @arr@
@@ -458,7 +450,7 @@ writeSlice arr i len s = undefined
458450-- @since 0.7.0
459451{-# INLINE writeSliceRev #-}
460452writeSliceRev :: (IsStream t, Monad m, Storable a)
461− => Array a -> Int -> Int -> t m a -> m ()
453+ => Array a -> Int -> Int -> t m a -> m (Array a)
462454writeSliceRev arr i len s = undefined
463455-}
464456
src/Streamly/Internal/Memory/Array/Types.hs+202−1030
@@ -1,12 +1,10 @@
1−{-# LANGUAGE UnboxedTuples #-}
2−
31#include "inline.hs"
42
53-- |
64-- Module : Streamly.Internal.Memory.Array.Types
7−-- Copyright : (c) 2019 Composewell Technologies
5+-- Copyright : (c) 2020 Composewell Technologies
86--
9−-- License : BSD3
7+-- License : BSD3-3-Clause
108-- Maintainer : streamly@composewell.com
119-- Stability : experimental
1210-- Portability : GHC
@@ -15,28 +13,25 @@ module Streamly.Internal.Memory.Array.Types
1513 (
1614 Array (..)
1715
16+ -- * Freezing and Thawing
17+ , unsafeFreeze
18+ , unsafeFreezeWithShrink
19+ , unsafeThaw
20+
1821 -- * Construction
19− , withNewArray
20− , newArray
21− , unsafeSnoc
22− , snoc
23− , spliceWithDoubling
2422 , spliceTwo
2523
26− , fromAddr#
27− , fromString#
2824 , fromList
2925 , fromListN
3026 , fromStreamDN
31− -- , fromStreamD
3227
3328 -- * Streams of arrays
3429 , fromStreamDArraysOf
35− , FlattenState (..) -- for inspection testing
30+ , MA.FlattenState (..) -- for inspection testing
3631 , flattenArrays
3732 , flattenArraysRev
3833 , packArraysChunksOf
39− , SpliceState (..)
34+ , MA.SpliceState (..)
4035 , lpackArraysChunksOf
4136#if !defined(mingw32_HOST_OS)
4237 , groupIOVecsOf
@@ -47,9 +42,9 @@ module Streamly.Internal.Memory.Array.Types
4742 -- * Elimination
4843 , unsafeIndexIO
4944 , unsafeIndex
50− , length
5145 , byteLength
52− , byteCapacity
46+ , length
47+
5348 , foldl'
5449 , foldr
5550 , splitAt
@@ -59,78 +54,60 @@ module Streamly.Internal.Memory.Array.Types
5954 , toStreamK
6055 , toStreamKRev
6156 , toList
57+
58+ , unlines
59+
60+ -- * Folds
6261 , toArrayMinChunk
6362 , writeN
6463 , writeNUnsafe
65− , ArrayUnsafe (..)
64+ , MA.ArrayUnsafe (..)
6665 , writeNAligned
6766 , writeNAlignedUnmanaged
6867 , write
6968 , writeAligned
7069
7170 -- * Utilities
72− , defaultChunkSize
73− , mkChunkSize
74− , mkChunkSizeKB
75− , unsafeInlineIO
76− , realloc
77− , shrinkToFit
78− , memcpy
79− , memcmp
80− , bytesToElemCount
81−
82− , unlines
71+ , MA.defaultChunkSize
72+ , MA.mkChunkSize
73+ , MA.mkChunkSizeKB
74+ , MA.unsafeInlineIO
75+
76+ , MA.memcpy
77+ , MA.memcmp
78+ , MA.bytesToElemCount
8379 )
8480where
8581
86−import Control.Exception (assert)
8782import Control.DeepSeq (NFData(..))
88−import Control.Monad (when, void)
8983import Control.Monad.IO.Class (MonadIO(..))
90−import Data.Functor.Identity (runIdentity)
91−#if __GLASGOW_HASKELL__ < 808
92−import Data.Semigroup (Semigroup(..))
93−#endif
9484import Data.Word (Word8)
95−import Foreign.C.String (CString)
96−import Foreign.C.Types (CSize(..), CInt(..))
97−import Foreign.ForeignPtr (withForeignPtr, touchForeignPtr)
98−import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
99−import Foreign.Ptr (plusPtr, minusPtr, castPtr, nullPtr)
10085import Foreign.Storable (Storable(..))
101−import Prelude hiding (length, foldr, read, unlines, splitAt)
102−import Text.Read (readPrec, readListPrec, readListPrecDefault)
103−
104−import GHC.Base (Addr#, nullAddr#, realWorld#, build)
86+import GHC.Base (nullAddr#)
10587import GHC.Exts (IsList, IsString(..))
106−import GHC.ForeignPtr (ForeignPtr(..), newForeignPtr_)
107−import GHC.IO (IO(IO), unsafePerformIO)
88+import GHC.ForeignPtr (ForeignPtr(..))
89+import GHC.IO (unsafePerformIO)
10890import GHC.Ptr (Ptr(..))
91+import Streamly.Internal.Data.Fold.Types (Fold(..), lmap)
92+import Text.Read (readPrec, readListPrec, readListPrecDefault)
10993
110−import Streamly.Internal.Data.Fold.Types (Fold(..))
111−import Streamly.Internal.Data.Strict (Tuple'(..))
112−import Streamly.Internal.Data.SVar (adaptState)
113−
114−#if !defined(mingw32_HOST_OS)
115−import Streamly.FileSystem.FDIO (IOVec(..))
116−#endif
94+import Prelude hiding (length, foldr, read, unlines, splitAt)
11795
118−import qualified Streamly.Memory.Malloc as Malloc
11996import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
12097import qualified Streamly.Internal.Data.Stream.StreamK as K
98+import qualified Streamly.Internal.Memory.Mutable.Array.Types as MA
12199import qualified GHC.Exts as Exts
122100
123−#ifdef DEVBUILD
124−import qualified Data.Foldable as F
101+#if __GLASGOW_HASKELL__ < 808
102+import Data.Semigroup (Semigroup(..))
125103#endif
126104
127−#if MIN_VERSION_base(4,10,0)
128−import Foreign.ForeignPtr (plusForeignPtr)
129−#else
130−import GHC.Base (Int(..), plusAddr#)
131−import GHC.ForeignPtr (ForeignPtr(..))
132−plusForeignPtr :: ForeignPtr a -> Int -> ForeignPtr b
133−plusForeignPtr (ForeignPtr addr c) (I# d) = ForeignPtr (plusAddr# addr d) c
105+#if !defined(mingw32_HOST_OS)
106+import Streamly.FileSystem.FDIO (IOVec(..))
107+#endif
108+
109+#ifdef DEVBUILD
110+import qualified Data.Foldable as F
134111#endif
135112
136113-------------------------------------------------------------------------------
@@ -186,253 +163,140 @@ data Array a =
186163#endif
187164 Array
188165 { aStart :: {-# UNPACK #-} !(ForeignPtr a) -- first address
189− , aEnd :: {-# UNPACK #-} !(Ptr a) -- first unused address
190− , aBound :: {-# UNPACK #-} !(Ptr a) -- first address beyond allocated memory
166+ , aEnd :: {-# UNPACK #-} !(Ptr a) -- first unused addres
191167 }
192168
193169-------------------------------------------------------------------------------
194−-- Utility functions
170+-- Freezing and Thawing
195171-------------------------------------------------------------------------------
196172
197−foreign import ccall unsafe "string.h memcpy" c_memcpy
198− :: Ptr Word8 -> Ptr Word8 -> CSize -> IO (Ptr Word8)
199−
200−foreign import ccall unsafe "string.h strlen" c_strlen
201− :: CString -> IO CSize
202−
203−foreign import ccall unsafe "string.h memchr" c_memchr
204− :: Ptr Word8 -> Word8 -> CSize -> IO (Ptr Word8)
173+-- | Returns an immutable array using the same underlying pointers of the
174+-- mutable array. If the underlying array is mutated, the immutable promise is
175+-- lost. Please make sure that the mutable array is never used after freezing it
176+-- using /unsafeFreeze/.
177+{-# INLINE unsafeFreeze #-}
178+unsafeFreeze :: MA.Array a -> Array a
179+unsafeFreeze (MA.Array as ae _) = Array as ae
180+
181+-- | Similar to 'unsafeFreeze' but uses 'MA.shrinkToFit' on the mutable array
182+-- first.
183+{-# INLINE unsafeFreezeWithShrink #-}
184+unsafeFreezeWithShrink :: Storable a => MA.Array a -> Array a
185+unsafeFreezeWithShrink arr = unsafePerformIO $ do
186+ MA.Array as ae _ <- MA.shrinkToFit arr
187+ return $ Array as ae
188+
189+-- | Returns a mutable array using the same underlying pointers of the immutable
190+-- array. If the resulting array is mutated, the older immutable array is
191+-- mutated as well. Please make sure that the immutable array is never used
192+-- after thawing it using /unsafeThaw/.
193+{-# INLINE unsafeThaw #-}
194+unsafeThaw :: Array a -> MA.Array a
195+unsafeThaw (Array as ae) = MA.Array as ae ae
205196
206−-- XXX we are converting Int to CSize
207−memcpy :: Ptr Word8 -> Ptr Word8 -> Int -> IO ()
208−memcpy dst src len = void (c_memcpy dst src (fromIntegral len))
197+-------------------------------------------------------------------------------
198+-- Construction
199+-------------------------------------------------------------------------------
209200
210−foreign import ccall unsafe "string.h memcmp" c_memcmp
211− :: Ptr Word8 -> Ptr Word8 -> CSize -> IO CInt
201+-- Splice two immutable arrays creating a new array.
202+{-# INLINE spliceTwo #-}
203+spliceTwo :: (MonadIO m, Storable a) => Array a -> Array a -> m (Array a)
204+spliceTwo arr1 arr2 =
205+ unsafeFreeze <$> MA.spliceTwo (unsafeThaw arr1) (unsafeThaw arr2)
212206
213−-- XXX we are converting Int to CSize
214−-- return True if the memory locations have identical contents
215−{-# INLINE memcmp #-}
216−memcmp :: Ptr Word8 -> Ptr Word8 -> Int -> IO Bool
217−memcmp p1 p2 len = do
218− r <- c_memcmp p1 p2 (fromIntegral len)
219− return $ r == 0
207+-- | Create an 'Array' from the first N elements of a list. The array is
208+-- allocated to size N, if the list terminates before N elements then the
209+-- array may hold less than N elements.
210+--
211+-- @since 0.7.0
212+{-# INLINABLE fromListN #-}
213+fromListN :: Storable a => Int -> [a] -> Array a
214+fromListN n xs = unsafeFreeze $ MA.fromListN n xs
220215
221−{-# INLINE unsafeInlineIO #-}
222−unsafeInlineIO :: IO a -> a
223−unsafeInlineIO (IO m) = case m realWorld# of (# _, r #) -> r
216+-- | Create an 'Array' from a list. The list must be of finite size.
217+--
218+-- @since 0.7.0
219+{-# INLINABLE fromList #-}
220+fromList :: Storable a => [a] -> Array a
221+fromList xs = unsafeFreeze $ MA.fromList xs
224222
225−{-# INLINE bytesToElemCount #-}
226−bytesToElemCount :: Storable a => a -> Int -> Int
227−bytesToElemCount x n =
228− let elemSize = sizeOf x
229− in n + elemSize - 1 `div` elemSize
223+{-# INLINE_NORMAL fromStreamDN #-}
224+fromStreamDN :: forall m a. (MonadIO m, Storable a)
225+ => Int -> D.Stream m a -> m (Array a)
226+fromStreamDN limit str = unsafeFreeze <$> MA.fromStreamDN limit str
230227
231228-------------------------------------------------------------------------------
232−-- Construction
229+-- Streams of arrays
233230-------------------------------------------------------------------------------
234231
235−-- | allocate a new array using the provided allocator function.
236−{-# INLINE newArrayAlignedAllocWith #-}
237−newArrayAlignedAllocWith :: forall a. Storable a
238− => (Int -> Int -> IO (ForeignPtr a)) -> Int -> Int -> IO (Array a)
239−newArrayAlignedAllocWith alloc alignSize count = do
240− let size = count * sizeOf (undefined :: a)
241− fptr <- alloc size alignSize
242− let p = unsafeForeignPtrToPtr fptr
243− return $ Array
244− { aStart = fptr
245− , aEnd = p
246− , aBound = p `plusPtr` size
247− }
248−
249−-- | Allocate a new array aligned to the specified alignmend and using
250−-- unmanaged pinned memory. The memory will not be automatically freed by GHC.
251−-- This could be useful in allocate once global data structures. Use carefully
252−-- as incorrect use can lead to memory leak.
253−{-# INLINE newArrayAlignedUnmanaged #-}
254−newArrayAlignedUnmanaged :: forall a. Storable a => Int -> Int -> IO (Array a)
255−newArrayAlignedUnmanaged =
256− newArrayAlignedAllocWith Malloc.mallocForeignPtrAlignedUnmanagedBytes
257−
258−{-# INLINE newArrayAligned #-}
259−newArrayAligned :: forall a. Storable a => Int -> Int -> IO (Array a)
260−newArrayAligned = newArrayAlignedAllocWith Malloc.mallocForeignPtrAlignedBytes
261−
262−-- XXX can unaligned allocation be more efficient when alignment is not needed?
263−--
264−-- | Allocate an array that can hold 'count' items. The memory of the array is
265−-- uninitialized.
266−--
267−-- Note that this is internal routine, the reference to this array cannot be
268−-- given out until the array has been written to and frozen.
269−{-# INLINE newArray #-}
270−newArray :: forall a. Storable a => Int -> IO (Array a)
271−newArray = newArrayAligned (alignment (undefined :: a))
272−
273−-- | Allocate an Array of the given size and run an IO action passing the array
274−-- start pointer.
275−{-# INLINE withNewArray #-}
276−withNewArray :: forall a. Storable a => Int -> (Ptr a -> IO ()) -> IO (Array a)
277−withNewArray count f = do
278− arr <- newArray count
279− withForeignPtr (aStart arr) $ \p -> f p >> return arr
280−
281−-- XXX grow the array when we are beyond bound.
282−--
283−-- Internal routine for when the array is being created. Appends one item at
284−-- the end of the array. Useful when sequentially writing a stream to the
285−-- array.
286−{-# INLINE unsafeSnoc #-}
287−unsafeSnoc :: forall a. Storable a => Array a -> a -> IO (Array a)
288−unsafeSnoc arr@Array{..} x = do
289− when (aEnd == aBound) $
290− error "BUG: unsafeSnoc: writing beyond array bounds"
291− poke aEnd x
292− touchForeignPtr aStart
293− return $ arr {aEnd = aEnd `plusPtr` sizeOf (undefined :: a)}
294−
295−{-# INLINE snoc #-}
296−snoc :: forall a. Storable a => Array a -> a -> IO (Array a)
297−snoc arr@Array {..} x =
298− if aEnd == aBound
299− then do
300− let oldStart = unsafeForeignPtrToPtr aStart
301− size = aEnd `minusPtr` oldStart
302− newSize = size + sizeOf (undefined :: a)
303− newPtr <- Malloc.mallocForeignPtrAlignedBytes
304− newSize (alignment (undefined :: a))
305− withForeignPtr newPtr $ \pNew -> do
306− memcpy (castPtr pNew) (castPtr oldStart) size
307− poke (pNew `plusPtr` size) x
308− touchForeignPtr aStart
309− return $ Array
310− { aStart = newPtr
311− , aEnd = pNew `plusPtr` (size + sizeOf (undefined :: a))
312− , aBound = pNew `plusPtr` newSize
313− }
314− else do
315− poke aEnd x
316− touchForeignPtr aStart
317− return $ arr {aEnd = aEnd `plusPtr` sizeOf (undefined :: a)}
318−
319−-- | Reallocate the array to the specified size in bytes. If the size is less
320−-- than the original array the array gets truncated.
321−{-# NOINLINE reallocAligned #-}
322−reallocAligned :: Int -> Int -> Array a -> IO (Array a)
323−reallocAligned alignSize newSize Array{..} = do
324− assert (aEnd <= aBound) (return ())
325− let oldStart = unsafeForeignPtrToPtr aStart
326− let size = aEnd `minusPtr` oldStart
327− newPtr <- Malloc.mallocForeignPtrAlignedBytes newSize alignSize
328− withForeignPtr newPtr $ \pNew -> do
329− memcpy (castPtr pNew) (castPtr oldStart) size
330− touchForeignPtr aStart
331− return $ Array
332− { aStart = newPtr
333− , aEnd = pNew `plusPtr` size
334− , aBound = pNew `plusPtr` newSize
335− }
336−
337−-- XXX can unaligned allocation be more efficient when alignment is not needed?
338−{-# INLINABLE realloc #-}
339−realloc :: forall a. Storable a => Int -> Array a -> IO (Array a)
340−realloc = reallocAligned (alignment (undefined :: a))
341−
342−-- | Remove the free space from an Array.
343−shrinkToFit :: forall a. Storable a => Array a -> IO (Array a)
344−shrinkToFit arr@Array{..} = do
345− assert (aEnd <= aBound) (return ())
346− let start = unsafeForeignPtrToPtr aStart
347− let used = aEnd `minusPtr` start
348− waste = aBound `minusPtr` aEnd
349− -- if used == waste == 0 then do not realloc
350− -- if the wastage is more than 25% of the array then realloc
351− if used < 3 * waste
352− then realloc used arr
353− else return arr
354−
355−-- See also: https://gitlab.haskell.org/ghc/ghc/issues/5218 (Add
356−-- unpackCStringLen# to create Strings from string literals)
357−--
358−-- TBD: We can also add template haskell quasiquotes to specify arrays of other
359−-- literal types. TH will encode them into a string literal and we read that as
360−-- an array of the required type. With template Haskell we can provide a safe
361−-- version of fromString#.
362−--
363−-- | Create an @Array Word8@ of the given length from a machine address
364−-- 'Addr#'. This API is unsafe for the following reasons:
365−--
366−-- 1. The address must point to pinned memory or foreign memory.
367−-- 2. The address must be legally accessible upto the given length.
368−-- 3. To guarantee that the array is immutable, the contents of the address
369−-- must be guaranteed to not change.
370−--
371−-- A common use case for this API is to create an array from a static unboxed
372−-- string literal. GHC string literals are of type 'Addr#', and must contain
373−-- characters that can be encoded in a byte i.e. characters or literal bytes in
374−-- the range from 0-255.
375−--
376−-- >>> fromAddr# 5 "hello world!"#
377−-- > [104,101,108,108,111]
378−--
379−-- >>> fromAddr# 3 "\255\NUL\255"#
380−-- > [255,0,255]
381−--
382−-- /See also: 'fromString#'/
383−--
384−-- /Unsafe/
385−--
386−-- /Time complexity: O(1)/
387−--
388−{-# INLINE fromAddr# #-}
389−fromAddr# :: Int -> Addr# -> IO (Array Word8)
390−fromAddr# n addr# = do
391− ptr <- newForeignPtr_ (castPtr $ Ptr addr#)
392− let p = unsafeForeignPtrToPtr ptr
393− let end = p `plusPtr` n
394− return $ Array
395− { aStart = ptr
396− , aEnd = end
397− , aBound = end
398− }
399−
400−-- | Like 'fromAddr#' but determines the length of the array upto and excluding
401−-- the first @0@ byte. The address must be legally accessible up to the first
402−-- NUL byte.
403−--
404−-- A common use case for this API is to create an array from an unboxed string
405−-- literal. Unboxed string literals are guaranteed to be terminated by a NUL
406−-- byte.
407−--
408−-- >>> fromString# "hello world!"#
409−-- > [104,101,108,108,111,32,119,111,114,108,100,33]
410−--
411−-- >>> fromString# "\255\NUL\255"#
412−-- > [255]
413−--
414−-- /See also: 'fromAddr#', 'fromString'/
415−--
416−-- /Unsafe/
232+-- | @fromStreamArraysOf n stream@ groups the input stream into a stream of
233+-- arrays of size n.
234+{-# INLINE_NORMAL fromStreamDArraysOf #-}
235+fromStreamDArraysOf :: forall m a. (MonadIO m, Storable a)
236+ => Int -> D.Stream m a -> D.Stream m (Array a)
237+-- fromStreamDArraysOf n str = D.groupsOf n (writeN n) str
238+fromStreamDArraysOf n str = D.map unsafeFreeze $ MA.fromStreamDArraysOf n str
239+
240+-- XXX concatMap does not seem to have the best possible performance so we have
241+-- a custom way to concat arrays.
242+{-# INLINE_NORMAL flattenArrays #-}
243+flattenArrays :: forall m a. (MonadIO m, Storable a)
244+ => D.Stream m (Array a) -> D.Stream m a
245+flattenArrays = MA.flattenArrays . D.map unsafeThaw
246+
247+{-# INLINE_NORMAL flattenArraysRev #-}
248+flattenArraysRev :: forall m a. (MonadIO m, Storable a)
249+ => D.Stream m (Array a) -> D.Stream m a
250+flattenArraysRev = MA.flattenArraysRev . D.map unsafeThaw
251+
252+
253+{-# INLINE_NORMAL packArraysChunksOf #-}
254+packArraysChunksOf :: (MonadIO m, Storable a)
255+ => Int -> D.Stream m (Array a) -> D.Stream m (Array a)
256+packArraysChunksOf n str =
257+ D.map unsafeFreeze $ MA.packArraysChunksOf n $ D.map unsafeThaw str
258+
259+-- XXX instead of writing two different versions of this operation, we should
260+-- write it as a pipe.
261+{-# INLINE_NORMAL lpackArraysChunksOf #-}
262+lpackArraysChunksOf :: (MonadIO m, Storable a)
263+ => Int -> Fold m (Array a) () -> Fold m (Array a) ()
264+lpackArraysChunksOf n fld =
265+ lmap unsafeThaw $ MA.lpackArraysChunksOf n (lmap unsafeFreeze fld)
266+
267+#if !defined(mingw32_HOST_OS)
268+
269+-- | @groupIOVecsOf maxBytes maxEntries@ groups arrays in the incoming stream
270+-- to create a stream of 'IOVec' arrays with a maximum of @maxBytes@ bytes in
271+-- each array and a maximum of @maxEntries@ entries in each array.
417272--
418−-- /Time complexity: O(n) (computes the length of the string)/
273+-- @since 0.7.0
274+{-# INLINE_NORMAL groupIOVecsOf #-}
275+groupIOVecsOf :: MonadIO m
276+ => Int -> Int -> D.Stream m (Array a) -> D.Stream m (Array IOVec)
277+groupIOVecsOf n maxIOVLen str =
278+ D.map unsafeFreeze $ MA.groupIOVecsOf n maxIOVLen $ D.map unsafeThaw str
279+#endif
280+
281+-- | Split a stream of arrays on a given separator byte, dropping the separator
282+-- and coalescing all the arrays between two separators into a single array.
419283--
420−{-# INLINE fromString# #-}
421−fromString# :: Addr# -> IO (Array Word8)
422−fromString# addr# = do
423− ptr <- newForeignPtr_ (castPtr cstr)
424− len <- c_strlen cstr
425− let n = fromIntegral len
426− let p = unsafeForeignPtrToPtr ptr
427− let end = p `plusPtr` n
428− return $ Array
429− { aStart = ptr
430− , aEnd = end
431− , aBound = end
432− }
433− where
434− cstr :: CString
435− cstr = Ptr addr#
284+-- @since 0.7.0
285+{-# INLINE_NORMAL splitOn #-}
286+splitOn
287+ :: MonadIO m
288+ => Word8
289+ -> D.Stream m (Array Word8)
290+ -> D.Stream m (Array Word8)
291+splitOn byte str = D.map unsafeFreeze $ MA.splitOn byte $ D.map unsafeThaw str
292+
293+-- Drops the separator byte
294+{-# INLINE breakOn #-}
295+breakOn :: MonadIO m
296+ => Word8 -> Array Word8 -> m (Array Word8, Maybe (Array Word8))
297+breakOn sep arr = do
298+ (a, b) <- MA.breakOn sep (unsafeThaw arr)
299+ return (unsafeFreeze a, unsafeFreeze <$> b)
436300
437301-------------------------------------------------------------------------------
438302-- Elimination
@@ -443,28 +307,19 @@ fromString# addr# = do
443307-- Unsafe because it does not check the bounds of the array.
444308{-# INLINE_NORMAL unsafeIndexIO #-}
445309unsafeIndexIO :: forall a. Storable a => Array a -> Int -> IO a
446−unsafeIndexIO Array {..} i =
447− withForeignPtr aStart $ \p -> do
448− let elemSize = sizeOf (undefined :: a)
449− elemOff = p `plusPtr` (elemSize * i)
450− assert (i >= 0 && elemOff `plusPtr` elemSize <= aEnd)
451− (return ())
452− peek elemOff
310+unsafeIndexIO arr = MA.unsafeIndexIO (unsafeThaw arr)
453311
454312-- | Return element at the specified index without checking the bounds.
455313{-# INLINE_NORMAL unsafeIndex #-}
456314unsafeIndex :: forall a. Storable a => Array a -> Int -> a
457−unsafeIndex arr i = let !r = unsafeInlineIO $ unsafeIndexIO arr i in r
315+unsafeIndex arr = MA.unsafeIndex (unsafeThaw arr)
458316
459317-- | /O(1)/ Get the byte length of the array.
460318--
461319-- @since 0.7.0
462320{-# INLINE byteLength #-}
463321byteLength :: Array a -> Int
464−byteLength Array{..} =
465− let p = unsafeForeignPtrToPtr aStart
466− len = aEnd `minusPtr` p
467− in assert (len >= 0) len
322+byteLength = MA.byteLength . unsafeThaw
468323
469324-- | /O(1)/ Get the length of the array i.e. the number of elements in the
470325-- array.
@@ -472,116 +327,56 @@ byteLength Array{..} =
472327-- @since 0.7.0
473328{-# INLINE length #-}
474329length :: forall a. Storable a => Array a -> Int
475−length arr = byteLength arr `div` sizeOf (undefined :: a)
476−
477−{-# INLINE byteCapacity #-}
478−byteCapacity :: Array a -> Int
479−byteCapacity Array{..} =
480− let p = unsafeForeignPtrToPtr aStart
481− len = aBound `minusPtr` p
482− in assert (len >= 0) len
330+length arr = MA.length (unsafeThaw arr)
483331
484332{-# INLINE_NORMAL toStreamD #-}
485333toStreamD :: forall m a. (Monad m, Storable a) => Array a -> D.Stream m a
486−toStreamD Array{..} =
487− let p = unsafeForeignPtrToPtr aStart
488− in D.Stream step p
489−
490− where
491−
492− {-# INLINE_LATE step #-}
493− step _ p | p == aEnd = return D.Stop
494− step _ p = do
495− -- unsafeInlineIO allows us to run this in Identity monad for pure
496− -- toList/foldr case which makes them much faster due to not
497− -- accumulating the list and fusing better with the pure consumers.
498− --
499− -- This should be safe as the array contents are guaranteed to be
500− -- evaluated/written to before we peek at them.
501− let !x = unsafeInlineIO $ do
502− r <- peek p
503− touchForeignPtr aStart
504− return r
505− return $ D.Yield x (p `plusPtr` sizeOf (undefined :: a))
334+toStreamD arr = MA.toStreamD (unsafeThaw arr)
506335
507336{-# INLINE toStreamK #-}
508337toStreamK :: forall t m a. (K.IsStream t, Storable a) => Array a -> t m a
509−toStreamK Array{..} =
510− let p = unsafeForeignPtrToPtr aStart
511− in go p
512−
513− where
514−
515− go p | p == aEnd = K.nil
516− | otherwise =
517− -- See Note in toStreamD.
518− let !x = unsafeInlineIO $ do
519− r <- peek p
520− touchForeignPtr aStart
521− return r
522− in x `K.cons` go (p `plusPtr` sizeOf (undefined :: a))
338+toStreamK arr = MA.toStreamK (unsafeThaw arr)
523339
524340{-# INLINE_NORMAL toStreamDRev #-}
525341toStreamDRev :: forall m a. (Monad m, Storable a) => Array a -> D.Stream m a
526−toStreamDRev Array{..} =
527− let p = aEnd `plusPtr` negate (sizeOf (undefined :: a))
528− in D.Stream step p
529−
530− where
531−
532− {-# INLINE_LATE step #-}
533− step _ p | p < unsafeForeignPtrToPtr aStart = return D.Stop
534− step _ p = do
535− -- See comments in toStreamD for why we use unsafeInlineIO
536− let !x = unsafeInlineIO $ do
537− r <- peek p
538− touchForeignPtr aStart
539− return r
540− return $ D.Yield x (p `plusPtr` negate (sizeOf (undefined :: a)))
342+toStreamDRev arr = MA.toStreamDRev (unsafeThaw arr)
541343
542344{-# INLINE toStreamKRev #-}
543345toStreamKRev :: forall t m a. (K.IsStream t, Storable a) => Array a -> t m a
544−toStreamKRev Array {..} =
545− let p = aEnd `plusPtr` negate (sizeOf (undefined :: a))
546− in go p
547−
548− where
549−
550− go p | p < unsafeForeignPtrToPtr aStart = K.nil
551− | otherwise =
552− let !x = unsafeInlineIO $ do
553− r <- peek p
554− touchForeignPtr aStart
555− return r
556− in x `K.cons` go (p `plusPtr` negate (sizeOf (undefined :: a)))
346+toStreamKRev arr = MA.toStreamKRev (unsafeThaw arr)
557347
558348{-# INLINE_NORMAL foldl' #-}
559349foldl' :: forall a b. Storable a => (b -> a -> b) -> b -> Array a -> b
560−foldl' f z arr = runIdentity $ D.foldl' f z $ toStreamD arr
350+foldl' f z arr = MA.foldl' f z (unsafeThaw arr)
561351
562352{-# INLINE_NORMAL foldr #-}
563353foldr :: Storable a => (a -> b -> b) -> b -> Array a -> b
564−foldr f z arr = runIdentity $ D.foldr f z $ toStreamD arr
354+foldr f z arr = MA.foldr f z (unsafeThaw arr)
565355
566−-------------------------------------------------------------------------------
567−-- Instances
568−-------------------------------------------------------------------------------
356+-- | Create two slices of an array without copying the original array. The
357+-- specified index @i@ is the first index of the second slice.
358+--
359+-- @since 0.7.0
360+splitAt :: forall a. Storable a => Int -> Array a -> (Array a, Array a)
361+splitAt i arr = (unsafeFreeze a, unsafeFreeze b)
362+ where
363+ (a, b) = MA.splitAt i (unsafeThaw arr)
569364
570−{-# INLINE_NORMAL writeNAllocWith #-}
571−writeNAllocWith :: forall m a. (MonadIO m, Storable a)
572− => (Int -> IO (Array a)) -> Int -> Fold m a (Array a)
573−writeNAllocWith alloc n = Fold step initial extract
365+-- | Convert an 'Array' into a list.
366+--
367+-- @since 0.7.0
368+{-# INLINE toList #-}
369+toList :: Storable a => Array a -> [a]
370+toList = MA.toList . unsafeThaw
574371
575− where
372+{-# INLINE_NORMAL unlines #-}
373+unlines :: forall m a. (MonadIO m, Storable a)
374+ => a -> D.Stream m (Array a) -> D.Stream m a
375+unlines sep str = MA.unlines sep (D.map unsafeThaw str)
576376
577− initial = liftIO $ alloc (max n 0)
578− step arr@(Array _ end bound) _ | end == bound = return arr
579− step (Array start end bound) x = do
580− liftIO $ poke end x
581− return $ Array start (end `plusPtr` sizeOf (undefined :: a)) bound
582− -- XXX note that shirkToFit does not maintain alignment, in case we are
583− -- using aligned allocation.
584− extract = return -- liftIO . shrinkToFit
377+-------------------------------------------------------------------------------
378+-- Folds
379+-------------------------------------------------------------------------------
585380
586381-- | @writeN n@ folds a maximum of @n@ elements from the input stream to an
587382-- 'Array'.
@@ -589,7 +384,7 @@ writeNAllocWith alloc n = Fold step initial extract
589384-- @since 0.7.0
590385{-# INLINE_NORMAL writeN #-}
591386writeN :: forall m a. (MonadIO m, Storable a) => Int -> Fold m a (Array a)
592−writeN = writeNAllocWith newArray
387+writeN = fmap unsafeFreeze . MA.writeN
593388
594389-- | @writeNAligned alignment n@ folds a maximum of @n@ elements from the input
595390-- stream to an 'Array' aligned to the given size.
@@ -599,7 +394,7 @@ writeN = writeNAllocWith newArray
599394{-# INLINE_NORMAL writeNAligned #-}
600395writeNAligned :: forall m a. (MonadIO m, Storable a)
601396 => Int -> Int -> Fold m a (Array a)
602−writeNAligned alignSize = writeNAllocWith (newArrayAligned alignSize)
397+writeNAligned alignSize = fmap unsafeFreeze . MA.writeNAligned alignSize
603398
604399-- | @writeNAlignedUnmanaged n@ folds a maximum of @n@ elements from the input
605400-- stream to an 'Array' aligned to the given size and using unmanaged memory.
@@ -612,11 +407,7 @@ writeNAligned alignSize = writeNAllocWith (newArrayAligned alignSize)
612407writeNAlignedUnmanaged :: forall m a. (MonadIO m, Storable a)
613408 => Int -> Int -> Fold m a (Array a)
614409writeNAlignedUnmanaged alignSize =
615− writeNAllocWith (newArrayAlignedUnmanaged alignSize)
616−
617−data ArrayUnsafe a = ArrayUnsafe
618− {-# UNPACK #-} !(ForeignPtr a) -- first address
619− {-# UNPACK #-} !(Ptr a) -- first unused address
410+ fmap unsafeFreeze . MA.writeNAlignedUnmanaged alignSize
620411
621412-- | Like 'writeN' but does not check the array bounds when writing. The fold
622413-- driver must not call the step function more than 'n' times otherwise it will
@@ -628,17 +419,7 @@ data ArrayUnsafe a = ArrayUnsafe
628419{-# INLINE_NORMAL writeNUnsafe #-}
629420writeNUnsafe :: forall m a. (MonadIO m, Storable a)
630421 => Int -> Fold m a (Array a)
631−writeNUnsafe n = Fold step initial extract
632−
633− where
634−
635− initial = do
636− (Array start end _) <- liftIO $ newArray (max n 0)
637− return $ ArrayUnsafe start end
638− step (ArrayUnsafe start end) x = do
639− liftIO $ poke end x
640− return $ ArrayUnsafe start (end `plusPtr` sizeOf (undefined :: a))
641− extract (ArrayUnsafe start end) = return $ Array start end end -- liftIO . shrinkToFit
422+writeNUnsafe n = unsafeFreeze <$> MA.writeNUnsafe n
642423
643424-- XXX The realloc based implementation needs to make one extra copy if we use
644425-- shrinkToFit. On the other hand, the stream of arrays implementation may
@@ -656,25 +437,8 @@ writeNUnsafe n = Fold step initial extract
656437toArrayMinChunk :: forall m a. (MonadIO m, Storable a)
657438 => Int -> Int -> Fold m a (Array a)
658439-- toArrayMinChunk n = FL.mapM spliceArrays $ toArraysOf n
659−toArrayMinChunk alignSize elemCount = Fold step initial extract
660−
661− where
662−
663− insertElem (Array start end bound) x = do
664− liftIO $ poke end x
665− return $ Array start (end `plusPtr` sizeOf (undefined :: a)) bound
666−
667− initial = do
668− when (elemCount < 0) $ error "toArrayMinChunk: elemCount is negative"
669− liftIO $ newArrayAligned alignSize elemCount
670− step arr@(Array start end bound) x | end == bound = do
671− let p = unsafeForeignPtrToPtr start
672− oldSize = end `minusPtr` p
673− newSize = max (oldSize * 2) 1
674− arr1 <- liftIO $ reallocAligned alignSize newSize arr
675− insertElem arr1 x
676− step arr x = insertElem arr x
677− extract = liftIO . shrinkToFit
440+toArrayMinChunk alignSize elemCount =
441+ unsafeFreeze <$> MA.toArrayMinChunk alignSize elemCount
678442
679443-- | Fold the whole input to a single array.
680444--
@@ -683,9 +447,7 @@ toArrayMinChunk alignSize elemCount = Fold step initial extract
683447-- @since 0.7.0
684448{-# INLINE write #-}
685449write :: forall m a. (MonadIO m, Storable a) => Fold m a (Array a)
686−write = toArrayMinChunk (alignment (undefined :: a))
687− (bytesToElemCount (undefined :: a)
688− (mkChunkSize 1024))
450+write = fmap unsafeFreeze MA.write
689451
690452-- | Like 'write' but the array memory is aligned according to the specified
691453-- alignment size. This could be useful when we have specific alignment, for
@@ -697,202 +459,16 @@ write = toArrayMinChunk (alignment (undefined :: a))
697459{-# INLINE writeAligned #-}
698460writeAligned :: forall m a. (MonadIO m, Storable a)
699461 => Int -> Fold m a (Array a)
700−writeAligned alignSize =
701− toArrayMinChunk alignSize
702− (bytesToElemCount (undefined :: a)
703− (mkChunkSize 1024))
704−
705−{-# INLINE_NORMAL fromStreamDN #-}
706−fromStreamDN :: forall m a. (MonadIO m, Storable a)
707− => Int -> D.Stream m a -> m (Array a)
708−fromStreamDN limit str = do
709− arr <- liftIO $ newArray limit
710− end <- D.foldlM' fwrite (return $ aEnd arr) $ D.take limit str
711− return $ arr {aEnd = end}
712−
713− where
714−
715− fwrite ptr x = do
716− liftIO $ poke ptr x
717− return $ ptr `plusPtr` sizeOf (undefined :: a)
718−
719−data GroupState s start end bound
720− = GroupStart s
721− | GroupBuffer s start end bound
722− | GroupYield start end bound (GroupState s start end bound)
723− | GroupFinish
724−
725−-- | @fromStreamArraysOf n stream@ groups the input stream into a stream of
726−-- arrays of size n.
727−{-# INLINE_NORMAL fromStreamDArraysOf #-}
728−fromStreamDArraysOf :: forall m a. (MonadIO m, Storable a)
729− => Int -> D.Stream m a -> D.Stream m (Array a)
730−-- fromStreamDArraysOf n str = D.groupsOf n (writeN n) str
731−fromStreamDArraysOf n (D.Stream step state) =
732− D.Stream step' (GroupStart state)
733−
734− where
735−
736− {-# INLINE_LATE step' #-}
737− step' _ (GroupStart st) = do
738− when (n <= 0) $
739− -- XXX we can pass the module string from the higher level API
740− error $ "Streamly.Internal.Memory.Array.Types.fromStreamDArraysOf: the size of "
741− ++ "arrays [" ++ show n ++ "] must be a natural number"
742− Array start end bound <- liftIO $ newArray n
743− return $ D.Skip (GroupBuffer st start end bound)
744−
745− step' gst (GroupBuffer st start end bound) = do
746− r <- step (adaptState gst) st
747− case r of
748− D.Yield x s -> do
749− liftIO $ poke end x
750− let end' = end `plusPtr` sizeOf (undefined :: a)
751− return $
752− if end' >= bound
753− then D.Skip (GroupYield start end' bound (GroupStart s))
754− else D.Skip (GroupBuffer s start end' bound)
755− D.Skip s -> return $ D.Skip (GroupBuffer s start end bound)
756− D.Stop -> return $ D.Skip (GroupYield start end bound GroupFinish)
757−
758− step' _ (GroupYield start end bound next) =
759− return $ D.Yield (Array start end bound) next
760−
761− step' _ GroupFinish = return D.Stop
462+writeAligned = fmap unsafeFreeze . MA.writeAligned
762463
763−-- XXX concatMap does not seem to have the best possible performance so we have
764−-- a custom way to concat arrays.
765−data FlattenState s a =
766− OuterLoop s
767− | InnerLoop s !(ForeignPtr a) !(Ptr a) !(Ptr a)
768−
769−{-# INLINE_NORMAL flattenArrays #-}
770−flattenArrays :: forall m a. (MonadIO m, Storable a)
771− => D.Stream m (Array a) -> D.Stream m a
772−flattenArrays (D.Stream step state) = D.Stream step' (OuterLoop state)
773−
774− where
775−
776− {-# INLINE_LATE step' #-}
777− step' gst (OuterLoop st) = do
778− r <- step (adaptState gst) st
779− return $ case r of
780− D.Yield Array{..} s ->
781− let p = unsafeForeignPtrToPtr aStart
782− in D.Skip (InnerLoop s aStart p aEnd)
783− D.Skip s -> D.Skip (OuterLoop s)
784− D.Stop -> D.Stop
785−
786− step' _ (InnerLoop st _ p end) | p == end =
787− return $ D.Skip $ OuterLoop st
788−
789− step' _ (InnerLoop st startf p end) = do
790− x <- liftIO $ do
791− r <- peek p
792− touchForeignPtr startf
793− return r
794− return $ D.Yield x (InnerLoop st startf
795− (p `plusPtr` sizeOf (undefined :: a)) end)
796−
797−{-# INLINE_NORMAL flattenArraysRev #-}
798−flattenArraysRev :: forall m a. (MonadIO m, Storable a)
799− => D.Stream m (Array a) -> D.Stream m a
800−flattenArraysRev (D.Stream step state) = D.Stream step' (OuterLoop state)
801−
802− where
803−
804− {-# INLINE_LATE step' #-}
805− step' gst (OuterLoop st) = do
806− r <- step (adaptState gst) st
807− return $ case r of
808− D.Yield Array{..} s ->
809− let p = aEnd `plusPtr` negate (sizeOf (undefined :: a))
810− -- XXX we do not need aEnd
811− in D.Skip (InnerLoop s aStart p aEnd)
812− D.Skip s -> D.Skip (OuterLoop s)
813− D.Stop -> D.Stop
814−
815− step' _ (InnerLoop st start p _) | p < unsafeForeignPtrToPtr start =
816− return $ D.Skip $ OuterLoop st
817−
818− step' _ (InnerLoop st startf p end) = do
819− x <- liftIO $ do
820− r <- peek p
821− touchForeignPtr startf
822− return r
823− return $ D.Yield x (InnerLoop st startf
824− (p `plusPtr` negate (sizeOf (undefined :: a))) end)
825−
826−-- CAUTION: a very large number (millions) of arrays can degrade performance
827−-- due to GC overhead because we need to buffer the arrays before we flatten
828−-- all the arrays.
829−--
830−-- We could take the approach of doubling the memory allocation on each
831−-- overflow. This would result in more or less the same amount of copying as in
832−-- the chunking approach. However, if we have to shrink in the end then it may
833−-- result in an extra copy of the entire data.
834−--
835−{-# INLINE fromStreamD #-}
836−fromStreamD :: (MonadIO m, Storable a) => D.Stream m a -> m (Array a)
837−fromStreamD m = do
838− let s = fromStreamDArraysOf defaultChunkSize m
839− buffered <- D.foldr K.cons K.nil s
840− len <- K.foldl' (+) 0 (K.map length buffered)
841− fromStreamDN len $ flattenArrays $ D.fromStreamK buffered
842−{-
843−fromStreamD m = runFold write m
844− where
845− runFold (Fold step begin done) = D.foldlMx' step begin done
846−-}
847−
848−-- Use foldr/build fusion to fuse with list consumers
849−-- This can be useful when using the IsList instance
850−{-# INLINE_LATE toListFB #-}
851−toListFB :: forall a b. Storable a => (a -> b -> b) -> b -> Array a -> b
852−toListFB c n Array{..} = go (unsafeForeignPtrToPtr aStart)
853− where
854−
855− go p | p == aEnd = n
856− go p =
857− -- unsafeInlineIO allows us to run this in Identity monad for pure
858− -- toList/foldr case which makes them much faster due to not
859− -- accumulating the list and fusing better with the pure consumers.
860− --
861− -- This should be safe as the array contents are guaranteed to be
862− -- evaluated/written to before we peek at them.
863− let !x = unsafeInlineIO $ do
864− r <- peek p
865− touchForeignPtr aStart
866− return r
867− in c x (go (p `plusPtr` sizeOf (undefined :: a)))
868−
869−-- | Convert an 'Array' into a list.
870−--
871−-- @since 0.7.0
872−{-# INLINE toList #-}
873−toList :: Storable a => Array a -> [a]
874−toList s = build (\c n -> toListFB c n s)
464+-------------------------------------------------------------------------------
465+-- Instances
466+-------------------------------------------------------------------------------
875467
876468instance (Show a, Storable a) => Show (Array a) where
877469 {-# INLINE showsPrec #-}
878470 showsPrec _ = shows . toList
879471
880−-- | Create an 'Array' from the first N elements of a list. The array is
881−-- allocated to size N, if the list terminates before N elements then the
882−-- array may hold less than N elements.
883−--
884−-- @since 0.7.0
885−{-# INLINABLE fromListN #-}
886−fromListN :: Storable a => Int -> [a] -> Array a
887−fromListN n xs = unsafePerformIO $ fromStreamDN n $ D.fromList xs
888−
889−-- | Create an 'Array' from a list. The list must be of finite size.
890−--
891−-- @since 0.7.0
892−{-# INLINABLE fromList #-}
893−fromList :: Storable a => [a] -> Array a
894−fromList xs = unsafePerformIO $ fromStreamD $ D.fromList xs
895−
896472instance (Storable a, Read a, Show a) => Read (Array a) where
897473 {-# INLINE readPrec #-}
898474 readPrec = fromList <$> readPrec
@@ -912,29 +488,11 @@ instance Storable a => IsList (Array a) where
912488 {-# INLINE toList #-}
913489 toList = toList
914490
915−{-# INLINE arrcmp #-}
916−arrcmp :: Array a -> Array a -> Bool
917−arrcmp arr1 arr2 =
918− let !res = unsafeInlineIO $ do
919− let ptr1 = unsafeForeignPtrToPtr $ aStart arr1
920− let ptr2 = unsafeForeignPtrToPtr $ aStart arr2
921− let len1 = aEnd arr1 `minusPtr` ptr1
922− let len2 = aEnd arr2 `minusPtr` ptr2
923−
924− if len1 == len2
925− then do
926− r <- memcmp (castPtr ptr1) (castPtr ptr2) len1
927− touchForeignPtr $ aStart arr1
928− touchForeignPtr $ aStart arr2
929− return r
930− else return False
931− in res
932−
933491-- XXX we are assuming that Storable equality means element equality. This may
934492-- or may not be correct? arrcmp is 40% faster compared to stream equality.
935493instance (Storable a, Eq a) => Eq (Array a) where
936494 {-# INLINE (==) #-}
937− (==) = arrcmp
495+ a == b = unsafeThaw a == unsafeThaw b
938496 -- arr1 == arr2 = runIdentity $ D.eqBy (==) (toStreamD arr1) (toStreamD arr2)
939497
940498instance (Storable a, NFData a) => NFData (Array a) where
@@ -943,8 +501,7 @@ instance (Storable a, NFData a) => NFData (Array a) where
943501
944502instance (Storable a, Ord a) => Ord (Array a) where
945503 {-# INLINE compare #-}
946− compare arr1 arr2 = unsafePerformIO $
947− D.cmpBy compare (toStreamD arr1) (toStreamD arr2)
504+ compare a b = compare (unsafeThaw a) (unsafeThaw b)
948505
949506 -- Default definitions defined in base do not have an INLINE on them, so we
950507 -- replicate them here with an INLINE.
@@ -1005,27 +562,6 @@ instance Foldable Array where
1005562-- Semigroup and Monoid
1006563-------------------------------------------------------------------------------
1007564
1008−-- Splice two immutable arrays creating a new array.
1009−{-# INLINE spliceTwo #-}
1010−spliceTwo :: (MonadIO m, Storable a) => Array a -> Array a -> m (Array a)
1011−spliceTwo arr1 arr2 = do
1012− let src1 = unsafeForeignPtrToPtr (aStart arr1)
1013− src2 = unsafeForeignPtrToPtr (aStart arr2)
1014− len1 = aEnd arr1 `minusPtr` src1
1015− len2 = aEnd arr2 `minusPtr` src2
1016−
1017− arr <- liftIO $ newArray (len1 + len2)
1018− let dst = unsafeForeignPtrToPtr (aStart arr)
1019−
1020− -- XXX Should we use copyMutableByteArray# instead? Is there an overhead to
1021− -- ccall?
1022− liftIO $ do
1023− memcpy (castPtr dst) (castPtr src1) len1
1024− touchForeignPtr (aStart arr1)
1025− memcpy (castPtr (dst `plusPtr` len1)) (castPtr src2) len2
1026− touchForeignPtr (aStart arr2)
1027− return arr { aEnd = dst `plusPtr` (len1 + len2) }
1028−
1029565instance Storable a => Semigroup (Array a) where
1030566 arr1 <> arr2 = unsafePerformIO $ spliceTwo arr1 arr2
1031567
@@ -1037,372 +573,8 @@ nil ::
1037573 Storable a =>
1038574#endif
1039575 Array a
1040−nil = Array nullForeignPtr (Ptr nullAddr#) (Ptr nullAddr#)
576+nil = Array nullForeignPtr (Ptr nullAddr#)
1041577
1042578instance Storable a => Monoid (Array a) where
1043579 mempty = nil
1044580 mappend = (<>)
1045−
1046−-------------------------------------------------------------------------------
1047−-- IO
1048−-------------------------------------------------------------------------------
1049−
1050−-- | GHC memory management allocation header overhead
1051−allocOverhead :: Int
1052−allocOverhead = 2 * sizeOf (undefined :: Int)
1053−
1054−mkChunkSize :: Int -> Int
1055−mkChunkSize n = let size = n - allocOverhead in max size 0
1056−
1057−mkChunkSizeKB :: Int -> Int
1058−mkChunkSizeKB n = mkChunkSize (n * k)
1059− where k = 1024
1060−
1061−-- | Default maximum buffer size in bytes, for reading from and writing to IO
1062−-- devices, the value is 32KB minus GHC allocation overhead, which is a few
1063−-- bytes, so that the actual allocation is 32KB.
1064−defaultChunkSize :: Int
1065−defaultChunkSize = mkChunkSizeKB 32
1066−
1067−{-# INLINE_NORMAL unlines #-}
1068−unlines :: forall m a. (MonadIO m, Storable a)
1069− => a -> D.Stream m (Array a) -> D.Stream m a
1070−unlines sep (D.Stream step state) = D.Stream step' (OuterLoop state)
1071− where
1072− {-# INLINE_LATE step' #-}
1073− step' gst (OuterLoop st) = do
1074− r <- step (adaptState gst) st
1075− return $ case r of
1076− D.Yield Array{..} s ->
1077− let p = unsafeForeignPtrToPtr aStart
1078− in D.Skip (InnerLoop s aStart p aEnd)
1079− D.Skip s -> D.Skip (OuterLoop s)
1080− D.Stop -> D.Stop
1081−
1082− step' _ (InnerLoop st _ p end) | p == end =
1083− return $ D.Yield sep $ OuterLoop st
1084−
1085− step' _ (InnerLoop st startf p end) = do
1086− x <- liftIO $ do
1087− r <- peek p
1088− touchForeignPtr startf
1089− return r
1090− return $ D.Yield x (InnerLoop st startf
1091− (p `plusPtr` sizeOf (undefined :: a)) end)
1092−
1093−-- Splice an array into a pre-reserved mutable array. The user must ensure
1094−-- that there is enough space in the mutable array.
1095−{-# INLINE spliceWith #-}
1096−spliceWith :: (MonadIO m) => Array a -> Array a -> m (Array a)
1097−spliceWith dst@(Array _ end bound) src = liftIO $ do
1098− let srcLen = byteLength src
1099− if end `plusPtr` srcLen > bound
1100− then error "Bug: spliceIntoUnsafe: Not enough space in the target array"
1101− else
1102− withForeignPtr (aStart dst) $ \_ ->
1103− withForeignPtr (aStart src) $ \psrc -> do
1104− let pdst = aEnd dst
1105− memcpy (castPtr pdst) (castPtr psrc) srcLen
1106− return $ dst { aEnd = pdst `plusPtr` srcLen }
1107−
1108−-- Splice a new array into a preallocated mutable array, doubling the space if
1109−-- there is no space in the target array.
1110−{-# INLINE spliceWithDoubling #-}
1111−spliceWithDoubling :: (MonadIO m, Storable a)
1112− => Array a -> Array a -> m (Array a)
1113−spliceWithDoubling dst@(Array start end bound) src = do
1114− assert (end <= bound) (return ())
1115− let srcLen = aEnd src `minusPtr` unsafeForeignPtrToPtr (aStart src)
1116−
1117− dst1 <-
1118− if end `plusPtr` srcLen >= bound
1119− then do
1120− let oldStart = unsafeForeignPtrToPtr start
1121− oldSize = end `minusPtr` oldStart
1122− newSize = max (oldSize * 2) (oldSize + srcLen)
1123− liftIO $ realloc newSize dst
1124− else return dst
1125− spliceWith dst1 src
1126−
1127−data SpliceState s arr
1128− = SpliceInitial s
1129− | SpliceBuffering s arr
1130− | SpliceYielding arr (SpliceState s arr)
1131− | SpliceFinish
1132−
1133−-- XXX can use general grouping combinators to achieve this?
1134−-- | Coalesce adjacent arrays in incoming stream to form bigger arrays of a
1135−-- maximum specified size. Note that if a single array is bigger than the
1136−-- specified size we do not split it to fit. When we coalesce multiple arrays
1137−-- if the size would exceed the specified size we do not coalesce therefore the
1138−-- actual array size may be less than the specified chunk size.
1139−--
1140−-- @since 0.7.0
1141−{-# INLINE_NORMAL packArraysChunksOf #-}
1142−packArraysChunksOf :: (MonadIO m, Storable a)
1143− => Int -> D.Stream m (Array a) -> D.Stream m (Array a)
1144−packArraysChunksOf n (D.Stream step state) =
1145− D.Stream step' (SpliceInitial state)
1146−
1147− where
1148−
1149− {-# INLINE_LATE step' #-}
1150− step' gst (SpliceInitial st) = do
1151− when (n <= 0) $
1152− -- XXX we can pass the module string from the higher level API
1153− error $ "Streamly.Internal.Memory.Array.Types.packArraysChunksOf: the size of "
1154− ++ "arrays [" ++ show n ++ "] must be a natural number"
1155− r <- step gst st
1156− case r of
1157− D.Yield arr s -> return $
1158− let len = byteLength arr
1159− in if len >= n
1160− then D.Skip (SpliceYielding arr (SpliceInitial s))
1161− else D.Skip (SpliceBuffering s arr)
1162− D.Skip s -> return $ D.Skip (SpliceInitial s)
1163− D.Stop -> return D.Stop
1164−
1165− step' gst (SpliceBuffering st buf) = do
1166− r <- step gst st
1167− case r of
1168− D.Yield arr s -> do
1169− let len = byteLength buf + byteLength arr
1170− if len > n
1171− then return $
1172− D.Skip (SpliceYielding buf (SpliceBuffering s arr))
1173− else do
1174− buf' <- if byteCapacity buf < n
1175− then liftIO $ realloc n buf
1176− else return buf
1177− buf'' <- spliceWith buf' arr
1178− return $ D.Skip (SpliceBuffering s buf'')
1179− D.Skip s -> return $ D.Skip (SpliceBuffering s buf)
1180− D.Stop -> return $ D.Skip (SpliceYielding buf SpliceFinish)
1181−
1182− step' _ SpliceFinish = return D.Stop
1183−
1184− step' _ (SpliceYielding arr next) = return $ D.Yield arr next
1185−
1186−-- XXX instead of writing two different versions of this operation, we should
1187−-- write it as a pipe.
1188−{-# INLINE_NORMAL lpackArraysChunksOf #-}
1189−lpackArraysChunksOf :: (MonadIO m, Storable a)
1190− => Int -> Fold m (Array a) () -> Fold m (Array a) ()
1191−lpackArraysChunksOf n (Fold step1 initial1 extract1) =
1192− Fold step initial extract
1193−
1194− where
1195−
1196− initial = do
1197− when (n <= 0) $
1198− -- XXX we can pass the module string from the higher level API
1199− error $ "Streamly.Internal.Memory.Array.Types.packArraysChunksOf: the size of "
1200− ++ "arrays [" ++ show n ++ "] must be a natural number"
1201− r1 <- initial1
1202− return (Tuple' Nothing r1)
1203−
1204− extract (Tuple' Nothing r1) = extract1 r1
1205− extract (Tuple' (Just buf) r1) = do
1206− r <- step1 r1 buf
1207− extract1 r
1208−
1209− step (Tuple' Nothing r1) arr =
1210− let len = byteLength arr
1211− in if len >= n
1212− then do
1213− r <- step1 r1 arr
1214− extract1 r
1215− r1' <- initial1
1216− return (Tuple' Nothing r1')
1217− else return (Tuple' (Just arr) r1)
1218−
1219− step (Tuple' (Just buf) r1) arr = do
1220− let len = byteLength buf + byteLength arr
1221− buf' <- if byteCapacity buf < len
1222− then liftIO $ realloc (max n len) buf
1223− else return buf
1224− buf'' <- spliceWith buf' arr
1225−
1226− if len >= n
1227− then do
1228− r <- step1 r1 buf''
1229− extract1 r
1230− r1' <- initial1
1231− return (Tuple' Nothing r1')
1232− else return (Tuple' (Just buf'') r1)
1233−
1234−#if !defined(mingw32_HOST_OS)
1235−data GatherState s arr
1236− = GatherInitial s
1237− | GatherBuffering s arr Int
1238− | GatherYielding arr (GatherState s arr)
1239− | GatherFinish
1240−
1241−-- | @groupIOVecsOf maxBytes maxEntries@ groups arrays in the incoming stream
1242−-- to create a stream of 'IOVec' arrays with a maximum of @maxBytes@ bytes in
1243−-- each array and a maximum of @maxEntries@ entries in each array.
1244−--
1245−-- @since 0.7.0
1246−{-# INLINE_NORMAL groupIOVecsOf #-}
1247−groupIOVecsOf :: MonadIO m
1248− => Int -> Int -> D.Stream m (Array a) -> D.Stream m (Array IOVec)
1249−groupIOVecsOf n maxIOVLen (D.Stream step state) =
1250− D.Stream step' (GatherInitial state)
1251−
1252− where
1253−
1254− {-# INLINE_LATE step' #-}
1255− step' gst (GatherInitial st) = do
1256− when (n <= 0) $
1257− -- XXX we can pass the module string from the higher level API
1258− error $ "Streamly.Internal.Memory.Array.Types.groupIOVecsOf: the size of "
1259− ++ "groups [" ++ show n ++ "] must be a natural number"
1260− when (maxIOVLen <= 0) $
1261− -- XXX we can pass the module string from the higher level API
1262− error $ "Streamly.Internal.Memory.Array.Types.groupIOVecsOf: the number of "
1263− ++ "IOVec entries [" ++ show n ++ "] must be a natural number"
1264− r <- step (adaptState gst) st
1265− case r of
1266− D.Yield arr s -> do
1267− let p = unsafeForeignPtrToPtr (aStart arr)
1268− len = byteLength arr
1269− iov <- liftIO $ newArray maxIOVLen
1270− iov' <- liftIO $ unsafeSnoc iov (IOVec (castPtr p)
1271− (fromIntegral len))
1272− if len >= n
1273− then return $ D.Skip (GatherYielding iov' (GatherInitial s))
1274− else return $ D.Skip (GatherBuffering s iov' len)
1275− D.Skip s -> return $ D.Skip (GatherInitial s)
1276− D.Stop -> return D.Stop
1277−
1278− step' gst (GatherBuffering st iov len) = do
1279− r <- step (adaptState gst) st
1280− case r of
1281− D.Yield arr s -> do
1282− let p = unsafeForeignPtrToPtr (aStart arr)
1283− alen = byteLength arr
1284− len' = len + alen
1285− if len' > n || length iov >= maxIOVLen
1286− then do
1287− iov' <- liftIO $ newArray maxIOVLen
1288− iov'' <- liftIO $ unsafeSnoc iov' (IOVec (castPtr p)
1289− (fromIntegral alen))
1290− return $ D.Skip (GatherYielding iov
1291− (GatherBuffering s iov'' alen))
1292− else do
1293− iov' <- liftIO $ unsafeSnoc iov (IOVec (castPtr p)
1294− (fromIntegral alen))
1295− return $ D.Skip (GatherBuffering s iov' len')
1296− D.Skip s -> return $ D.Skip (GatherBuffering s iov len)
1297− D.Stop -> return $ D.Skip (GatherYielding iov GatherFinish)
1298−
1299− step' _ GatherFinish = return D.Stop
1300−
1301− step' _ (GatherYielding iov next) = return $ D.Yield iov next
1302−#endif
1303−
1304−-- | Create two slices of an array without copying the original array. The
1305−-- specified index @i@ is the first index of the second slice.
1306−--
1307−-- @since 0.7.0
1308−splitAt :: forall a. Storable a => Int -> Array a -> (Array a, Array a)
1309−splitAt i arr@Array{..} =
1310− let maxIndex = length arr - 1
1311− in if i < 0
1312− then error "sliceAt: negative array index"
1313− else if i > maxIndex
1314− then error $ "sliceAt: specified array index " ++ show i
1315− ++ " is beyond the maximum index " ++ show maxIndex
1316− else let off = i * sizeOf (undefined :: a)
1317− p = unsafeForeignPtrToPtr aStart `plusPtr` off
1318− in ( Array
1319− { aStart = aStart
1320− , aEnd = p
1321− , aBound = p
1322− }
1323− , Array
1324− { aStart = aStart `plusForeignPtr` off
1325− , aEnd = aEnd
1326− , aBound = aBound
1327− }
1328− )
1329−
1330−-- Drops the separator byte
1331−{-# INLINE breakOn #-}
1332−breakOn :: MonadIO m
1333− => Word8 -> Array Word8 -> m (Array Word8, Maybe (Array Word8))
1334−breakOn sep arr@Array{..} = liftIO $ do
1335− let p = unsafeForeignPtrToPtr aStart
1336− loc <- c_memchr p sep (fromIntegral $ aEnd `minusPtr` p)
1337− return $
1338− if loc == nullPtr
1339− then (arr, Nothing)
1340− else
1341− ( Array
1342− { aStart = aStart
1343− , aEnd = loc
1344− , aBound = loc
1345− }
1346− , Just $ Array
1347− { aStart = aStart `plusForeignPtr` (loc `minusPtr` p + 1)
1348− , aEnd = aEnd
1349− , aBound = aBound
1350− }
1351− )
1352−
1353−data SplitState s arr
1354− = Initial s
1355− | Buffering s arr
1356− | Splitting s arr
1357− | Yielding arr (SplitState s arr)
1358− | Finishing
1359−
1360−-- | Split a stream of arrays on a given separator byte, dropping the separator
1361−-- and coalescing all the arrays between two separators into a single array.
1362−--
1363−-- @since 0.7.0
1364−{-# INLINE_NORMAL splitOn #-}
1365−splitOn
1366− :: MonadIO m
1367− => Word8
1368− -> D.Stream m (Array Word8)
1369− -> D.Stream m (Array Word8)
1370−splitOn byte (D.Stream step state) = D.Stream step' (Initial state)
1371−
1372− where
1373−
1374− {-# INLINE_LATE step' #-}
1375− step' gst (Initial st) = do
1376− r <- step gst st
1377− case r of
1378− D.Yield arr s -> do
1379− (arr1, marr2) <- breakOn byte arr
1380− return $ case marr2 of
1381− Nothing -> D.Skip (Buffering s arr1)
1382− Just arr2 -> D.Skip (Yielding arr1 (Splitting s arr2))
1383− D.Skip s -> return $ D.Skip (Initial s)
1384− D.Stop -> return D.Stop
1385−
1386− step' gst (Buffering st buf) = do
1387− r <- step gst st
1388− case r of
1389− D.Yield arr s -> do
1390− (arr1, marr2) <- breakOn byte arr
1391− buf' <- spliceTwo buf arr1
1392− return $ case marr2 of
1393− Nothing -> D.Skip (Buffering s buf')
1394− Just x -> D.Skip (Yielding buf' (Splitting s x))
1395− D.Skip s -> return $ D.Skip (Buffering s buf)
1396− D.Stop -> return $
1397− if byteLength buf == 0
1398− then D.Stop
1399− else D.Skip (Yielding buf Finishing)
1400−
1401− step' _ (Splitting st buf) = do
1402− (arr1, marr2) <- breakOn byte buf
1403− return $ case marr2 of
1404− Nothing -> D.Skip $ Buffering st arr1
1405− Just arr2 -> D.Skip $ Yielding arr1 (Splitting st arr2)
1406−
1407− step' _ (Yielding arr next) = return $ D.Yield arr next
1408− step' _ Finishing = return D.Stop
src/Streamly/Internal/Memory/ArrayStream.hs+33−14
@@ -2,9 +2,9 @@
22
33-- |
44-- Module : Streamly.Internal.Memory.ArrayStream
5−-- Copyright : (c) 2019 Composewell Technologies
5+-- Copyright : (c) 2020 Composewell Technologies
66--
7−-- License : BSD3
7+-- License : BSD3-3-Clause
88-- Maintainer : streamly@composewell.com
99-- Stability : experimental
1010-- Portability : GHC
@@ -47,6 +47,7 @@ import Streamly.Internal.Data.Stream.StreamK.Type (IsStream)
4747
4848import qualified Streamly.Internal.Memory.Array as A
4949import qualified Streamly.Internal.Memory.Array.Types as A
50+import qualified Streamly.Internal.Memory.Mutable.Array.Types as MA
5051import qualified Streamly.Internal.Prelude as S
5152import qualified Streamly.Internal.Data.Stream.StreamD as D
5253import qualified Streamly.Internal.Data.Stream.Prelude as P
@@ -155,17 +156,35 @@ arraysOf n str =
155156-- stream.
156157{-# INLINE spliceArraysLenUnsafe #-}
157158spliceArraysLenUnsafe :: (MonadIO m, Storable a)
158− => Int -> SerialT m (Array a) -> m (Array a)
159+ => Int -> SerialT m (MA.Array a) -> m (MA.Array a)
159160spliceArraysLenUnsafe len buffered = do
160− arr <- liftIO $ A.newArray len
161− end <- S.foldlM' writeArr (return $ aEnd arr) buffered
162− return $ arr {aEnd = end}
161+ arr <- liftIO $ MA.newArray len
162+ end <- S.foldlM' writeArr (return $ MA.aEnd arr) buffered
163+ return $ arr {MA.aEnd = end}
163164
164165 where
165166
166− writeArr dst Array{..} =
167− liftIO $ withForeignPtr aStart $ \src -> do
168− let count = aEnd `minusPtr` src
167+ writeArr dst (MA.Array as ae _) =
168+ liftIO $ withForeignPtr as $ \src -> do
169+ let count = ae `minusPtr` src
170+ A.memcpy (castPtr dst) (castPtr src) count
171+ return $ dst `plusPtr` count
172+
173+{-# INLINE _spliceArrays #-}
174+_spliceArrays :: (MonadIO m, Storable a)
175+ => SerialT m (Array a) -> m (Array a)
176+_spliceArrays s = do
177+ buffered <- P.foldr S.cons S.nil s
178+ len <- S.sum (S.map length buffered)
179+ arr <- liftIO $ MA.newArray len
180+ end <- S.foldlM' writeArr (return $ MA.aEnd arr) s
181+ return $ A.unsafeFreeze $ arr {MA.aEnd = end}
182+
183+ where
184+
185+ writeArr dst (Array as ae) =
186+ liftIO $ withForeignPtr as $ \src -> do
187+ let count = ae `minusPtr` src
169188 A.memcpy (castPtr dst) (castPtr src) count
170189 return $ dst `plusPtr` count
171190
@@ -175,17 +194,17 @@ _spliceArraysBuffered :: (MonadIO m, Storable a)
175194_spliceArraysBuffered s = do
176195 buffered <- P.foldr S.cons S.nil s
177196 len <- S.sum (S.map length buffered)
178− spliceArraysLenUnsafe len s
197+ A.unsafeFreeze <$> spliceArraysLenUnsafe len (S.map A.unsafeThaw s)
179198
180199{-# INLINE spliceArraysRealloced #-}
181200spliceArraysRealloced :: forall m a. (MonadIO m, Storable a)
182201 => SerialT m (Array a) -> m (Array a)
183202spliceArraysRealloced s = do
184− let idst = liftIO $ A.newArray (A.bytesToElemCount (undefined :: a)
185− (A.mkChunkSizeKB 4))
203+ let idst = liftIO $ MA.newArray (A.bytesToElemCount (undefined :: a)
204+ (A.mkChunkSizeKB 4))
186205
187− arr <- S.foldlM' A.spliceWithDoubling idst s
188− liftIO $ A.shrinkToFit arr
206+ arr <- S.foldlM' MA.spliceWithDoubling idst (S.map A.unsafeThaw s)
207+ liftIO $ A.unsafeFreeze <$> MA.shrinkToFit arr
189208
190209-- | Given a stream of arrays, splice them all together to generate a single
191210-- array. The stream must be /finite/.
src/Streamly/Internal/Memory/Mutable/Array/Types.hsnew file+1440
@@ -0,0 +1,1440 @@
1+{-# LANGUAGE UnboxedTuples #-}
2+
3+#include "inline.hs"
4+
5+-- |
6+-- Module : Streamly.Internal.Memory.Mutable.Array.Types
7+-- Copyright : (c) 2020 Composewell Technologies
8+--
9+-- License : BSD3-3-Clause
10+-- Maintainer : streamly@composewell.com
11+-- Stability : experimental
12+-- Portability : GHC
13+--
14+module Streamly.Internal.Memory.Mutable.Array.Types
15+ (
16+ Array (..)
17+ , mutableArray
18+
19+ -- * Construction
20+ , withNewArray
21+ , newArray
22+ , newArrayAligned
23+ , newArrayAlignedUnmanaged
24+ , newArrayAlignedAllocWith
25+ , unsafeSnoc
26+ , snoc
27+ , spliceWith
28+ , spliceWithDoubling
29+ , spliceTwo
30+
31+ , fromAddr#
32+ , fromString#
33+ , fromList
34+ , fromListN
35+ , fromStreamDN
36+ -- , fromStreamD
37+
38+ -- * Streams of arrays
39+ , fromStreamDArraysOf
40+ , FlattenState (..) -- for inspection testing
41+ , flattenArrays
42+ , flattenArraysRev
43+ , packArraysChunksOf
44+ , SpliceState (..)
45+ , lpackArraysChunksOf
46+#if !defined(mingw32_HOST_OS)
47+ , groupIOVecsOf
48+#endif
49+ , splitOn
50+ , breakOn
51+
52+ -- * Elimination
53+ , unsafeIndexIO
54+ , unsafeIndex
55+ , length
56+ , byteLength
57+ , byteCapacity
58+ , foldl'
59+ , foldr
60+ , splitAt
61+
62+ , toStreamD
63+ , toStreamDRev
64+ , toStreamK
65+ , toStreamKRev
66+ , toList
67+
68+ , unlines
69+
70+ -- * Folds
71+ , toArrayMinChunk
72+ , writeNAllocWith
73+ , writeN
74+ , writeNUnsafe
75+ , ArrayUnsafe (..)
76+ , writeNAligned
77+ , writeNAlignedUnmanaged
78+ , write
79+ , writeAligned
80+
81+ -- * Utilities
82+ , defaultChunkSize
83+ , mkChunkSize
84+ , mkChunkSizeKB
85+ , unsafeInlineIO
86+ , realloc
87+ , shrinkToFit
88+ , memcpy
89+ , memcmp
90+ , bytesToElemCount
91+ )
92+where
93+
94+import Control.Exception (assert)
95+import Control.DeepSeq (NFData(..))
96+import Control.Monad (when, void)
97+import Control.Monad.IO.Class (MonadIO(..))
98+import Data.Functor.Identity (runIdentity)
99+import Data.Word (Word8)
100+import Foreign.C.String (CString)
101+import Foreign.C.Types (CSize(..), CInt(..))
102+import Foreign.ForeignPtr (withForeignPtr, touchForeignPtr)
103+import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
104+import Foreign.Ptr (plusPtr, minusPtr, castPtr, nullPtr)
105+import Foreign.Storable (Storable(..))
106+import GHC.Base (Addr#, nullAddr#, realWorld#, build)
107+import GHC.Exts (IsList, IsString(..))
108+import GHC.ForeignPtr (ForeignPtr(..), newForeignPtr_)
109+import GHC.IO (IO(IO), unsafePerformIO)
110+import GHC.Ptr (Ptr(..))
111+import Streamly.Internal.Data.Fold.Types (Fold(..))
112+import Streamly.Internal.Data.Strict (Tuple'(..))
113+import Streamly.Internal.Data.SVar (adaptState)
114+import Text.Read (readPrec, readListPrec, readListPrecDefault)
115+
116+import qualified Streamly.Memory.Malloc as Malloc
117+import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
118+import qualified Streamly.Internal.Data.Stream.StreamK as K
119+import qualified GHC.Exts as Exts
120+
121+import Prelude hiding (length, foldr, read, unlines, splitAt)
122+
123+#if __GLASGOW_HASKELL__ < 808
124+import Data.Semigroup (Semigroup(..))
125+#endif
126+
127+#if !defined(mingw32_HOST_OS)
128+import Streamly.FileSystem.FDIO (IOVec(..))
129+#endif
130+
131+#ifdef DEVBUILD
132+import qualified Data.Foldable as F
133+#endif
134+
135+#if MIN_VERSION_base(4,10,0)
136+import Foreign.ForeignPtr (plusForeignPtr)
137+#else
138+import GHC.Base (Int(..), plusAddr#)
139+import GHC.ForeignPtr (ForeignPtr(..))
140+plusForeignPtr :: ForeignPtr a -> Int -> ForeignPtr b
141+plusForeignPtr (ForeignPtr addr c) (I# d) = ForeignPtr (plusAddr# addr d) c
142+#endif
143+
144+-------------------------------------------------------------------------------
145+-- Design Notes
146+-------------------------------------------------------------------------------
147+
148+-- There are two goals that we want to fulfill with arrays. One, holding large
149+-- amounts of data in non-GC memory and the ability to pass this data to and
150+-- from the operating system without an extra copy overhead. Two, allow random
151+-- access to elements based on their indices. The first one falls in the
152+-- category of storage buffers while the second one falls in the category of
153+-- maps/multisets/hashmaps.
154+--
155+-- For the first requirement we use an array of Storables and store it in a
156+-- ForeignPtr. We can have both immutable and mutable variants of this array
157+-- using wrappers over the same underlying type.
158+--
159+-- For the second requirement, we need a separate type for arrays of
160+-- polymorphic values, for example vectors of handler functions, lookup tables.
161+-- We can call this type a "vector" in contrast to arrays. It should not
162+-- require Storable instance for the type. In that case we need to use an
163+-- Array# instead of a ForeignPtr. This type of array would not reduce the GC
164+-- overhead much because each element of the array still needs to be scanned by
165+-- the GC. However, it can store polymorphic elements and allow random access
166+-- to those. But in most cases random access means storage, and it means we
167+-- need to avoid GC scanning except in cases of trivially small storage. One
168+-- way to achieve that would be to put the array in a Compact region. However,
169+-- if and when we mutate this, we will have to use a manual GC copying out to
170+-- another CR and freeing the old one.
171+
172+-------------------------------------------------------------------------------
173+-- Array Data Type
174+-------------------------------------------------------------------------------
175+
176+-- We require that an array stores only Storable. Arrays are used for buffering
177+-- data while streams are used for processing. If you want something to be
178+-- buffered it better be Storable so that we can store it in non-GC memory.
179+--
180+-- We can use a "Storable" constraint in the Array type and the Constraint can
181+-- be automatically provided to a function that pattern matches on the Array
182+-- type. However, it has huge performance cost, so we do not use it.
183+-- XXX Can this cost be alleviated in GHC-8.10 specialization fix?
184+--
185+-- XXX Another way to not require the Storable constraint in array operations
186+-- is to store the elemSize in the array at construction and use that instead
187+-- of using sizeOf. Need to charaterize perf cost of this.
188+--
189+-- XXX rename the fields to "start, next, end".
190+--
191+data Array a =
192+#ifdef DEVBUILD
193+ Storable a =>
194+#endif
195+ Array
196+ { aStart :: {-# UNPACK #-} !(ForeignPtr a) -- first address
197+ , aEnd :: {-# UNPACK #-} !(Ptr a) -- first unused address
198+ , aBound :: {-# UNPACK #-} !(Ptr a) -- first address beyond allocated memory
199+ }
200+
201+{-# INLINE mutableArray #-}
202+mutableArray :: ForeignPtr a -> Ptr a -> Ptr a -> Array a
203+mutableArray = Array
204+
205+-------------------------------------------------------------------------------
206+-- Utility functions
207+-------------------------------------------------------------------------------
208+
209+foreign import ccall unsafe "string.h memcpy" c_memcpy
210+ :: Ptr Word8 -> Ptr Word8 -> CSize -> IO (Ptr Word8)
211+
212+foreign import ccall unsafe "string.h strlen" c_strlen
213+ :: CString -> IO CSize
214+
215+foreign import ccall unsafe "string.h memchr" c_memchr
216+ :: Ptr Word8 -> Word8 -> CSize -> IO (Ptr Word8)
217+
218+-- XXX we are converting Int to CSize
219+memcpy :: Ptr Word8 -> Ptr Word8 -> Int -> IO ()
220+memcpy dst src len = void (c_memcpy dst src (fromIntegral len))
221+
222+foreign import ccall unsafe "string.h memcmp" c_memcmp
223+ :: Ptr Word8 -> Ptr Word8 -> CSize -> IO CInt
224+
225+-- XXX we are converting Int to CSize
226+-- return True if the memory locations have identical contents
227+{-# INLINE memcmp #-}
228+memcmp :: Ptr Word8 -> Ptr Word8 -> Int -> IO Bool
229+memcmp p1 p2 len = do
230+ r <- c_memcmp p1 p2 (fromIntegral len)
231+ return $ r == 0
232+
233+{-# INLINE unsafeInlineIO #-}
234+unsafeInlineIO :: IO a -> a
235+unsafeInlineIO (IO m) = case m realWorld# of (# _, r #) -> r
236+
237+{-# INLINE bytesToElemCount #-}
238+bytesToElemCount :: Storable a => a -> Int -> Int
239+bytesToElemCount x n =
240+ let elemSize = sizeOf x
241+ in n + elemSize - 1 `div` elemSize
242+
243+
244+-- | GHC memory management allocation header overhead
245+allocOverhead :: Int
246+allocOverhead = 2 * sizeOf (undefined :: Int)
247+
248+mkChunkSize :: Int -> Int
249+mkChunkSize n = let size = n - allocOverhead in max size 0
250+
251+mkChunkSizeKB :: Int -> Int
252+mkChunkSizeKB n = mkChunkSize (n * k)
253+ where k = 1024
254+
255+-- | Default maximum buffer size in bytes, for reading from and writing to IO
256+-- devices, the value is 32KB minus GHC allocation overhead, which is a few
257+-- bytes, so that the actual allocation is 32KB.
258+defaultChunkSize :: Int
259+defaultChunkSize = mkChunkSizeKB 32
260+
261+-- | Remove the free space from an Array.
262+shrinkToFit :: forall a. Storable a => Array a -> IO (Array a)
263+shrinkToFit arr@Array{..} = do
264+ assert (aEnd <= aBound) (return ())
265+ let start = unsafeForeignPtrToPtr aStart
266+ let used = aEnd `minusPtr` start
267+ waste = aBound `minusPtr` aEnd
268+ -- if used == waste == 0 then do not realloc
269+ -- if the wastage is more than 25% of the array then realloc
270+ if used < 3 * waste
271+ then realloc used arr
272+ else return arr
273+
274+-------------------------------------------------------------------------------
275+-- Construction
276+-------------------------------------------------------------------------------
277+
278+-- | allocate a new array using the provided allocator function.
279+{-# INLINE newArrayAlignedAllocWith #-}
280+newArrayAlignedAllocWith :: forall a. Storable a
281+ => (Int -> Int -> IO (ForeignPtr a)) -> Int -> Int -> IO (Array a)
282+newArrayAlignedAllocWith alloc alignSize count = do
283+ let size = count * sizeOf (undefined :: a)
284+ fptr <- alloc size alignSize
285+ let p = unsafeForeignPtrToPtr fptr
286+ return $ Array
287+ { aStart = fptr
288+ , aEnd = p
289+ , aBound = p `plusPtr` size
290+ }
291+
292+-- | Allocate a new array aligned to the specified alignmend and using
293+-- unmanaged pinned memory. The memory will not be automatically freed by GHC.
294+-- This could be useful in allocate once global data structures. Use carefully
295+-- as incorrect use can lead to memory leak.
296+{-# INLINE newArrayAlignedUnmanaged #-}
297+newArrayAlignedUnmanaged :: forall a. Storable a => Int -> Int -> IO (Array a)
298+newArrayAlignedUnmanaged =
299+ newArrayAlignedAllocWith Malloc.mallocForeignPtrAlignedUnmanagedBytes
300+
301+{-# INLINE newArrayAligned #-}
302+newArrayAligned :: forall a. Storable a => Int -> Int -> IO (Array a)
303+newArrayAligned = newArrayAlignedAllocWith Malloc.mallocForeignPtrAlignedBytes
304+
305+-- XXX can unaligned allocation be more efficient when alignment is not needed?
306+--
307+-- | Allocate an array that can hold 'count' items. The memory of the array is
308+-- uninitialized.
309+--
310+-- Note that this is internal routine, the reference to this array cannot be
311+-- given out until the array has been written to and frozen.
312+{-# INLINE newArray #-}
313+newArray :: forall a. Storable a => Int -> IO (Array a)
314+newArray = newArrayAligned (alignment (undefined :: a))
315+
316+-- | Allocate an Array of the given size and run an IO action passing the array
317+-- start pointer.
318+{-# INLINE withNewArray #-}
319+withNewArray :: forall a. Storable a => Int -> (Ptr a -> IO ()) -> IO (Array a)
320+withNewArray count f = do
321+ arr <- newArray count
322+ withForeignPtr (aStart arr) $ \p -> f p >> return arr
323+
324+-- XXX grow the array when we are beyond bound.
325+--
326+-- Internal routine for when the array is being created. Appends one item at
327+-- the end of the array. Useful when sequentially writing a stream to the
328+-- array.
329+{-# INLINE unsafeSnoc #-}
330+unsafeSnoc :: forall a. Storable a => Array a -> a -> IO (Array a)
331+unsafeSnoc arr@Array {..} x = do
332+ when (aEnd == aBound) $ error "BUG: unsafeSnoc: writing beyond array bounds"
333+ poke aEnd x
334+ touchForeignPtr aStart
335+ return $ arr {aEnd = aEnd `plusPtr` sizeOf (undefined :: a)}
336+
337+{-# INLINE snoc #-}
338+snoc :: forall a. Storable a => Array a -> a -> IO (Array a)
339+snoc arr@Array {..} x =
340+ if aEnd == aBound
341+ then do
342+ let oldStart = unsafeForeignPtrToPtr aStart
343+ size = aEnd `minusPtr` oldStart
344+ newSize = size + sizeOf (undefined :: a)
345+ newPtr <-
346+ Malloc.mallocForeignPtrAlignedBytes
347+ newSize
348+ (alignment (undefined :: a))
349+ withForeignPtr newPtr $ \pNew -> do
350+ memcpy (castPtr pNew) (castPtr oldStart) size
351+ poke (pNew `plusPtr` size) x
352+ touchForeignPtr aStart
353+ return $
354+ Array
355+ { aStart = newPtr
356+ , aEnd = pNew `plusPtr` (size + sizeOf (undefined :: a))
357+ , aBound = pNew `plusPtr` newSize
358+ }
359+ else do
360+ poke aEnd x
361+ touchForeignPtr aStart
362+ return $ arr {aEnd = aEnd `plusPtr` sizeOf (undefined :: a)}
363+
364+-- | Reallocate the array to the specified size in bytes. If the size is less
365+-- than the original array the array gets truncated.
366+{-# NOINLINE reallocAligned #-}
367+reallocAligned :: Int -> Int -> Array a -> IO (Array a)
368+reallocAligned alignSize newSize Array{..} = do
369+ assert (aEnd <= aBound) (return ())
370+ let oldStart = unsafeForeignPtrToPtr aStart
371+ let size = aEnd `minusPtr` oldStart
372+ newPtr <- Malloc.mallocForeignPtrAlignedBytes newSize alignSize
373+ withForeignPtr newPtr $ \pNew -> do
374+ memcpy (castPtr pNew) (castPtr oldStart) size
375+ touchForeignPtr aStart
376+ return $ Array
377+ { aStart = newPtr
378+ , aEnd = pNew `plusPtr` size
379+ , aBound = pNew `plusPtr` newSize
380+ }
381+
382+-- XXX can unaligned allocation be more efficient when alignment is not needed?
383+{-# INLINABLE realloc #-}
384+realloc :: forall a. Storable a => Int -> Array a -> IO (Array a)
385+realloc = reallocAligned (alignment (undefined :: a))
386+
387+-- XXX when converting an array of Word8 from a literal string we can simply
388+-- refer to the literal string. Is it possible to write rules such that
389+-- fromList Word8 can be rewritten so that GHC does not first convert the
390+-- literal to [Char] and then we convert it back to an Array Word8?
391+--
392+-- TBD: We can also add template haskell quasiquotes to specify arrays of other
393+-- literal types. TH will encode them into a string literal and we read that as
394+-- an array of the required type. With template Haskell we can provide a safe
395+-- version of fromString#.
396+--
397+-- | Create an @Array Word8@ of the given length from a machine address
398+-- 'Addr#'. This API is unsafe for the following reasons:
399+--
400+-- 1. The address must point to pinned memory or foreign memory.
401+-- 2. The address must be legally accessible upto the given length.
402+-- 3. To guarantee that the array is immutable, the contents of the address
403+-- must be guaranteed to not change.
404+--
405+-- A common use case for this API is to create an array from a static unboxed
406+-- string literal. GHC string literals are of type 'Addr#', and must contain
407+-- characters that can be encoded in a byte i.e. characters or literal bytes in
408+-- the range from 0-255.
409+--
410+-- >>> fromAddr# 5 "hello world!"#
411+-- > [104,101,108,108,111]
412+--
413+-- >>> fromAddr# 3 "\255\NUL\255"#
414+-- > [255,0,255]
415+--
416+-- /See also: 'fromString#'/
417+--
418+-- /Unsafe/
419+--
420+-- /Time complexity: O(1)/
421+--
422+{-# INLINE fromAddr# #-}
423+fromAddr# :: Int -> Addr# -> IO (Array Word8)
424+fromAddr# n addr# = do
425+ ptr <- newForeignPtr_ (castPtr $ Ptr addr#)
426+ let p = unsafeForeignPtrToPtr ptr
427+ let end = p `plusPtr` n
428+ return $ Array
429+ { aStart = ptr
430+ , aEnd = end
431+ , aBound = end
432+ }
433+
434+-- | Like 'fromAddr#' but determines the length of the array upto and excluding
435+-- the first @0@ byte. The address must be legally accessible up to the first
436+-- NUL byte.
437+--
438+-- A common use case for this API is to create an array from an unboxed string
439+-- literal. Unboxed string literals are guaranteed to be terminated by a NUL
440+-- byte.
441+--
442+-- >>> fromString# "hello world!"#
443+-- > [104,101,108,108,111,32,119,111,114,108,100,33]
444+--
445+-- >>> fromString# "\255\NUL\255"#
446+-- > [255]
447+--
448+-- /See also: 'fromAddr#', 'fromString'/
449+--
450+-- /Unsafe/
451+--
452+-- /Time complexity: O(n) (computes the length of the string)/
453+--
454+{-# INLINE fromString# #-}
455+fromString# :: Addr# -> IO (Array Word8)
456+fromString# addr# = do
457+ ptr <- newForeignPtr_ (castPtr cstr)
458+ len <- c_strlen cstr
459+ let n = fromIntegral len
460+ let p = unsafeForeignPtrToPtr ptr
461+ let end = p `plusPtr` n
462+ return $ Array
463+ { aStart = ptr
464+ , aEnd = end
465+ , aBound = end
466+ }
467+ where
468+ cstr :: CString
469+ cstr = Ptr addr#
470+
471+-- | Create an 'Array' from the first N elements of a list. The array is
472+-- allocated to size N, if the list terminates before N elements then the
473+-- array may hold less than N elements.
474+--
475+-- @since 0.7.0
476+{-# INLINABLE fromListN #-}
477+fromListN :: Storable a => Int -> [a] -> Array a
478+fromListN n xs = unsafePerformIO $ fromStreamDN n $ D.fromList xs
479+
480+-- | Create an 'Array' from a list. The list must be of finite size.
481+--
482+-- @since 0.7.0
483+{-# INLINABLE fromList #-}
484+fromList :: Storable a => [a] -> Array a
485+fromList xs = unsafePerformIO $ fromStreamD $ D.fromList xs
486+
487+{-# INLINE_NORMAL fromStreamDN #-}
488+fromStreamDN :: forall m a. (MonadIO m, Storable a)
489+ => Int -> D.Stream m a -> m (Array a)
490+fromStreamDN limit str = do
491+ arr <- liftIO $ newArray limit
492+ end <- D.foldlM' fwrite (return $ aEnd arr) $ D.take limit str
493+ return $ arr {aEnd = end}
494+
495+ where
496+
497+ fwrite ptr x = do
498+ liftIO $ poke ptr x
499+ return $ ptr `plusPtr` sizeOf (undefined :: a)
500+
501+-- CAUTION: a very large number (millions) of arrays can degrade performance
502+-- due to GC overhead because we need to buffer the arrays before we flatten
503+-- all the arrays.
504+--
505+-- We could take the approach of doubling the memory allocation on each
506+-- overflow. This would result in more or less the same amount of copying as in
507+-- the chunking approach. However, if we have to shrink in the end then it may
508+-- result in an extra copy of the entire data.
509+--
510+{-# INLINE fromStreamD #-}
511+fromStreamD :: (MonadIO m, Storable a) => D.Stream m a -> m (Array a)
512+fromStreamD m = do
513+ let s = fromStreamDArraysOf defaultChunkSize m
514+ buffered <- D.foldr K.cons K.nil s
515+ len <- K.foldl' (+) 0 (K.map length buffered)
516+ fromStreamDN len $ flattenArrays $ D.fromStreamK buffered
517+{-
518+fromStreamD m = runFold write m
519+ where
520+ runFold (Fold step begin done) = D.foldlMx' step begin done
521+-}
522+
523+
524+-- Splice two immutable arrays creating a new array.
525+{-# INLINE spliceTwo #-}
526+spliceTwo :: (MonadIO m, Storable a) => Array a -> Array a -> m (Array a)
527+spliceTwo arr1 arr2 = do
528+ let src1 = unsafeForeignPtrToPtr (aStart arr1)
529+ src2 = unsafeForeignPtrToPtr (aStart arr2)
530+ len1 = aEnd arr1 `minusPtr` src1
531+ len2 = aEnd arr2 `minusPtr` src2
532+
533+ arr <- liftIO $ newArray (len1 + len2)
534+ let dst = unsafeForeignPtrToPtr (aStart arr)
535+
536+ -- XXX Should we use copyMutableByteArray# instead? Is there an overhead to
537+ -- ccall?
538+ liftIO $ do
539+ memcpy (castPtr dst) (castPtr src1) len1
540+ touchForeignPtr (aStart arr1)
541+ memcpy (castPtr (dst `plusPtr` len1)) (castPtr src2) len2
542+ touchForeignPtr (aStart arr2)
543+ return arr { aEnd = dst `plusPtr` (len1 + len2) }
544+
545+-- Splice an array into a pre-reserved mutable array. The user must ensure
546+-- that there is enough space in the mutable array.
547+{-# INLINE spliceWith #-}
548+spliceWith :: (MonadIO m) => Array a -> Array a -> m (Array a)
549+spliceWith dst@(Array _ end bound) src =
550+ liftIO $ do
551+ let srcLen = byteLength src
552+ if end `plusPtr` srcLen > bound
553+ then error
554+ "Bug: spliceIntoUnsafe: Not enough space in the target array"
555+ else withForeignPtr (aStart dst) $ \_ ->
556+ withForeignPtr (aStart src) $ \psrc -> do
557+ let pdst = aEnd dst
558+ memcpy (castPtr pdst) (castPtr psrc) srcLen
559+ return $ dst {aEnd = pdst `plusPtr` srcLen}
560+
561+-- Splice a new array into a preallocated mutable array, doubling the space if
562+-- there is no space in the target array.
563+{-# INLINE spliceWithDoubling #-}
564+spliceWithDoubling :: (MonadIO m, Storable a)
565+ => Array a -> Array a -> m (Array a)
566+spliceWithDoubling dst@(Array start end bound) src = do
567+ assert (end <= bound) (return ())
568+ let srcLen = aEnd src `minusPtr` unsafeForeignPtrToPtr (aStart src)
569+
570+ dst1 <-
571+ if end `plusPtr` srcLen >= bound
572+ then do
573+ let oldStart = unsafeForeignPtrToPtr start
574+ oldSize = end `minusPtr` oldStart
575+ newSize = max (oldSize * 2) (oldSize + srcLen)
576+ liftIO $ realloc newSize dst
577+ else return dst
578+ spliceWith dst1 src
579+
580+-------------------------------------------------------------------------------
581+-- Streams of arrays
582+-------------------------------------------------------------------------------
583+
584+data GroupState s start end bound
585+ = GroupStart s
586+ | GroupBuffer s start end bound
587+ | GroupYield start end bound (GroupState s start end bound)
588+ | GroupFinish
589+
590+-- | @fromStreamArraysOf n stream@ groups the input stream into a stream of
591+-- arrays of size n.
592+{-# INLINE_NORMAL fromStreamDArraysOf #-}
593+fromStreamDArraysOf :: forall m a. (MonadIO m, Storable a)
594+ => Int -> D.Stream m a -> D.Stream m (Array a)
595+-- fromStreamDArraysOf n str = D.groupsOf n (writeN n) str
596+fromStreamDArraysOf n (D.Stream step state) =
597+ D.Stream step' (GroupStart state)
598+
599+ where
600+
601+ {-# INLINE_LATE step' #-}
602+ step' _ (GroupStart st) = do
603+ when (n <= 0) $
604+ -- XXX we can pass the module string from the higher level API
605+ error $ "Streamly.Internal.Memory.Mutable.Array.Types.fromStreamDArraysOf: the size of "
606+ ++ "arrays [" ++ show n ++ "] must be a natural number"
607+ Array start end bound <- liftIO $ newArray n
608+ return $ D.Skip (GroupBuffer st start end bound)
609+
610+ step' gst (GroupBuffer st start end bound) = do
611+ r <- step (adaptState gst) st
612+ case r of
613+ D.Yield x s -> do
614+ liftIO $ poke end x
615+ let end' = end `plusPtr` sizeOf (undefined :: a)
616+ return $
617+ if end' >= bound
618+ then D.Skip (GroupYield start end' bound (GroupStart s))
619+ else D.Skip (GroupBuffer s start end' bound)
620+ D.Skip s -> return $ D.Skip (GroupBuffer s start end bound)
621+ D.Stop -> return $ D.Skip (GroupYield start end bound GroupFinish)
622+
623+ step' _ (GroupYield start end bound next) =
624+ return $ D.Yield (Array start end bound) next
625+
626+ step' _ GroupFinish = return D.Stop
627+
628+-- XXX concatMap does not seem to have the best possible performance so we have
629+-- a custom way to concat arrays.
630+data FlattenState s a =
631+ OuterLoop s
632+ | InnerLoop s !(ForeignPtr a) !(Ptr a) !(Ptr a)
633+
634+{-# INLINE_NORMAL flattenArrays #-}
635+flattenArrays :: forall m a. (MonadIO m, Storable a)
636+ => D.Stream m (Array a) -> D.Stream m a
637+flattenArrays (D.Stream step state) = D.Stream step' (OuterLoop state)
638+
639+ where
640+
641+ {-# INLINE_LATE step' #-}
642+ step' gst (OuterLoop st) = do
643+ r <- step (adaptState gst) st
644+ return $ case r of
645+ D.Yield Array{..} s ->
646+ let p = unsafeForeignPtrToPtr aStart
647+ in D.Skip (InnerLoop s aStart p aEnd)
648+ D.Skip s -> D.Skip (OuterLoop s)
649+ D.Stop -> D.Stop
650+
651+ step' _ (InnerLoop st _ p end) | p == end =
652+ return $ D.Skip $ OuterLoop st
653+
654+ step' _ (InnerLoop st startf p end) = do
655+ x <- liftIO $ do
656+ r <- peek p
657+ touchForeignPtr startf
658+ return r
659+ return $ D.Yield x (InnerLoop st startf
660+ (p `plusPtr` sizeOf (undefined :: a)) end)
661+
662+{-# INLINE_NORMAL flattenArraysRev #-}
663+flattenArraysRev :: forall m a. (MonadIO m, Storable a)
664+ => D.Stream m (Array a) -> D.Stream m a
665+flattenArraysRev (D.Stream step state) = D.Stream step' (OuterLoop state)
666+
667+ where
668+
669+ {-# INLINE_LATE step' #-}
670+ step' gst (OuterLoop st) = do
671+ r <- step (adaptState gst) st
672+ return $ case r of
673+ D.Yield Array{..} s ->
674+ let p = aEnd `plusPtr` negate (sizeOf (undefined :: a))
675+ -- XXX we do not need aEnd
676+ in D.Skip (InnerLoop s aStart p aEnd)
677+ D.Skip s -> D.Skip (OuterLoop s)
678+ D.Stop -> D.Stop
679+
680+ step' _ (InnerLoop st start p _) | p < unsafeForeignPtrToPtr start =
681+ return $ D.Skip $ OuterLoop st
682+
683+ step' _ (InnerLoop st startf p end) = do
684+ x <- liftIO $ do
685+ r <- peek p
686+ touchForeignPtr startf
687+ return r
688+ return $ D.Yield x (InnerLoop st startf
689+ (p `plusPtr` negate (sizeOf (undefined :: a))) end)
690+
691+data SpliceState s arr
692+ = SpliceInitial s
693+ | SpliceBuffering s arr
694+ | SpliceYielding arr (SpliceState s arr)
695+ | SpliceFinish
696+
697+-- XXX can use general grouping combinators to achieve this?
698+-- | Coalesce adjacent arrays in incoming stream to form bigger arrays of a
699+-- maximum specified size. Note that if a single array is bigger than the
700+-- specified size we do not split it to fit. When we coalesce multiple arrays
701+-- if the size would exceed the specified size we do not coalesce therefore the
702+-- actual array size may be less than the specified chunk size.
703+--
704+-- @since 0.7.0
705+{-# INLINE_NORMAL packArraysChunksOf #-}
706+packArraysChunksOf :: (MonadIO m, Storable a)
707+ => Int -> D.Stream m (Array a) -> D.Stream m (Array a)
708+packArraysChunksOf n (D.Stream step state) =
709+ D.Stream step' (SpliceInitial state)
710+
711+ where
712+
713+ {-# INLINE_LATE step' #-}
714+ step' gst (SpliceInitial st) = do
715+ when (n <= 0) $
716+ -- XXX we can pass the module string from the higher level API
717+ error $ "Streamly.Internal.Memory.Mutable.Array.Types.packArraysChunksOf: the size of "
718+ ++ "arrays [" ++ show n ++ "] must be a natural number"
719+ r <- step gst st
720+ case r of
721+ D.Yield arr s -> return $
722+ let len = byteLength arr
723+ in if len >= n
724+ then D.Skip (SpliceYielding arr (SpliceInitial s))
725+ else D.Skip (SpliceBuffering s arr)
726+ D.Skip s -> return $ D.Skip (SpliceInitial s)
727+ D.Stop -> return D.Stop
728+
729+ step' gst (SpliceBuffering st buf) = do
730+ r <- step gst st
731+ case r of
732+ D.Yield arr s -> do
733+ let len = byteLength buf + byteLength arr
734+ if len > n
735+ then return $
736+ D.Skip (SpliceYielding buf (SpliceBuffering s arr))
737+ else do
738+ buf' <- if byteCapacity buf < n
739+ then liftIO $ realloc n buf
740+ else return buf
741+ buf'' <- spliceWith buf' arr
742+ return $ D.Skip (SpliceBuffering s buf'')
743+ D.Skip s -> return $ D.Skip (SpliceBuffering s buf)
744+ D.Stop -> return $ D.Skip (SpliceYielding buf SpliceFinish)
745+
746+ step' _ SpliceFinish = return D.Stop
747+
748+ step' _ (SpliceYielding arr next) = return $ D.Yield arr next
749+
750+-- XXX instead of writing two different versions of this operation, we should
751+-- write it as a pipe.
752+{-# INLINE_NORMAL lpackArraysChunksOf #-}
753+lpackArraysChunksOf :: (MonadIO m, Storable a)
754+ => Int -> Fold m (Array a) () -> Fold m (Array a) ()
755+lpackArraysChunksOf n (Fold step1 initial1 extract1) =
756+ Fold step initial extract
757+
758+ where
759+
760+ initial = do
761+ when (n <= 0) $
762+ -- XXX we can pass the module string from the higher level API
763+ error $ "Streamly.Internal.Memory.Mutable.Array.Types.packArraysChunksOf: the size of "
764+ ++ "arrays [" ++ show n ++ "] must be a natural number"
765+ r1 <- initial1
766+ return (Tuple' Nothing r1)
767+
768+ extract (Tuple' Nothing r1) = extract1 r1
769+ extract (Tuple' (Just buf) r1) = do
770+ r <- step1 r1 buf
771+ extract1 r
772+
773+ step (Tuple' Nothing r1) arr = do
774+ let len = byteLength arr
775+ if len >= n
776+ then do
777+ r <- step1 r1 arr
778+ extract1 r
779+ r1' <- initial1
780+ return (Tuple' Nothing r1')
781+ else return (Tuple' (Just arr) r1)
782+
783+ step (Tuple' (Just buf) r1) arr = do
784+ let len = byteLength buf + byteLength arr
785+ buf' <- if byteCapacity buf < len
786+ then liftIO $ realloc (max n len) buf
787+ else return buf
788+ buf'' <- spliceWith buf' arr
789+
790+ if len >= n
791+ then do
792+ r <- step1 r1 buf''
793+ extract1 r
794+ r1' <- initial1
795+ return (Tuple' Nothing r1')
796+ else return (Tuple' (Just buf'') r1)
797+
798+#if !defined(mingw32_HOST_OS)
799+data GatherState s arr
800+ = GatherInitial s
801+ | GatherBuffering s arr Int
802+ | GatherYielding arr (GatherState s arr)
803+ | GatherFinish
804+
805+-- | @groupIOVecsOf maxBytes maxEntries@ groups arrays in the incoming stream
806+-- to create a stream of 'IOVec' arrays with a maximum of @maxBytes@ bytes in
807+-- each array and a maximum of @maxEntries@ entries in each array.
808+--
809+-- @since 0.7.0
810+{-# INLINE_NORMAL groupIOVecsOf #-}
811+groupIOVecsOf :: MonadIO m
812+ => Int -> Int -> D.Stream m (Array a) -> D.Stream m (Array IOVec)
813+groupIOVecsOf n maxIOVLen (D.Stream step state) =
814+ D.Stream step' (GatherInitial state)
815+
816+ where
817+
818+ {-# INLINE_LATE step' #-}
819+ step' gst (GatherInitial st) = do
820+ when (n <= 0) $
821+ -- XXX we can pass the module string from the higher level API
822+ error $ "Streamly.Internal.Memory.Mutable.Array.Types.groupIOVecsOf: the size of "
823+ ++ "groups [" ++ show n ++ "] must be a natural number"
824+ when (maxIOVLen <= 0) $
825+ -- XXX we can pass the module string from the higher level API
826+ error $ "Streamly.Internal.Memory.Mutable.Array.Types.groupIOVecsOf: the number of "
827+ ++ "IOVec entries [" ++ show n ++ "] must be a natural number"
828+ r <- step (adaptState gst) st
829+ case r of
830+ D.Yield arr s -> do
831+ let p = unsafeForeignPtrToPtr (aStart arr)
832+ len = byteLength arr
833+ iov <- liftIO $ newArray maxIOVLen
834+ iov' <- liftIO $ unsafeSnoc iov (IOVec (castPtr p)
835+ (fromIntegral len))
836+ if len >= n
837+ then return $ D.Skip (GatherYielding iov' (GatherInitial s))
838+ else return $ D.Skip (GatherBuffering s iov' len)
839+ D.Skip s -> return $ D.Skip (GatherInitial s)
840+ D.Stop -> return D.Stop
841+
842+ step' gst (GatherBuffering st iov len) = do
843+ r <- step (adaptState gst) st
844+ case r of
845+ D.Yield arr s -> do
846+ let p = unsafeForeignPtrToPtr (aStart arr)
847+ alen = byteLength arr
848+ len' = len + alen
849+ if len' > n || length iov >= maxIOVLen
850+ then do
851+ iov' <- liftIO $ newArray maxIOVLen
852+ iov'' <- liftIO $ unsafeSnoc iov' (IOVec (castPtr p)
853+ (fromIntegral alen))
854+ return $ D.Skip (GatherYielding iov
855+ (GatherBuffering s iov'' alen))
856+ else do
857+ iov' <- liftIO $ unsafeSnoc iov (IOVec (castPtr p)
858+ (fromIntegral alen))
859+ return $ D.Skip (GatherBuffering s iov' len')
860+ D.Skip s -> return $ D.Skip (GatherBuffering s iov len)
861+ D.Stop -> return $ D.Skip (GatherYielding iov GatherFinish)
862+
863+ step' _ GatherFinish = return D.Stop
864+
865+ step' _ (GatherYielding iov next) = return $ D.Yield iov next
866+#endif
867+
868+
869+data SplitState s arr
870+ = Initial s
871+ | Buffering s arr
872+ | Splitting s arr
873+ | Yielding arr (SplitState s arr)
874+ | Finishing
875+
876+-- | Split a stream of arrays on a given separator byte, dropping the separator
877+-- and coalescing all the arrays between two separators into a single array.
878+--
879+-- @since 0.7.0
880+{-# INLINE_NORMAL splitOn #-}
881+splitOn
882+ :: MonadIO m
883+ => Word8
884+ -> D.Stream m (Array Word8)
885+ -> D.Stream m (Array Word8)
886+splitOn byte (D.Stream step state) = D.Stream step' (Initial state)
887+
888+ where
889+
890+ {-# INLINE_LATE step' #-}
891+ step' gst (Initial st) = do
892+ r <- step gst st
893+ case r of
894+ D.Yield arr s -> do
895+ (arr1, marr2) <- breakOn byte arr
896+ return $ case marr2 of
897+ Nothing -> D.Skip (Buffering s arr1)
898+ Just arr2 -> D.Skip (Yielding arr1 (Splitting s arr2))
899+ D.Skip s -> return $ D.Skip (Initial s)
900+ D.Stop -> return D.Stop
901+
902+ step' gst (Buffering st buf) = do
903+ r <- step gst st
904+ case r of
905+ D.Yield arr s -> do
906+ (arr1, marr2) <- breakOn byte arr
907+ buf' <- spliceTwo buf arr1
908+ return $ case marr2 of
909+ Nothing -> D.Skip (Buffering s buf')
910+ Just x -> D.Skip (Yielding buf' (Splitting s x))
911+ D.Skip s -> return $ D.Skip (Buffering s buf)
912+ D.Stop -> return $
913+ if byteLength buf == 0
914+ then D.Stop
915+ else D.Skip (Yielding buf Finishing)
916+
917+ step' _ (Splitting st buf) = do
918+ (arr1, marr2) <- breakOn byte buf
919+ return $ case marr2 of
920+ Nothing -> D.Skip $ Buffering st arr1
921+ Just arr2 -> D.Skip $ Yielding arr1 (Splitting st arr2)
922+
923+ step' _ (Yielding arr next) = return $ D.Yield arr next
924+ step' _ Finishing = return D.Stop
925+
926+
927+-- Drops the separator byte
928+{-# INLINE breakOn #-}
929+breakOn :: MonadIO m
930+ => Word8 -> Array Word8 -> m (Array Word8, Maybe (Array Word8))
931+breakOn sep arr@Array{..} = liftIO $ do
932+ let p = unsafeForeignPtrToPtr aStart
933+ loc <- c_memchr p sep (fromIntegral $ aEnd `minusPtr` p)
934+ return $
935+ if loc == nullPtr
936+ then (arr, Nothing)
937+ else
938+ ( Array
939+ { aStart = aStart
940+ , aEnd = loc
941+ , aBound = loc
942+ }
943+ , Just $ Array
944+ { aStart = aStart `plusForeignPtr` (loc `minusPtr` p + 1)
945+ , aEnd = aEnd
946+ , aBound = aBound
947+ }
948+ )
949+
950+-------------------------------------------------------------------------------
951+-- Elimination
952+-------------------------------------------------------------------------------
953+
954+-- | Return element at the specified index without checking the bounds.
955+--
956+-- Unsafe because it does not check the bounds of the array.
957+{-# INLINE_NORMAL unsafeIndexIO #-}
958+unsafeIndexIO :: forall a. Storable a => Array a -> Int -> IO a
959+unsafeIndexIO Array {..} i =
960+ withForeignPtr aStart $ \p -> do
961+ let elemSize = sizeOf (undefined :: a)
962+ elemOff = p `plusPtr` (elemSize * i)
963+ assert (i >= 0 && elemOff `plusPtr` elemSize <= aEnd)
964+ (return ())
965+ peek elemOff
966+
967+-- | Return element at the specified index without checking the bounds.
968+{-# INLINE_NORMAL unsafeIndex #-}
969+unsafeIndex :: forall a. Storable a => Array a -> Int -> a
970+unsafeIndex arr i = let !r = unsafeInlineIO $ unsafeIndexIO arr i in r
971+
972+-- | /O(1)/ Get the byte length of the array.
973+--
974+-- @since 0.7.0
975+{-# INLINE byteLength #-}
976+byteLength :: Array a -> Int
977+byteLength Array{..} =
978+ let p = unsafeForeignPtrToPtr aStart
979+ len = aEnd `minusPtr` p
980+ in assert (len >= 0) len
981+
982+-- | /O(1)/ Get the length of the array i.e. the number of elements in the
983+-- array.
984+--
985+-- @since 0.7.0
986+{-# INLINE length #-}
987+length :: forall a. Storable a => Array a -> Int
988+length arr = byteLength arr `div` sizeOf (undefined :: a)
989+
990+{-# INLINE byteCapacity #-}
991+byteCapacity :: Array a -> Int
992+byteCapacity Array{..} =
993+ let p = unsafeForeignPtrToPtr aStart
994+ len = aBound `minusPtr` p
995+ in assert (len >= 0) len
996+
997+-- Use foldr/build fusion to fuse with list consumers
998+-- This can be useful when using the IsList instance
999+{-# INLINE_LATE toListFB #-}
1000+toListFB :: forall a b. Storable a => (a -> b -> b) -> b -> Array a -> b
1001+toListFB c n Array{..} = go (unsafeForeignPtrToPtr aStart)
1002+ where
1003+
1004+ go p | p == aEnd = n
1005+ go p =
1006+ -- unsafeInlineIO allows us to run this in Identity monad for pure
1007+ -- toList/foldr case which makes them much faster due to not
1008+ -- accumulating the list and fusing better with the pure consumers.
1009+ --
1010+ -- This should be safe as the array contents are guaranteed to be
1011+ -- evaluated/written to before we peek at them.
1012+ let !x = unsafeInlineIO $ do
1013+ r <- peek p
1014+ touchForeignPtr aStart
1015+ return r
1016+ in c x (go (p `plusPtr` sizeOf (undefined :: a)))
1017+
1018+-- | Convert an 'Array' into a list.
1019+--
1020+-- @since 0.7.0
1021+{-# INLINE toList #-}
1022+toList :: Storable a => Array a -> [a]
1023+toList s = build (\c n -> toListFB c n s)
1024+
1025+{-# INLINE_NORMAL toStreamD #-}
1026+toStreamD :: forall m a. (Monad m, Storable a) => Array a -> D.Stream m a
1027+toStreamD Array{..} =
1028+ let p = unsafeForeignPtrToPtr aStart
1029+ in D.Stream step p
1030+
1031+ where
1032+
1033+ {-# INLINE_LATE step #-}
1034+ step _ p | p == aEnd = return D.Stop
1035+ step _ p = do
1036+ -- unsafeInlineIO allows us to run this in Identity monad for pure
1037+ -- toList/foldr case which makes them much faster due to not
1038+ -- accumulating the list and fusing better with the pure consumers.
1039+ --
1040+ -- This should be safe as the array contents are guaranteed to be
1041+ -- evaluated/written to before we peek at them.
1042+ let !x = unsafeInlineIO $ do
1043+ r <- peek p
1044+ touchForeignPtr aStart
1045+ return r
1046+ return $ D.Yield x (p `plusPtr` sizeOf (undefined :: a))
1047+
1048+{-# INLINE toStreamK #-}
1049+toStreamK :: forall t m a. (K.IsStream t, Storable a) => Array a -> t m a
1050+toStreamK Array{..} =
1051+ let p = unsafeForeignPtrToPtr aStart
1052+ in go p
1053+
1054+ where
1055+
1056+ go p | p == aEnd = K.nil
1057+ | otherwise =
1058+ -- See Note in toStreamD.
1059+ let !x = unsafeInlineIO $ do
1060+ r <- peek p
1061+ touchForeignPtr aStart
1062+ return r
1063+ in x `K.cons` go (p `plusPtr` sizeOf (undefined :: a))
1064+
1065+{-# INLINE_NORMAL toStreamDRev #-}
1066+toStreamDRev :: forall m a. (Monad m, Storable a) => Array a -> D.Stream m a
1067+toStreamDRev Array{..} =
1068+ let p = aEnd `plusPtr` negate (sizeOf (undefined :: a))
1069+ in D.Stream step p
1070+
1071+ where
1072+
1073+ {-# INLINE_LATE step #-}
1074+ step _ p | p < unsafeForeignPtrToPtr aStart = return D.Stop
1075+ step _ p = do
1076+ -- See comments in toStreamD for why we use unsafeInlineIO
1077+ let !x = unsafeInlineIO $ do
1078+ r <- peek p
1079+ touchForeignPtr aStart
1080+ return r
1081+ return $ D.Yield x (p `plusPtr` negate (sizeOf (undefined :: a)))
1082+
1083+{-# INLINE toStreamKRev #-}
1084+toStreamKRev :: forall t m a. (K.IsStream t, Storable a) => Array a -> t m a
1085+toStreamKRev Array {..} =
1086+ let p = aEnd `plusPtr` negate (sizeOf (undefined :: a))
1087+ in go p
1088+
1089+ where
1090+
1091+ go p | p < unsafeForeignPtrToPtr aStart = K.nil
1092+ | otherwise =
1093+ let !x = unsafeInlineIO $ do
1094+ r <- peek p
1095+ touchForeignPtr aStart
1096+ return r
1097+ in x `K.cons` go (p `plusPtr` negate (sizeOf (undefined :: a)))
1098+
1099+{-# INLINE_NORMAL foldl' #-}
1100+foldl' :: forall a b. Storable a => (b -> a -> b) -> b -> Array a -> b
1101+foldl' f z arr = runIdentity $ D.foldl' f z $ toStreamD arr
1102+
1103+{-# INLINE_NORMAL foldr #-}
1104+foldr :: Storable a => (a -> b -> b) -> b -> Array a -> b
1105+foldr f z arr = runIdentity $ D.foldr f z $ toStreamD arr
1106+
1107+
1108+-- | Create two slices of an array without copying the original array. The
1109+-- specified index @i@ is the first index of the second slice.
1110+--
1111+-- @since 0.7.0
1112+splitAt :: forall a. Storable a => Int -> Array a -> (Array a, Array a)
1113+splitAt i arr@Array{..} =
1114+ let maxIndex = length arr - 1
1115+ in if i < 0
1116+ then error "sliceAt: negative array index"
1117+ else if i > maxIndex
1118+ then error $ "sliceAt: specified array index " ++ show i
1119+ ++ " is beyond the maximum index " ++ show maxIndex
1120+ else let off = i * sizeOf (undefined :: a)
1121+ p = unsafeForeignPtrToPtr aStart `plusPtr` off
1122+ in ( Array
1123+ { aStart = aStart
1124+ , aEnd = p
1125+ , aBound = p
1126+ }
1127+ , Array
1128+ { aStart = aStart `plusForeignPtr` off
1129+ , aEnd = aEnd
1130+ , aBound = aBound
1131+ }
1132+ )
1133+
1134+
1135+{-# INLINE_NORMAL unlines #-}
1136+unlines :: forall m a. (MonadIO m, Storable a)
1137+ => a -> D.Stream m (Array a) -> D.Stream m a
1138+unlines sep (D.Stream step state) = D.Stream step' (OuterLoop state)
1139+ where
1140+ {-# INLINE_LATE step' #-}
1141+ step' gst (OuterLoop st) = do
1142+ r <- step (adaptState gst) st
1143+ return $ case r of
1144+ D.Yield Array{..} s ->
1145+ let p = unsafeForeignPtrToPtr aStart
1146+ in D.Skip (InnerLoop s aStart p aEnd)
1147+ D.Skip s -> D.Skip (OuterLoop s)
1148+ D.Stop -> D.Stop
1149+
1150+ step' _ (InnerLoop st _ p end) | p == end =
1151+ return $ D.Yield sep $ OuterLoop st
1152+
1153+ step' _ (InnerLoop st startf p end) = do
1154+ x <- liftIO $ do
1155+ r <- peek p
1156+ touchForeignPtr startf
1157+ return r
1158+ return $ D.Yield x (InnerLoop st startf
1159+ (p `plusPtr` sizeOf (undefined :: a)) end)
1160+
1161+-------------------------------------------------------------------------------
1162+-- Folds
1163+-------------------------------------------------------------------------------
1164+
1165+{-# INLINE_NORMAL writeNAllocWith #-}
1166+writeNAllocWith :: forall m a. (MonadIO m, Storable a)
1167+ => (Int -> IO (Array a)) -> Int -> Fold m a (Array a)
1168+writeNAllocWith alloc n = Fold step initial extract
1169+
1170+ where
1171+
1172+ initial = liftIO $ alloc (max n 0)
1173+ step arr@(Array _ end bound) _ | end == bound = return arr
1174+ step (Array start end bound) x = do
1175+ liftIO $ poke end x
1176+ return $ Array start (end `plusPtr` sizeOf (undefined :: a)) bound
1177+ -- XXX note that shirkToFit does not maintain alignment, in case we are
1178+ -- using aligned allocation.
1179+ extract = return -- liftIO . shrinkToFit
1180+
1181+-- | @writeN n@ folds a maximum of @n@ elements from the input stream to an
1182+-- 'Array'.
1183+--
1184+-- @since 0.7.0
1185+{-# INLINE_NORMAL writeN #-}
1186+writeN :: forall m a. (MonadIO m, Storable a) => Int -> Fold m a (Array a)
1187+writeN = writeNAllocWith newArray
1188+
1189+-- | @writeNAligned alignment n@ folds a maximum of @n@ elements from the input
1190+-- stream to an 'Array' aligned to the given size.
1191+--
1192+-- /Internal/
1193+--
1194+{-# INLINE_NORMAL writeNAligned #-}
1195+writeNAligned :: forall m a. (MonadIO m, Storable a)
1196+ => Int -> Int -> Fold m a (Array a)
1197+writeNAligned alignSize = writeNAllocWith (newArrayAligned alignSize)
1198+
1199+-- | @writeNAlignedUnmanaged n@ folds a maximum of @n@ elements from the input
1200+-- stream to an 'Array' aligned to the given size and using unmanaged memory.
1201+-- This could be useful to allocate memory that we need to allocate only once
1202+-- in the lifetime of the program.
1203+--
1204+-- /Internal/
1205+--
1206+{-# INLINE_NORMAL writeNAlignedUnmanaged #-}
1207+writeNAlignedUnmanaged :: forall m a. (MonadIO m, Storable a)
1208+ => Int -> Int -> Fold m a (Array a)
1209+writeNAlignedUnmanaged alignSize =
1210+ writeNAllocWith (newArrayAlignedUnmanaged alignSize)
1211+
1212+data ArrayUnsafe a = ArrayUnsafe
1213+ {-# UNPACK #-} !(ForeignPtr a) -- first address
1214+ {-# UNPACK #-} !(Ptr a) -- first unused address
1215+
1216+-- | Like 'writeN' but does not check the array bounds when writing. The fold
1217+-- driver must not call the step function more than 'n' times otherwise it will
1218+-- corrupt the memory and crash. This function exists mainly because any
1219+-- conditional in the step function blocks fusion causing 10x performance
1220+-- slowdown.
1221+--
1222+-- @since 0.7.0
1223+{-# INLINE_NORMAL writeNUnsafe #-}
1224+writeNUnsafe :: forall m a. (MonadIO m, Storable a)
1225+ => Int -> Fold m a (Array a)
1226+writeNUnsafe n = Fold step initial extract
1227+
1228+ where
1229+
1230+ initial = do
1231+ (Array start end _) <- liftIO $ newArray (max n 0)
1232+ return $ ArrayUnsafe start end
1233+ step (ArrayUnsafe start end) x = do
1234+ liftIO $ poke end x
1235+ return $ ArrayUnsafe start (end `plusPtr` sizeOf (undefined :: a))
1236+ extract (ArrayUnsafe start end) = return $ Array start end end -- liftIO . shrinkToFit
1237+
1238+-- XXX The realloc based implementation needs to make one extra copy if we use
1239+-- shrinkToFit. On the other hand, the stream of arrays implementation may
1240+-- buffer the array chunk pointers in memory but it does not have to shrink as
1241+-- we know the exact size in the end. However, memory copying does not seems to
1242+-- be as expensive as the allocations. Therefore, we need to reduce the number
1243+-- of allocations instead. Also, the size of allocations matters, right sizing
1244+-- an allocation even at the cost of copying sems to help. Should be measured
1245+-- on a big stream with heavy calls to toArray to see the effect.
1246+--
1247+-- XXX check if GHC's memory allocator is efficient enough. We can try the C
1248+-- malloc to compare against.
1249+
1250+{-# INLINE_NORMAL toArrayMinChunk #-}
1251+toArrayMinChunk :: forall m a. (MonadIO m, Storable a)
1252+ => Int -> Int -> Fold m a (Array a)
1253+-- toArrayMinChunk n = FL.mapM spliceArrays $ toArraysOf n
1254+toArrayMinChunk alignSize elemCount = Fold step initial extract
1255+
1256+ where
1257+
1258+ insertElem (Array start end bound) x = do
1259+ liftIO $ poke end x
1260+ return $ Array start (end `plusPtr` sizeOf (undefined :: a)) bound
1261+
1262+ initial = do
1263+ when (elemCount < 0) $ error "toArrayMinChunk: elemCount is negative"
1264+ liftIO $ newArrayAligned alignSize elemCount
1265+ step arr@(Array start end bound) x | end == bound = do
1266+ let p = unsafeForeignPtrToPtr start
1267+ oldSize = end `minusPtr` p
1268+ newSize = max (oldSize * 2) 1
1269+ arr1 <- liftIO $ reallocAligned alignSize newSize arr
1270+ insertElem arr1 x
1271+ step arr x = insertElem arr x
1272+ extract = liftIO . shrinkToFit
1273+
1274+-- | Fold the whole input to a single array.
1275+--
1276+-- /Caution! Do not use this on infinite streams./
1277+--
1278+-- @since 0.7.0
1279+{-# INLINE write #-}
1280+write :: forall m a. (MonadIO m, Storable a) => Fold m a (Array a)
1281+write = toArrayMinChunk (alignment (undefined :: a))
1282+ (bytesToElemCount (undefined :: a)
1283+ (mkChunkSize 1024))
1284+
1285+-- | Like 'write' but the array memory is aligned according to the specified
1286+-- alignment size. This could be useful when we have specific alignment, for
1287+-- example, cache aligned arrays for lookup table etc.
1288+--
1289+-- /Caution! Do not use this on infinite streams./
1290+--
1291+-- @since 0.7.0
1292+{-# INLINE writeAligned #-}
1293+writeAligned :: forall m a. (MonadIO m, Storable a)
1294+ => Int -> Fold m a (Array a)
1295+writeAligned alignSize =
1296+ toArrayMinChunk alignSize
1297+ (bytesToElemCount (undefined :: a)
1298+ (mkChunkSize 1024))
1299+
1300+-------------------------------------------------------------------------------
1301+-- Instances
1302+-------------------------------------------------------------------------------
1303+
1304+instance (Show a, Storable a) => Show (Array a) where
1305+ {-# INLINE showsPrec #-}
1306+ showsPrec _ = shows . toList
1307+
1308+instance (Storable a, Read a, Show a) => Read (Array a) where
1309+ {-# INLINE readPrec #-}
1310+ readPrec = do
1311+ xs <- readPrec
1312+ return (fromList xs)
1313+ readListPrec = readListPrecDefault
1314+
1315+instance (a ~ Char) => IsString (Array a) where
1316+ {-# INLINE fromString #-}
1317+ fromString = fromList
1318+
1319+-- GHC versions 8.0 and below cannot derive IsList
1320+instance Storable a => IsList (Array a) where
1321+ type (Item (Array a)) = a
1322+ {-# INLINE fromList #-}
1323+ fromList = fromList
1324+ {-# INLINE fromListN #-}
1325+ fromListN = fromListN
1326+ {-# INLINE toList #-}
1327+ toList = toList
1328+
1329+{-# INLINE arrcmp #-}
1330+arrcmp :: Array a -> Array a -> Bool
1331+arrcmp arr1 arr2 =
1332+ let !res = unsafeInlineIO $ do
1333+ let ptr1 = unsafeForeignPtrToPtr $ aStart arr1
1334+ let ptr2 = unsafeForeignPtrToPtr $ aStart arr2
1335+ let len1 = aEnd arr1 `minusPtr` ptr1
1336+ let len2 = aEnd arr2 `minusPtr` ptr2
1337+
1338+ if len1 == len2
1339+ then do
1340+ r <- memcmp (castPtr ptr1) (castPtr ptr2) len1
1341+ touchForeignPtr $ aStart arr1
1342+ touchForeignPtr $ aStart arr2
1343+ return r
1344+ else return False
1345+ in res
1346+
1347+-- XXX we are assuming that Storable equality means element equality. This may
1348+-- or may not be correct? arrcmp is 40% faster compared to stream equality.
1349+instance (Storable a, Eq a) => Eq (Array a) where
1350+ {-# INLINE (==) #-}
1351+ (==) = arrcmp
1352+ -- arr1 == arr2 = runIdentity $ D.eqBy (==) (toStreamD arr1) (toStreamD arr2)
1353+
1354+instance (Storable a, NFData a) => NFData (Array a) where
1355+ {-# INLINE rnf #-}
1356+ rnf = foldl' (\_ x -> rnf x) ()
1357+
1358+instance (Storable a, Ord a) => Ord (Array a) where
1359+ {-# INLINE compare #-}
1360+ compare arr1 arr2 = unsafePerformIO $
1361+ D.cmpBy compare (toStreamD arr1) (toStreamD arr2)
1362+
1363+ -- Default definitions defined in base do not have an INLINE on them, so we
1364+ -- replicate them here with an INLINE.
1365+ {-# INLINE (<) #-}
1366+ x < y = case compare x y of { LT -> True; _ -> False }
1367+
1368+ {-# INLINE (<=) #-}
1369+ x <= y = case compare x y of { GT -> False; _ -> True }
1370+
1371+ {-# INLINE (>) #-}
1372+ x > y = case compare x y of { GT -> True; _ -> False }
1373+
1374+ {-# INLINE (>=) #-}
1375+ x >= y = case compare x y of { LT -> False; _ -> True }
1376+
1377+ -- These two default methods use '<=' rather than 'compare'
1378+ -- because the latter is often more expensive
1379+ {-# INLINE max #-}
1380+ max x y = if x <= y then y else x
1381+
1382+ {-# INLINE min #-}
1383+ min x y = if x <= y then x else y
1384+
1385+#ifdef DEVBUILD
1386+-- Definitions using the Storable constraint from the Array type. These are to
1387+-- make the Foldable instance possible though it is much slower (7x slower).
1388+--
1389+{-# INLINE_NORMAL toStreamD_ #-}
1390+toStreamD_ :: forall m a. MonadIO m => Int -> Array a -> D.Stream m a
1391+toStreamD_ size Array{..} =
1392+ let p = unsafeForeignPtrToPtr aStart
1393+ in D.Stream step p
1394+
1395+ where
1396+
1397+ {-# INLINE_LATE step #-}
1398+ step _ p | p == aEnd = return D.Stop
1399+ step _ p = do
1400+ x <- liftIO $ do
1401+ r <- peek p
1402+ touchForeignPtr aStart
1403+ return r
1404+ return $ D.Yield x (p `plusPtr` size)
1405+
1406+{-# INLINE_NORMAL _foldr #-}
1407+_foldr :: forall a b. (a -> b -> b) -> b -> Array a -> b
1408+_foldr f z arr@Array {..} =
1409+ let !n = sizeOf (undefined :: a)
1410+ in unsafePerformIO $ D.foldr f z $ toStreamD_ n arr
1411+
1412+-- | Note that the 'Foldable' instance is 7x slower than the direct
1413+-- operations.
1414+instance Foldable Array where
1415+ foldr = _foldr
1416+#endif
1417+
1418+-------------------------------------------------------------------------------
1419+-- Semigroup and Monoid
1420+-------------------------------------------------------------------------------
1421+
1422+instance Storable a => Semigroup (Array a) where
1423+ {-# INLINE (<>) #-}
1424+ arr1 <> arr2 = unsafePerformIO $ spliceTwo arr1 arr2
1425+
1426+nullForeignPtr :: ForeignPtr a
1427+nullForeignPtr = ForeignPtr nullAddr# (error "nullForeignPtr")
1428+
1429+nil ::
1430+#ifdef DEVBUILD
1431+ Storable a =>
1432+#endif
1433+ Array a
1434+nil = Array nullForeignPtr (Ptr nullAddr#) (Ptr nullAddr#)
1435+
1436+instance Storable a => Monoid (Array a) where
1437+ {-# INLINE mempty #-}
1438+ mempty = nil
1439+ {-# INLINE mappend #-}
1440+ mappend = (<>)
src/Streamly/Internal/Network/Socket.hs+5−5
@@ -90,6 +90,7 @@ import qualified Network.Socket as Net
9090import Streamly (MonadAsync)
9191import Streamly.Internal.Data.Unfold.Types (Unfold(..))
9292import Streamly.Internal.Memory.Array.Types (Array(..), lpackArraysChunksOf)
93+import Streamly.Internal.Memory.Mutable.Array.Types (mutableArray)
9394import Streamly.Internal.Data.Stream.Serial (SerialT)
9495import Streamly.Internal.Data.Stream.StreamK.Type (IsStream, mkStream)
9596import Streamly.Data.Fold (Fold)
@@ -255,11 +256,9 @@ readArrayUptoWith f size h = do
255256 -- ptr <- mallocPlainForeignPtrAlignedBytes size (alignment (undefined :: Word8))
256257 withForeignPtr ptr $ \p -> do
257258 n <- f h p size
258− let v = Array
259− { aStart = ptr
260− , aEnd = p `plusPtr` n
261− , aBound = p `plusPtr` size
262− }
259+ let v = A.unsafeFreeze
260+ $ mutableArray ptr (p `plusPtr` n) (p `plusPtr` size)
261+
263262 -- XXX shrink only if the diff is significant
264263 -- A.shrinkToFit v
265264 return v
@@ -469,6 +468,7 @@ writeChunksWithBufferOf :: (MonadIO m, Storable a)
469468 => Int -> Socket -> Fold m (Array a) ()
470469writeChunksWithBufferOf n h = lpackArraysChunksOf n (writeChunks h)
471470
471+
472472-- | Write a stream of strings to a socket in Latin1 encoding. Output is
473473-- flushed to the socket for each string.
474474--
src/Streamly/Memory/Ring.hs+2−2
@@ -101,7 +101,7 @@ unsafeEqArrayN Ring{..} rh A.Array{..} n =
101101 let !res = A.unsafeInlineIO $ do
102102 let rs = unsafeForeignPtrToPtr ringStart
103103 as = unsafeForeignPtrToPtr aStart
104− assert (aBound `minusPtr` as >= ringBound `minusPtr` rs) (return ())
104+ assert (aEnd `minusPtr` as >= ringBound `minusPtr` rs) (return ())
105105 let len = ringBound `minusPtr` rh
106106 r1 <- A.memcmp (castPtr rh) (castPtr as) (min len n)
107107 r2 <- if n > len
@@ -127,7 +127,7 @@ unsafeEqArray Ring{..} rh A.Array{..} =
127127 let !res = A.unsafeInlineIO $ do
128128 let rs = unsafeForeignPtrToPtr ringStart
129129 let as = unsafeForeignPtrToPtr aStart
130− assert (aBound `minusPtr` as >= ringBound `minusPtr` rs)
130+ assert (aEnd `minusPtr` as >= ringBound `minusPtr` rs)
131131 (return ())
132132 let len = ringBound `minusPtr` rh
133133 r1 <- A.memcmp (castPtr rh) (castPtr as) len
streamly.cabal+1
@@ -382,6 +382,7 @@ library
382382 , Streamly.Internal.Data.Prim.Array
383383 , Streamly.Internal.Data.SmallArray.Types
384384 , Streamly.Internal.Data.SmallArray
385+ , Streamly.Internal.Memory.Mutable.Array.Types
385386 , Streamly.Internal.Memory.Array.Types
386387 , Streamly.Internal.Memory.Array
387388 , Streamly.Internal.Memory.ArrayStream