packages feed

mason 0.1 → 0.2

raw patch · 5 files changed

+369/−97 lines, 5 filesdep +arrayPVP ok

version bump matches the API change (PVP)

Dependencies added: array

API changes (from Hackage documentation)

- Mason.Builder: padded :: Word8 -> Int -> BoundedPrim a -> a -> Builder
- Mason.Builder: zeroPadded :: Int -> BoundedPrim a -> a -> Builder
- Mason.Builder.Internal: PBE :: !Handle -> !IORef (ForeignPtr Word8) -> !IORef Int -> PutBuilderEnv
- Mason.Builder.Internal: SE :: !Socket -> !IORef (ForeignPtr Word8) -> !IORef Int -> SocketEnv
- Mason.Builder.Internal: [pbBuffer] :: PutBuilderEnv -> !IORef (ForeignPtr Word8)
- Mason.Builder.Internal: [pbHandle] :: PutBuilderEnv -> !Handle
- Mason.Builder.Internal: [pbTotal] :: PutBuilderEnv -> !IORef Int
- Mason.Builder.Internal: [seBuffer] :: SocketEnv -> !IORef (ForeignPtr Word8)
- Mason.Builder.Internal: [seCounter] :: SocketEnv -> !IORef Int
- Mason.Builder.Internal: [seSocket] :: SocketEnv -> !Socket
- Mason.Builder.Internal: data PutBuilderEnv
- Mason.Builder.Internal: data SocketEnv
- Mason.Builder.Internal: grisu3 :: Double -> Maybe (ByteString, Int)
- Mason.Builder.Internal: instance Mason.Builder.Internal.Buildable Mason.Builder.Internal.PutBuilderEnv
- Mason.Builder.Internal: instance Mason.Builder.Internal.Buildable Mason.Builder.Internal.SocketEnv
- Mason.Builder.Internal: padded :: Word8 -> Int -> BoundedPrim a -> a -> Builder
- Mason.Builder.Internal: zeroPadded :: Int -> BoundedPrim a -> a -> Builder
+ Mason.Builder: doubleExp :: Int -> Double -> Builder
+ Mason.Builder: doubleFixed :: Int -> Double -> Builder
+ Mason.Builder: doubleSI :: Int -> Double -> Builder
+ Mason.Builder: intVLQ :: Int -> Builder
+ Mason.Builder: intVLQBP :: BoundedPrim Int
+ Mason.Builder: paddedBoundedPrim :: Word8 -> Int -> BoundedPrim a -> a -> Builder
+ Mason.Builder: prefixVarInt :: Word -> Builder
+ Mason.Builder: prefixVarIntBP :: BoundedPrim Word
+ Mason.Builder: textUtf8 :: Text -> Builder
+ Mason.Builder: wordVLQ :: Word -> Builder
+ Mason.Builder: wordVLQBP :: BoundedPrim Word
+ Mason.Builder: zeroPaddedBoundedPrim :: Int -> BoundedPrim a -> a -> Builder
+ Mason.Builder.Dynamic: DynChannel :: !Channel -> DynamicBackend
+ Mason.Builder.Dynamic: DynGrowingBuffer :: !GrowingBuffer -> DynamicBackend
+ Mason.Builder.Dynamic: DynPutEnv :: !PutEnv -> DynamicBackend
+ Mason.Builder.Dynamic: data DynamicBackend
+ Mason.Builder.Dynamic: hPutBuilder :: Handle -> DynBuilder -> IO ()
+ Mason.Builder.Dynamic: hPutBuilderLen :: Handle -> DynBuilder -> IO Int
+ Mason.Builder.Dynamic: instance Mason.Builder.Internal.Buildable Mason.Builder.Dynamic.DynamicBackend
+ Mason.Builder.Dynamic: sendBuilder :: Socket -> DynBuilder -> IO Int
+ Mason.Builder.Dynamic: toLazyByteString :: DynBuilder -> ByteString
+ Mason.Builder.Dynamic: toStrictByteString :: DynBuilder -> ByteString
+ Mason.Builder.Dynamic: type DynBuilder = BuilderFor DynamicBackend
+ Mason.Builder.Internal: PutEnv :: !Int -> !Ptr Word8 -> Ptr Word8 -> IO () -> !IORef (ForeignPtr Word8) -> !IORef Int -> PutEnv
+ Mason.Builder.Internal: [peBuffer] :: PutEnv -> !IORef (ForeignPtr Word8)
+ Mason.Builder.Internal: [pePut] :: PutEnv -> !Ptr Word8 -> Ptr Word8 -> IO ()
+ Mason.Builder.Internal: [peThreshold] :: PutEnv -> !Int
+ Mason.Builder.Internal: [peTotal] :: PutEnv -> !IORef Int
+ Mason.Builder.Internal: data PutEnv
+ Mason.Builder.Internal: instance Mason.Builder.Internal.Buildable Mason.Builder.Internal.PutEnv
+ Mason.Builder.Internal: paddedBoundedPrim :: Word8 -> Int -> BoundedPrim a -> a -> Builder
+ Mason.Builder.Internal: roundDigit :: Int -> Int -> Ptr Word8 -> IO Bool
+ Mason.Builder.Internal: withGrisu3 :: Double -> IO r -> (Ptr Word8 -> Int -> Int -> IO r) -> IO r
+ Mason.Builder.Internal: withGrisu3Rounded :: Int -> Double -> (Ptr Word8 -> Int -> Int -> IO r) -> IO r
+ Mason.Builder.Internal: zeroPaddedBoundedPrim :: Int -> BoundedPrim a -> a -> Builder
- Mason.Builder: hPutBuilder :: Handle -> BuilderFor PutBuilderEnv -> IO ()
+ Mason.Builder: hPutBuilder :: Handle -> BuilderFor PutEnv -> IO ()
- Mason.Builder: hPutBuilderLen :: Handle -> BuilderFor PutBuilderEnv -> IO Int
+ Mason.Builder: hPutBuilderLen :: Handle -> BuilderFor PutEnv -> IO Int
- Mason.Builder: sendBuilder :: Socket -> BuilderFor SocketEnv -> IO Int
+ Mason.Builder: sendBuilder :: Socket -> BuilderFor PutEnv -> IO Int
- Mason.Builder.Internal: hPutBuilderLen :: Handle -> BuilderFor PutBuilderEnv -> IO Int
+ Mason.Builder.Internal: hPutBuilderLen :: Handle -> BuilderFor PutEnv -> IO Int
- Mason.Builder.Internal: sendBuilder :: Socket -> BuilderFor SocketEnv -> IO Int
+ Mason.Builder.Internal: sendBuilder :: Socket -> BuilderFor PutEnv -> IO Int

