mempack-0.2.0.0: src/Data/MemPack/Buffer.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples #-}
-- |
-- Module : Data.MemPack.Buffer
-- Copyright : (c) Alexey Kuleshevich 2024-2025
-- License : BSD3
-- Maintainer : Alexey Kuleshevich <alexey@kuleshevi.ch>
-- Stability : experimental
-- Portability : non-portable
module Data.MemPack.Buffer (
Buffer (..),
newMutableByteArray,
freezeMutableByteArray,
withPtrByteStringST,
withAddrByteStringST,
withForeignPtrST,
pinnedByteArrayToByteString,
pinnedByteArrayToForeignPtr,
byteArrayToShortByteString,
byteArrayFromShortByteString,
)
where
import Data.Array.Byte
import qualified Data.ByteString as BS
import qualified Data.ByteString.Internal as BS
import qualified Data.ByteString.Short.Internal as SBS
import Data.Primitive.PrimArray (PrimArray (..))
import Data.Word (Word8)
import GHC.Exts
import GHC.ForeignPtr
import GHC.ST
#if !MIN_VERSION_primitive(0,8,0)
import qualified Data.Primitive.ByteArray as Prim (ByteArray(..))
#endif
import qualified Data.Vector.Primitive as VP (Vector (..))
import qualified Data.Vector.Storable as VS (
Vector,
length,
unsafeFromForeignPtr0,
unsafeToForeignPtr0,
)
-- | Immutable memory buffer
class Buffer b where
-- | Number of accessible bytes in the buffer.
bufferByteCount :: b -> Int
-- | Use one of the two suppplied functions to access memory of the buffer:
buffer ::
-- | A type that contains the actual buffer that will be accessed
b ->
-- | In case when a buffer is backed by a `ByteArray#` it will be accessed as such with an
-- offset from the beginning of the ByteArray
(ByteArray# -> Int# -> a) ->
-- | In case when a buffer is backed by a pointer or a pinned `ByteArray#` it can be accessed as
-- an `Addr#`. No offset is necessary here, since same affect can be achieved with pointer
-- arithmetic.
(Addr# -> a) ->
a
mkBuffer :: ByteArray# -> b
bufferHasToBePinned :: Bool
instance Buffer ByteArray where
bufferByteCount (ByteArray ba#) = I# (sizeofByteArray# ba#)
{-# INLINE bufferByteCount #-}
buffer (ByteArray ba#) f _ = f ba# 0#
{-# INLINE buffer #-}
mkBuffer ba# = ByteArray ba#
{-# INLINE mkBuffer #-}
bufferHasToBePinned = False
#if !MIN_VERSION_primitive(0,8,0)
instance Buffer Prim.ByteArray where
bufferByteCount (Prim.ByteArray ba#) = I# (sizeofByteArray# ba#)
{-# INLINE bufferByteCount #-}
buffer (Prim.ByteArray ba#) f _ = f ba# 0#
{-# INLINE buffer #-}
mkBuffer ba# = Prim.ByteArray ba#
{-# INLINE mkBuffer #-}
bufferHasToBePinned = False
#endif
instance Buffer SBS.ShortByteString where
bufferByteCount = SBS.length
{-# INLINE bufferByteCount #-}
buffer (SBS.SBS ba#) f _ = f ba# 0#
{-# INLINE buffer #-}
mkBuffer ba# = SBS.SBS ba#
{-# INLINE mkBuffer #-}
bufferHasToBePinned = False
instance Buffer BS.ByteString where
bufferByteCount = BS.length
{-# INLINE bufferByteCount #-}
buffer bs _f g =
runST $ withAddrByteStringST bs $ \addr# -> pure (g addr#)
{-# INLINE buffer #-}
mkBuffer ba# = pinnedByteArrayToByteString (ByteArray ba#)
{-# INLINE mkBuffer #-}
bufferHasToBePinned = True
instance Buffer (PrimArray Word8) where
bufferByteCount (PrimArray ba#) = I# (sizeofByteArray# ba#)
{-# INLINE bufferByteCount #-}
buffer (PrimArray ba#) f _ = f ba# 0#
{-# INLINE buffer #-}
mkBuffer ba# = PrimArray ba#
{-# INLINE mkBuffer #-}
bufferHasToBePinned = False
instance Buffer (VP.Vector Word8) where
bufferByteCount (VP.Vector _ len _) = len
{-# INLINE bufferByteCount #-}
buffer (VP.Vector (I# off#) _ ba) f = buffer ba (\ba# _ -> f ba# off#)
{-# INLINE buffer #-}
mkBuffer ba# =
let ba = mkBuffer ba#
in VP.Vector 0 (bufferByteCount ba) ba
{-# INLINE mkBuffer #-}
bufferHasToBePinned = False
instance Buffer (VS.Vector Word8) where
bufferByteCount = VS.length
{-# INLINE bufferByteCount #-}
buffer v _f g =
runST $ withForeignPtrST (fst $ VS.unsafeToForeignPtr0 v) $ \addr# -> pure (g addr#)
{-# INLINE buffer #-}
mkBuffer ba# =
VS.unsafeFromForeignPtr0 (pinnedByteArrayToForeignPtr ba#) (I# (sizeofByteArray# ba#))
{-# INLINE mkBuffer #-}
bufferHasToBePinned = True
-- | Allocate a new uninitialized `MutableByteArray`.
--
-- * __Warning__ - Memory allocated might contain random garbage and must be fully overwritten.
--
-- __⚠__ - Violation of the above rule could lead non-determinism and breakage of referential
-- transparency.
--
-- @since 0.1.0
newMutableByteArray ::
-- | Should the mutable array be allocated as pinned or not
Bool ->
-- | Size of the mutable array in number of bytes.
Int ->
ST s (MutableByteArray s)
newMutableByteArray isPinned (I# len#) =
ST $ \s# -> case (if isPinned then newPinnedByteArray# else newByteArray#) len# s# of
(# s'#, mba# #) -> (# s'#, MutableByteArray mba# #)
{-# INLINE newMutableByteArray #-}
-- | /O(1)/ - Cast a `MutableByteArray` to an immutable `ByteArray` without copy.
--
-- * __Warning__ - Source mutable array must not be mutated, after this action.
--
-- __⚠__ - Violation of the above rule could potentially lead to corrupt memory and segfaults.
--
-- @since 0.1.0
freezeMutableByteArray :: MutableByteArray d -> ST d ByteArray
freezeMutableByteArray (MutableByteArray mba#) =
ST $ \s# -> case unsafeFreezeByteArray# mba# s# of
(# s'#, ba# #) -> (# s'#, ByteArray ba# #)
-- | Run ST action on the underlying `Ptr` that points to the beginning of the `ByteString`
-- buffer. It is ok to use ByteString withing ST, as long as underlying pointer is never mutated or
-- returned from the supplied action.
--
-- * __Warning__ - It is important for the supplied action to not produce bottom, i.e. runtime
-- exceptions or infinite loops are not allowed within its body.
--
-- __⚠__ - Violation of the above rule could potentially lead to corrupt memory and segfaults.
--
-- @since 0.1.0
withPtrByteStringST :: BS.ByteString -> (Ptr a -> ST s b) -> ST s b
withPtrByteStringST bs f = withAddrByteStringST bs $ \addr# -> f (Ptr addr#)
{-# INLINE withPtrByteStringST #-}
-- | Same as `withPtrByteStringST`, except the supplied action expects an `Addr#` instead of a
-- `Ptr`.
--
-- __⚠__ - Violation of the rule from `withPtrByteStringST` could potentially lead to corrupt memory
-- and segfaults.
--
-- @since 0.2.0
withAddrByteStringST :: BS.ByteString -> (Addr# -> ST s b) -> ST s b
#if MIN_VERSION_bytestring(0,11,0)
withAddrByteStringST (BS.BS fp _) = withForeignPtrST fp
#else
withAddrByteStringST (BS.PS fp offset _) = withForeignPtrST (fp `plusForeignPtr` offset)
#endif
{-# INLINE withAddrByteStringST #-}
-- | Run an `ST` action on the underlying `Ptr` that points to the beginning of the `ByteString`
-- buffer. It is ok to use ByteString withing ST, as long as underlying pointer is never mutated or
-- returned from the supplied action.
--
-- * __Warning__ - It is important for the memory that backs the underlying `ForeignPtr` to not be
-- mutated outside of the `ST` monad that this action operates in, which is only allowed if it was
-- allocated in this execution of the `ST` monad.
--
-- * __Warning__ - It is important for the memory that backs the underlying `ForeignPtr` to not be
-- mutated at all if `ForeignPtr` was not allocated within the `ST` monad that this action operates
-- in.
--
-- * __Warning__ - It is important for the supplied action to not produce bottom, i.e. runtime
-- exceptions or infinite loops are not allowed within its body.
--
-- __⚠__ - Violation of the above rules could potentially lead to corrupt memory and segfaults.
--
-- @since 0.2.0
withForeignPtrST :: ForeignPtr a -> (Addr# -> ST s b) -> ST s b
withForeignPtrST (ForeignPtr addr# ptrContents) f = do
!r <- f addr#
-- It is safe to use `touch#` within ST, so using `unsafeCoerce#` here is totally OK
ST $ \s# -> (# unsafeCoerce# (touch# ptrContents (unsafeCoerce# s#)), () #)
pure r
{-# INLINE withForeignPtrST #-}
-- | /O(1)/ - Convert a pinned `ByteArray` to `BS.ByteString`.
--
-- * __Warning__ - There is no check that source `ByteArray` was allocated as pinned, so user of this
-- function must guarantee this invariant.
--
-- __⚠__ - Violation of the above rules could potentially lead to corrupt memory and segfaults.
--
-- @since 0.1.0
pinnedByteArrayToByteString :: ByteArray -> BS.ByteString
pinnedByteArrayToByteString (ByteArray ba#) =
BS.PS (pinnedByteArrayToForeignPtr ba#) 0 (I# (sizeofByteArray# ba#))
{-# INLINE pinnedByteArrayToByteString #-}
-- | /O(1)/ - Convert a pinned `ByteArray#` to `ForeignPtr`.
--
-- * __Warning__ - There is no check that source `ByteArray#` was allocated as pinned, so user of this
-- function must guarantee this invariant.
--
-- __⚠__ - Violation of the above rules could potentially lead to corrupt memory and segfaults.
--
-- @since 0.1.0
pinnedByteArrayToForeignPtr :: ByteArray# -> ForeignPtr a
pinnedByteArrayToForeignPtr ba# =
ForeignPtr (byteArrayContents# ba#) (PlainPtr (unsafeCoerce# ba#))
{-# INLINE pinnedByteArrayToForeignPtr #-}
-- | /O(1)/ - Convert `ByteArray` to `SBS.ShortByteString`
--
-- @since 0.1.0
byteArrayToShortByteString :: ByteArray -> SBS.ShortByteString
byteArrayToShortByteString (ByteArray ba#) = SBS.SBS ba#
{-# INLINE byteArrayToShortByteString #-}
-- | /O(1)/ - Inverse of `byteArrayToShortByteString`. Convert `SBS.ShortByteString` to `ByteArray`
--
-- @since 0.1.0
byteArrayFromShortByteString :: SBS.ShortByteString -> ByteArray
byteArrayFromShortByteString (SBS.SBS ba#) = ByteArray ba#
{-# INLINE byteArrayFromShortByteString #-}