lsm-tree-1.0.0.0: src-core/Database/LSMTree/Internal/ByteString.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples #-}
{-# OPTIONS_HADDOCK not-home #-}
-- | @bytestring@ extras
--
module Database.LSMTree.Internal.ByteString (
tryCheapToShort,
tryGetByteArray,
shortByteStringFromTo,
byteArrayFromTo,
byteArrayToByteString,
unsafePinnedByteArrayToByteString,
byteArrayToSBS,
) where
import Control.Exception (assert)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Builder as BB
import qualified Data.ByteString.Builder.Internal as BB
import qualified Data.ByteString.Internal as BS.Internal
import Data.ByteString.Short (ShortByteString (SBS))
import qualified Data.ByteString.Short.Internal as SBS
import Data.Primitive.ByteArray
import Database.LSMTree.Internal.Assertions (isValidSlice)
import Foreign.Ptr (minusPtr, plusPtr)
import GHC.Exts
import qualified GHC.ForeignPtr as Foreign
import GHC.Stack (HasCallStack)
-- | \( O(1) \) conversion, if possible.
--
-- In addition to the conditions explained for 'tryGetByteArray', the
-- bytestring must use the full length of the underlying byte array.
tryCheapToShort :: BS.ByteString -> Either String ShortByteString
tryCheapToShort bs =
tryGetByteArray bs >>= \(ba , n) ->
if n /= sizeofByteArray ba then
Left "ByteString does not use full ByteArray"
else
let !(ByteArray ba#) = ba in Right (SBS ba#)
-- | \( O(1) \) conversion from a strict 'BS.ByteString' to its underlying
-- pinned 'ByteArray', if possible. Also returns the length (in bytes) of the
-- byte array prefix that was used by the bytestring.
--
-- Strict bytestrings are allocated using 'mallocPlainForeignPtrBytes', so we
-- are expecting a 'PlainPtr' (or 'FinalPtr' when the length is 0).
-- We also require that bytestrings referencing a byte array point point at the
-- beginning, without any offset.
tryGetByteArray :: BS.ByteString -> Either String (ByteArray, Int)
tryGetByteArray (BS.Internal.BS (Foreign.ForeignPtr addr# contents) n) =
case contents of
Foreign.PlainPtr mba# ->
case mutableByteArrayContents# mba# `eqAddr#` addr# of
0# -> Left "non-zero offset into ByteArray"
_ -> -- safe, ByteString's content is considered immutable
Right $ case unsafeFreezeByteArray# mba# realWorld# of
(# _, ba# #) -> (ByteArray ba#, n)
Foreign.MallocPtr {} ->
Left ("unsupported MallocPtr (length " <> show n <> ")")
Foreign.PlainForeignPtr {} ->
Left ("unsupported PlainForeignPtr (length " <> show n <> ")")
Foreign.FinalPtr | n == 0 ->
-- We can also handle empty bytestrings ('BS.empty' uses 'FinalPtr').
Right (emptyByteArray, 0)
Foreign.FinalPtr ->
Left ("unsupported FinalPtr (length " <> show n <> ")")
-- | Copy of 'SBS.shortByteString', but with bounds (unchecked).
--
-- https://github.com/haskell/bytestring/issues/664
{-# INLINE shortByteStringFromTo #-}
shortByteStringFromTo :: Int -> Int -> ShortByteString -> BB.Builder
shortByteStringFromTo = \i j sbs -> BB.builder $ shortByteStringCopyStepFromTo i j sbs
-- | Like 'shortByteStringFromTo' but for 'ByteArray'
--
-- https://github.com/haskell/bytestring/issues/664
byteArrayFromTo :: Int -> Int -> ByteArray -> BB.Builder
byteArrayFromTo = \i j (ByteArray ba) -> BB.builder $ shortByteStringCopyStepFromTo i j (SBS ba)
-- | Copy of 'SBS.shortByteStringCopyStep' but with bounds (unchecked)
{-# INLINE shortByteStringCopyStepFromTo #-}
shortByteStringCopyStepFromTo ::
Int -> Int -> ShortByteString -> BB.BuildStep a -> BB.BuildStep a
shortByteStringCopyStepFromTo !ip0 !ipe0 !sbs k =
go ip0 ipe0
where
go !ip !ipe (BB.BufferRange op ope)
| inpRemaining <= outRemaining = do
SBS.copyToPtr sbs ip op inpRemaining
let !br' = BB.BufferRange (op `plusPtr` inpRemaining) ope
k br'
| otherwise = do
SBS.copyToPtr sbs ip op outRemaining
let !ip' = ip + outRemaining
pure $ BB.bufferFull 1 ope (go ip' ipe)
where
outRemaining = ope `minusPtr` op
inpRemaining = ipe - ip
-- | \( O(1) \) conversion if the byte array is pinned, \( O(n) \) otherwise.
-- Takes offset and length of the slice to be used.
byteArrayToByteString :: Int -> Int -> ByteArray -> BS.ByteString
byteArrayToByteString off len ba =
assert (isValidSlice off len ba) $
if isByteArrayPinned ba
then unsafePinnedByteArrayToByteString off len ba
else unsafePinnedByteArrayToByteString 0 len $ runByteArray $ do
mba <- newPinnedByteArray len
copyByteArray mba 0 ba off len
pure mba
-- | \( O(1) \) conversion. Takes offset and length of the slice to be used.
-- Fails if the byte array is not pinned.
--
-- Based on 'SBS.fromShort'.
unsafePinnedByteArrayToByteString :: HasCallStack => Int -> Int -> ByteArray -> BS.ByteString
unsafePinnedByteArrayToByteString off@(I# off#) len ba@(ByteArray ba#) =
assert (isValidSlice off len ba) $
if isByteArrayPinned ba
then BS.Internal.BS fp len
else error $ "unsafePinnedByteArrayToByteString: not pinned, length "
<> show (sizeofByteArray ba)
where
addr# = plusAddr# (byteArrayContents# ba#) off#
fp = Foreign.ForeignPtr addr# (Foreign.PlainPtr (unsafeCoerce# ba#))
-- | \( O(1) \) conversion.
byteArrayToSBS :: ByteArray -> ShortByteString
#if MIN_VERSION_bytestring(0,12,0)
byteArrayToSBS ba = SBS.ShortByteString ba
#else
byteArrayToSBS (ByteArray ba) = SBS.SBS ba
#endif