1{-# LANGUAGE ScopedTypeVariables #-}
2{-# LANGUAGE RecordWildCards #-}
3{-# LANGUAGE BangPatterns #-}
4{-# LANGUAGE ExistentialQuantification #-}
7-- Module : Streamly.Memory.Ring
8-- Copyright : (c) 2019 Composewell Technologies
10-- Maintainer : streamly@composewell.com
11-- Stability : experimental
15module Streamly.Memory.Ring
29 -- * Fast Byte Comparisons
34import Control.Exception (assert)
35import Foreign.ForeignPtr (ForeignPtr, withForeignPtr)
36import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
37import Foreign.Ptr (plusPtr, minusPtr, castPtr)
38import Foreign.Storable (Storable(..))
39import GHC.ForeignPtr (mallocPlainForeignPtrAlignedBytes)
40import GHC.Ptr (Ptr(..))
41import Prelude hiding (length, concat)
43import qualified Streamly.Internal.Memory.Array.Types as A
45-- | A ring buffer is a mutable array of fixed size. Initially the array is
46-- empty, with ringStart pointing at the start of allocated memory. We call the
47-- next location to be written in the ring as ringHead. Initially ringHead ==
48-- ringStart. When the first item is added, ringHead points to ringStart +
49-- sizeof item. When the buffer becomes full ringHead would wrap around to
50-- ringStart. When the buffer is full, ringHead always points at the oldest
51-- item in the ring and the newest item added always overwrites the oldest
54-- When using it we should keep in mind that a ringBuffer is a mutable data
55-- structure. We should not leak out references to it for immutable use.
58 { ringStart :: !(ForeignPtr a) -- first address
59 , ringBound :: !(Ptr a) -- first address beyond allocated memory
62-- | Create a new ringbuffer and return the ring buffer and the ringHead.
63-- Returns the ring and the ringHead, the ringHead is same as ringStart.
65new :: forall a. Storable a => Int -> IO (Ring a, Ptr a)
67 let size = count * sizeOf (undefined :: a)
68 fptr <- mallocPlainForeignPtrAlignedBytes size (alignment (undefined :: a))
69 let p = unsafeForeignPtrToPtr fptr
72 , ringBound = p `plusPtr` size
75-- | Advance the ringHead by 1 item, wrap around if we hit the end of the
78advance :: forall a. Storable a => Ring a -> Ptr a -> Ptr a
79advance Ring{..} ringHead =
80 let ptr = ringHead `plusPtr` sizeOf (undefined :: a)
83 else unsafeForeignPtrToPtr ringStart
85-- | Insert an item at the head of the ring, when the ring is full this
86-- replaces the oldest item in the ring with the new item. This is unsafe
87-- beause ringHead supplied is not verified to be within the Ring. Also,
88-- the ringStart foreignPtr must be guaranteed to be alive by the caller.
89{-# INLINE unsafeInsert #-}
90unsafeInsert :: Storable a => Ring a -> Ptr a -> a -> IO (Ptr a)
91unsafeInsert rb ringHead newVal = do
93 -- touchForeignPtr (ringStart rb)
94 return $ advance rb ringHead
96-- XXX remove all usage of A.unsafeInlineIO
98-- | Like 'unsafeEqArray' but compares only N bytes instead of entire length of
99-- the ring buffer. This is unsafe because the ringHead Ptr is not checked to
101{-# INLINE unsafeEqArrayN #-}
102unsafeEqArrayN :: Ring a -> Ptr a -> A.Array a -> Int -> Bool
103unsafeEqArrayN Ring{..} rh A.Array{..} n =
104 let !res = A.unsafeInlineIO $ do
105 let rs = unsafeForeignPtrToPtr ringStart
106 let as = unsafeForeignPtrToPtr aStart
107 assert (aBound `minusPtr` as >= ringBound `minusPtr` rs) (return ())
108 let len = ringBound `minusPtr` rh
109 r1 <- A.memcmp (castPtr rh) (castPtr as) (min len n)
111 then A.memcmp (castPtr rs) (castPtr (as `plusPtr` len))
112 (min (rh `minusPtr` rs) (n - len))
114 -- XXX enable these, check perf impact
115 -- touchForeignPtr ringStart
116 -- touchForeignPtr aStart
120-- | Byte compare the entire length of ringBuffer with the given array,
121-- starting at the supplied ringHead pointer. Returns true if the Array and
122-- the ringBuffer have identical contents.
124-- This is unsafe because the ringHead Ptr is not checked to be in range. The
125-- supplied array must be equal to or bigger than the ringBuffer, ARRAY BOUNDS
127{-# INLINE unsafeEqArray #-}
128unsafeEqArray :: Ring a -> Ptr a -> A.Array a -> Bool
129unsafeEqArray Ring{..} rh A.Array{..} =
130 let !res = A.unsafeInlineIO $ do
131 let rs = unsafeForeignPtrToPtr ringStart
132 let as = unsafeForeignPtrToPtr aStart
133 assert (aBound `minusPtr` as >= ringBound `minusPtr` rs)
135 let len = ringBound `minusPtr` rh
136 r1 <- A.memcmp (castPtr rh) (castPtr as) len
137 r2 <- A.memcmp (castPtr rs) (castPtr (as `plusPtr` len))
139 -- XXX enable these, check perf impact
140 -- touchForeignPtr ringStart
141 -- touchForeignPtr aStart
147-- | Fold the buffer starting from ringStart up to the given 'Ptr' using a pure
148-- step function. This is useful to fold the items in the ring when the ring is
149-- not full. The supplied pointer is usually the end of the ring.
151-- Unsafe because the supplied Ptr is not checked to be in range.
152{-# INLINE unsafeFoldRing #-}
153unsafeFoldRing :: forall a b. Storable a
154 => Ptr a -> (b -> a -> b) -> b -> Ring a -> b
155unsafeFoldRing ptr f z Ring{..} =
156 let !res = A.unsafeInlineIO $ withForeignPtr ringStart $ \p ->
161 | p == q = return acc
164 go (f acc x) (p `plusPtr` sizeOf (undefined :: a)) q
166-- | Like unsafeFoldRing but with a monadic step function.
167{-# INLINE unsafeFoldRingM #-}
168unsafeFoldRingM :: forall m a b. (Monad m, Storable a)
169 => Ptr a -> (b -> a -> m b) -> b -> Ring a -> m b
170unsafeFoldRingM ptr f z Ring{..} = go z (unsafeForeignPtrToPtr ringStart) ptr
173 | start == end = return acc
175 let !x = A.unsafeInlineIO $ peek start
177 go acc' (start `plusPtr` sizeOf (undefined :: a)) end
179-- | Fold the entire length of a ring buffer starting at the supplied ringHead
180-- pointer. Assuming the supplied ringHead pointer points to the oldest item,
181-- this would fold the ring starting from the oldest item to the newest item in
183{-# INLINE unsafeFoldRingFullM #-}
184unsafeFoldRingFullM :: forall m a b. (Monad m, Storable a)
185 => Ptr a -> (b -> a -> m b) -> b -> Ring a -> m b
186unsafeFoldRingFullM rh f z rb@Ring{..} = go z rh
189 let !x = A.unsafeInlineIO $ peek start
191 let ptr = advance rb start
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