{-# LANGUAGE ScopedTypeVariables, CPP, BangPatterns, RankNTypes #-}
#if __GLASGOW_HASKELL__ >= 701
{-# LANGUAGE Unsafe #-}
#endif
{-# OPTIONS_HADDOCK hide #-}
module Data.ByteString.Builder.Internal (
Buffer(..)
, BufferRange(..)
, newBuffer
, bufferSize
, byteStringFromBuffer
, ChunkIOStream(..)
, buildStepToCIOS
, ciosUnitToLazyByteString
, ciosToLazyByteString
, BuildSignal
, BuildStep
, finalBuildStep
, done
, bufferFull
, insertChunk
, fillWithBuildStep
, Builder
, builder
, runBuilder
, runBuilderWith
, empty
, append
, flush
, ensureFree
, byteStringCopy
, byteStringInsert
, byteStringThreshold
, lazyByteStringCopy
, lazyByteStringInsert
, lazyByteStringThreshold
, shortByteString
, maximalCopySize
, byteString
, lazyByteString
, toLazyByteStringWith
, AllocationStrategy
, safeStrategy
, untrimmedStrategy
, customStrategy
, L.smallChunkSize
, L.defaultChunkSize
, L.chunkOverhead
, Put
, put
, runPut
, putToLazyByteString
, putToLazyByteStringWith
, hPut
, putBuilder
, fromPut
) where
import Control.Arrow (second)
import Control.Applicative (Applicative(..), (<$>))
import Data.Monoid
import qualified Data.ByteString as S
import qualified Data.ByteString.Internal as S
import qualified Data.ByteString.Lazy.Internal as L
import qualified Data.ByteString.Short.Internal as Sh
#if __GLASGOW_HASKELL__ >= 611
import qualified GHC.IO.Buffer as IO (Buffer(..), newByteBuffer)
import GHC.IO.Handle.Internals (wantWritableHandle, flushWriteBuffer)
import GHC.IO.Handle.Types (Handle__, haByteBuffer, haBufferMode)
import System.IO (hFlush, BufferMode(..))
import Data.IORef
#else
import qualified Data.ByteString.Lazy as L
#endif
import System.IO (Handle)
#if MIN_VERSION_base(4,4,0)
#if MIN_VERSION_base(4,7,0)
import Foreign
#else
import Foreign hiding (unsafeForeignPtrToPtr)
#endif
import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
import System.IO.Unsafe (unsafeDupablePerformIO)
#else
import Foreign
import GHC.IO (unsafeDupablePerformIO)
#endif
data BufferRange = BufferRange {-# UNPACK #-} !(Ptr Word8)
{-# UNPACK #-} !(Ptr Word8)
data Buffer = Buffer {-# UNPACK #-} !(ForeignPtr Word8)
{-# UNPACK #-} !BufferRange
{-# INLINE bufferSize #-}
bufferSize :: Buffer -> Int
bufferSize (Buffer fpbuf (BufferRange _ ope)) =
ope `minusPtr` unsafeForeignPtrToPtr fpbuf
{-# INLINE newBuffer #-}
newBuffer :: Int -> IO Buffer
newBuffer size = do
fpbuf <- S.mallocByteString size
let pbuf = unsafeForeignPtrToPtr fpbuf
return $! Buffer fpbuf (BufferRange pbuf (pbuf `plusPtr` size))
{-# INLINE byteStringFromBuffer #-}
byteStringFromBuffer :: Buffer -> S.ByteString
byteStringFromBuffer (Buffer fpbuf (BufferRange op _)) =
S.PS fpbuf 0 (op `minusPtr` unsafeForeignPtrToPtr fpbuf)
{-# INLINE trimmedChunkFromBuffer #-}
trimmedChunkFromBuffer :: AllocationStrategy -> Buffer
-> L.ByteString -> L.ByteString
trimmedChunkFromBuffer (AllocationStrategy _ _ trim) buf k
| S.null bs = k
| trim (S.length bs) (bufferSize buf) = L.Chunk (S.copy bs) k
| otherwise = L.Chunk bs k
where
bs = byteStringFromBuffer buf
data ChunkIOStream a =
Finished Buffer a
| Yield1 S.ByteString (IO (ChunkIOStream a))
{-# INLINE yield1 #-}
yield1 :: S.ByteString -> IO (ChunkIOStream a) -> IO (ChunkIOStream a)
yield1 bs cios | S.null bs = cios
| otherwise = return $ Yield1 bs cios
{-# INLINE ciosUnitToLazyByteString #-}
ciosUnitToLazyByteString :: AllocationStrategy
-> L.ByteString -> ChunkIOStream () -> L.ByteString
ciosUnitToLazyByteString strategy k = go
where
go (Finished buf _) = trimmedChunkFromBuffer strategy buf k
go (Yield1 bs io) = L.Chunk bs $ unsafeDupablePerformIO (go <$> io)
{-# INLINE ciosToLazyByteString #-}
ciosToLazyByteString :: AllocationStrategy
-> (a -> (b, L.ByteString))
-> ChunkIOStream a
-> (b, L.ByteString)
ciosToLazyByteString strategy k =
go
where
go (Finished buf x) =
second (trimmedChunkFromBuffer strategy buf) $ k x
go (Yield1 bs io) = second (L.Chunk bs) $ unsafeDupablePerformIO (go <$> io)
type BuildStep a = BufferRange -> IO (BuildSignal a)
data BuildSignal a =
Done {-# UNPACK #-} !(Ptr Word8) a
| BufferFull
{-# UNPACK #-} !Int
{-# UNPACK #-} !(Ptr Word8)
(BuildStep a)
| InsertChunk
{-# UNPACK #-} !(Ptr Word8)
S.ByteString
(BuildStep a)
{-# INLINE done #-}
done :: Ptr Word8
-> a
-> BuildSignal a
done = Done
{-# INLINE bufferFull #-}
bufferFull :: Int
-> Ptr Word8
-> BuildStep a
-> BuildSignal a
bufferFull = BufferFull
{-# INLINE insertChunk #-}
insertChunk :: Ptr Word8
-> S.ByteString
-> BuildStep a
-> BuildSignal a
insertChunk op bs = InsertChunk op bs
{-# INLINE fillWithBuildStep #-}
fillWithBuildStep
:: BuildStep a
-> (Ptr Word8 -> a -> IO b)
-> (Ptr Word8 -> Int -> BuildStep a -> IO b)
-> (Ptr Word8 -> S.ByteString -> BuildStep a -> IO b)
-> BufferRange
-> IO b
fillWithBuildStep step fDone fFull fChunk !br = do
signal <- step br
case signal of
Done op x -> fDone op x
BufferFull minSize op nextStep -> fFull op minSize nextStep
InsertChunk op bs nextStep -> fChunk op bs nextStep
newtype Builder = Builder (forall r. BuildStep r -> BuildStep r)
{-# INLINE builder #-}
builder :: (forall r. BuildStep r -> BuildStep r)
-> Builder
builder = Builder
finalBuildStep :: BuildStep ()
finalBuildStep !(BufferRange op _) = return $ Done op ()
{-# INLINE runBuilder #-}
runBuilder :: Builder
-> BuildStep ()
runBuilder b = runBuilderWith b finalBuildStep
{-# INLINE runBuilderWith #-}
runBuilderWith :: Builder
-> BuildStep a
-> BuildStep a
runBuilderWith (Builder b) = b
{-# INLINE[1] empty #-}
empty :: Builder
empty = Builder id
{-# INLINE[1] append #-}
append :: Builder -> Builder -> Builder
append (Builder b1) (Builder b2) = Builder $ b1 . b2
instance Monoid Builder where
{-# INLINE mempty #-}
mempty = empty
{-# INLINE mappend #-}
mappend = append
{-# INLINE mconcat #-}
mconcat = foldr mappend mempty
{-# INLINE flush #-}
flush :: Builder
flush = builder step
where
step k !(BufferRange op _) = return $ insertChunk op S.empty k
newtype Put a = Put { unPut :: forall r. (a -> BuildStep r) -> BuildStep r }
{-# INLINE put #-}
put :: (forall r. (a -> BuildStep r) -> BuildStep r)
-> Put a
put = Put
{-# INLINE runPut #-}
runPut :: Put a
-> BuildStep a
runPut (Put p) = p $ \x (BufferRange op _) -> return $ Done op x
instance Functor Put where
fmap f p = Put $ \k -> unPut p (\x -> k (f x))
{-# INLINE fmap #-}
{-# INLINE[1] ap_l #-}
ap_l :: Put a -> Put b -> Put a
ap_l (Put a) (Put b) = Put $ \k -> a (\a' -> b (\_ -> k a'))
{-# INLINE[1] ap_r #-}
ap_r :: Put a -> Put b -> Put b
ap_r (Put a) (Put b) = Put $ \k -> a (\_ -> b k)
instance Applicative Put where
{-# INLINE pure #-}
pure x = Put $ \k -> k x
{-# INLINE (<*>) #-}
Put f <*> Put a = Put $ \k -> f (\f' -> a (\a' -> k (f' a')))
#if MIN_VERSION_base(4,2,0)
{-# INLINE (<*) #-}
(<*) = ap_l
{-# INLINE (*>) #-}
(*>) = ap_r
#endif
instance Monad Put where
{-# INLINE return #-}
return x = Put $ \k -> k x
{-# INLINE (>>=) #-}
Put m >>= f = Put $ \k -> m (\m' -> unPut (f m') k)
{-# INLINE (>>) #-}
(>>) = ap_r
{-# INLINE[1] putBuilder #-}
putBuilder :: Builder -> Put ()
putBuilder (Builder b) = Put $ \k -> b (k ())
{-# INLINE fromPut #-}
fromPut :: Put () -> Builder
fromPut (Put p) = Builder $ \k -> p (\_ -> k)
{-# RULES
"ap_l/putBuilder" forall b1 b2.
ap_l (putBuilder b1) (putBuilder b2)
= putBuilder (append b1 b2)
"ap_l/putBuilder/assoc_r" forall b1 b2 (p :: Put a).
ap_l (putBuilder b1) (ap_l (putBuilder b2) p)
= ap_l (putBuilder (append b1 b2)) p
"ap_l/putBuilder/assoc_l" forall (p :: Put a) b1 b2.
ap_l (ap_l p (putBuilder b1)) (putBuilder b2)
= ap_l p (putBuilder (append b1 b2))
#-}
{-# RULES
"ap_r/putBuilder" forall b1 b2.
ap_r (putBuilder b1) (putBuilder b2)
= putBuilder (append b1 b2)
"ap_r/putBuilder/assoc_r" forall b1 b2 (p :: Put a).
ap_r (putBuilder b1) (ap_r (putBuilder b2) p)
= ap_r (putBuilder (append b1 b2)) p
"ap_r/putBuilder/assoc_l" forall (p :: Put a) b1 b2.
ap_r (ap_r p (putBuilder b1)) (putBuilder b2)
= ap_r p (putBuilder (append b1 b2))
#-}
{-# RULES
"ap_l/ap_r/putBuilder/assoc_r" forall b1 b2 (p :: Put a).
ap_l (putBuilder b1) (ap_r (putBuilder b2) p)
= ap_l (putBuilder (append b1 b2)) p
"ap_r/ap_l/putBuilder/assoc_r" forall b1 b2 (p :: Put a).
ap_r (putBuilder b1) (ap_l (putBuilder b2) p)
= ap_l (putBuilder (append b1 b2)) p
"ap_l/ap_r/putBuilder/assoc_l" forall (p :: Put a) b1 b2.
ap_l (ap_r p (putBuilder b1)) (putBuilder b2)
= ap_r p (putBuilder (append b1 b2))
"ap_r/ap_l/putBuilder/assoc_l" forall (p :: Put a) b1 b2.
ap_r (ap_l p (putBuilder b1)) (putBuilder b2)
= ap_r p (putBuilder (append b1 b2))
#-}
hPut :: forall a. Handle -> Put a -> IO a
#if __GLASGOW_HASKELL__ >= 611
hPut h p = do
fillHandle 1 (runPut p)
where
fillHandle :: Int -> BuildStep a -> IO a
fillHandle !minFree step = do
next <- wantWritableHandle "hPut" h fillHandle_
next
where
fillHandle_ :: Handle__ -> IO (IO a)
fillHandle_ h_ = do
makeSpace =<< readIORef refBuf
fillBuffer =<< readIORef refBuf
where
refBuf = haByteBuffer h_
freeSpace buf = IO.bufSize buf - IO.bufR buf
makeSpace buf
| IO.bufSize buf < minFree = do
flushWriteBuffer h_
s <- IO.bufState <$> readIORef refBuf
IO.newByteBuffer minFree s >>= writeIORef refBuf
| freeSpace buf < minFree = flushWriteBuffer h_
| otherwise =
#if __GLASGOW_HASKELL__ >= 613
return ()
#else
flushWriteBuffer h_
#endif
fillBuffer buf
| freeSpace buf < minFree =
error $ unlines
[ "Data.ByteString.Builder.Internal.hPut: internal error."
, " Not enough space after flush."
, " required: " ++ show minFree
, " free: " ++ show (freeSpace buf)
]
| otherwise = do
let !br = BufferRange op (pBuf `plusPtr` IO.bufSize buf)
res <- fillWithBuildStep step doneH fullH insertChunkH br
touchForeignPtr fpBuf
return res
where
fpBuf = IO.bufRaw buf
pBuf = unsafeForeignPtrToPtr fpBuf
op = pBuf `plusPtr` IO.bufR buf
{-# INLINE updateBufR #-}
updateBufR op' = do
let !off' = op' `minusPtr` pBuf
!buf' = buf {IO.bufR = off'}
writeIORef refBuf buf'
doneH op' x = do
updateBufR op'
case haBufferMode h_ of
BlockBuffering _ -> return $ return x
_line_or_no_buffering -> return $ hFlush h >> return x
fullH op' minSize nextStep = do
updateBufR op'
return $ fillHandle minSize nextStep
insertChunkH op' bs nextStep = do
updateBufR op'
return $ do
S.hPut h bs
fillHandle 1 nextStep
#else
hPut h p =
go =<< buildStepToCIOS strategy (runPut p)
where
strategy = untrimmedStrategy L.smallChunkSize L.defaultChunkSize
go (Finished buf x) = S.hPut h (byteStringFromBuffer buf) >> return x
go (Yield1 bs io) = S.hPut h bs >> io >>= go
#endif
{-# NOINLINE putToLazyByteString #-}
putToLazyByteString
:: Put a
-> (a, L.ByteString)
putToLazyByteString = putToLazyByteStringWith
(safeStrategy L.smallChunkSize L.defaultChunkSize) (\x -> (x, L.Empty))
{-# INLINE putToLazyByteStringWith #-}
putToLazyByteStringWith
:: AllocationStrategy
-> (a -> (b, L.ByteString))
-> Put a
-> (b, L.ByteString)
putToLazyByteStringWith strategy k p =
ciosToLazyByteString strategy k $ unsafeDupablePerformIO $
buildStepToCIOS strategy (runPut p)
{-# INLINE ensureFree #-}
ensureFree :: Int -> Builder
ensureFree minFree =
builder step
where
step k br@(BufferRange op ope)
| ope `minusPtr` op < minFree = return $ bufferFull minFree op k
| otherwise = k br
wrappedBytesCopyStep :: BufferRange
-> BuildStep a -> BuildStep a
wrappedBytesCopyStep !(BufferRange ip0 ipe) k =
go ip0
where
go !ip !(BufferRange op ope)
| inpRemaining <= outRemaining = do
copyBytes op ip inpRemaining
let !br' = BufferRange (op `plusPtr` inpRemaining) ope
k br'
| otherwise = do
copyBytes op ip outRemaining
let !ip' = ip `plusPtr` outRemaining
return $ bufferFull 1 ope (go ip')
where
outRemaining = ope `minusPtr` op
inpRemaining = ipe `minusPtr` ip
{-# INLINE byteStringThreshold #-}
byteStringThreshold :: Int -> S.ByteString -> Builder
byteStringThreshold maxCopySize =
\bs -> builder $ step bs
where
step !bs@(S.PS _ _ len) !k br@(BufferRange !op _)
| len <= maxCopySize = byteStringCopyStep bs k br
| otherwise = return $ insertChunk op bs k
{-# INLINE byteStringCopy #-}
byteStringCopy :: S.ByteString -> Builder
byteStringCopy = \bs -> builder $ byteStringCopyStep bs
{-# INLINE byteStringCopyStep #-}
byteStringCopyStep :: S.ByteString -> BuildStep a -> BuildStep a
byteStringCopyStep (S.PS ifp ioff isize) !k0 br0@(BufferRange op ope)
| op' <= ope = do copyBytes op ip isize
touchForeignPtr ifp
k0 (BufferRange op' ope)
| otherwise = do wrappedBytesCopyStep (BufferRange ip ipe) k br0
where
op' = op `plusPtr` isize
ip = unsafeForeignPtrToPtr ifp `plusPtr` ioff
ipe = ip `plusPtr` isize
k br = do touchForeignPtr ifp
k0 br
{-# INLINE byteStringInsert #-}
byteStringInsert :: S.ByteString -> Builder
byteStringInsert =
\bs -> builder $ \k (BufferRange op _) -> return $ insertChunk op bs k
{-# INLINE shortByteString #-}
shortByteString :: Sh.ShortByteString -> Builder
shortByteString = \sbs -> builder $ shortByteStringCopyStep sbs
{-# INLINE shortByteStringCopyStep #-}
shortByteStringCopyStep :: Sh.ShortByteString
-> BuildStep a -> BuildStep a
shortByteStringCopyStep !sbs k =
go 0 (Sh.length sbs)
where
go !ip !ipe !(BufferRange op ope)
| inpRemaining <= outRemaining = do
Sh.copyToPtr sbs ip op inpRemaining
let !br' = BufferRange (op `plusPtr` inpRemaining) ope
k br'
| otherwise = do
Sh.copyToPtr sbs ip op outRemaining
let !ip' = ip + outRemaining
return $ bufferFull 1 ope (go ip' ipe)
where
outRemaining = ope `minusPtr` op
inpRemaining = ipe - ip
{-# INLINE lazyByteStringThreshold #-}
lazyByteStringThreshold :: Int -> L.ByteString -> Builder
lazyByteStringThreshold maxCopySize =
L.foldrChunks (\bs b -> byteStringThreshold maxCopySize bs `mappend` b) mempty
{-# INLINE lazyByteStringCopy #-}
lazyByteStringCopy :: L.ByteString -> Builder
lazyByteStringCopy =
L.foldrChunks (\bs b -> byteStringCopy bs `mappend` b) mempty
{-# INLINE lazyByteStringInsert #-}
lazyByteStringInsert :: L.ByteString -> Builder
lazyByteStringInsert =
L.foldrChunks (\bs b -> byteStringInsert bs `mappend` b) mempty
{-# INLINE byteString #-}
byteString :: S.ByteString -> Builder
byteString = byteStringThreshold maximalCopySize
{-# INLINE lazyByteString #-}
lazyByteString :: L.ByteString -> Builder
lazyByteString = lazyByteStringThreshold maximalCopySize
maximalCopySize :: Int
maximalCopySize = 2 * L.smallChunkSize
data AllocationStrategy = AllocationStrategy
(Maybe (Buffer, Int) -> IO Buffer)
{-# UNPACK #-} !Int
(Int -> Int -> Bool)
{-# INLINE customStrategy #-}
customStrategy
:: (Maybe (Buffer, Int) -> IO Buffer)
-> Int
-> (Int -> Int -> Bool)
-> AllocationStrategy
customStrategy = AllocationStrategy
{-# INLINE sanitize #-}
sanitize :: Int -> Int
sanitize = max (sizeOf (undefined :: Int))
{-# INLINE untrimmedStrategy #-}
untrimmedStrategy :: Int
-> Int
-> AllocationStrategy
untrimmedStrategy firstSize bufSize =
AllocationStrategy nextBuffer (sanitize bufSize) (\_ _ -> False)
where
{-# INLINE nextBuffer #-}
nextBuffer Nothing = newBuffer $ sanitize firstSize
nextBuffer (Just (_, minSize)) = newBuffer minSize
{-# INLINE safeStrategy #-}
safeStrategy :: Int
-> Int
-> AllocationStrategy
safeStrategy firstSize bufSize =
AllocationStrategy nextBuffer (sanitize bufSize) trim
where
trim used size = 2 * used < size
{-# INLINE nextBuffer #-}
nextBuffer Nothing = newBuffer $ sanitize firstSize
nextBuffer (Just (_, minSize)) = newBuffer minSize
{-# INLINE toLazyByteStringWith #-}
toLazyByteStringWith
:: AllocationStrategy
-> L.ByteString
-> Builder
-> L.ByteString
toLazyByteStringWith strategy k b =
ciosUnitToLazyByteString strategy k $ unsafeDupablePerformIO $
buildStepToCIOS strategy (runBuilder b)
{-# INLINE buildStepToCIOS #-}
buildStepToCIOS
:: AllocationStrategy
-> BuildStep a
-> IO (ChunkIOStream a)
buildStepToCIOS !(AllocationStrategy nextBuffer bufSize trim) =
\step -> nextBuffer Nothing >>= fill step
where
fill !step !buf@(Buffer fpbuf br@(BufferRange _ pe)) = do
res <- fillWithBuildStep step doneH fullH insertChunkH br
touchForeignPtr fpbuf
return res
where
pbuf = unsafeForeignPtrToPtr fpbuf
doneH op' x = return $
Finished (Buffer fpbuf (BufferRange op' pe)) x
fullH op' minSize nextStep =
wrapChunk op' $ const $
nextBuffer (Just (buf, max minSize bufSize)) >>= fill nextStep
insertChunkH op' bs nextStep =
wrapChunk op' $ \isEmpty -> yield1 bs $
if isEmpty
then fill nextStep buf
else do buf' <- nextBuffer (Just (buf, bufSize))
fill nextStep buf'
{-# INLINE wrapChunk #-}
wrapChunk !op' mkCIOS
| chunkSize == 0 = mkCIOS True
| trim chunkSize size = do
bs <- S.create chunkSize $ \pbuf' ->
copyBytes pbuf' pbuf chunkSize
return $ Yield1 bs (mkCIOS False)
| otherwise =
return $ Yield1 (S.PS fpbuf 0 chunkSize) (mkCIOS False)
where
chunkSize = op' `minusPtr` pbuf
size = pe `minusPtr` pbuf