packages feed

small-bytearray-builder 0.2.1.0 → 0.3.0.0

raw patch · 11 files changed

+932/−233 lines, 11 filesdep ~byteslicedep ~text-shortdep ~vectorPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: byteslice, text-short, vector

API changes (from Hackage documentation)

- Data.ByteArray.Builder: bytearray :: ByteArray -> Builder
- Data.ByteArray.Builder: construct :: (forall s. MutableBytes s -> ST s (Maybe Int)) -> Builder
- Data.ByteArray.Builder: pasteArrayIO :: MutableBytes RealWorld -> (a -> Builder) -> Vector a -> IO (Vector a, MutableBytes RealWorld)
- Data.ByteArray.Builder: pasteArrayST :: MutableBytes s -> (a -> Builder) -> Vector a -> ST s (Vector a, MutableBytes s)
- Data.ByteArray.Builder: pasteGrowIO :: Int -> Builder -> MutableByteArrayOffset RealWorld -> IO (MutableByteArrayOffset RealWorld)
- Data.ByteArray.Builder: pasteGrowST :: Int -> Builder -> MutableByteArrayOffset s -> ST s (MutableByteArrayOffset s)
- Data.ByteArray.Builder: pasteIO :: Builder -> MutableBytes RealWorld -> IO (Maybe Int)
- Data.ByteArray.Builder: pasteST :: Builder -> MutableBytes s -> ST s (Maybe Int)
+ Data.ByteArray.Builder: byteArray :: ByteArray -> Builder
+ Data.ByteArray.Builder: consLength32BE :: Builder -> Builder
+ Data.ByteArray.Builder: consLength64BE :: Builder -> Builder
+ Data.ByteArray.Builder: copy :: Bytes -> Builder
+ Data.ByteArray.Builder: flush :: Int -> Builder
+ Data.ByteArray.Builder: insert :: Bytes -> Builder
+ Data.ByteArray.Builder: int16ArrayBE :: PrimArray Int16 -> Int -> Int -> Builder
+ Data.ByteArray.Builder: int16ArrayLE :: PrimArray Int16 -> Int -> Int -> Builder
+ Data.ByteArray.Builder: int16BE :: Int16 -> Builder
+ Data.ByteArray.Builder: int16LE :: Int16 -> Builder
+ Data.ByteArray.Builder: int32ArrayBE :: PrimArray Int32 -> Int -> Int -> Builder
+ Data.ByteArray.Builder: int32ArrayLE :: PrimArray Int32 -> Int -> Int -> Builder
+ Data.ByteArray.Builder: int32BE :: Int32 -> Builder
+ Data.ByteArray.Builder: int32LE :: Int32 -> Builder
+ Data.ByteArray.Builder: int64ArrayBE :: PrimArray Int64 -> Int -> Int -> Builder
+ Data.ByteArray.Builder: int64ArrayLE :: PrimArray Int64 -> Int -> Int -> Builder
+ Data.ByteArray.Builder: int64BE :: Int64 -> Builder
+ Data.ByteArray.Builder: int64LE :: Int64 -> Builder
+ Data.ByteArray.Builder: shortByteString :: ShortByteString -> Builder
+ Data.ByteArray.Builder: word16ArrayBE :: PrimArray Word16 -> Int -> Int -> Builder
+ Data.ByteArray.Builder: word16ArrayLE :: PrimArray Word16 -> Int -> Int -> Builder
+ Data.ByteArray.Builder: word16LE :: Word16 -> Builder
+ Data.ByteArray.Builder: word32ArrayBE :: PrimArray Word32 -> Int -> Int -> Builder
+ Data.ByteArray.Builder: word32ArrayLE :: PrimArray Word32 -> Int -> Int -> Builder
+ Data.ByteArray.Builder: word32LE :: Word32 -> Builder
+ Data.ByteArray.Builder: word64ArrayBE :: PrimArray Word64 -> Int -> Int -> Builder
+ Data.ByteArray.Builder: word64ArrayLE :: PrimArray Word64 -> Int -> Int -> Builder
+ Data.ByteArray.Builder: word64LE :: Word64 -> Builder
+ Data.ByteArray.Builder: word8Array :: PrimArray Word8 -> Int -> Int -> Builder
+ Data.ByteArray.Builder.Bounded: int16BE :: Int16 -> Builder 2
+ Data.ByteArray.Builder.Bounded: int16LE :: Int16 -> Builder 2
+ Data.ByteArray.Builder.Bounded: int32BE :: Int32 -> Builder 4
+ Data.ByteArray.Builder.Bounded: int32LE :: Int32 -> Builder 4
+ Data.ByteArray.Builder.Bounded: int64BE :: Int64 -> Builder 8
+ Data.ByteArray.Builder.Bounded: int64LE :: Int64 -> Builder 8
+ Data.ByteArray.Builder.Bounded: word16LE :: Word16 -> Builder 2
+ Data.ByteArray.Builder.Bounded: word32LE :: Word32 -> Builder 4
+ Data.ByteArray.Builder.Bounded: word64LE :: Word64 -> Builder 8
+ Data.ByteArray.Builder.Unsafe: Immutable :: ByteArray# -> Int# -> Int# -> !Commits s -> Commits s
+ Data.ByteArray.Builder.Unsafe: Initial :: Commits s
+ Data.ByteArray.Builder.Unsafe: Mutable :: MutableByteArray# s -> Int# -> !Commits s -> Commits s
+ Data.ByteArray.Builder.Unsafe: data Commits s
+ Data.Bytes.Chunks: ChunksCons :: {-# UNPACK #-} !Bytes -> !Chunks -> Chunks
+ Data.Bytes.Chunks: ChunksNil :: Chunks
+ Data.Bytes.Chunks: concat :: Chunks -> ByteArray
+ Data.Bytes.Chunks: data Chunks
- Data.ByteArray.Builder: run :: Int -> Builder -> ByteArray
+ Data.ByteArray.Builder: run :: Int -> Builder -> Chunks
- Data.ByteArray.Builder.Unsafe: Builder :: (forall s. MutableByteArray# s -> Int# -> Int# -> State# s -> (# State# s, Int# #)) -> Builder
+ Data.ByteArray.Builder.Unsafe: Builder :: (forall s. MutableByteArray# s -> Int# -> Int# -> Commits s -> State# s -> (# State# s, MutableByteArray# s, Int#, Int#, Commits s #)) -> Builder

Files

CHANGELOG.md view
@@ -1,5 +1,23 @@ # Revision history for small-bytearray-builder +## 0.3.0.0 -- 2019-10-17++* Breaking change: Change the internal implementation of `Builder`. This+  now works a lot more like the builder from `bytestring`. It accumulates+  chunks and can do a zero-copy appends when working with a sufficiently+  large immutable chunk. This introduces a mild performance regression+  (around 10%), but it makes the libary more generally useful.+* Introduce `consLengthBE32` and `consLength64BE` for efficient serialization+  of wire protocols that require prefixing a payload with its length.+* Add `int{16,32,64}BE` and `int{16,32,64LE}` as conveniences.+* Add little-endian encoding functions for `Word16`, `Word32`, and `Word64`.+* Add big-endian and little-endian functions for copying a+  `PrimArray` of numbers (both signed and unsigned) into a builder.+* Add `flush`, `copy`, and `insert` for better control when+  converting byte sequences to builders.+* Add `shortByteString` to improve interoperability with the+  `bytestring` library. + ## 0.2.1.0 -- 2019-09-05  * Stop exporting data constructor in `Data.ByteArray.Builder`.
+ bench/Cell.hs view
@@ -0,0 +1,32 @@+{-# language OverloadedLists #-}+{-# language OverloadedStrings #-}++module Cell+  ( Cell(..)+  , cells+  ) where++import Data.Word (Word32)+import Data.Text.Short (ShortText)+import Data.Primitive (SmallArray)++-- A cell in a CSV file+data Cell+  = CellString !ShortText+  | CellNumber !Word32++-- Some sample data to encode as a CSV+cells :: SmallArray (SmallArray Cell)+cells =+  [ [ CellString "Randy", CellString "Gutiérrez", CellNumber 41, CellNumber 343 ]+  , [ CellString "Édith", CellString "Piaf", CellNumber 63, CellNumber 453 ]+  , [ CellString "Martha", CellString "Washington", CellNumber 51, CellNumber 634 ]+  , [ CellString "Julius", CellString "Caesar", CellNumber 1, CellNumber 6922 ]+  , [ CellString "Robert", CellString "Redford", CellNumber 24, CellNumber 617 ]+  , [ CellString "Violet", CellString "Crawley", CellNumber 71, CellNumber 150 ]+  , [ CellString "Lázaro", CellString "Cárdenas", CellNumber 58, CellNumber 299 ]+  , [ CellString "Anastasia", CellString "San Martin", CellNumber 103, CellNumber 3214 ]+  , [ CellString "Mad", CellString "Max", CellNumber 37, CellNumber 918 ]+  , [ CellString "Sidonie-Gabrielle", CellString "Collette", CellNumber 25, CellNumber 904 ]+  ]+
bench/Main.hs view
@@ -1,12 +1,19 @@+{-# language LambdaCase #-}+{-# language OverloadedStrings #-}+ import Data.Primitive (ByteArray) import Data.Word (Word64) import Gauge (bgroup,bench,whnf) import Gauge.Main (defaultMain)  import qualified Arithmetic.Nat as Nat+import qualified Data.ByteArray.Builder as B import qualified Data.ByteArray.Builder.Bounded as U +import qualified Cell+import qualified SimpleCsv import qualified HexWord64+import qualified Word16Tree  main :: IO () main = defaultMain@@ -15,6 +22,20 @@       [ bench "library" (whnf encodeHexWord64s w64s)       , bench "loop" (whnf encodeHexWord64sLoop w64s)       ]+    ]+  , bgroup "unbounded"+    [ bench "csv-no-escape" $ whnf+        (\x -> B.run 4080 (SimpleCsv.encodeRows x))+        Cell.cells+    , bench "word-16-tree-small" $ whnf+        (\x -> B.run 4080 (Word16Tree.encode x))+        Word16Tree.exampleSmall+    , bench "word-16-tree-2000" $ whnf+        (\x -> B.run ((4096 * 16) - 16) (Word16Tree.encode x))+        Word16Tree.example2000+    , bench "word-16-tree-9000" $ whnf+        (\x -> B.run ((4096 * 64) - 16) (Word16Tree.encode x))+        Word16Tree.example9000     ]   ] 
+ bench/SimpleCsv.hs view
@@ -0,0 +1,31 @@+{-# language LambdaCase #-}++-- A variant of CSV encoding that does not perform+-- any escaping or quoting. This is in its own module+-- to make it easy to analyze the GHC Core that it+-- gets compiled to.+module SimpleCsv+  ( encodeRows+  ) where++import Cell (Cell(..))+import Data.Primitive (SmallArray)++import qualified Data.Foldable as F+import qualified Data.ByteArray.Builder as B++encodeRows :: SmallArray (SmallArray Cell) -> B.Builder+encodeRows = F.foldr+  (\r x -> encodeSimpleCsvRow r (B.ascii '\n' <> x))+  mempty++encodeSimpleCsvRow :: SmallArray Cell -> B.Builder -> B.Builder+encodeSimpleCsvRow cs b = F.foldr+  (\c x -> encodeSimpleCsvCell c <> B.ascii ',' <> x)+  b+  cs++encodeSimpleCsvCell :: Cell -> B.Builder+encodeSimpleCsvCell = \case+  CellNumber n -> B.word32Dec n+  CellString t -> B.shortTextUtf8 t
+ common/Word16Tree.hs view
@@ -0,0 +1,84 @@+{-# language BangPatterns #-}++module Word16Tree+  ( Word16Tree+  , encode+  , exampleSmall+  , example2000+  , example9000+  , expectedSmall+  ) where++import Data.ByteArray.Builder as B+import Data.Word (Word16)+import Data.Primitive (ByteArray)+import qualified Data.Bytes as Bytes++data Word16Tree+  = Branch !Word16Tree !Word16Tree+  | Leaf {-# UNPACK #-} !Word16++encode :: Word16Tree -> Builder+encode (Leaf w) = B.word16PaddedUpperHex w+encode (Branch a b) =+  B.ascii '('+  <>+  encode a+  <>+  B.ascii ','+  <>+  encode b+  <>+  B.ascii ')'++expectedSmall :: ByteArray+expectedSmall = Bytes.toByteArray $ Bytes.fromAsciiString+  "((AB59,(1F33,2E71)),((((FA9A,247B),890C),(0F13,((55BF,7CF1),389B))),1205))"+++exampleSmall :: Word16Tree+exampleSmall = Branch+  (Branch+    (Leaf 0xAB59)+    (Branch+      (Leaf 0x1F33)+      (Leaf 0x2E71)+    )+  )+  (Branch+    (Branch +      (Branch+        (Branch+          (Leaf 0xFA9A)+          (Leaf 0x247B)+        )+        (Leaf 0x890C)+      )+      (Branch+        (Leaf 0x0F13)+        (Branch+          (Branch+            (Leaf 0x55BF)+            (Leaf 0x7CF1)+          )+          (Leaf 0x389B)+        )+      )+    )+    (Leaf 0x1205)+  )++example2000 :: Word16Tree+{-# noinline example2000 #-}+example2000 = balanced 0 2000++example9000 :: Word16Tree+{-# noinline example9000 #-}+example9000 = balanced 0 9000++balanced :: Word16 -> Word16 -> Word16Tree+balanced !off !n+  | n == 0 = Leaf off+  | n == 1 = Leaf (off + 1)+  | otherwise = let x = div n 2 in+      Branch (balanced off x) (balanced (off + x) (n - x))
small-bytearray-builder.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: small-bytearray-builder-version: 0.2.1.0+version: 0.3.0.0 synopsis: Serialize to a small byte arrays description:   This is similar to the builder facilities provided by@@ -36,6 +36,7 @@  library   exposed-modules:+    Data.Bytes.Chunks     Data.ByteArray.Builder     Data.ByteArray.Builder.Unsafe     Data.ByteArray.Builder.Bounded@@ -45,7 +46,6 @@     , byteslice >=0.1 && <0.2     , primitive-offset >=0.2 && <0.3     , run-st >=0.1 && <0.2-    , vector >=0.12.0.3 && <0.13     , bytestring >=0.10.8.2 && <0.11     , text-short >=0.1.3 && <0.2     , natural-arithmetic >=0.1 && <0.2@@ -65,6 +65,7 @@   ghc-options: -O2 -Wall   other-modules:     HexWord64+    Word16Tree   build-depends:     , QuickCheck >=2.13.1 && <2.14     , base >=4.12.0.0 && <5@@ -87,9 +88,14 @@     , natural-arithmetic     , primitive     , small-bytearray-builder+    , text-short+    , byteslice   ghc-options: -Wall -O2   default-language: Haskell2010   hs-source-dirs: bench, common   main-is: Main.hs   other-modules:+    Cell     HexWord64+    SimpleCsv+    Word16Tree
src/Data/ByteArray/Builder.hs view
@@ -1,4 +1,5 @@ {-# language BangPatterns #-}+{-# language DataKinds #-} {-# language DuplicateRecordFields #-} {-# language LambdaCase #-} {-# language MagicHash #-}@@ -9,19 +10,15 @@ module Data.ByteArray.Builder   ( -- * Bounded Primitives     Builder-  , construct   , fromBounded     -- * Evaluation   , run-  , pasteST-  , pasteIO-  , pasteGrowST-  , pasteGrowIO-  , pasteArrayST-  , pasteArrayIO     -- * Materialized Byte Sequences   , bytes-  , bytearray+  , copy+  , insert+  , byteArray+  , shortByteString   , shortTextUtf8   , shortTextJsonString   , cstring@@ -54,138 +51,99 @@   , ascii   , char     -- ** Machine-Readable+    -- *** One+  , word8+    -- **** Big Endian   , word64BE   , word32BE   , word16BE-  , word8+  , int64BE+  , int32BE+  , int16BE+    -- **** Little Endian+  , word64LE+  , word32LE+  , word16LE+  , int64LE+  , int32LE+  , int16LE+    -- *** Many+  , word8Array+    -- **** Big Endian+  , word16ArrayBE+  , word32ArrayBE+  , word64ArrayBE+  , int64ArrayBE+  , int32ArrayBE+  , int16ArrayBE+    -- **** Little Endian+  , word16ArrayLE+  , word32ArrayLE+  , word64ArrayLE+  , int64ArrayLE+  , int32ArrayLE+  , int16ArrayLE+    -- ** Prefixing with Length+  , consLength32BE+  , consLength64BE     -- * Encode Floating-Point Types     -- ** Human-Readable   , doubleDec+    -- * Control+  , flush   ) where  import Control.Monad.Primitive (primitive_)-import Control.Monad.ST (ST,stToIO)-import Control.Monad.ST.Run (runByteArrayST)+import Control.Monad.ST (ST,runST) import Data.ByteArray.Builder.Unsafe (Builder(Builder))+import Data.ByteArray.Builder.Unsafe (Commits(Initial,Mutable,Immutable)) import Data.ByteArray.Builder.Unsafe (stringUtf8,cstring) import Data.ByteString.Short.Internal (ShortByteString(SBS))-import Data.Bytes.Types (Bytes(Bytes),MutableBytes(MutableBytes))+import Data.Bytes.Types (Bytes(Bytes)) import Data.Char (ord) import Data.Int (Int64,Int32,Int16,Int8)-import Data.Primitive (ByteArray(..),MutableByteArray(..))-import Data.Primitive.ByteArray.Offset (MutableByteArrayOffset(..))+import Data.Primitive (ByteArray(..),MutableByteArray(..),PrimArray(..)) import Data.Text.Short (ShortText) import Data.Word (Word64,Word32,Word16,Word8)-import GHC.Exts (Int(I#),Char(C#),Int#,State#,ByteArray#,RealWorld,(>=#),(/=#))+import GHC.Exts (Int(I#),Char(C#),Int#,State#,ByteArray#,(>=#))+import GHC.Exts (MutableByteArray#,(+#),(-#),(<#)) import GHC.ST (ST(ST))+import Data.Bytes.Chunks (Chunks(..))+import GHC.ByteOrder (ByteOrder(BigEndian,LittleEndian),targetByteOrder)  import qualified Arithmetic.Nat as Nat import qualified Arithmetic.Types as Arithmetic import qualified GHC.Exts as Exts import qualified Data.Text.Short as TS import qualified Data.Primitive as PM-import qualified Data.Vector as V import qualified Data.ByteArray.Builder.Bounded as Bounded import qualified Data.ByteArray.Builder.Bounded.Unsafe as UnsafeBounded --- | Run a builder. An accurate size hint is important for good performance.--- The size hint should be slightly larger than the actual size.+-- | Run a builder. run ::-     Int -- ^ Hint for upper bound on size+     Int -- ^ Size of initial chunk (use 4080 if uncertain)   -> Builder -- ^ Builder-  -> ByteArray-run hint b = runByteArrayST $ do-  let go !n = do-        arr <- PM.newByteArray n-        pasteST b (MutableBytes arr 0 n) >>= \case-          Nothing -> go (n + 64)-          Just len -> do-            shrinkMutableByteArray arr len-            PM.unsafeFreezeByteArray arr-  go (max hint 1)---- | Variant of 'pasteArrayST' that runs in 'IO'.-pasteArrayIO ::-     MutableBytes RealWorld -- ^ Buffer-  -> (a -> Builder) -- ^ Builder-  -> V.Vector a -- ^ Elements to serialize-  -> IO (V.Vector a, MutableBytes RealWorld) -- ^ Shifted vector, shifted buffer-pasteArrayIO !arr f !xs = stToIO (pasteArrayST arr f xs)---- | Fold over a vector, applying the builder to each element until--- the buffer cannot accomodate any more.-pasteArrayST ::-     MutableBytes s -- ^ Buffer-  -> (a -> Builder) -- ^ Builder-  -> V.Vector a -- ^ Elements to serialize-  -> ST s (V.Vector a, MutableBytes s) -- ^ Shifted vector, shifted buffer-pasteArrayST (MutableBytes arr off0 len0) f !xs0 = do-  let go !xs !ixBufA !lenBufA = if V.length xs > 0-        then do-          let a = V.unsafeHead xs-          pasteST (f a) (MutableBytes arr ixBufA lenBufA) >>= \case-            Nothing -> pure (xs,MutableBytes arr ixBufA lenBufA)-            Just ixBufB ->-              go (V.unsafeTail xs) ixBufB (lenBufA + (ixBufA - ixBufB))-        else pure (xs,MutableBytes arr ixBufA lenBufA)-  go xs0 off0 len0---- | Paste the builder into the byte array starting at offset zero.--- This repeatedly reallocates the byte array if it cannot accomodate--- the builder, replaying the builder each time.-pasteGrowST ::-     Int -- ^ How many bytes to grow by at a time-  -> Builder-  -> MutableByteArrayOffset s-     -- ^ Initial buffer, used linearly. Do not reuse this argument.-  -> ST s (MutableByteArrayOffset s)-     -- ^ Final buffer that accomodated the builder.-pasteGrowST !n b !(MutableByteArrayOffset arr0 off0) = do-  let go !arr !sz = pasteST b (MutableBytes arr off0 (sz - off0)) >>= \case-        Nothing -> do-          let szNext = sz + n-          arrNext <- PM.resizeMutableByteArray arr szNext-          go arrNext szNext-        Just ix -> pure (MutableByteArrayOffset{array=arr,offset=ix})-  go arr0 =<< PM.getSizeofMutableByteArray arr0---- | Variant of 'pasteGrowST' that runs in 'IO'.-pasteGrowIO ::-     Int -- ^ How many bytes to grow by at a time-  -> Builder-  -> MutableByteArrayOffset RealWorld-     -- ^ Initial buffer, used linearly. Do not reuse this argument.-  -> IO (MutableByteArrayOffset RealWorld)-     -- ^ Final buffer that accomodated the builder.-pasteGrowIO !n b !arr = stToIO (pasteGrowST n b arr)---- | Execute the builder, pasting its contents into a buffer.--- If the buffer is not large enough, this returns 'Nothing'.--- Otherwise, it returns the index in the buffer that follows--- the payload just written.-pasteST :: Builder -> MutableBytes s -> ST s (Maybe Int)-{-# inline pasteST #-}-pasteST (Builder f) (MutableBytes (MutableByteArray arr) (I# off) (I# len)) =-  ST $ \s0 -> case f arr off len s0 of-    (# s1, r #) -> if Exts.isTrue# (r /=# (-1#))-      then (# s1, Just (I# r) #)-      else (# s1, Nothing #)---- | Variant of 'pasteST' that runs in 'IO'.-pasteIO :: Builder -> MutableBytes RealWorld -> IO (Maybe Int)-{-# inline pasteIO #-}-pasteIO b m = stToIO (pasteST b m)+  -> Chunks+run hint@(I# hint# ) (Builder f) = runST $ do+  MutableByteArray buf0 <- PM.newByteArray hint+  cs <- ST $ \s0 -> case f buf0 0# hint# Initial s0 of+    (# s1, bufX, offX, _, csX #) ->+      (# s1, Mutable bufX offX csX #)+  commitsOntoChunks ChunksNil cs --- | Constructor for 'Builder' that works on a function with lifted--- arguments instead of unlifted ones. This is just as unsafe as the--- actual constructor.-construct :: (forall s. MutableBytes s -> ST s (Maybe Int)) -> Builder-construct f = Builder-  $ \arr off len s0 ->-    case unST (f (MutableBytes (MutableByteArray arr) (I# off) (I# len))) s0 of-      (# s1, m #) -> case m of-        Nothing -> (# s1, (-1#) #)-        Just (I# n) -> (# s1, n #)+-- Internal. This freezes all the mutable byte arrays in-place,+-- so be careful. It also reverses the chunks since everything+-- is backwards.+commitsOntoChunks :: Chunks -> Commits s -> ST s Chunks+commitsOntoChunks !xs Initial = pure xs+commitsOntoChunks !xs (Immutable arr off len cs) =+  commitsOntoChunks (ChunksCons (Bytes (ByteArray arr) (I# off) (I# len)) xs) cs+commitsOntoChunks !xs (Mutable buf len cs) = case len of+  0# -> commitsOntoChunks xs cs+  _ -> do+    shrinkMutableByteArray (MutableByteArray buf) (I# len)+    arr <- PM.unsafeFreezeByteArray (MutableByteArray buf)+    commitsOntoChunks (ChunksCons (Bytes arr 0 (I# len)) xs) cs  -- | Convert a bounded builder to an unbounded one. If the size -- is a constant, use @Arithmetic.Nat.constant@ as the first argument@@ -195,54 +153,249 @@   -> Bounded.Builder n   -> Builder {-# inline fromBounded #-}-fromBounded n (UnsafeBounded.Builder f) = Builder $ \arr off len s0 ->-  let !(I# req) = Nat.demote n in-  case len >=# req of-    1# -> f arr off s0-    _ -> (# s0, (-1#) #)+fromBounded n (UnsafeBounded.Builder f) = Builder $ \buf0 off0 len0 cs0 s0 ->+  let !(I# req) = Nat.demote n+      !(# s1, buf1, off1, len1, cs1 #) = case len0 >=# req of+        1# -> (# s0, buf0, off0, len0, cs0 #)+        _ -> let !(I# lenX) = max 4080 (I# req) in+          case Exts.newByteArray# lenX s0 of+            (# sX, bufX #) ->+              (# sX, bufX, 0#, lenX, Mutable buf0 off0 cs0 #)+   in case f buf1 off1 s1 of+        (# s2, off2 #) -> (# s2, buf1, off2, len1 -# (off2 -# off1), cs1 #) +-- This is a micro-optimization that uses an equality check instead+-- of an inequality check when the required number of bytes is one.+-- Use this instead of fromBounded (where possible) leads to marginally+-- better results in benchmarks.+fromBoundedOne ::+     Bounded.Builder 1+  -> Builder+{-# inline fromBoundedOne #-}+fromBoundedOne (UnsafeBounded.Builder f) = Builder $ \buf0 off0 len0 cs0 s0 ->+  let !(# s1, buf1, off1, len1, cs1 #) = case len0 of+        0# -> case Exts.newByteArray# 4080# s0 of+          (# sX, bufX #) ->+            (# sX, bufX, 0#, 4080#, Mutable buf0 off0 cs0 #)+        _ -> (# s0, buf0, off0, len0, cs0 #)+   in case f buf1 off1 s1 of+        (# s2, off2 #) -> (# s2, buf1, off2, len1 -# (off2 -# off1), cs1 #)+ -- | Create a builder from an unsliced byte sequence.-bytearray :: ByteArray -> Builder-bytearray a = bytes (Bytes a 0 (PM.sizeofByteArray a))+byteArray :: ByteArray -> Builder+byteArray a = bytes (Bytes a 0 (PM.sizeofByteArray a)) --- | Create a builder from a sliced byte sequence.+-- | Create a builder from a short bytestring.+shortByteString :: ShortByteString -> Builder+shortByteString (SBS x) = bytes (Bytes a 0 (PM.sizeofByteArray a))+  where a = ByteArray x++-- | Create a builder from a sliced byte sequence. The variants+-- 'copy' and 'insert' provide more control over whether or not+-- the byte sequence is copied or aliased. This function is preferred+-- when the user does not know the size of the byte sequence. bytes :: Bytes -> Builder-bytes (Bytes src soff slen) = construct $ \(MutableBytes arr off len) -> if len >= slen-  then do-    PM.copyByteArray arr off src soff slen-    pure (Just (off + slen))-  else pure Nothing+bytes (Bytes (ByteArray src# ) (I# soff# ) (I# slen# )) = Builder+  -- There are three cases to consider: (1) there is not enough+  -- space and (1a) the chunk is not small or (1b) the chunk is+  -- small; (2) There is enough space for a copy.+  (\buf0 off0 len0 cs0 s0 -> case len0 <# slen# of+    1# -> case slen# >=# 256# of+      1# -> case Exts.newByteArray# 0# s0 of+        (# s1, buf1 #) -> (# s1, buf1, 0#, 0#, Immutable src# soff# slen# (Mutable buf0 off0 cs0) #)+      _ -> case Exts.newByteArray# 4080# s0 of+        (# s1, buf1 #) -> case Exts.copyByteArray# src# soff# buf1 0# slen# s1 of+          s2 -> (# s2, buf1, slen#, 4080# -# slen#, Mutable buf0 off0 cs0 #)+    _ -> let !s1 = Exts.copyByteArray# src# soff# buf0 off0 slen# s0 in+      (# s1, buf0, off0 +# slen#, len0 -# slen#, cs0 #)+  ) +-- | Create a builder from a byte sequence. This always results in a+-- call to @memcpy@. This is beneficial when the byte sequence is+-- known to be small (less than 256 bytes).+copy :: Bytes -> Builder+copy (Bytes (ByteArray src# ) (I# soff# ) (I# slen# )) = Builder+  (\buf0 off0 len0 cs0 s0 -> case len0 <# slen# of+    1# -> case Exts.newByteArray# newSz s0 of+        (# s1, buf1 #) -> case Exts.copyByteArray# src# soff# buf1 0# slen# s1 of+          s2 -> (# s2, buf1, slen#, newSz -# slen#, Mutable buf0 off0 cs0 #)+    _ -> let !s1 = Exts.copyByteArray# src# soff# buf0 off0 slen# s0 in+      (# s1, buf0, off0 +# slen#, len0 -# slen#, cs0 #)+  )+  where+  !(I# newSz) = max (I# slen#) 4080++-- | Create a builder from a byte sequence. This never calls @memcpy@.+-- Instead, it pushes a chunk that references the argument byte sequence.+-- This wastes the remaining space in the active chunk, so it may adversely+-- affect performance if used carelessly. See 'flush' for a way to mitigate+-- this problem. This functions is most beneficial when the byte sequence+-- is known to be large (more than 8192 bytes).+insert :: Bytes -> Builder+insert (Bytes (ByteArray src# ) (I# soff# ) (I# slen# )) = Builder+  (\buf0 off0 _ cs0 s0 -> case Exts.newByteArray# 0# s0 of+    (# s1, buf1 #) ->+      (# s1, buf1, 0#, 0#, Immutable src# soff# slen# (Mutable buf0 off0 cs0) #)+  )++-- | Create a builder from a slice of an array of 'Word8'. There is the same+-- as 'bytes' but is provided as a convenience for users working with different+-- types.+word8Array :: PrimArray Word8 -> Int -> Int -> Builder+word8Array (PrimArray arr) off len = bytes (Bytes (ByteArray arr) off len)++int64ArrayLE :: PrimArray Int64 -> Int -> Int -> Builder+int64ArrayLE (PrimArray x) = word64ArrayLE (PrimArray x)++int64ArrayBE :: PrimArray Int64 -> Int -> Int -> Builder+int64ArrayBE (PrimArray x) = word64ArrayBE (PrimArray x)++int32ArrayLE :: PrimArray Int32 -> Int -> Int -> Builder+int32ArrayLE (PrimArray x) = word32ArrayLE (PrimArray x)++int32ArrayBE :: PrimArray Int32 -> Int -> Int -> Builder+int32ArrayBE (PrimArray x) = word32ArrayBE (PrimArray x)++int16ArrayLE :: PrimArray Int16 -> Int -> Int -> Builder+int16ArrayLE (PrimArray x) = word16ArrayLE (PrimArray x)++int16ArrayBE :: PrimArray Int16 -> Int -> Int -> Builder+int16ArrayBE (PrimArray x) = word16ArrayBE (PrimArray x)++word64ArrayLE :: PrimArray Word64 -> Int -> Int -> Builder+word64ArrayLE src@(PrimArray arr) soff0 slen0 = case targetByteOrder of+  LittleEndian -> bytes (Bytes (ByteArray arr) (soff0 * 8) (slen0 * 8))+  BigEndian -> word64ArraySwap src soff0 slen0++word64ArrayBE :: PrimArray Word64 -> Int -> Int -> Builder+word64ArrayBE src@(PrimArray arr) soff0 slen0 = case targetByteOrder of+  BigEndian -> bytes (Bytes (ByteArray arr) (soff0 * 8) (slen0 * 8))+  LittleEndian -> word64ArraySwap src soff0 slen0++word32ArrayLE :: PrimArray Word32 -> Int -> Int -> Builder+word32ArrayLE src@(PrimArray arr) soff0 slen0 = case targetByteOrder of+  LittleEndian -> bytes (Bytes (ByteArray arr) (soff0 * 4) (slen0 * 4))+  BigEndian -> word32ArraySwap src soff0 slen0++word32ArrayBE :: PrimArray Word32 -> Int -> Int -> Builder+word32ArrayBE src@(PrimArray arr) soff0 slen0 = case targetByteOrder of+  BigEndian -> bytes (Bytes (ByteArray arr) (soff0 * 4) (slen0 * 4))+  LittleEndian -> word32ArraySwap src soff0 slen0++word16ArrayLE :: PrimArray Word16 -> Int -> Int -> Builder+word16ArrayLE src@(PrimArray arr) soff0 slen0 = case targetByteOrder of+  LittleEndian -> bytes (Bytes (ByteArray arr) (soff0 * 2) (slen0 * 2))+  BigEndian -> word16ArraySwap src soff0 slen0++word16ArrayBE :: PrimArray Word16 -> Int -> Int -> Builder+word16ArrayBE src@(PrimArray arr) soff0 slen0 = case targetByteOrder of+  BigEndian -> bytes (Bytes (ByteArray arr) (soff0 * 2) (slen0 * 2))+  LittleEndian -> word16ArraySwap src soff0 slen0++word16ArraySwap :: PrimArray Word16 -> Int -> Int -> Builder+word16ArraySwap src soff0 slen0 =+  fromFunction (slen0 * 2) (go (soff0 * 2) ((soff0 + slen0) * 2))+  where+  go :: Int -> Int -> MutableByteArray s -> Int -> ST s Int+  go !soff !send !dst !doff = if soff < send+    then do+      let v0 = PM.indexPrimArray (asWord8s src) soff+          v1 = PM.indexPrimArray (asWord8s src) (soff + 1)+      PM.writeByteArray dst doff v1+      PM.writeByteArray dst (doff + 1) v0+      go (soff + 2) send dst (doff + 2)+    else pure doff++word32ArraySwap :: PrimArray Word32 -> Int -> Int -> Builder+word32ArraySwap src soff0 slen0 =+  fromFunction (slen0 * 4) (go (soff0 * 4) ((soff0 + slen0) * 4))+  where+  go :: Int -> Int -> MutableByteArray s -> Int -> ST s Int+  go !soff !send !dst !doff = if soff < send+    then do+      let v0 = PM.indexPrimArray (asWord8s src) soff+          v1 = PM.indexPrimArray (asWord8s src) (soff + 1)+          v2 = PM.indexPrimArray (asWord8s src) (soff + 2)+          v3 = PM.indexPrimArray (asWord8s src) (soff + 3)+      PM.writeByteArray dst doff v3+      PM.writeByteArray dst (doff + 1) v2+      PM.writeByteArray dst (doff + 2) v1+      PM.writeByteArray dst (doff + 3) v0+      go (soff + 4) send dst (doff + 4)+    else pure doff++word64ArraySwap :: PrimArray Word64 -> Int -> Int -> Builder+word64ArraySwap src soff0 slen0 =+  fromFunction (slen0 * 8) (go (soff0 * 8) ((soff0 + slen0) * 8))+  where+  go :: Int -> Int -> MutableByteArray s -> Int -> ST s Int+  go !soff !send !dst !doff = if soff < send+    then do+      let v0 = PM.indexPrimArray (asWord8s src) soff+          v1 = PM.indexPrimArray (asWord8s src) (soff + 1)+          v2 = PM.indexPrimArray (asWord8s src) (soff + 2)+          v3 = PM.indexPrimArray (asWord8s src) (soff + 3)+          v4 = PM.indexPrimArray (asWord8s src) (soff + 4)+          v5 = PM.indexPrimArray (asWord8s src) (soff + 5)+          v6 = PM.indexPrimArray (asWord8s src) (soff + 6)+          v7 = PM.indexPrimArray (asWord8s src) (soff + 7)+      PM.writeByteArray dst doff v7+      PM.writeByteArray dst (doff + 1) v6+      PM.writeByteArray dst (doff + 2) v5+      PM.writeByteArray dst (doff + 3) v4+      PM.writeByteArray dst (doff + 4) v3+      PM.writeByteArray dst (doff + 5) v2+      PM.writeByteArray dst (doff + 6) v1+      PM.writeByteArray dst (doff + 7) v0+      go (soff + 8) send dst (doff + 8)+    else pure doff++asWord8s :: PrimArray a -> PrimArray Word8+asWord8s (PrimArray x) = PrimArray x+ -- Internal function. Precondition, the referenced slice of the -- byte sequence is UTF-8 encoded text. slicedUtf8TextJson :: ByteArray# -> Int# -> Int# -> Builder {-# inline slicedUtf8TextJson #-}-slicedUtf8TextJson !src# !soff0# !slen0# = construct $ \(MutableBytes dst doff0 dlen0) ->-  let slen0 = I# slen0#-   in if dlen0 > (2 * slen0) + 2-        then do-          PM.writeByteArray dst doff0 (c2w '"')-          let go !soff !slen !doff = if slen > 0-                then case indexChar8Array (ByteArray src#) soff of-                  '\\' -> write2 dst doff '\\' '\\' *> go (soff + 1) (slen - 1) (doff + 2)-                  '\"' -> write2 dst doff '\\' '\"' *> go (soff + 1) (slen - 1) (doff + 2)-                  '\n' -> write2 dst doff '\\' 'n' *> go (soff + 1) (slen - 1) (doff + 2)-                  '\r' -> write2 dst doff '\\' 'r' *> go (soff + 1) (slen - 1) (doff + 2)-                  '\t' -> write2 dst doff '\\' 't' *> go (soff + 1) (slen - 1) (doff + 2)-                  c -> if c >= '\x20'-                    then PM.writeByteArray dst doff (c2w c) *> go (soff + 1) (slen - 1) (doff + 1)-                    else do-                      write2 dst doff '\\' 'u'-                      doff' <- UnsafeBounded.pasteST-                        (Bounded.word16PaddedUpperHex (fromIntegral (c2w c)))-                        dst (doff + 2)-                      go (soff + 1) (slen - 1) doff'-                else pure doff-          doffRes <- go (I# soff0#) (I# slen0#) (doff0 + 1)-          PM.writeByteArray dst doffRes (c2w '"')-          pure (Just (doffRes + 1))-        else pure Nothing+slicedUtf8TextJson !src# !soff0# !slen0# = fromFunction reqLen $ \dst doff0 -> do+  PM.writeByteArray dst doff0 (c2w '"')+  let go !soff !slen !doff = if slen > 0+        then case indexChar8Array (ByteArray src#) soff of+          '\\' -> write2 dst doff '\\' '\\' *> go (soff + 1) (slen - 1) (doff + 2)+          '\"' -> write2 dst doff '\\' '\"' *> go (soff + 1) (slen - 1) (doff + 2)+          '\n' -> write2 dst doff '\\' 'n' *> go (soff + 1) (slen - 1) (doff + 2)+          '\r' -> write2 dst doff '\\' 'r' *> go (soff + 1) (slen - 1) (doff + 2)+          '\t' -> write2 dst doff '\\' 't' *> go (soff + 1) (slen - 1) (doff + 2)+          c -> if c >= '\x20'+            then PM.writeByteArray dst doff (c2w c) *> go (soff + 1) (slen - 1) (doff + 1)+            else do+              write2 dst doff '\\' 'u'+              doff' <- UnsafeBounded.pasteST+                (Bounded.word16PaddedUpperHex (fromIntegral (c2w c)))+                dst (doff + 2)+              go (soff + 1) (slen - 1) doff'+        else pure doff+  doffRes <- go (I# soff0#) (I# slen0#) (doff0 + 1)+  PM.writeByteArray dst doffRes (c2w '"')+  pure (doffRes + 1)+  where+  slen0 = I# slen0#+  reqLen = (2 * slen0) + 2 +-- | Constructor for 'Builder' that works on a function with lifted+-- arguments instead of unlifted ones. This is just as unsafe as the+-- actual constructor.+fromFunction :: Int -> (forall s. MutableByteArray s -> Int -> ST s Int) -> Builder+fromFunction (I# req) f = Builder $ \buf0 off0 len0 cs0 s0 ->+  let !(# s1, buf1, off1, len1, cs1 #) = case len0 >=# req of+        1# -> (# s0, buf0, off0, len0, cs0 #)+        _ -> let !(I# lenX) = max 4080 (I# req) in+          case Exts.newByteArray# lenX s0 of+            (# sX, bufX #) ->+              (# sX, bufX, 0#, lenX, Mutable buf0 off0 cs0 #)+   in case unST (f (MutableByteArray buf1) (I# off1)) s1 of+        (# s2, I# off2 #) -> (# s2, buf1, off2, len1 -# (off2 -# off1), cs1 #)+ -- Internal. Write two characters in the ASCII plane to a byte array. write2 :: MutableByteArray s -> Int -> Char -> Char -> ST s () write2 marr ix a b = do@@ -399,7 +552,7 @@ -- | Encode an ASCII char. -- Precondition: Input must be an ASCII character. This is not checked. ascii :: Char -> Builder-ascii c = fromBounded Nat.constant (Bounded.char c)+ascii c = fromBoundedOne (Bounded.ascii c)  -- | Encode an UTF8 char. This only uses as much space as is required. char :: Char -> Builder@@ -413,6 +566,51 @@   primitive_ (Exts.shrinkMutableByteArray# arr sz)  -- | Requires exactly 8 bytes. Dump the octets of a 64-bit+-- signed integer in a little-endian fashion.+int64LE :: Int64 -> Builder+int64LE w = fromBounded Nat.constant (Bounded.int64LE w)++-- | Requires exactly 4 bytes. Dump the octets of a 32-bit+-- signed integer in a little-endian fashion.+int32LE :: Int32 -> Builder+int32LE w = fromBounded Nat.constant (Bounded.int32LE w)++-- | Requires exactly 2 bytes. Dump the octets of a 16-bit+-- signed integer in a little-endian fashion.+int16LE :: Int16 -> Builder+int16LE w = fromBounded Nat.constant (Bounded.int16LE w)++-- | Requires exactly 8 bytes. Dump the octets of a 64-bit+-- signed integer in a big-endian fashion.+int64BE :: Int64 -> Builder+int64BE w = fromBounded Nat.constant (Bounded.int64BE w)++-- | Requires exactly 4 bytes. Dump the octets of a 32-bit+-- signed integer in a big-endian fashion.+int32BE :: Int32 -> Builder+int32BE w = fromBounded Nat.constant (Bounded.int32BE w)++-- | Requires exactly 2 bytes. Dump the octets of a 16-bit+-- signed integer in a big-endian fashion.+int16BE :: Int16 -> Builder+int16BE w = fromBounded Nat.constant (Bounded.int16BE w)++-- | Requires exactly 8 bytes. Dump the octets of a 64-bit+-- word in a little-endian fashion.+word64LE :: Word64 -> Builder+word64LE w = fromBounded Nat.constant (Bounded.word64LE w)++-- | Requires exactly 4 bytes. Dump the octets of a 32-bit+-- word in a little-endian fashion.+word32LE :: Word32 -> Builder+word32LE w = fromBounded Nat.constant (Bounded.word32LE w)++-- | Requires exactly 2 bytes. Dump the octets of a 16-bit+-- word in a little-endian fashion.+word16LE :: Word16 -> Builder+word16LE w = fromBounded Nat.constant (Bounded.word16LE w)++-- | Requires exactly 8 bytes. Dump the octets of a 64-bit -- word in a big-endian fashion. word64BE :: Word64 -> Builder word64BE w = fromBounded Nat.constant (Bounded.word64BE w)@@ -427,8 +625,90 @@ word16BE :: Word16 -> Builder word16BE w = fromBounded Nat.constant (Bounded.word16BE w) +-- | Requires exactly 1 byte. word8 :: Word8 -> Builder-word8 w = fromBounded Nat.constant (Bounded.word8 w)+word8 w = fromBoundedOne (Bounded.word8 w)++-- | Prefix a builder with its size in bytes. This size is+-- presented as a big-endian 32-bit word. The need to prefix+-- a builder with its length shows up a numbers of wire protocols+-- including those of PostgreSQL and Apache Kafka. Note the+-- equivalence:+-- +-- > forall (n :: Int) (x :: Builder).+-- >   let sz = sizeofByteArray (run n (consLength32BE x))+-- >   consLength32BE x === word32BE (fromIntegral sz) <> x+--+-- However, using 'consLength32BE' is much more efficient here+-- since it only materializes the 'ByteArray' once.+consLength32BE :: Builder -> Builder+consLength32BE (Builder f) = Builder $ \buf0 off0 len0 cs0 s0 ->+  let !(# s1, buf1, off1, len1, cs1 #) = case len0 >=# 4# of+        1# -> (# s0, buf0, off0, len0, cs0 #)+        _ -> case Exts.newByteArray# 4080# s0 of+          (# sX, bufX #) ->+            (# sX, bufX, 0#, 4080#, Mutable buf0 off0 cs0 #)+   in case f buf1 (off1 +# 4# ) (len1 -# 4# ) cs1 s1 of+        (# s2, buf2, off2, len2, cs2 #) ->+          let !dist = case Exts.sameMutableByteArray# buf1 buf2 of+                1# -> off2 -# off1+                _ -> commitDistance buf1 off2 cs2 -# off1+              ST g = UnsafeBounded.pasteST+                (Bounded.word32BE (fromIntegral (I# (dist -# 4# ))))+                (MutableByteArray buf1)+                (I# off1)+           in case g s2 of+                (# s3, _ #) -> (# s3, buf2, off2, len2, cs2 #)+++-- | Prefix a builder with its size in bytes. This size is+-- presented as a big-endian 64-bit word. See 'consLength32BE'.+consLength64BE :: Builder -> Builder+consLength64BE (Builder f) = Builder $ \buf0 off0 len0 cs0 s0 ->+  let !(# s1, buf1, off1, len1, cs1 #) = case len0 >=# 8# of+        1# -> (# s0, buf0, off0, len0, cs0 #)+        _ -> case Exts.newByteArray# 4080# s0 of+          (# sX, bufX #) ->+            (# sX, bufX, 0#, 4080#, Mutable buf0 off0 cs0 #)+   in case f buf1 (off1 +# 8# ) (len1 -# 8# ) cs1 s1 of+        (# s2, buf2, off2, len2, cs2 #) ->+          let !dist = case Exts.sameMutableByteArray# buf1 buf2 of+                1# -> off2 -# off1+                _ -> commitDistance buf1 off2 cs2 -# off1+              ST g = UnsafeBounded.pasteST+                (Bounded.word64BE (fromIntegral (I# (dist -# 8# ))))+                (MutableByteArray buf1)+                (I# off1)+           in case g s2 of+                (# s3, _ #) -> (# s3, buf2, off2, len2, cs2 #)++commitDistance :: MutableByteArray# s -> Int# -> Commits s -> Int#+commitDistance _ !_ Initial = error "chunkDistance: chunk not found"+commitDistance target !n (Immutable _ _ len cs) =+  commitDistance target (n +# len) cs+commitDistance target !n (Mutable buf len cs) =+  case Exts.sameMutableByteArray# target buf of+    1# -> n +# len+    _ -> commitDistance target (n +# len) cs++-- | Push the buffer currently being filled onto the chunk list,+-- allocating a new active buffer of the requested size. This is+-- helpful when a small builder is sandwhiched between two large+-- zero-copy builders:+--+-- > insert bigA <> flush 1 <> word8 0x42 <> insert bigB+--+-- Without @flush 1@, @word8 0x42@ would see the zero-byte active+-- buffer that 'insert' returned, decide that it needed more space,+-- and allocate a 4080-byte buffer to which only a single byte+-- would be written.+flush :: Int -> Builder+flush !reqSz = Builder $ \buf0 off0 _ cs0 s0 ->+  case Exts.newByteArray# sz# s0 of+    (# sX, bufX #) ->+      (# sX, bufX, 0#, sz#, Mutable buf0 off0 cs0 #)+  where+  !(I# sz# ) = max reqSz 0  -- ShortText is already UTF-8 encoded. This is a no-op. shortTextToByteArray :: ShortText -> ByteArray
src/Data/ByteArray/Builder/Bounded.hs view
@@ -52,10 +52,22 @@   , ascii   , char     -- ** Machine-Readable+    -- *** One+  , word8+    -- **** Big Endian   , word64BE   , word32BE   , word16BE-  , word8+  , int64BE+  , int32BE+  , int16BE+    -- **** Little Endian+  , word64LE+  , word32LE+  , word16LE+  , int64LE+  , int32LE+  , int16LE     -- * Encode Floating-Point Types   , doubleDec   ) where@@ -507,10 +519,12 @@   where   w = W# w# --- | Encode an ASCII char.+-- | Encode an ASCII character. -- Precondition: Input must be an ASCII character. This is not checked. ascii :: Char -> Builder 1-ascii c = word8 (fromIntegral @Int @Word8 (ord c))+ascii (C# c) = Unsafe.construct $ \(MutableByteArray arr) (I# off) -> do+  primitive_ (writeCharArray# arr off c)+  pure (I# (off +# 1# ))  -- | Encode a character as UTF-8. This only uses as much space as is required. char :: Char -> Builder 4@@ -577,7 +591,39 @@     byteFourFour :: Word -> Word     byteFourFour w = (0b00111111 .&. w) .|. 0b10000000 +int64BE :: Int64 -> Builder 8+int64BE (I64# i) = word64BE (W64# (int2Word# i))++int32BE :: Int32 -> Builder 4+int32BE (I32# i) = word32BE (W32# (int2Word# i))++int16BE :: Int16 -> Builder 2+int16BE (I16# i) = word16BE (W16# (int2Word# i))++int64LE :: Int64 -> Builder 8+int64LE (I64# i) = word64LE (W64# (int2Word# i))++int32LE :: Int32 -> Builder 4+int32LE (I32# i) = word32LE (W32# (int2Word# i))++int16LE :: Int16 -> Builder 2+int16LE (I16# i) = word16LE (W16# (int2Word# i))+ -- | Requires exactly 8 bytes. Dump the octets of a 64-bit+-- word in a little-endian fashion.+word64LE :: Word64 -> Builder 8+word64LE w = Unsafe.construct $ \arr off -> do+  writeByteArray arr (off + 7) (fromIntegral @Word64 @Word8 (unsafeShiftR w 56))+  writeByteArray arr (off + 6) (fromIntegral @Word64 @Word8 (unsafeShiftR w 48))+  writeByteArray arr (off + 5) (fromIntegral @Word64 @Word8 (unsafeShiftR w 40))+  writeByteArray arr (off + 4) (fromIntegral @Word64 @Word8 (unsafeShiftR w 32))+  writeByteArray arr (off + 3) (fromIntegral @Word64 @Word8 (unsafeShiftR w 24))+  writeByteArray arr (off + 2) (fromIntegral @Word64 @Word8 (unsafeShiftR w 16))+  writeByteArray arr (off + 1) (fromIntegral @Word64 @Word8 (unsafeShiftR w 8))+  writeByteArray arr (off    ) (fromIntegral @Word64 @Word8 w)+  pure (off + 8)++-- | Requires exactly 8 bytes. Dump the octets of a 64-bit -- word in a big-endian fashion. word64BE :: Word64 -> Builder 8 word64BE w = Unsafe.construct $ \arr off -> do@@ -592,6 +638,16 @@   pure (off + 8)  -- | Requires exactly 4 bytes. Dump the octets of a 32-bit+-- word in a little-endian fashion.+word32LE :: Word32 -> Builder 4+word32LE w = Unsafe.construct $ \arr off -> do+  writeByteArray arr (off + 3) (fromIntegral @Word32 @Word8 (unsafeShiftR w 24))+  writeByteArray arr (off + 2) (fromIntegral @Word32 @Word8 (unsafeShiftR w 16))+  writeByteArray arr (off + 1) (fromIntegral @Word32 @Word8 (unsafeShiftR w 8))+  writeByteArray arr (off    ) (fromIntegral @Word32 @Word8 w)+  pure (off + 4)++-- | Requires exactly 4 bytes. Dump the octets of a 32-bit -- word in a big-endian fashion. word32BE :: Word32 -> Builder 4 word32BE w = Unsafe.construct $ \arr off -> do@@ -600,6 +656,14 @@   writeByteArray arr (off + 2) (fromIntegral @Word32 @Word8 (unsafeShiftR w 8))   writeByteArray arr (off + 3) (fromIntegral @Word32 @Word8 w)   pure (off + 4)++-- | Requires exactly 2 bytes. Dump the octets of a 16-bit+-- word in a little-endian fashion.+word16LE :: Word16 -> Builder 2+word16LE w = Unsafe.construct $ \arr off -> do+  writeByteArray arr (off + 1) (fromIntegral @Word16 @Word8 (unsafeShiftR w 8))+  writeByteArray arr (off    ) (fromIntegral @Word16 @Word8 w)+  pure (off + 2)  -- | Requires exactly 2 bytes. Dump the octets of a 16-bit -- word in a big-endian fashion.
src/Data/ByteArray/Builder/Unsafe.hs view
@@ -9,6 +9,7 @@ module Data.ByteArray.Builder.Unsafe   ( -- * Types     Builder(..)+  , Commits(..)     -- * Safe Functions     -- | These functions are actually completely safe, but they are defined     -- here because they are used by typeclass instances. Import them from@@ -17,10 +18,10 @@   , cstring   ) where -import Data.Primitive (MutableByteArray(MutableByteArray))+import Data.Primitive (MutableByteArray(MutableByteArray),ByteArray) import Foreign.C.String (CString)-import GHC.Exts ((-#),(+#),(/=#),(>#))-import GHC.Exts (Addr#,Int(I#),Ptr(Ptr))+import GHC.Exts ((-#),(+#),(>#))+import GHC.Exts (Addr#,ByteArray#,MutableByteArray#,Int(I#),Ptr(Ptr)) import GHC.Exts (IsString,Int#,State#,MutableByteArray#) import GHC.ST (ST(ST)) import GHC.Base (unpackCString#,unpackCStringUtf8#)@@ -32,10 +33,14 @@ -- | An unmaterialized sequence of bytes that may be pasted -- into a mutable byte array. newtype Builder-  = Builder (forall s. MutableByteArray# s -> Int# -> Int# -> State# s -> (# State# s, Int# #))-    -- ^ This function takes a buffer, an offset, and a number of remaining bytes.-    --   It returns the new offset (should be greater than the old offset), or if-    --   there was not enough space left in buffer, it returns -1.+  = Builder (forall s.+      MutableByteArray# s ->   -- buffer we are currently writing to+      Int# ->   -- offset into the current buffer+      Int# ->   -- number of bytes remaining in the current buffer+      Commits s ->   -- buffers and immutable byte slices that we have already committed+      State# s ->+      (# State# s, MutableByteArray# s, Int#, Int#, Commits s #) -- all the same things+    )  instance IsString Builder where   {-# inline fromString #-}@@ -43,53 +48,73 @@  instance Semigroup Builder where   {-# inline (<>) #-}-  Builder f <> Builder g = Builder $ \arr off0 len0 s0 -> case f arr off0 len0 s0 of-    (# s1, r #) -> case r /=# (-1#) of-      1# -> g arr r (len0 +# (off0 -# r)) s1-      _ -> (# s1, (-1#) #)+  Builder f <> Builder g = Builder $ \buf0 off0 len0 cs0 s0 -> case f buf0 off0 len0 cs0 s0 of+    (# s1, buf1, off1, len1, cs1 #) -> g buf1 off1 len1 cs1 s1  instance Monoid Builder where   {-# inline mempty #-}-  mempty = Builder $ \_ off0 _ s0 -> (# s0, off0 #)+  mempty = Builder $ \buf0 off0 len0 cs0 s0 -> (# s0, buf0, off0, len0, cs0 #) +data Commits s+  = Mutable+      (MutableByteArray# s)+      -- ^ Mutable buffer, start index implicitly zero+      Int# -- ^ Length (may be smaller than actual length)+      !(Commits s)+  | Immutable+      ByteArray# -- ^ Immutable chunk+      Int# -- ^ Offset into chunk, not necessarily zero+      Int# -- ^ Length (may be smaller than actual length)+      !(Commits s)+  | Initial+ -- | Create a builder from a cons-list of 'Char'. These -- are be UTF-8 encoded. stringUtf8 :: String -> Builder {-# inline stringUtf8 #-}-stringUtf8 cs = Builder (\arr off0 len0 s0 -> goString cs arr off0 len0 s0)+stringUtf8 cs = Builder (goString cs)  -- | Create a builder from a @NUL@-terminated 'CString'. This ignores any -- textual encoding, copying bytes until @NUL@ is reached. cstring :: CString -> Builder {-# inline cstring #-}-cstring (Ptr cs) = Builder (\arr off0 len0 s0 -> goCString cs arr off0 len0 s0)+cstring (Ptr cs) = Builder (goCString cs) -goString :: String -> MutableByteArray# s -> Int# -> Int# -> State# s -> (# State# s, Int# #)+goString :: String+  -> MutableByteArray# s -> Int# -> Int# -> Commits s+  -> State# s -> (# State# s, MutableByteArray# s, Int#, Int#, Commits s #) {-# noinline goString #-}-goString [] _ off0 _ s0 = (# s0, off0 #)-goString (c : cs) buf off0 len0 s0 = case len0 ># 3# of-  1# -> case unST (UnsafeBounded.pasteST (Bounded.char c) (MutableByteArray buf) (I# off0)) s0 of-    (# s1, I# off1 #) -> goString cs buf off1 (len0 -# (off1 -# off0)) s1-  _ -> (# s0, (-1#) #)+goString [] buf0 off0 len0 cs0 s0 = (# s0, buf0, off0, len0, cs0 #)+goString (c : cs) buf0 off0 len0 cs0 s0 = case len0 ># 3# of+  1# -> case unST (UnsafeBounded.pasteST (Bounded.char c) (MutableByteArray buf0) (I# off0)) s0 of+    (# s1, I# off1 #) -> goString cs buf0 off1 (len0 -# (off1 -# off0)) cs0 s1+  _ -> case Exts.newByteArray# 4080# s0 of+    (# s1, buf1 #) -> case unST (UnsafeBounded.pasteST (Bounded.char c) (MutableByteArray buf1) 0) s1 of+      (# s2, I# off1 #) -> goString cs buf1 off1 (4080# -# off1) (Mutable buf0 off0 cs0) s2  -- We have to have a rule for both unpackCString# and unpackCStringUtf8# -- since GHC uses a different function based on whether or not non-ASCII -- codepoints are used in the string. {-# RULES-"Builder stringUtf8/cstring" forall s a b c d.-  goString (unpackCString# s) a b c d = goCString s a b c d-"Builder stringUtf8/cstring-utf8" forall s a b c d.-  goString (unpackCStringUtf8# s) a b c d = goCString s a b c d+"Builder stringUtf8/cstring" forall s a b c d e.+  goString (unpackCString# s) a b c d e = goCString s a b c d e+"Builder stringUtf8/cstring-utf8" forall s a b c d e.+  goString (unpackCStringUtf8# s) a b c d e = goCString s a b c d e #-} -goCString :: Addr# -> MutableByteArray# s -> Int# -> Int# -> State# s -> (# State# s, Int# #)-goCString addr buf off0 len0 s0 = case Exts.indexWord8OffAddr# addr 0# of-  0## -> (# s0, off0 #)+goCString :: Addr# -> MutableByteArray# s -> Int# -> Int# -> Commits s+  -> State# s -> (# State# s, MutableByteArray# s, Int#, Int#, Commits s #)+goCString addr buf0 off0 len0 cs0 s0 = case Exts.indexWord8OffAddr# addr 0# of+  0## -> (# s0, buf0, off0, len0, cs0 #)   w -> case len0 of-    0# -> (# s0, (-1#) #)-    _ -> case Exts.writeWord8Array# buf off0 w s0 of-      s1 -> goCString (Exts.plusAddr# addr 1# ) buf (off0 +# 1# ) (len0 -# 1# ) s1+    0# -> case Exts.newByteArray# 4080# s0 of+      (# s1, buf1 #) -> case Exts.writeWord8Array# buf1 0# w s1 of+        s2 -> goCString+          (Exts.plusAddr# addr 1# ) buf1 1# (4080# -# 1# )+          (Mutable buf0 off0 cs0)+          s2+    _ -> case Exts.writeWord8Array# buf0 off0 w s0 of+      s1 -> goCString (Exts.plusAddr# addr 1# ) buf0 (off0 +# 1# ) (len0 -# 1# ) cs0 s1  unST :: ST s a -> State# s -> (# State# s, a #) unST (ST f) = f-
+ src/Data/Bytes/Chunks.hs view
@@ -0,0 +1,76 @@+{-# language BangPatterns #-}+{-# language DerivingStrategies #-}+{-# language TypeFamilies #-}+{-# language MagicHash #-}+{-# language UnboxedTuples #-}+{-# language NamedFieldPuns #-}++module Data.Bytes.Chunks+  ( Chunks(..)+  , concat+  ) where++import Prelude hiding (length,concat)++import GHC.ST (ST(..))+import Data.Bytes.Types (Bytes(..))+import Data.Primitive (ByteArray(..),MutableByteArray(..))+import GHC.Exts (ByteArray#,MutableByteArray#)+import GHC.Exts (IsList,Int#,State#,Int(I#),(+#),(-#))+import Control.Monad.ST.Run (runByteArrayST)++import qualified GHC.Exts as Exts+import qualified Data.Primitive as PM++data Chunks+  = ChunksCons {-# UNPACK #-} !Bytes !Chunks+  | ChunksNil++concat :: Chunks -> ByteArray+concat x = ByteArray (concat# x)++concat# :: Chunks -> ByteArray#+{-# noinline concat# #-}+concat# ChunksNil = case mempty of {ByteArray x -> x}+concat# (ChunksCons (Bytes{array=c,offset=coff,length=szc}) cs) = case cs of+  ChunksNil -> case c of {ByteArray x -> x}+  ChunksCons (Bytes{array=d,offset=doff,length=szd}) ds ->+    unBa $ runByteArrayST $ do+      let szboth = szc + szd+          len = chunksLengthGo szboth ds+      dst <- PM.newByteArray len+      PM.copyByteArray dst 0 c coff szc+      PM.copyByteArray dst szc d doff szd+      _ <- copy dst szboth ds+      PM.unsafeFreezeByteArray dst++chunksLengthGo :: Int -> Chunks -> Int+chunksLengthGo !n ChunksNil = n+chunksLengthGo !n (ChunksCons (Bytes{length}) cs) =+  chunksLengthGo (n + length) cs++-- | Copy the contents of the chunks into a mutable array.+-- Precondition: The destination must have enough space to+-- house the contents. This is not checked.+copy ::+     MutableByteArray s -- ^ Destination+  -> Int -- ^ Destination offset+  -> Chunks -- ^ Source+  -> ST s Int -- ^ Returns the next index into the destination after the payload+{-# inline copy #-}+copy (MutableByteArray dst) (I# off) cs = ST+  (\s0 -> case copy# dst off cs s0 of+    (# s1, nextOff #) -> (# s1, I# nextOff #)+  )++copy# :: MutableByteArray# s -> Int# -> Chunks -> State# s -> (# State# s, Int# #)+copy# _ off ChunksNil s0 = (# s0, off #)+copy# marr off (ChunksCons (Bytes{array,offset,length}) cs) s0 =+  case Exts.copyByteArray# (unBa array) (unI offset) marr off (unI length) s0 of+    s1 -> copy# marr (off +# unI length) cs s1++unI :: Int -> Int#+unI (I# i) = i++unBa :: ByteArray -> ByteArray#+unBa (ByteArray x) = x
test/Main.hs view
@@ -4,10 +4,10 @@ {-# language OverloadedStrings #-}  import Control.Monad.ST (runST)-import Data.Bytes.Types (MutableBytes(..)) import Data.ByteArray.Builder+import Data.Primitive (PrimArray) import Data.Word-import Data.Char (ord)+import Data.Char (ord,chr) import Data.Primitive (ByteArray) import Test.Tasty (defaultMain,testGroup,TestTree) import Test.QuickCheck ((===))@@ -18,16 +18,17 @@ import qualified Data.ByteString as ByteString import qualified Data.ByteString.Builder as BB import qualified Data.ByteString.Lazy.Char8 as LB+import qualified Data.Bytes.Chunks as Chunks import qualified Data.List as L import qualified Data.Primitive as PM import qualified Data.Text as T import qualified Data.Text.Encoding as TE-import qualified Data.Vector as V import qualified GHC.Exts as Exts import qualified Test.Tasty.HUnit as THU import qualified Test.Tasty.QuickCheck as TQC  import qualified HexWord64+import qualified Word16Tree  main :: IO () main = defaultMain tests@@ -36,68 +37,137 @@ tests = testGroup "Tests"   [ testGroup "live"     [ TQC.testProperty "word64Dec" $ \w ->-        run 1 (word64Dec w) === pack (show w)+        runConcat 1 (word64Dec w) === pack (show w)     , TQC.testProperty "word64Dec-x3" $ \x y z ->-        run 1 (word64Dec x <> word64Dec y <> word64Dec z)+        runConcat 1 (word64Dec x <> word64Dec y <> word64Dec z)         ===         pack (show x ++ show y ++ show z)     , TQC.testProperty "int64Dec-x3" $ \x y z ->-        run 1 (int64Dec x <> int64Dec y <> int64Dec z)+        runConcat 1 (int64Dec x <> int64Dec y <> int64Dec z)         ===         pack (show x ++ show y ++ show z)     , TQC.testProperty "word64BE-x3" $ \x y z ->-        run 1 (word64BE x <> word64BE y <> word64BE z)+        runConcat 1 (word64BE x <> word64BE y <> word64BE z)         ===         pack (LB.unpack (BB.toLazyByteString (BB.word64BE x <> BB.word64BE y <> BB.word64BE z)))     , TQC.testProperty "word64PaddedUpperHex" $ \w ->-        run 1 (word64PaddedUpperHex w)+        runConcat 1 (word64PaddedUpperHex w)         ===         pack (showWord64PaddedUpperHex w)     , TQC.testProperty "word8Dec" $ \w ->-        run 1 (word8Dec w)+        runConcat 1 (word8Dec w)         ===         pack (show w)-    , TQC.testProperty "pasteArrayST" $ \(xs :: [Word64]) ->-        (runArray word64Dec (V.fromList xs))+    , TQC.testProperty "consLength32BE" $ \w ->+        runConcat 1 (consLength32BE (word8Dec w))         ===-        pack (foldMap show xs)+        pack ('\x00' : '\x00' : '\x00' : chr (L.length (show w)) : show w)+    , TQC.testProperty "consLength64BE-uni" $ \w ->+        pack+          ( '\x00' : '\x00' : '\x00' : '\x00'+          : '\x00' : '\x00' : '\x00' : chr (L.length (show w))+          : show w+          )+        ===+        runConcat 1 (consLength64BE (word16Dec w))+    , TQC.testProperty "consLength64BE-multi" $ \w ->+        pack+          ( '\x00' : '\x00' : '\x00' : '\x00'+          : '\x00' : '\x00' : '\x00' : chr (1 + L.length (show w))+          : '\x42' : show w+          )+        ===+        runConcat 1 (consLength64BE (word8 0x42 <> flush 2 <> word16Dec w))     , THU.testCase "stringUtf8" $         packUtf8 "¿Cómo estás? I am doing well." @=?-          run 1 (stringUtf8 "¿Cómo estás? I am doing well.")+          runConcat 1 (stringUtf8 "¿Cómo estás? I am doing well.")     , THU.testCase "doubleDec-A" $-        pack (show (2 :: Int)) @=? run 1 (doubleDec 2.0)+        pack (show (2 :: Int)) @=? runConcat 1 (doubleDec 2.0)     , THU.testCase "doubleDec-B" $-        pack (show (2.5 :: Double)) @=? run 1 (doubleDec 2.5)+        pack (show (2.5 :: Double)) @=? runConcat 1 (doubleDec 2.5)     , THU.testCase "doubleDec-C" $-        pack ("1e+15") @=? run 1 (doubleDec 1e15)+        pack ("1e+15") @=? runConcat 1 (doubleDec 1e15)     , THU.testCase "doubleDec-D" $-        pack ("-42") @=? run 1 (doubleDec (-42))+        pack ("-42") @=? runConcat 1 (doubleDec (-42))     , THU.testCase "doubleDec-E" $-        pack ("-8.88888888888888e+14") @=? run 1 (doubleDec (-888888888888888.8888888))+        pack ("-8.88888888888888e+14") @=? runConcat 1 (doubleDec (-888888888888888.8888888))     , THU.testCase "doubleDec-F" $-        pack ("42") @=? run 1 (doubleDec 42)+        pack ("42") @=? runConcat 1 (doubleDec 42)     , THU.testCase "doubleDec-G" $-        pack ("0") @=? run 1 (doubleDec 0)+        pack ("0") @=? runConcat 1 (doubleDec 0)     , THU.testCase "doubleDec-H" $-        pack ("0.5") @=? run 1 (doubleDec 0.5)+        pack ("0.5") @=? runConcat 1 (doubleDec 0.5)     , THU.testCase "doubleDec-I" $-        pack ("-0.5") @=? run 1 (doubleDec (-0.5))+        pack ("-0.5") @=? runConcat 1 (doubleDec (-0.5))     , THU.testCase "doubleDec-J" $-        pack ("999999999") @=? run 1 (doubleDec 999999999)+        pack ("999999999") @=? runConcat 1 (doubleDec 999999999)     , THU.testCase "doubleDec-K" $-        pack ("-99999999") @=? run 1 (doubleDec (-99999999))+        pack ("-99999999") @=? runConcat 1 (doubleDec (-99999999))     , THU.testCase "shortTextJsonString-A" $-        pack ("\"hello\"") @=? run 1 (shortTextJsonString "hello")+        pack ("\"hello\"") @=? runConcat 1 (shortTextJsonString "hello")     , THU.testCase "shortTextJsonString-B" $-        pack ("\"\\\\_\\\"_/\"") @=? run 1 (shortTextJsonString "\\_\"_/")+        pack ("\"\\\\_\\\"_/\"") @=? runConcat 1 (shortTextJsonString "\\_\"_/")     , THU.testCase "shortTextJsonString-C" $-        pack ("\"Hi\\r\\nLo\"") @=? run 1 (shortTextJsonString "Hi\r\nLo")+        pack ("\"Hi\\r\\nLo\"") @=? runConcat 1 (shortTextJsonString "Hi\r\nLo")     , THU.testCase "shortTextJsonString-D" $-        pack ("\"Hi\\u001BLo\"") @=? run 1 (shortTextJsonString "Hi\ESCLo")+        pack ("\"Hi\\u001BLo\"") @=? runConcat 1 (shortTextJsonString "Hi\ESCLo")+    , THU.testCase "word-16-tree" $+        Word16Tree.expectedSmall @=? runConcat 1+          (Word16Tree.encode Word16Tree.exampleSmall)+    , THU.testCase "byteArray-small" $+        let a = replicateByte 3 0x50+            b = replicateByte 5 0x51+         in mconcat [a,b] @=? runConcat 1+              ( byteArray a <> byteArray b )+    , THU.testCase "byteArray-big" $+        let a = replicateByte 2105 0x50+            b = replicateByte 725 0x51+            c = replicateByte 900 0x52+            d = replicateByte 800 0x53+            e = replicateByte 700 0x54+            f = replicateByte 950 0x55+            g = replicateByte 975 0x56+            h = replicateByte 3000 0x57+            i = replicateByte 125 0x58+         in mconcat [a,b,c,d,e,f,g,h,i] @=? runConcat 1+              ( byteArray a <> byteArray b <> byteArray c <>+                byteArray d <> byteArray e <> byteArray f <>+                byteArray g <> byteArray h <> byteArray i+              )+    , TQC.testProperty "word16ArrayLE" $ \(xs :: [Word16]) ->+        let ys = Exts.fromList xs :: PrimArray Word16+         in runConcat 1 (foldMap word16LE xs)+            ===+            runConcat 1 (word16ArrayLE ys 0 (Prelude.length xs))+    , TQC.testProperty "word16ArrayBE" $ \(xs :: [Word16]) ->+        let ys = Exts.fromList xs :: PrimArray Word16+         in runConcat 1 (foldMap word16BE xs)+            ===+            runConcat 1 (word16ArrayBE ys 0 (Prelude.length xs))+    , TQC.testProperty "word32ArrayLE" $ \(xs :: [Word32]) ->+        let ys = Exts.fromList xs :: PrimArray Word32+         in runConcat 1 (foldMap word32LE xs)+            ===+            runConcat 1 (word32ArrayLE ys 0 (Prelude.length xs))+    , TQC.testProperty "word32ArrayBE" $ \(xs :: [Word32]) ->+        let ys = Exts.fromList xs :: PrimArray Word32+         in runConcat 1 (foldMap word32BE xs)+            ===+            runConcat 1 (word32ArrayBE ys 0 (Prelude.length xs))+    , TQC.testProperty "word64ArrayLE" $ \(xs :: [Word64]) ->+        let ys = Exts.fromList xs :: PrimArray Word64+         in runConcat 1 (foldMap word64LE xs)+            ===+            runConcat 1 (word64ArrayLE ys 0 (Prelude.length xs))+    , TQC.testProperty "word64ArrayBE" $ \(xs :: [Word64]) ->+        let ys = Exts.fromList xs :: PrimArray Word64+         in runConcat 1 (foldMap word64BE xs)+            ===+            runConcat 1 (word64ArrayBE ys 0 (Prelude.length xs))     ]   , testGroup "alternate"     [ TQC.testProperty "HexWord64" $ \x y ->-        run 1+        runConcat 1           (  fromBounded Nat.constant (HexWord64.word64PaddedUpperHex x)           <> fromBounded Nat.constant (HexWord64.word64PaddedUpperHex y)           )@@ -106,28 +176,20 @@     ]   ] +replicateByte :: Int -> Word8 -> ByteArray+replicateByte n w = runST $ do+  m <- PM.newByteArray n+  PM.setByteArray m 0 n w+  PM.unsafeFreezeByteArray m+ pack :: String -> ByteArray pack = Exts.fromList . map (fromIntegral @Int @Word8 . ord)  packUtf8 :: String -> ByteArray packUtf8 = Exts.fromList . ByteString.unpack . TE.encodeUtf8 . T.pack --- This is used to test pasteArrayST-runArray ::-     (a -> Builder) -- ^ Builder-  -> V.Vector a -- ^ Elements to serialize-  -> ByteArray -- ^ Number of elements serialized, serialization-runArray f !xs = runST $ do-  let go !v0 !sz !chunks = if V.null v0-        then pure (mconcat (L.reverse chunks))-        else do-          arr <- PM.newByteArray sz-          (v1,MutableBytes _ off _) <- pasteArrayST (MutableBytes arr 0 sz) f v0-          -- If nothing was serialized, we need a bigger buffer-          let szNext = if V.length v0 == V.length v1 then sz + 1 else sz-          c <- PM.unsafeFreezeByteArray =<< PM.resizeMutableByteArray arr off-          go v1 szNext (c : chunks)-  go xs 1 []- showWord64PaddedUpperHex :: Word64 -> String showWord64PaddedUpperHex = printf "%016X" ++runConcat :: Int -> Builder -> ByteArray+runConcat n = Chunks.concat . run n