2-- Module : Streamly.Memory.Ring
3-- Copyright : (c) 2019 Composewell Technologies
5-- Maintainer : streamly@composewell.com
6-- Stability : experimental
10module Streamly.Memory.Ring
28 -- * Fast Byte Comparisons
33import Control.Exception (assert)
34import Foreign.ForeignPtr (ForeignPtr, withForeignPtr, touchForeignPtr)
35import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
36import Foreign.Ptr (plusPtr, minusPtr, castPtr)
37import Foreign.Storable (Storable(..))
38import GHC.ForeignPtr (mallocPlainForeignPtrAlignedBytes)
39import GHC.Ptr (Ptr(..))
40import Prelude hiding (length, concat)
42import Control.Monad.IO.Class (MonadIO(..))
44import qualified Streamly.Internal.Data.Array.Storable.Foreign.Types as A
46-- | A ring buffer is a mutable array of fixed size. Initially the array is
47-- empty, with ringStart pointing at the start of allocated memory. We call the
48-- next location to be written in the ring as ringHead. Initially ringHead ==
49-- ringStart. When the first item is added, ringHead points to ringStart +
50-- sizeof item. When the buffer becomes full ringHead would wrap around to
51-- ringStart. When the buffer is full, ringHead always points at the oldest
52-- item in the ring and the newest item added always overwrites the oldest
55-- When using it we should keep in mind that a ringBuffer is a mutable data
56-- structure. We should not leak out references to it for immutable use.
59 { ringStart :: {-# UNPACK #-} !(ForeignPtr a) -- first address
60 , ringBound :: {-# UNPACK #-} !(Ptr a) -- first address beyond allocated memory
63-- | Get the first address of the ring as a pointer.
64startOf :: Ring a -> Ptr a
65startOf = unsafeForeignPtrToPtr . ringStart
67-- | Create a new ringbuffer and return the ring buffer and the ringHead.
68-- Returns the ring and the ringHead, the ringHead is same as ringStart.
70new :: forall a. Storable a => Int -> IO (Ring a, Ptr a)
72 let size = count * sizeOf (undefined :: a)
73 fptr <- mallocPlainForeignPtrAlignedBytes size (alignment (undefined :: a))
74 let p = unsafeForeignPtrToPtr fptr
77 , ringBound = p `plusPtr` size
80-- | Advance the ringHead by 1 item, wrap around if we hit the end of the
83advance :: forall a. Storable a => Ring a -> Ptr a -> Ptr a
84advance Ring{..} ringHead =
85 let ptr = ringHead `plusPtr` sizeOf (undefined :: a)
88 else unsafeForeignPtrToPtr ringStart
90-- | Move the ringHead by n items. The direction depends on the sign on whether
91-- n is positive or negative. Wrap around if we hit the beginning or end of the
94moveBy :: forall a. Storable a => Int -> Ring a -> Ptr a -> Ptr a
95moveBy by Ring {..} ringHead = ringStartPtr `plusPtr` advanceFromHead
99 elemSize = sizeOf (undefined :: a)
100 ringStartPtr = unsafeForeignPtrToPtr ringStart
101 lenInBytes = ringBound `minusPtr` ringStartPtr
102 offInBytes = ringHead `minusPtr` ringStartPtr
103 len = assert (lenInBytes `mod` elemSize == 0) $ lenInBytes `div` elemSize
104 off = assert (offInBytes `mod` elemSize == 0) $ offInBytes `div` elemSize
105 advanceFromHead = (off + by `mod` len) * elemSize
107-- | Insert an item at the head of the ring, when the ring is full this
108-- replaces the oldest item in the ring with the new item. This is unsafe
109-- beause ringHead supplied is not verified to be within the Ring. Also,
110-- the ringStart foreignPtr must be guaranteed to be alive by the caller.
111{-# INLINE unsafeInsert #-}
112unsafeInsert :: Storable a => Ring a -> Ptr a -> a -> IO (Ptr a)
113unsafeInsert rb ringHead newVal = do
115 -- touchForeignPtr (ringStart rb)
116 return $ advance rb ringHead
118-- XXX remove all usage of A.unsafeInlineIO
120-- | Like 'unsafeEqArray' but compares only N bytes instead of entire length of
121-- the ring buffer. This is unsafe because the ringHead Ptr is not checked to
123{-# INLINE unsafeEqArrayN #-}
124unsafeEqArrayN :: Ring a -> Ptr a -> A.Array a -> Int -> Bool
125unsafeEqArrayN Ring{..} rh A.Array{..} n =
126 let !res = A.unsafeInlineIO $ do
127 let rs = unsafeForeignPtrToPtr ringStart
128 as = unsafeForeignPtrToPtr aStart
129 assert (aEnd `minusPtr` as >= ringBound `minusPtr` rs) (return ())
130 let len = ringBound `minusPtr` rh
131 r1 <- A.memcmp (castPtr rh) (castPtr as) (min len n)
133 then A.memcmp (castPtr rs) (castPtr (as `plusPtr` len))
134 (min (rh `minusPtr` rs) (n - len))
136 -- XXX enable these, check perf impact
137 -- touchForeignPtr ringStart
138 -- touchForeignPtr aStart
142-- | Byte compare the entire length of ringBuffer with the given array,
143-- starting at the supplied ringHead pointer. Returns true if the Array and
144-- the ringBuffer have identical contents.
146-- This is unsafe because the ringHead Ptr is not checked to be in range. The
147-- supplied array must be equal to or bigger than the ringBuffer, ARRAY BOUNDS
149{-# INLINE unsafeEqArray #-}
150unsafeEqArray :: Ring a -> Ptr a -> A.Array a -> Bool
151unsafeEqArray Ring{..} rh A.Array{..} =
152 let !res = A.unsafeInlineIO $ do
153 let rs = unsafeForeignPtrToPtr ringStart
154 let as = unsafeForeignPtrToPtr aStart
155 assert (aEnd `minusPtr` as >= ringBound `minusPtr` rs)
157 let len = ringBound `minusPtr` rh
158 r1 <- A.memcmp (castPtr rh) (castPtr as) len
159 r2 <- A.memcmp (castPtr rs) (castPtr (as `plusPtr` len))
161 -- XXX enable these, check perf impact
162 -- touchForeignPtr ringStart
163 -- touchForeignPtr aStart
169-- | Fold the buffer starting from ringStart up to the given 'Ptr' using a pure
170-- step function. This is useful to fold the items in the ring when the ring is
171-- not full. The supplied pointer is usually the end of the ring.
173-- Unsafe because the supplied Ptr is not checked to be in range.
174{-# INLINE unsafeFoldRing #-}
175unsafeFoldRing :: forall a b. Storable a
176 => Ptr a -> (b -> a -> b) -> b -> Ring a -> b
177unsafeFoldRing ptr f z Ring{..} =
178 let !res = A.unsafeInlineIO $ withForeignPtr ringStart $ \p ->
183 | p == q = return acc
186 go (f acc x) (p `plusPtr` sizeOf (undefined :: a)) q
188-- XXX Can we remove MonadIO here?
189withForeignPtrM :: MonadIO m => ForeignPtr a -> (Ptr a -> m b) -> m b
190withForeignPtrM fp fn = do
191 r <- fn $ unsafeForeignPtrToPtr fp
192 liftIO $ touchForeignPtr fp
195-- | Like unsafeFoldRing but with a monadic step function.
196{-# INLINE unsafeFoldRingM #-}
197unsafeFoldRingM :: forall m a b. (MonadIO m, Storable a)
198 => Ptr a -> (b -> a -> m b) -> b -> Ring a -> m b
199unsafeFoldRingM ptr f z Ring {..} =
200 withForeignPtrM ringStart $ \x -> go z x ptr
203 | start == end = return acc
205 let !x = A.unsafeInlineIO $ peek start
207 go acc' (start `plusPtr` sizeOf (undefined :: a)) end
209-- | Fold the entire length of a ring buffer starting at the supplied ringHead
210-- pointer. Assuming the supplied ringHead pointer points to the oldest item,
211-- this would fold the ring starting from the oldest item to the newest item in
214-- Note, this will crash on ring of 0 size.
216{-# INLINE unsafeFoldRingFullM #-}
217unsafeFoldRingFullM :: forall m a b. (MonadIO m, Storable a)
218 => Ptr a -> (b -> a -> m b) -> b -> Ring a -> m b
219unsafeFoldRingFullM rh f z rb@Ring {..} =
220 withForeignPtrM ringStart $ \_ -> go z rh
223 let !x = A.unsafeInlineIO $ peek start
225 let ptr = advance rb start
230-- | Fold @Int@ items in the ring starting at @Ptr a@. Won't fold more
231-- than the length of the ring.
233-- Note, this will crash on ring of 0 size.
235{-# INLINE unsafeFoldRingNM #-}
236unsafeFoldRingNM :: forall m a b. (MonadIO m, Storable a)
237 => Int -> Ptr a -> (b -> a -> m b) -> b -> Ring a -> m b
238unsafeFoldRingNM count rh f z rb@Ring {..} =
239 withForeignPtrM ringStart $ \_ -> go count z rh
243 go 0 acc _ = return acc
244 go !n !acc !start = do
245 let !x = A.unsafeInlineIO $ peek start
247 let ptr = advance rb start
248 if ptr == rh || n == 0
250 else go (n - 1) acc' ptr
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list