primal-memory-0.1.0.0: src/Data/Prim/Memory/Internal.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RoleAnnotations #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeFamilyDependencies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
-- |
-- Module : Data.Prim.Memory.Internal
-- Copyright : (c) Alexey Kuleshevich 2020
-- License : BSD3
-- Maintainer : Alexey Kuleshevich <alexey@kuleshevi.ch>
-- Stability : experimental
-- Portability : non-portable
--
module Data.Prim.Memory.Internal
( Bytes(..)
, MBytes(..)
, Pinned(..)
, module Data.Prim.Memory.Internal
) where
import Control.Exception
import Data.List.NonEmpty (NonEmpty(..))
import Control.Monad.ST
import Control.Prim.Monad
import Control.Prim.Monad.Unsafe
import Data.Foldable as Foldable
import Data.Prim
import Data.Prim.Memory.Bytes.Internal
( Bytes(..)
, MBytes(..)
, Pinned(..)
, allocMBytes
, reallocMBytes
, byteCountBytes
, compareByteOffBytes
, copyByteOffBytesToMBytes
, freezeMBytes
, getByteCountMBytes
, indexByteOffBytes
, indexOffBytes
, isSameBytes
, moveByteOffMBytesToMBytes
, readByteOffMBytes
, readOffMBytes
, setMBytes
, thawBytes
, writeByteOffMBytes
, writeOffMBytes
)
import Data.List as List
import Data.Prim.Memory.ByteString
import Data.Prim.Memory.ForeignPtr
import Data.Prim.Memory.Ptr
import Foreign.Prim
import Numeric (showHex)
import qualified Data.Semigroup as Semigroup
import qualified Data.Monoid as Monoid
import Data.Kind
class MemRead r where
byteCountMem :: r -> Count Word8
indexOffMem :: Prim e => r -> Off e -> e
indexByteOffMem :: Prim e => r -> Off Word8 -> e
-- | Source and target can't refer to the same memory chunks
copyByteOffToMBytesMem ::
(MonadPrim s m, Prim e) => r -> Off Word8 -> MBytes p s -> Off Word8 -> Count e -> m ()
-- | Source and target can't refer to the same memory chunks
copyByteOffToPtrMem ::
(MonadPrim s m, Prim e) => r -> Off Word8 -> Ptr e -> Off Word8 -> Count e -> m ()
compareByteOffToPtrMem ::
(MonadPrim s m, Prim e) => r -> Off Word8 -> Ptr e -> Off Word8 -> Count e -> m Ordering
compareByteOffToBytesMem ::
(MonadPrim s m, Prim e) => r -> Off Word8 -> Bytes p -> Off Word8 -> Count e -> m Ordering
compareByteOffMem ::
(MemRead r', Prim e) => r' -> Off Word8 -> r -> Off Word8 -> Count e -> Ordering
-- | Generalized memory allocation and pure/mutable state conversion.
class (MemRead (FrozenMem a), MemWrite a) => MemAlloc a where
type FrozenMem a = (fa :: Type) | fa -> a
getByteCountMem :: MonadPrim s m => a s -> m (Count Word8)
allocByteCountMem :: MonadPrim s m => Count Word8 -> m (a s)
thawMem :: MonadPrim s m => FrozenMem a -> m (a s)
freezeMem :: MonadPrim s m => a s -> m (FrozenMem a)
resizeMem :: (MonadPrim s m, Prim e) => a s -> Count e -> m (a s)
resizeMem = defaultResizeMem
class MemWrite w where
readOffMem :: (MonadPrim s m, Prim e) => w s -> Off e -> m e
readByteOffMem :: (MonadPrim s m, Prim e) => w s -> Off Word8 -> m e
writeOffMem :: (MonadPrim s m, Prim e) => w s -> Off e -> e -> m ()
writeByteOffMem :: (MonadPrim s m, Prim e) => w s -> Off Word8 -> e -> m ()
-- | Source and target can be overlapping memory chunks
moveByteOffToMBytesMem ::
(MonadPrim s m, Prim e) => w s -> Off Word8 -> MBytes p s -> Off Word8 -> Count e -> m ()
-- | Source and target can be overlapping memory chunks
moveByteOffToPtrMem ::
(MonadPrim s m, Prim e) => w s -> Off Word8 -> Ptr e -> Off Word8 -> Count e -> m ()
copyByteOffMem ::
(MonadPrim s m, MemRead r, Prim e) => r -> Off Word8 -> w s -> Off Word8 -> Count e -> m ()
moveByteOffMem ::
(MonadPrim s m, MemWrite w', Prim e) => w' s -> Off Word8 -> w s -> Off Word8 -> Count e -> m ()
-- TODO: Potential feature for the future implementation. Will require extra function in `Prim`.
--setByteOffMem :: (MonadPrim s m, Prim e) => w s -> Off Word8 -> Count e -> e -> m ()
-- | Write the same value into each cell starting at an offset.
setMem
:: (MonadPrim s m, Prim e)
=> w s -- ^ Writable memory. Must have enough bytes, at least: (off+count)*(sizeOf e)
-> Off e -- ^ An offset into writable memory at which element setting should start.
-> Count e -- ^ Numer of cells to write the elemnt into
-> e -- ^ Element to write into all memory cells specified by offset and count. Even
-- if the count is @0@ this element might be still fully evaluated.
-> m ()
instance MemRead ByteString where
byteCountMem (PS _ _ c) = Count c
{-# INLINE byteCountMem #-}
indexOffMem bs i = unsafeInlineIO $ withPtrAccess bs (`readOffPtr` i)
{-# INLINE indexOffMem #-}
indexByteOffMem bs i = unsafeInlineIO $ withPtrAccess bs (`readByteOffPtr` i)
{-# INLINE indexByteOffMem #-}
copyByteOffToMBytesMem bs srcOff mb dstOff c =
withPtrAccess bs $ \srcPtr -> copyByteOffPtrToMBytes srcPtr srcOff mb dstOff c
{-# INLINE copyByteOffToMBytesMem #-}
copyByteOffToPtrMem bs srcOff dstPtr dstOff c =
withPtrAccess bs $ \srcPtr -> copyByteOffPtrToPtr srcPtr srcOff dstPtr dstOff c
{-# INLINE copyByteOffToPtrMem #-}
compareByteOffToPtrMem bs off1 ptr2 off2 c =
withPtrAccess bs $ \ptr1 -> pure $ compareByteOffPtrToPtr ptr1 off1 ptr2 off2 c
{-# INLINE compareByteOffToPtrMem #-}
compareByteOffToBytesMem bs off1 bytes off2 c =
withPtrAccess bs $ \ptr1 -> pure $ compareByteOffPtrToBytes ptr1 off1 bytes off2 c
{-# INLINE compareByteOffToBytesMem #-}
compareByteOffMem mem1 off1 bs off2 c =
unsafeInlineIO $ withPtrAccess bs $ \ptr2 -> compareByteOffToPtrMem mem1 off1 ptr2 off2 c
{-# INLINE compareByteOffMem #-}
instance MemAlloc MByteString where
type FrozenMem MByteString = ByteString
getByteCountMem (MByteString (PS _ _ c)) = pure $ Count c
{-# INLINE getByteCountMem #-}
allocByteCountMem c = do
fp <- mallocByteCountPlainForeignPtr c
pure $ MByteString (PS fp 0 (coerce c))
{-# INLINE allocByteCountMem #-}
thawMem bs = pure $ MByteString bs
{-# INLINE thawMem #-}
freezeMem (MByteString bs) = pure bs
{-# INLINE freezeMem #-}
resizeMem bsm@(MByteString (PS fp o n)) newc
| newn > n = defaultResizeMem bsm newc
| otherwise = pure $ MByteString (PS fp o newn)
where -- constant slice if we need to reduce the size
Count newn = toByteCount newc
{-# INLINE resizeMem #-}
instance MemWrite MByteString where
readOffMem (MByteString mbs) i = withPtrAccess mbs (`readOffPtr` i)
{-# INLINE readOffMem #-}
readByteOffMem (MByteString mbs) i = withPtrAccess mbs (`readByteOffPtr` i)
{-# INLINE readByteOffMem #-}
writeOffMem (MByteString mbs) i a = withPtrAccess mbs $ \ptr -> writeOffPtr ptr i a
{-# INLINE writeOffMem #-}
writeByteOffMem (MByteString mbs) i a = withPtrAccess mbs $ \ptr -> writeByteOffPtr ptr i a
{-# INLINE writeByteOffMem #-}
moveByteOffToPtrMem (MByteString fsrc) srcOff dstPtr dstOff c =
withPtrAccess fsrc $ \srcPtr -> moveByteOffPtrToPtr srcPtr srcOff dstPtr dstOff c
{-# INLINE moveByteOffToPtrMem #-}
moveByteOffToMBytesMem (MByteString fsrc) srcOff dst dstOff c =
withPtrAccess fsrc $ \srcPtr -> moveByteOffPtrToMBytes srcPtr srcOff dst dstOff c
{-# INLINE moveByteOffToMBytesMem #-}
copyByteOffMem src srcOff (MByteString fdst) dstOff c =
withPtrAccess fdst $ \dstPtr -> copyByteOffToPtrMem src srcOff dstPtr dstOff c
{-# INLINE copyByteOffMem #-}
moveByteOffMem src srcOff (MByteString fdst) dstOff c =
withPtrAccess fdst $ \dstPtr -> moveByteOffToPtrMem src srcOff dstPtr dstOff c
{-# INLINE moveByteOffMem #-}
setMem (MByteString mbs) off c a = withPtrAccess mbs $ \ptr -> setOffPtr ptr off c a
{-# INLINE setMem #-}
instance MemRead ShortByteString where
byteCountMem = byteCountMem . fromShortByteStringBytes
{-# INLINE byteCountMem #-}
indexOffMem sbs = indexOffMem (fromShortByteStringBytes sbs)
{-# INLINE indexOffMem #-}
indexByteOffMem sbs = indexByteOffMem (fromShortByteStringBytes sbs)
{-# INLINE indexByteOffMem #-}
copyByteOffToMBytesMem sbs = copyByteOffToMBytesMem (fromShortByteStringBytes sbs)
{-# INLINE copyByteOffToMBytesMem #-}
copyByteOffToPtrMem sbs = copyByteOffToPtrMem (fromShortByteStringBytes sbs)
{-# INLINE copyByteOffToPtrMem #-}
compareByteOffToPtrMem sbs = compareByteOffToPtrMem (fromShortByteStringBytes sbs)
{-# INLINE compareByteOffToPtrMem #-}
compareByteOffToBytesMem sbs = compareByteOffToBytesMem (fromShortByteStringBytes sbs)
{-# INLINE compareByteOffToBytesMem #-}
compareByteOffMem mem off1 sbs = compareByteOffMem mem off1 (fromShortByteStringBytes sbs)
{-# INLINE compareByteOffMem #-}
-- | A wrapper that adds a phantom state token. It can be use with types that either
-- doesn't have such state token or are designed to work in `IO` and therefore restricted
-- to `RW`. Using this wrapper is very much unsafe, so make sure you know what you are
-- doing.
newtype MemState a s = MemState { unMemState :: a }
instance MemWrite (MemState (ForeignPtr a)) where
readOffMem (MemState fptr) i = withForeignPtr fptr $ \ptr -> readOffPtr (castPtr ptr) i
{-# INLINE readOffMem #-}
readByteOffMem (MemState fptr) i =
withForeignPtr fptr $ \ptr -> readByteOffPtr (castPtr ptr) i
{-# INLINE readByteOffMem #-}
writeOffMem (MemState fptr) i a = withForeignPtr fptr $ \ptr -> writeOffPtr (castPtr ptr) i a
{-# INLINE writeOffMem #-}
writeByteOffMem (MemState fptr) i a =
withForeignPtr fptr $ \ptr -> writeByteOffPtr (castPtr ptr) i a
{-# INLINE writeByteOffMem #-}
moveByteOffToPtrMem (MemState fsrc) srcOff dstPtr dstOff c =
withForeignPtr fsrc $ \srcPtr -> moveByteOffPtrToPtr (castPtr srcPtr) srcOff dstPtr dstOff c
{-# INLINE moveByteOffToPtrMem #-}
moveByteOffToMBytesMem (MemState fsrc) srcOff dst dstOff c =
withForeignPtr fsrc $ \srcPtr -> moveByteOffPtrToMBytes (castPtr srcPtr) srcOff dst dstOff c
{-# INLINE moveByteOffToMBytesMem #-}
copyByteOffMem src srcOff (MemState fdst) dstOff c =
withForeignPtr fdst $ \dstPtr ->
copyByteOffToPtrMem src srcOff (castPtr dstPtr) dstOff c
{-# INLINE copyByteOffMem #-}
moveByteOffMem src srcOff (MemState fdst) dstOff c =
withForeignPtr fdst $ \dstPtr ->
moveByteOffToPtrMem src srcOff (castPtr dstPtr) dstOff c
{-# INLINE moveByteOffMem #-}
setMem (MemState fptr) off c a = withForeignPtr fptr $ \ptr -> setOffPtr (castPtr ptr) off c a
{-# INLINE setMem #-}
modifyFetchOldMem ::
(MemWrite w, MonadPrim s m, Prim b) => w s -> Off b -> (b -> b) -> m b
modifyFetchOldMem mem o f = modifyFetchOldMemM mem o (pure . f)
{-# INLINE modifyFetchOldMem #-}
modifyFetchNewMem ::
(MemWrite w, MonadPrim s m, Prim b) => w s -> Off b -> (b -> b) -> m b
modifyFetchNewMem mem o f = modifyFetchNewMemM mem o (pure . f)
{-# INLINE modifyFetchNewMem #-}
modifyFetchOldMemM ::
(MemWrite w, MonadPrim s m, Prim b) => w s -> Off b -> (b -> m b) -> m b
modifyFetchOldMemM mem o f = do
a <- readOffMem mem o
a <$ (writeOffMem mem o =<< f a)
{-# INLINE modifyFetchOldMemM #-}
modifyFetchNewMemM ::
(MemWrite w, MonadPrim s m, Prim b) => w s -> Off b -> (b -> m b) -> m b
modifyFetchNewMemM mem o f = do
a <- readOffMem mem o
a' <- f a
a' <$ writeOffMem mem o a'
{-# INLINE modifyFetchNewMemM #-}
defaultResizeMem ::
(Prim e, MemAlloc a, MonadPrim s m) => a s -> Count e -> m (a s)
defaultResizeMem mem c = do
let newByteCount = toByteCount c
oldByteCount <- getByteCountMem mem
if oldByteCount == newByteCount
then pure mem
else do
newMem <- allocByteCountMem newByteCount
newMem <$ moveMem mem 0 newMem 0 oldByteCount
-- | Make @n@ copies of supplied region of memory into a contiguous chunk of memory.
cycleMemN :: (MemAlloc a, MemRead r) => Int -> r -> FrozenMem a
cycleMemN n r
| n <= 0 = emptyMem
| otherwise =
runST $ do
let bc@(Count chunk) = byteCountMem r
c@(Count c8) = Count n * bc
mem <- allocByteCountMem c
let go i = when (i < c8) $ copyByteOffMem r 0 mem (Off i) bc >> go (i + chunk)
go 0
freezeMem mem
{-# INLINE cycleMemN #-}
-- | Chunk of empty memory.
emptyMem :: MemAlloc a => FrozenMem a
emptyMem = createMemST_ (0 :: Count Word8) (\_ -> pure ())
{-# INLINE emptyMem #-}
-- | A region of memory that hold a single element.
singletonMem ::
forall e a. (MemAlloc a, Prim e)
=> e
-> FrozenMem a
singletonMem a = createMemST_ (1 :: Count e) $ \mem -> writeOffMem mem 0 a
{-# INLINE singletonMem #-}
-- | Allocate enough memory for number of elements. Memory is not initialized and may
-- contain garbage. Use `allocZeroMem` if clean memory is needed.
--
-- [Unsafe Count] Negative element count will result in unpredictable behavior
--
-- @since 0.1.0
allocMem :: (MemAlloc a, MonadPrim s m, Prim e) => Count e -> m (a s)
allocMem n = allocByteCountMem (toByteCount n)
{-# INLINE allocMem #-}
-- | Same as `allocMem`, but also use @memset@ to initialize all the new memory to zeros.
--
-- [Unsafe Count] Negative element count will result in unpredictable behavior
--
-- @since 0.1.0
allocZeroMem ::
(MemAlloc a, MonadPrim s m, Prim e) => Count e -> m (a s)
allocZeroMem n = do
m <- allocMem n
m <$ setMem m 0 (toByteCount n) (0 :: Word8)
{-# INLINE allocZeroMem #-}
createMemST :: (MemAlloc a, Prim e) => Count e -> (forall s . a s -> ST s b) -> (b, FrozenMem a)
createMemST n f = runST $ do
m <- allocMem n
res <- f m
i <- freezeMem m
pure (res, i)
{-# INLINE createMemST #-}
createMemST_ :: (MemAlloc a, Prim e) => Count e -> (forall s . a s -> ST s b) -> FrozenMem a
createMemST_ n f = runST (allocMem n >>= \m -> f m >> freezeMem m)
{-# INLINE createMemST_ #-}
createZeroMemST :: (MemAlloc a, Prim e) => Count e -> (forall s . a s -> ST s b) -> (b, FrozenMem a)
createZeroMemST n f = runST $ do
m <- allocZeroMem n
res <- f m
i <- freezeMem m
pure (res, i)
{-# INLINE createZeroMemST #-}
createZeroMemST_ :: (MemAlloc a, Prim e) => Count e -> (forall s . a s -> ST s b) -> FrozenMem a
createZeroMemST_ n f = runST (allocZeroMem n >>= \m -> f m >> freezeMem m)
{-# INLINE createZeroMemST_ #-}
copyMem ::
(MonadPrim s m, MemRead r, MemWrite w, Prim e)
=> r -- ^ Source memory region
-> Off e -- ^ Offset into the source in number of elements
-> w s -- ^ Destination memory region
-> Off e -- ^ Offset into destination in number of elements
-> Count e -- ^ Number of elements to copy over
-> m ()
copyMem src srcOff dst dstOff = copyByteOffMem src (toByteOff srcOff) dst (toByteOff dstOff)
{-# INLINE copyMem #-}
moveMem ::
(MonadPrim s m, MemWrite w1, MemWrite w2, Prim e)
=> w1 s -- ^ Source memory region
-> Off e -- ^ Offset into the source in number of elements
-> w2 s -- ^ Destination memory region
-> Off e -- ^ Offset into destination in number of elements
-> Count e -- ^ Number of elements to copy over
-> m ()
moveMem src srcOff dst dstOff = moveByteOffMem src (toByteOff srcOff) dst (toByteOff dstOff)
{-# INLINE moveMem #-}
appendMem :: (MemRead r1, MemRead r2, MemAlloc a) => r1 -> r2 -> FrozenMem a
appendMem r1 r2 =
createMemST_ (n1 + n2) $ \mem -> do
copyMem r1 0 mem 0 n1
copyMem r2 (coerce n1) mem (coerce n1) n2
where
n1 = byteCountMem r1
n2 = byteCountMem r2
{-# INLINABLE appendMem #-}
concatMem :: (MemRead r, MemAlloc a) => [r] -> FrozenMem a
concatMem xs = do
let c = Foldable.foldl' (\ !acc b -> acc + byteCountMem b) 0 xs
createMemST_ c $ \mb -> do
let load i b = do
let cb@(Count n) = byteCountMem b :: Count Word8
(i + Off n) <$ copyMem b 0 mb i cb
foldM_ load 0 xs
{-# INLINABLE concatMem #-}
thawCopyMem ::
(MemRead r, MemAlloc a, MonadPrim s m, Prim e) => r -> Off e -> Count e -> m (a s)
thawCopyMem a off c = do
mem <- allocMem c
mem <$ copyMem a off mem 0 c
{-# INLINE thawCopyMem #-}
freezeCopyMem ::
(MemAlloc a, MonadPrim s m, Prim e)
=> a s
-> Off e
-> Count e
-> m (FrozenMem a)
freezeCopyMem mem off c = freezeMem mem >>= \r -> thawCopyMem r off c >>= freezeMem
{-# INLINE freezeCopyMem #-}
thawCloneMem :: (MemRead r, MemAlloc a, MonadPrim s m) => r -> m (a s)
thawCloneMem a = thawCopyMem a 0 (byteCountMem a)
{-# INLINE thawCloneMem #-}
freezeCloneMem :: (MemAlloc a, MonadPrim s m) => a s -> m (FrozenMem a)
freezeCloneMem = freezeMem >=> thawCloneMem >=> freezeMem
{-# INLINE freezeCloneMem #-}
-- | /O(n)/ - Convert a read-only memory region into a newly allocated other type of
-- memory region
--
-- >>> import Data.ByteString
-- >>> bs = pack [0x10 .. 0x20]
-- >>> bs
-- "\DLE\DC1\DC2\DC3\DC4\NAK\SYN\ETB\CAN\EM\SUB\ESC\FS\GS\RS\US "
-- >>> convertMem bs :: Bytes 'Inc
-- [0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0x20]
--
-- @since 0.1.0
convertMem :: (MemRead r, MemAlloc a) => r -> FrozenMem a
convertMem a = runST $ thawCloneMem a >>= freezeMem
{-# INLINE convertMem #-}
-- | Figure out how many elements can fit into the region of memory. It is possible that
-- there is a remainder of bytes left, see `countRemMem` for getting that too.
--
-- ====__Examples__
--
-- >>> b = fromListMem [0 .. 5 :: Word8] :: Bytes 'Pin
-- >>> b
-- [0x00,0x01,0x02,0x03,0x04,0x05]
-- >>> countMem b :: Count Word16
-- Count {unCount = 3}
-- >>> countMem b :: Count Word32
-- Count {unCount = 1}
--
-- @since 0.1.0
countMem ::
forall e r. (MemRead r, Prim e)
=> r -- ^ Read-only memory type
-> Count e
countMem = fromByteCount . byteCountMem
{-# INLINE countMem #-}
-- | Compute how many elements and a byte size remainder that can fit into the region of memory.
--
-- ====__Examples__
--
-- >>> b = fromListMem [0 .. 5 :: Word8] :: Bytes 'Pin
-- >>> b
-- [0x00,0x01,0x02,0x03,0x04,0x05]
-- >>> countRemMem @Word16 b
-- (Count {unCount = 3},0)
-- >>> countRemMem @Word32 b
-- (Count {unCount = 1},2)
--
-- @since 0.1.0
countRemMem :: forall e r. (MemRead r, Prim e) => r -> (Count e, Count Word8)
countRemMem = fromByteCountRem . byteCountMem
{-# INLINE countRemMem #-}
getCountMem :: (MemAlloc r, MonadPrim s m, Prim e) => r s -> m (Count e)
getCountMem = fmap (fromByteCount . coerce) . getByteCountMem
{-# INLINE getCountMem #-}
getCountRemMem :: (MemAlloc r, MonadPrim s m, Prim e) => r s -> m (Count e, Count Word8)
getCountRemMem = fmap (fromByteCountRem . coerce) . getByteCountMem
{-# INLINE getCountRemMem #-}
clone :: (MemAlloc r, MonadPrim s m) => r s -> m (r s)
clone mb = do
n <- getByteCountMem mb
mb' <- allocMem n
mb' <$ moveMem mb 0 mb' 0 n
{-# INLINE clone #-}
eqMem :: (MemRead r1, MemRead r2) => r1 -> r2 -> Bool
eqMem b1 b2 = n == byteCountMem b2 && compareByteOffMem b1 0 b2 0 n == EQ
where
n = byteCountMem b1
{-# INLINE eqMem #-}
-- | Compare two regions of memory byte-by-byte. It will return `EQ` whenever both regions
-- are exactly the same and `LT` or `GT` as soon as the first byte is reached that is less
-- than or greater than respectfully in the first region when compared to the second
-- one. It is safe for both regions to refer to the same part of memory, since this is a
-- pure function and both regions of memory are read-only.
compareMem ::
(MemRead r1, MemRead r2, Prim e)
=> r1 -- ^ First region of memory
-> Off e -- ^ Offset in number of elements into the first region
-> r2 -- ^ Second region of memory
-> Off e -- ^ Offset in number of elements into the second region
-> Count e -- ^ Number of elements to compare
-> Ordering
compareMem r1 off1 r2 off2 = compareByteOffMem r1 (toByteOff off1) r2 (toByteOff off2)
{-# INLINE compareMem #-}
-- | It is only guaranteed to convert the whole memory to a list whenever the size of
-- allocated memory is exactly divisible by the size of the element, otherwise there will
-- be some slack left unaccounted for.
toListMem :: (MemRead r, Prim e) => r -> [e]
toListMem ba = build (\ c n -> foldrCountMem (countMem ba) c n ba)
{-# INLINE toListMem #-}
{-# SPECIALIZE toListMem :: Prim e => Bytes p -> [e] #-}
-- | Same as `toListMem`, except if there is some slack at the end of the memory that
-- didn't fit in a list it will be returned as a list of bytes
--
-- ====__Examples__
--
-- >>> import Data.Word
-- >>> :set -XDataKinds
-- >>> a = fromListMem [0 .. 10 :: Word8] :: Bytes 'Pin
-- >>> a
-- [0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a]
-- >>> toListSlackMem a :: ([Word8], [Word8])
-- ([0,1,2,3,4,5,6,7,8,9,10],[])
-- >>> toListSlackMem a :: ([Word16], [Word8])
-- ([256,770,1284,1798,2312],[10])
-- >>> toListSlackMem a :: ([Word32], [Word8])
-- ([50462976,117835012],[8,9,10])
-- >>> toListSlackMem a :: ([Word64], [Word8])
-- ([506097522914230528],[8,9,10])
--
-- @since 0.1.0
toListSlackMem ::
forall e r. (MemRead r, Prim e)
=> r
-> ([e], [Word8])
toListSlackMem mem =
(build (\c n -> foldrCountMem k c n mem), getSlack (k8 + r8) [])
where
(k, Count r8) = countRemMem mem
Count k8 = toByteCount k
getSlack i !acc
| i == k8 = acc
| otherwise =
let i' = i - 1
in getSlack i' (indexByteOffMem mem (Off i') : acc)
{-# INLINABLE toListSlackMem #-}
-- | Right fold that is useful for converting to list while tapping into list fusion.
foldrCountMem :: (MemRead r, Prim e) => Count e -> (e -> b -> b) -> b -> r -> b
foldrCountMem (Count k) c nil bs = go 0
where
go i
| i == k = nil
| otherwise =
let !v = indexOffMem bs (Off i)
in v `c` go (i + 1)
{-# INLINE[0] foldrCountMem #-}
loadListMemN ::
(MemWrite r, MonadPrim s m, Prim e)
=> Count e
-> Count Word8
-> [e]
-> r s
-> m Ordering
loadListMemN (Count n) (Count slack) ys mb = do
let go [] !i = pure (compare i n <> compare 0 slack)
go (x:xs) !i
| i < n = writeOffMem mb (Off i) x >> go xs (i + 1)
| otherwise = pure GT
go ys 0
{-# INLINABLE loadListMemN #-}
loadListMemN_ :: (MemWrite r, MonadPrim s m, Prim e) => Count e -> [e] -> r s -> m ()
loadListMemN_ (Count n) ys mb =
let go [] _ = pure ()
go (x:xs) i = when (i < n) $ writeOffMem mb (Off i) x >> go xs (i + 1)
in go ys 0
{-# INLINABLE loadListMemN_ #-}
-- | Returns `EQ` if the full list did fit into the supplied memory chunk exactly.
-- Otherwise it will return either `LT` if the list was smaller than allocated memory or
-- `GT` if the list was bigger than the available memory and did not fit into `MBytes`.
loadListMem :: (MonadPrim s m, MemAlloc r, Prim e) => [e] -> r s -> m Ordering
loadListMem ys mb = do
(c, slack) <- getCountRemMem mb
loadListMemN (countAsProxy ys c) slack ys mb
{-# INLINE loadListMem #-}
loadListMem_ :: (MonadPrim s m, MemAlloc r, Prim e) => [e] -> r s -> m ()
loadListMem_ ys mb = do
c <- getCountMem mb
loadListMemN_ (countAsProxy ys c) ys mb
{-# INLINE loadListMem_ #-}
fromListMemN :: (MemAlloc a, Prim e) => Count e -> [e] -> (Ordering, FrozenMem a)
fromListMemN n xs = createMemST n (loadListMemN n 0 xs)
{-# INLINE fromListMemN #-}
fromListMemN_ :: (MemAlloc a, Prim e) => Count e -> [e] -> FrozenMem a
fromListMemN_ !n xs = createMemST_ n (loadListMemN_ n xs)
{-# INLINE fromListMemN_ #-}
fromListMem :: (MemAlloc a, Prim e) => [e] -> FrozenMem a
fromListMem xs = fromListMemN_ (countAsProxy xs (coerce (length xs))) xs
{-# INLINE fromListMem #-}
-- | Load a list of bytes into a newly allocated memory region. Equivalent to
-- `Data.ByteString.pack` for `Data.ByteString.ByteString`
--
-- ====__Examples__
--
-- >>> fromByteListMem [0..10] :: Bytes 'Pin
-- [0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a]
--
-- @since 0.1.0
fromByteListMem :: MemAlloc a => [Word8] -> FrozenMem a
fromByteListMem = fromListMem
{-# INLINE fromByteListMem #-}
-- | Convert a memory region to a list of bytes. Equivalent to `Data.ByteString.unpack`
-- for `Data.ByteString.ByteString`
--
-- >>> toByteListMem (fromByteListMem [0..10] :: Bytes 'Pin)
-- [0,1,2,3,4,5,6,7,8,9,10]
--
-- @since 0.1.0
toByteListMem :: MemAlloc a => FrozenMem a -> [Word8]
toByteListMem = toListMem
{-# INLINE toByteListMem #-}
mapByteMem :: (MemRead r, MemAlloc a, Prim e) => (Word8 -> e) -> r -> FrozenMem a
mapByteMem f = mapByteOffMem (const f)
-- | Map an index aware function over memory region
--
-- >>> a = fromListMem [1 .. 10 :: Word8] :: Bytes 'Inc
-- >>> a
-- [0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a]
-- >>> imapMem (\i e -> (fromIntegral i :: Int8, e + 0xf0)) a :: Bytes 'Pin
-- [0x00,0xf1,0x01,0xf2,0x02,0xf3,0x03,0xf4,0x04,0xf5,0x05,0xf6,0x06,0xf7,0x07,0xf8,0x08,0xf9,0x09,0xfa]
--
-- @since 0.1.0
mapByteOffMem ::
(MemRead r, MemAlloc a, Prim e) => (Off Word8 -> Word8 -> e) -> r -> FrozenMem a
mapByteOffMem f r = runST $ mapByteOffMemM (\i -> pure . f i) r
-- @since 0.1.0
mapByteMemM ::
(MemRead r, MemAlloc a, MonadPrim s m, Prim e)
=> (Word8 -> m e)
-> r
-> m (FrozenMem a)
mapByteMemM f = mapByteOffMemM (const f)
-- @since 0.1.0
mapByteOffMemM ::
(MemRead r, MemAlloc a, MonadPrim s m, Prim e)
=> (Off Word8 -> Word8 -> m e)
-> r
-> m (FrozenMem a)
mapByteOffMemM f r = do
let bc@(Count n) = byteCountMem r
c = countAsProxy (f 0 0) (Count n)
mem <- allocMem c
_ <- forByteOffMemM_ r 0 bc f
-- let go i =
-- when (i < n) $ do
-- f i (indexByteOffMem r (Off i)) >>=
-- writeOffMem mem (offAsProxy c (Off i))
-- go (i + 1)
-- go 0
freezeMem mem
-- | Iterate over a region of memory
forByteOffMemM_ ::
(MemRead r, MonadPrim s m, Prim e)
=> r
-> Off Word8
-> Count e
-> (Off Word8 -> e -> m b)
-> m (Off Word8)
forByteOffMemM_ r (Off byteOff) c f =
let n = coerce (toByteCount c) + byteOff
Count k = byteCountProxy c
go i
| i < n = f (Off i) (indexByteOffMem r (Off i)) >> go (i + k)
| otherwise = pure $ Off i
in go byteOff
loopShortM :: Monad m => Int -> (Int -> a -> Bool) -> (Int -> Int) -> a -> (Int -> a -> m a) -> m a
loopShortM !startAt condition increment !initAcc f = go startAt initAcc
where
go !step !acc
| condition step acc = f step acc >>= go (increment step)
| otherwise = pure acc
{-# INLINE loopShortM #-}
loopShortM' :: Monad m => Int -> (Int -> a -> m Bool) -> (Int -> Int) -> a -> (Int -> a -> m a) -> m a
loopShortM' !startAt condition increment !initAcc f = go startAt initAcc
where
go !step !acc =
condition step acc >>= \cont ->
if cont
then f step acc >>= go (increment step)
else pure acc
{-# INLINE loopShortM' #-}
-- -- | Iterate over a region of memory
-- loopMemM_ ::
-- (MemRead r, MonadPrim s m, Prim e)
-- => r
-- -> Off Word8
-- -> Count e
-- -> (Count Word8 -> a -> Bool)
-- -> (Off Word8 -> e -> m b)
-- -> m (Off Word8)
-- foldlByteOffMemM_ r (Off byteOff) c f =
-- loopShortM byteOff (\i -> f (coerce i))
-- let n = coerce (toByteCount c) + byteOff
-- Count k = byteCountProxy c
-- go i
-- | i < n = f (Off i) (indexByteOffMem r (Off i)) >> go (i + k)
-- | otherwise = pure $ Off i
-- in go byteOff
data MemView a = MemView
{ mvOffset :: {-# UNPACK #-} !(Off Word8)
, mvCount :: {-# UNPACK #-} !(Count Word8)
, mvMem :: !a
}
data MMemView a s = MMemView
{ mmvOffset :: {-# UNPACK #-} !(Off Word8)
, mmvCount :: {-# UNPACK #-} !(Count Word8)
, mmvMem :: !(a s)
}
izipWithByteOffMemM_ ::
(MemRead r1, MemRead r2, MonadPrim s m, Prim e)
=> r1
-> Off Word8
-> r2
-> Off Word8
-> Count e
-> (Off Word8 -> e -> Off Word8 -> e -> m b)
-> m (Off Word8)
izipWithByteOffMemM_ r1 (Off byteOff1) r2 off2 c f =
let n = coerce (toByteCount c) + byteOff1
Count k = byteCountProxy c
go i
| i < n =
let o1 = Off i
o2 = Off i + off2
in f o1 (indexByteOffMem r1 o1) o2 (indexByteOffMem r2 o2) >>
go (i + k)
| otherwise = pure $ Off i
in go byteOff1
izipWithOffMemM_ ::
(MemRead r1, MemRead r2, MonadPrim s m, Prim e1, Prim e2)
=> r1
-> Off e1
-> r2
-> Off e2
-> Int
-> (Off e1 -> e1 -> Off e2 -> e2 -> m b)
-> m ()
izipWithOffMemM_ r1 off1 r2 off2 nc f =
let n = nc + coerce off1
go o1@(Off i) o2 =
when (i < n) $
f o1 (indexOffMem r1 o1) o2 (indexOffMem r2 o2) >> go (o1 + 1) (o2 + 1)
in go off1 off2
-- class Mut f => MFunctor f where
-- mmap :: (Elt f a, Elt f b, MonadPrim s m) => (a -> b) -> f a s -> m (f b s)
-- class Mut f => MTraverse f where
-- mmapM :: (Elt f a, Elt f b, MonadPrim s m) => (a -> m b) -> f a s -> m (f b s)
-- class MFunctor f => MApplicative f where
-- pureMut :: (Elt f a, MonadPrim s m) => a -> m (f a s)
-- liftMut ::
-- (Elt f a, Elt f b, Elt f c, MonadPrim s m) => (a -> b -> m c) -> f a s -> f b s -> m (f c s)
-- class MApplicative f => MMonad f where
-- bindMut ::
-- (Elt f a, Elt f b, MonadPrim s m) => f a s -> (a -> m b) -> f b s -> m (f c s)
-- instance MFunctor MAddr where
-- mmap f maddr = do
-- Count n <- getCountMAddr maddr
-- maddr' <- allocMAddr (Count n)
-- let go i =
-- when (i < n) $ do
-- writeOffMAddr maddr' (Off i) . f =<< readOffMAddr maddr (Off i)
-- go (i + 1)
-- maddr' <$ go 0
-- instance MTraverse MAddr where
-- mmapM f maddr = do
-- Count n <- getCountMAddr maddr
-- maddr' <- allocMAddr (Count n)
-- let go i =
-- when (i < n) $ do
-- writeOffMAddr maddr' (Off i) =<< f =<< readOffMAddr maddr (Off i)
-- go (i + 1)
-- maddr' <$ go 0
-------------------
-- Bytes orphans --
-------------------
instance MemRead (Bytes p) where
byteCountMem = byteCountBytes
{-# INLINE byteCountMem #-}
indexOffMem = indexOffBytes
{-# INLINE indexOffMem #-}
indexByteOffMem = indexByteOffBytes
{-# INLINE indexByteOffMem #-}
copyByteOffToMBytesMem = copyByteOffBytesToMBytes
{-# INLINE copyByteOffToMBytesMem #-}
copyByteOffToPtrMem = copyByteOffBytesToPtr
{-# INLINE copyByteOffToPtrMem #-}
compareByteOffToPtrMem bytes1 off1 ptr2 off2 c =
pure $ compareByteOffBytesToPtr bytes1 off1 ptr2 off2 c
{-# INLINE compareByteOffToPtrMem #-}
compareByteOffToBytesMem bytes1 off1 bytes2 off2 c =
pure $ compareByteOffBytes bytes1 off1 bytes2 off2 c
{-# INLINE compareByteOffToBytesMem #-}
compareByteOffMem mem1 off1 bs off2 c =
unsafeInlineIO $ compareByteOffToBytesMem mem1 off1 bs off2 c
{-# INLINE compareByteOffMem #-}
instance Typeable p => MemAlloc (MBytes p) where
type FrozenMem (MBytes p) = Bytes p
getByteCountMem = getByteCountMBytes
{-# INLINE getByteCountMem #-}
allocByteCountMem = allocMBytes
{-# INLINE allocByteCountMem #-}
thawMem = thawBytes
{-# INLINE thawMem #-}
freezeMem = freezeMBytes
{-# INLINE freezeMem #-}
resizeMem = reallocMBytes
{-# INLINE resizeMem #-}
instance MemWrite (MBytes p) where
readOffMem = readOffMBytes
{-# INLINE readOffMem #-}
readByteOffMem = readByteOffMBytes
{-# INLINE readByteOffMem #-}
writeOffMem = writeOffMBytes
{-# INLINE writeOffMem #-}
writeByteOffMem = writeByteOffMBytes
{-# INLINE writeByteOffMem #-}
moveByteOffToPtrMem = moveByteOffMBytesToPtr
{-# INLINE moveByteOffToPtrMem #-}
moveByteOffToMBytesMem = moveByteOffMBytesToMBytes
{-# INLINE moveByteOffToMBytesMem #-}
moveByteOffMem = moveByteOffToMBytesMem
{-# INLINE moveByteOffMem #-}
copyByteOffMem = copyByteOffToMBytesMem
{-# INLINE copyByteOffMem #-}
setMem = setMBytes
{-# INLINE setMem #-}
instance Show (Bytes p) where
show b =
Foldable.foldr' ($) "]" $
('[' :) : List.intersperse (',' :) (map (("0x" ++) .) (showsHexMem b))
instance Typeable p => IsList (Bytes p) where
type Item (Bytes p) = Word8
fromList = fromListMem
fromListN n = fromListMemN_ (Count n)
toList = toListMem
instance Eq (Bytes p) where
b1 == b2 = isSameBytes b1 b2 || eqMem b1 b2
instance Ord (Bytes p) where
compare b1 b2 =
compare n (byteCountBytes b2) <> compareByteOffBytes b1 0 b2 0 n
where
n = byteCountBytes b1
instance Typeable p => Semigroup.Semigroup (Bytes p) where
(<>) = appendMem
sconcat (x :| xs) = concatMem (x:xs)
stimes i = cycleMemN (fromIntegral i)
instance Typeable p => Monoid.Monoid (Bytes p) where
mappend = appendMem
mconcat = concatMem
mempty = emptyMem
-- | A list of `ShowS` that covert bytes to base16 encoded strings. Each element of the list
-- is a function that will convert one byte.
--
-- >>> mb <- newPinnedMBytes (Count 5 :: Count Int)
-- >>> mapM_ (\i -> writeOffMBytes mb (pred i) i) [1 .. 5]
-- >>> foldr ($) "" . showsBytesHex <$> freezeMBytes mb
-- "01000000000000000200000000000000030000000000000004000000000000000500000000000000"
--
showsHexMem :: MemRead r => r -> [ShowS]
showsHexMem b = map toHex (toListMem b :: [Word8])
where
toHex b8 =
(if b8 <= 0x0f
then ('0' :)
else id) .
showHex b8
-- | Ensure that memory is filled with zeros before and after it is used.
withScrubbedMem ::
(MonadUnliftPrim RW m, Prim e, MemAlloc mem)
=> Count e
-> (mem RW -> m a)
-> m a
withScrubbedMem c f = do
mem <- allocZeroMem c
f mem `finallyPrim` setMem mem 0 (toByteCount c) 0
where
finallyPrim m1 m2 = withRunInPrimBase $ \run -> finally (run m1) (run m2)