{-# LANGUAGE BangPatterns #-} -- dependencies of both VPutFini and VPut module Database.VCache.VPutAux ( reserving, reserve , unsafePutWord8 , putWord8 , putVarNat , putVarInt , putVarNatR , peekBufferSize , peekChildren ) where import Control.Applicative import Data.Bits import Data.Word import Data.IORef import Foreign.Storable import Foreign.Ptr import Foreign.Marshal.Alloc import Database.VCache.Types reserving :: Int -> VPut a -> VPut a reserving n op = reserve n >> op {-# RULES "reserving >> reserving" forall n1 n2 f g . reserving n1 f >> reserving n2 g = reserving (n1+n2) (f>>g) #-} {-# INLINABLE reserving #-} -- | Ensure that at least N bytes are available for storage without -- growing the underlying buffer. Use this before unsafePutWord8 -- and similar operations. If the buffer must grow, it will grow -- exponentially to ensure amortized constant allocation costs. reserve :: Int -> VPut () reserve n = VPut $ \ s -> let avail = vput_limit s `minusPtr` vput_target s in if (avail >= n) then return (VPutR () s) else VPutR () <$> grow n s {-# INLINE reserve #-} grow :: Int -> VPutS -> IO VPutS grow n s = readIORef (vput_buffer s) >>= \ pBuff -> let currSize = vput_limit s `minusPtr` pBuff in let bytesUsed = vput_target s `minusPtr` pBuff in -- heuristic exponential growth let bytesNeeded = (2 * currSize) + n + 1000 in reallocBytes pBuff bytesNeeded >>= \ pBuff' -> -- (realloc will throw if it fails) writeIORef (vput_buffer s) pBuff' >> let target' = pBuff' `plusPtr` bytesUsed in let limit' = pBuff' `plusPtr` bytesNeeded in return $ s { vput_target = target' , vput_limit = limit' } {-# NOINLINE grow #-} -- | Store an 8 bit word *assuming* enough space has been reserved. -- This can be used safely together with 'reserve'. unsafePutWord8 :: Word8 -> VPut () unsafePutWord8 w8 = VPut $ \ s -> let pTgt = vput_target s in let s' = s { vput_target = (pTgt `plusPtr` 1) } in poke pTgt w8 >> return (VPutR () s') {-# INLINE unsafePutWord8 #-} -- | Store an 8 bit word. putWord8 :: Word8 -> VPut () putWord8 w8 = reserving 1 $ unsafePutWord8 w8 {-# INLINE putWord8 #-} -- | Put an arbitrary non-negative integer in 'varint' format associated -- with Google protocol buffers. This takes one byte for values 0..127, -- two bytes for 128..16k, etc.. Will fail if given a negative argument. putVarNat :: Integer -> VPut () putVarNat n | (n < 0) = fail $ "putVarNat with " ++ show n | otherwise = _putVarNat q >> putWord8 bLo where q = n `shiftR` 7 bLo = 0x7f .&. fromIntegral n _putVarNat :: Integer -> VPut () _putVarNat 0 = return () _putVarNat n = _putVarNat q >> putWord8 b where q = n `shiftR` 7 b = 0x80 .|. (0x7f .&. fromIntegral n) -- | Put an arbitrary integer in a 'varint' format associated with -- Google protocol buffers with zigzag encoding of negative numbers. -- This takes one byte for values -64..63, two bytes for -8k..8k, -- three bytes for -1M..1M, etc.. Very useful if most numbers are -- near 0. putVarInt :: Integer -> VPut () putVarInt = putVarNat . zigZag {-# INLINE putVarInt #-} zigZag :: Integer -> Integer zigZag n | (n < 0) = (negate n * 2) - 1 | otherwise = (n * 2) {-# INLINE zigZag #-} -- | write a varNat, but reversed (i.e. little-endian) -- -- This is only used by VPutFini: the last entry is the size (in bytes) -- of the children list. But we write backwards so we can later read it -- from the end of the buffer. putVarNatR :: Int -> VPut () putVarNatR n | (n < 0) = fail $ "putVarNatR with " ++ show n | otherwise = putWord8 bLo >> _putVarNatR q where bLo = 0x7f .&. fromIntegral n q = n `shiftR` 7 _putVarNatR :: Int -> VPut () _putVarNatR 0 = return () _putVarNatR n = putWord8 b >> _putVarNatR q where b = 0x80 .|. (0x7f .&. fromIntegral n) q = n `shiftR` 7 -- | Obtain the number of bytes output by this VPut effort so far. -- This might be useful if you're breaking data structures up by their -- serialization sizes. This does not include VRefs or PVars, only -- raw binary data. See also peekChildCount. peekBufferSize :: VPut Int peekBufferSize = VPut $ \ s -> readIORef (vput_buffer s) >>= \ pStart -> let size = (vput_target s) `minusPtr` pStart in size `seq` return (VPutR size s) {-# INLINE peekBufferSize #-} peekChildren :: VPut [PutChild] peekChildren = VPut $ \ s -> let r = vput_children s in return (VPutR r s) {-# INLINE peekChildren #-}