Files

CHANGELOG.md view
@@ -1,5 +1,13 @@ # Revision history for mason +## 0.2++* Added `doubleFixed`, `doubleSI` and `doubleExp`+* Added `textUtf8`+* Added `prefixVarInt`, `wordVLQ` and `intVLQ`+* Renamed `padded` and `zeroPadded` to `paddedBoundedPrim` and `zeroPaddedBoundedPrim` respectively+* Added `Mason.Builder.Dynamic`+ ## 0 -- 2019-12-05  * First version. Released on an unsuspecting world.
mason.cabal view
@@ -1,23 +1,30 @@ cabal-version:       2.4  name:                mason-version:             0.1+version:             0.2 synopsis:            Fast and extensible bytestring builder-description:         See README.md+description:+  This package provides efficient implementation of bytestring builders.+  See README.md for details bug-reports:         https://github.com/fumieval/mason/issues license:             BSD-3-Clause license-file:        LICENSE author:              Fumiaki Kinoshita maintainer:          fumiexcel@gmail.com-copyright:           2019 Fumiaki Kinoshita, Don Stewart 2005-2009, Duncan Coutts 2006-2015, David Roundy 2003-2005, Jasper Van der Jeugt 2010, Simon Meier 2010-2013, Ben Gamari 2017+copyright:           2020 Fumiaki Kinoshita, Don Stewart 2005-2009, Duncan Coutts 2006-2015, David Roundy 2003-2005, Jasper Van der Jeugt 2010, Simon Meier 2010-2013, Ben Gamari 2017 category:            Data extra-source-files:  CHANGELOG.md, README.md tested-with:         GHC == 8.6.5, GHC==8.8.1 +source-repository head+  type: git+  location: https://github.com/fumieval/mason.git+ library   exposed-modules:     Mason.Builder     Mason.Builder.Internal+    Mason.Builder.Dynamic    c-sources: cbits/dtoa.c   ghc-options: -Wall -O2@@ -27,5 +34,6 @@     , network >= 2.7 && <3.2     , integer-gmp     , ghc-prim+    , array   hs-source-dirs: src   default-language:    Haskell2010
src/Mason/Builder.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE MagicHash, CPP, UnboxedTuples #-} {-# LANGUAGE ForeignFunctionInterface #-} {-# LANGUAGE RankNTypes #-}+{-# LANGUAGE LambdaCase #-} ---------------------------------------------------------------------------- -- | -- Module      :  Mason.Builders@@ -22,11 +23,21 @@   , sendBuilder   -- * Primitives   , flush-  , encodeUtf8Builder-  , encodeUtf8BuilderEscaped+  -- * Bytes   , byteString   , lazyByteString   , shortByteString+  -- * Text+  , textUtf8+  , encodeUtf8Builder+  , encodeUtf8BuilderEscaped+  , char7+  , string7+  , char8+  , string8+  , charUtf8+  , stringUtf8+  -- * Primitive   , storable   , int8   , word8@@ -46,14 +57,12 @@   , word64BE   , floatBE   , doubleBE-  , char7-  , string7-  , char8-  , string8-  , charUtf8-  , stringUtf8+  -- * Numeral   , floatDec   , doubleDec+  , doubleSI+  , doubleExp+  , doubleFixed   , word8Dec   , word16Dec   , word32Dec@@ -82,22 +91,32 @@   , doubleHexFixed   , byteStringHex   , lazyByteStringHex+  -- * Variable-length encoding+  , intVLQ+  , intVLQBP+  , wordVLQ+  , wordVLQBP+  , prefixVarInt+  , prefixVarIntBP   -- * Advanced-  , padded-  , zeroPadded+  , paddedBoundedPrim+  , zeroPaddedBoundedPrim   , primFixed   , primBounded   , lengthPrefixedWithin   ) where  import Control.Monad+import qualified Data.Array as A import Data.Bits import Data.Word import Data.Int import qualified Data.Text as T import Foreign.C.Types-import Foreign.Ptr (Ptr, plusPtr)+import Foreign.Ptr (Ptr, plusPtr, castPtr)+import Foreign.Storable import qualified Data.ByteString as B+import qualified Data.ByteString.Internal as B import qualified Data.ByteString.Lazy as BL import Mason.Builder.Internal as B import qualified Data.ByteString.Builder.Prim as P@@ -107,7 +126,7 @@ import System.IO (Handle)  -- | Put the content of a 'Builder' to a 'Handle'.-hPutBuilder :: Handle -> BuilderFor PutBuilderEnv -> IO ()+hPutBuilder :: Handle -> BuilderFor PutEnv -> IO () hPutBuilder h b = void $ hPutBuilderLen h b {-# INLINE hPutBuilder #-} @@ -259,11 +278,16 @@ charUtf8 :: Char -> Builder charUtf8 = B.primBounded P.charUtf8 --- | Encode 'T.Text' as a UTF-8 byte stream.+-- | Encode 'T.Text' as a UTF-8 byte stream. Synonym for 'textUtf8'. encodeUtf8Builder :: T.Text -> Builder-encodeUtf8Builder = B.encodeUtf8BuilderEscaped (P.liftFixedToBounded P.word8)+encodeUtf8Builder = textUtf8 {-# INLINE encodeUtf8Builder #-} +-- | Encode 'T.Text' as a UTF-8 byte stream.+textUtf8 :: T.Text -> Builder+textUtf8 = B.encodeUtf8BuilderEscaped (P.liftFixedToBounded P.word8)+{-# INLINE textUtf8 #-}+ -------------------- -- Unsigned integers --------------------@@ -301,16 +325,21 @@ floatDec :: Float -> Builder floatDec = string7 . show +wrapDoubleDec :: (Double -> Builder) -> Double -> Builder+wrapDoubleDec k x+  | isNaN x = string7 "NaN"+  | isInfinite x = if x < 0 then string7 "-Infinity" else string7 "Infinity"+  | isNegativeZero x = char7 '-' <> k 0.0+  | x < 0 = char7 '-' <> k (-x)+  | otherwise = k x+{-# INLINE wrapDoubleDec #-}+ -- | Decimal encoding of an IEEE 'Double'. {-# INLINE doubleDec #-} doubleDec :: Double -> Builder-doubleDec x-  | isNaN x = string7 "NaN"-  | isInfinite x = if x < 0 then string7 "-Infinity" else string7 "Infinity"-  | x < 0 = char7 '-' <> grisu (-x)-  | isNegativeZero x = string7 "-0.0"-  | x == 0 = string7 "0.0"-  | otherwise = grisu x+doubleDec = wrapDoubleDec $ \case+  0 -> string7 "0.0"+  x -> grisu x   where     grisu v = withPtr 24 $ \ptr -> do       n <- dtoa_grisu3 v ptr@@ -319,6 +348,93 @@ foreign import ccall unsafe "static dtoa_grisu3"   dtoa_grisu3 :: Double -> Ptr Word8 -> IO CInt +-- | Attach an SI prefix so that abs(mantissa) is within [1, 1000). Omits c, d, da and h.+doubleSI :: Int -- ^ precision: must be equal or greater than 3+  -> Double+  -> Builder+doubleSI prec | prec < 3 = error "Mason.Builder.doubleSI: precision less than 3"+doubleSI prec = wrapDoubleDec $ \case+  0 -> zeroes prec+  val -> Builder $ \env buf -> withGrisu3Rounded prec val $ \ptr len e -> do+    let (pindex, dp) = divMod (e - 1) 3+    print (dp, prec, len)+    let mantissa+          -- when the decimal separator would be at the end+          | dp + 1 == prec = withPtr (prec + dp - 2) $ \dst -> do+            _ <- B.memset dst 48 $ fromIntegral (prec + dp - 2)+            B.memcpy dst ptr $ min len prec+            return $ dst `plusPtr` (prec + dp - 2)+          | otherwise = withPtr (prec + 1) $ \dst -> do+            _ <- B.memset dst 48 $ fromIntegral (prec + 1)+            B.memcpy dst ptr $ min len $ dp + 1+            pokeElemOff dst (dp + 1) 46+            B.memcpy (dst `plusPtr` (dp + 2)) (ptr `plusPtr` (dp + 1)) $ max 0 $ len - dp - 1+            return $ dst `plusPtr` (prec + 1)+    let prefix+          | pindex == 0 = mempty+          | pindex > 8 || pindex < (-8) = char7 'e' <> intDec (3 * pindex)+          | otherwise = charUtf8 (prefices A.! pindex)+    unBuilder (mantissa <> prefix) env buf+  where+    prefices = A.listArray (-8,8) "yzafpnμm\NULkMGTPEZY"++zeroes :: Int -> Builder+zeroes n = withPtr (n + 1) $ \dst -> do+  _ <- B.memset dst 48 $ fromIntegral $ n + 1+  pokeElemOff dst 1 46+  return $ dst `plusPtr` (n + 1)++-- | Always use exponents+doubleExp :: Int -- ^ number of digits in the mantissa+  -> Double+  -> Builder+doubleExp prec | prec < 1 = error "Mason.Builder.doubleFixed: precision too small"+doubleExp prec = wrapDoubleDec $ \case+  0 -> zeroes prec <> string7 "e0"+  val -> Builder $ \env buf -> withGrisu3Rounded prec val $ \ptr len dp -> do+    let len' = 1 + prec++    firstDigit <- peek ptr++    unBuilder (withPtr len' (\dst -> do+      _ <- B.memset dst 48 $ fromIntegral len'+      poke dst firstDigit+      poke (dst `plusPtr` 1) (46 :: Word8)+      B.memcpy (dst `plusPtr` 2) (ptr `plusPtr` 1) (min (len - 1) len')+      return (dst `plusPtr` len'))+      <> char7 'e' <> intDec (dp - 1)) env buf++-- | Fixed precision+doubleFixed :: Int -- ^ decimal points+  -> Double+  -> Builder+doubleFixed 0 = intDec . round+doubleFixed prec | prec < 0 = error "Mason.Builder.doubleFixed: negative precision"+doubleFixed prec = wrapDoubleDec $ \case+  0 -> zeroes (prec + 1)+  val -> Builder $ \env buf -> withGrisu3 val (unBuilder (doubleDec val) env buf) $ \ptr0 len e0 -> do+    bump <- roundDigit (prec + e0) len ptr0+    let dp+          | bump = e0 + 1+          | otherwise = e0+    let ptr+          | bump = ptr0+          | otherwise = ptr0 `plusPtr` 1+    let len' = 1 + prec + max 1 dp++    unBuilder (withPtr len' $ \dst -> do+      _ <- B.memset dst 48 $ fromIntegral len'+      if dp >= 1+        then do+          B.memcpy dst ptr $ min len dp+          pokeElemOff dst dp 46+          B.memcpy (dst `plusPtr` (dp + 1)) (ptr `plusPtr` dp) $ max 0 (len - dp)+        else do+          pokeElemOff dst 1 46+          B.memcpy (dst `plusPtr` (2 - dp)) ptr len+      return $ dst `plusPtr` len'+      ) env buf+ ------------------------------------------------------------------------------ -- Decimal Encoding ------------------------------------------------------------------------------@@ -534,3 +650,71 @@ intDecPadded = P.liftFixedToBounded $ caseWordSize_32_64     (P.fixedPrim  9 $ c_int_dec_padded9            . fromIntegral)     (P.fixedPrim 18 $ c_long_long_int_dec_padded18 . fromIntegral)++-- Variable-length encoding+----++-- | Signed VLQ encoding (the first bit is a sign)+intVLQ :: Int -> Builder+intVLQ = primBounded intVLQBP+{-# INLINE intVLQ #-}++intVLQBP :: P.BoundedPrim Int+intVLQBP = P.boudedPrim 10 writeIntFinite+{-# INLINE CONLIKE intVLQBP #-}++-- | Unsigned VLQ encoding+wordVLQ :: Word -> Builder+wordVLQ = primBounded wordVLQBP++wordVLQBP :: P.BoundedPrim Word+wordVLQBP = P.boudedPrim 10 (writeUnsignedFinite pure)++writeWord8 :: Word8 -> Ptr Word8 -> IO (Ptr Word8)+writeWord8 w p = do+  poke p w+  return $! plusPtr p 1++writeIntFinite :: Int -> Ptr Word8 -> IO (Ptr Word8)+writeIntFinite n+  | n < 0 = case negate n of+    n'+      | n' < 0x40 -> writeWord8 (fromIntegral n' `setBit` 6)+      | otherwise ->+          writeWord8 (0xc0 .|. fromIntegral n') >=>+            writeUnsignedFinite pure (unsafeShiftR n' 6)+  | n < 0x40 = writeWord8 (fromIntegral n)+  | otherwise = writeWord8 (fromIntegral n `setBit` 7 `clearBit` 6) >=>+      writeUnsignedFinite pure (unsafeShiftR n 6)+{-# INLINE writeIntFinite #-}++writeUnsignedFinite :: (Bits a, Integral a) => (Ptr Word8 -> IO r) -> a -> Ptr Word8 -> IO r+writeUnsignedFinite k = go+  where+    go m+      | m < 0x80 = writeWord8 (fromIntegral m) >=> k+      | otherwise = writeWord8 (setBit (fromIntegral m) 7) >=> go (unsafeShiftR m 7)+{-# INLINE writeUnsignedFinite #-}++-- | Encode a Word in <https://github.com/stoklund/varint#prefixvarint PrefixVarInt>+prefixVarInt :: Word -> Builder+prefixVarInt = primBounded prefixVarIntBP++prefixVarIntBP :: P.BoundedPrim Word+prefixVarIntBP = P.boudedPrim 9 $ \x ptr0 -> do+  let bits = 64 - countLeadingZeros (x .|. 1)+  if bits > 56+    then do+      poke ptr0 0+      poke (castPtr ptr0 `plusPtr` 1) x+      return $! ptr0 `plusPtr` 9+    else do+      let bytes = 1 + (bits - 1) `div` 7+      let end = ptr0 `plusPtr` bytes+      let go ptr n+            | ptr == end = pure ptr+            | otherwise = do+              poke ptr (fromIntegral n .&. 0xff)+              go (ptr `plusPtr` 1) (n `shiftR` 8)+      go ptr0 $! (2 * x + 1) `shiftL` (bytes - 1)+{-# INLINE CONLIKE prefixVarIntBP #-}
+ src/Mason/Builder/Dynamic.hs view
@@ -0,0 +1,60 @@+module Mason.Builder.Dynamic+  ( DynBuilder+  , DynamicBackend(..)+  -- * Runners+  , toStrictByteString+  , toLazyByteString+  , hPutBuilderLen+  , hPutBuilder+  , sendBuilder+  ) where++import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as BL+import qualified Mason.Builder as B+import qualified Mason.Builder.Internal as B+import Network.Socket (Socket)+import System.IO (Handle)++data DynamicBackend = DynGrowingBuffer !B.GrowingBuffer+  | DynChannel !B.Channel+  | DynPutEnv !B.PutEnv++instance B.Buildable DynamicBackend where+  byteString bs = B.Builder $ \env buf -> case env of+    DynGrowingBuffer e -> B.unBuilder (B.byteString bs) e buf+    DynChannel e -> B.unBuilder (B.byteString bs) e buf+    DynPutEnv e -> B.unBuilder (B.byteString bs) e buf+  flush = B.Builder $ \env buf -> case env of+    DynGrowingBuffer e -> B.unBuilder B.flush e buf+    DynChannel e -> B.unBuilder B.flush e buf+    DynPutEnv e -> B.unBuilder B.flush e buf+  allocate n = B.Builder $ \env buf -> case env of+    DynGrowingBuffer e -> B.unBuilder (B.allocate n) e buf+    DynChannel e -> B.unBuilder (B.allocate n) e buf+    DynPutEnv e -> B.unBuilder (B.allocate n) e buf++-- | Builder with a fixed set of backends. This helps reducing code size+-- and unoptimised code especially on complex/recursive structures, at the cost of+-- extensibility.+type DynBuilder = B.BuilderFor DynamicBackend++toStrictByteString :: DynBuilder -> B.ByteString+toStrictByteString b = B.toStrictByteString $ B.Builder $ B.unBuilder b . DynGrowingBuffer+{-# INLINE toStrictByteString #-}++toLazyByteString :: DynBuilder -> BL.ByteString+toLazyByteString b = B.toLazyByteString $ B.Builder $ B.unBuilder b . DynChannel+{-# INLINE toLazyByteString #-}++hPutBuilder :: Handle -> DynBuilder -> IO ()+hPutBuilder h b = B.hPutBuilder h $ B.Builder $ B.unBuilder b . DynPutEnv+{-# INLINE hPutBuilder #-}++hPutBuilderLen :: Handle -> DynBuilder -> IO Int+hPutBuilderLen h b = B.hPutBuilderLen h $ B.Builder $ B.unBuilder b . DynPutEnv+{-# INLINE hPutBuilderLen #-}++sendBuilder :: Socket -> DynBuilder -> IO Int+sendBuilder h b = B.sendBuilder h $ B.Builder $ B.unBuilder b . DynPutEnv+{-# INLINE sendBuilder #-}
src/Mason/Builder/Internal.hs view
@@ -3,6 +3,7 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE MagicHash #-} {-# LANGUAGE UnboxedTuples #-}+{-# LANGUAGE RecordWildCards #-} module Mason.Builder.Internal (Builder   , BuilderFor(..)   , Buildable(..)@@ -21,21 +22,22 @@   , primMapListBounded   , primMapByteStringFixed   , primMapLazyByteStringFixed+  , PutEnv(..)   , hPutBuilderLen-  , PutBuilderEnv(..)   , encodeUtf8BuilderEscaped   , sendBuilder-  , SocketEnv(..)   , cstring   , cstringUtf8   , withPtr   , storable-  , padded-  , zeroPadded+  , paddedBoundedPrim+  , zeroPaddedBoundedPrim   -- * Internal   , ensure   , allocateConstant-  , grisu3+  , withGrisu3+  , withGrisu3Rounded+  , roundDigit   ) where  import Control.Concurrent@@ -66,7 +68,6 @@ import qualified Data.Text.Internal as T import qualified Data.Text.Internal.Encoding.Utf16 as U16 import qualified Network.Socket as S-import qualified Network.Socket.ByteString as S import GHC.Prim (eqWord#, plusAddr#, indexWord8OffAddr#) import GHC.Ptr (Ptr(..)) import GHC.Word (Word8(..))@@ -244,12 +245,13 @@ primMapLazyByteStringFixed fp = BL.foldr (mappend . primFixed fp) mempty {-# INLINE primMapLazyByteStringFixed #-} -padded :: Word8+paddedBoundedPrim+  :: Word8 -- ^ filler   -> Int -- ^ pad if shorter than this   -> B.BoundedPrim a   -> a   -> Builder-padded ch size bp a = ensure (B.sizeBound bp) $ \(Buffer end ptr) -> do+paddedBoundedPrim ch size bp a = ensure (B.sizeBound bp) $ \(Buffer end ptr) -> do   ptr' <- B.runB bp a ptr   let len = ptr' `minusPtr` ptr   let pad = size - len@@ -258,8 +260,8 @@     void $ B.memset ptr ch (fromIntegral pad)   return $ Buffer end $ ptr' `plusPtr` max pad 0 -zeroPadded :: Int -> B.BoundedPrim a -> a -> Builder-zeroPadded = padded 48+zeroPaddedBoundedPrim :: Int -> B.BoundedPrim a -> a -> Builder+zeroPaddedBoundedPrim = paddedBoundedPrim 48  newtype GrowingBuffer = GrowingBuffer (IORef (ForeignPtr Word8)) @@ -304,7 +306,7 @@  instance Buildable Channel where   byteString bs-    | B.length bs < 3272 = byteStringCopy bs+    | B.length bs < 4096 = byteStringCopy bs     | otherwise = flush <> Builder (\(Channel v _) b -> b <$ putMVar v bs)   {-# INLINE byteString #-}   flush = Builder $ \(Channel v ref) (Buffer end ptr) -> do@@ -321,14 +323,15 @@ toLazyByteString body = unsafePerformIO $ do   resp <- newEmptyMVar -  fptr <- mallocForeignPtrBytes defaultBufferSize+  let initialSize = 4096+  fptr <- mallocForeignPtrBytes initialSize   ref <- newIORef fptr   let ptr = unsafeForeignPtrToPtr fptr    let final (Left e) = throw e       final (Right _) = putMVar resp B.empty   _ <- flip forkFinally final $ unBuilder (body <> flush) (Channel resp ref)-    $ Buffer (ptr `plusPtr` defaultBufferSize) ptr+    $ Buffer (ptr `plusPtr` initialSize) ptr    let go _ = unsafePerformIO $ do         bs <- takeMVar resp@@ -338,11 +341,13 @@   return $ go () {-# INLINE toLazyByteString #-} --- | Environemnt for handle output-data PutBuilderEnv = PBE-  { pbHandle :: !Handle-  , pbBuffer :: !(IORef (ForeignPtr Word8))-  , pbTotal :: !(IORef Int)+-- | Environment for handle output+data PutEnv = PutEnv+  { peThreshold :: !Int+  , pePut :: !(Ptr Word8 -> Ptr Word8 -> IO ())+  -- ^ takes a pointer range and returns the number of bytes written+  , peBuffer :: !(IORef (ForeignPtr Word8))+  , peTotal :: !(IORef Int)   }  -- | Allocate a new buffer.@@ -354,50 +359,44 @@   return $! Buffer (ptr1 `plusPtr` len) ptr1 {-# INLINE allocateConstant #-} -instance Buildable PutBuilderEnv where-  byteString bs-    | len > defaultBufferSize = mappend flush $ Builder $ \(PBE h _ _) buf -> do-      B.hPut h bs-      return buf-    | otherwise = byteStringCopy bs-    where-      len = B.length bs+instance Buildable PutEnv where+  byteString bs@(B.PS fptr ofs len) = Builder $ \env@PutEnv{..} buf -> if len > peThreshold+    then do+      buf' <- unBuilder flush env buf+      withForeignPtr fptr $ \ptr -> do+        let ptr0 = ptr `plusPtr` ofs+        pePut ptr0 (ptr0 `plusPtr` len)+      pure buf'+    else unBuilder (byteStringCopy bs) env buf   {-# INLINE byteString #-} -  flush = Builder $ \(PBE h ref counter) (Buffer end ptr) -> do-    ptr0 <- unsafeForeignPtrToPtr <$> readIORef ref+  flush = Builder $ \PutEnv{..} (Buffer end ptr) -> do+    ptr0 <- unsafeForeignPtrToPtr <$> readIORef peBuffer     let len = minusPtr ptr ptr0-    modifyIORef' counter (+len)-    hPutBuf h ptr0 len+    modifyIORef' peTotal (+len)+    pePut ptr0 ptr     return $! Buffer end ptr0   {-# INLINE flush #-} -  allocate = allocateConstant pbBuffer+  allocate = allocateConstant peBuffer   {-# INLINE allocate #-} -defaultBufferSize :: Int-defaultBufferSize = 2048- -- | Write a 'Builder' into a handle and obtain the number of bytes written. -- 'flush' does not imply actual disk operations. Set 'NoBuffering' if you want -- it to write the content immediately.-hPutBuilderLen :: Handle -> BuilderFor PutBuilderEnv -> IO Int+hPutBuilderLen :: Handle -> BuilderFor PutEnv -> IO Int hPutBuilderLen h b = do-  fptr <- mallocForeignPtrBytes defaultBufferSize+  let initialSize = 4096+  fptr <- mallocForeignPtrBytes initialSize   ref <- newIORef fptr   let ptr = unsafeForeignPtrToPtr fptr   counter <- newIORef 0-  _ <- unBuilder (b <> flush) (PBE h ref counter) (Buffer (ptr `plusPtr` defaultBufferSize) ptr)+  _ <- unBuilder (b <> flush)+    (PutEnv initialSize (\ptr0 ptr1 -> hPutBuf h ptr (minusPtr ptr1 ptr0)) ref counter)+    (Buffer (ptr `plusPtr` initialSize) ptr)   readIORef counter {-# INLINE hPutBuilderLen #-} --- | Environemnt for socket output-data SocketEnv = SE-  { seSocket :: !S.Socket-  , seBuffer :: !(IORef (ForeignPtr Word8))-  , seCounter :: !(IORef Int)-  }- sendBufRange :: S.Socket -> Ptr Word8 -> Ptr Word8 -> IO () sendBufRange sock ptr0 ptr1 = go ptr0 where   go p@@ -407,33 +406,17 @@       S.withFdSocket sock $ threadWaitWrite . fromIntegral       when (sent > 0) $ go $ p `plusPtr` sent -instance Buildable SocketEnv where-  byteString bs-    | len > defaultBufferSize = mappend flush $ Builder $ \(SE sock _ _) (Buffer end ptr) -> do-      S.sendAll sock bs-      return $! Buffer end ptr-    | otherwise = byteStringCopy bs-    where-      len = B.length bs-  {-# INLINE byteString #-}--  flush = Builder $ \(SE sock ref counter) (Buffer end ptr1) -> do-    ptr0 <- unsafeForeignPtrToPtr <$> readIORef ref-    sendBufRange sock ptr0 ptr1-    modifyIORef' counter (+minusPtr ptr1 ptr0)-    return $! Buffer end ptr0-  {-# INLINE flush #-}--  allocate = allocateConstant seBuffer- -- | Write a 'Builder' into a handle and obtain the number of bytes written.-sendBuilder :: S.Socket -> BuilderFor SocketEnv -> IO Int+sendBuilder :: S.Socket -> BuilderFor PutEnv -> IO Int sendBuilder sock b = do-  fptr <- mallocForeignPtrBytes defaultBufferSize+  let initialSize = 4096+  fptr <- mallocForeignPtrBytes initialSize   ref <- newIORef fptr   let ptr = unsafeForeignPtrToPtr fptr   counter <- newIORef 0-  _ <- unBuilder (b <> flush) (SE sock ref counter) (Buffer (ptr `plusPtr` defaultBufferSize) ptr)+  _ <- unBuilder (b <> flush)+    (PutEnv initialSize (sendBufRange sock) ref counter)+    (Buffer (ptr `plusPtr` initialSize) ptr)   readIORef counter {-# INLINE sendBuilder #-} @@ -485,18 +468,47 @@     c_memmove :: Ptr Word8 -> Ptr Word8 -> Int -> IO ()  -- | Decimal encoding of a positive 'Double'.-{-# INLINE grisu3 #-}-grisu3 :: Double -> Maybe (B.ByteString, Int)-grisu3 d = unsafeDupablePerformIO $ allocaArray 2 $ \plen -> do-  fptr <- B.mallocByteString 18+{-# INLINE withGrisu3 #-}+withGrisu3 :: Double -> IO r -> (Ptr Word8 -> Int -> Int -> IO r) -> IO r+withGrisu3 d contFail cont = allocaArray 2 $ \plen -> allocaArray 19 $ \ptr -> do   let pexp = plusPtr plen (S.sizeOf (undefined :: CInt))-  success <- withForeignPtr fptr $ \ptr -> c_grisu3 (realToFrac d) ptr plen pexp+  success <- c_grisu3 (realToFrac d) (ptr `plusPtr` 1) plen pexp   if success == 0-    then return Nothing+    then contFail     else do       len <- fromIntegral <$> S.peek plen       e <- fromIntegral <$> S.peek pexp-      return $ Just (B.PS fptr 0 len, len + e)+      cont ptr len (len + e)++{-# INLINE withGrisu3Rounded #-}+withGrisu3Rounded :: Int -> Double -> (Ptr Word8 -> Int -> Int -> IO r) -> IO r+withGrisu3Rounded prec val cont = withGrisu3 val (error "withGrisu3Rounded: failed") $ \ptr len e -> do+  let len' = min prec len+  bump <- roundDigit prec len ptr+  if bump then cont ptr len' (e + 1) else cont (ptr `plusPtr` 1) len' e++-- | Round up to the supplied precision inplace.+roundDigit+  :: Int -- ^ precision+  -> Int -- ^ available digits+  -> Ptr Word8 -- ^ content+  -> IO Bool+roundDigit prec len _ | prec >= len = pure False+roundDigit prec _ ptr = do+  rd <- peekElemOff ptr (prec + 1)+  let carry 0 = do+        poke ptr 49+        pure True+      carry i = do+        d <- peekElemOff ptr i+        if d == 57+          then pokeElemOff ptr i 48 *> carry (i - 1)+          else do+            pokeElemOff ptr i (d + 1)+            pure False+  if rd >= 53+    then carry prec+    else pure False  foreign import ccall unsafe "static grisu3"   c_grisu3 :: CDouble -> Ptr Word8 -> Ptr CInt -> Ptr CInt -> IO CInt