on master help sign in
Pass the array size to deserialize for bound check
commit7bc450d725636f22b50e9847f54c26b4e5f9cb5b authorHarendra Kumar <harendra@composewell.com> dateMon 21 Aug 2023, 11:33 (3 years ago) parentad84b44
5 files changed+65−41
benchmark/Streamly/Benchmark/Data/Serialize.hs+8−9 core/src/Streamly/Internal/Data/Serialize.hs+39−19 core/src/Streamly/Internal/Data/Serialize/TH.hs+7−4 test/Streamly/Test/Data/Serialize.hs+1−1 test/Streamly/Test/Data/Unbox.hs+10−8
benchmark/Streamly/Benchmark/Data/Serialize.hs+8−9
@@ -304,14 +304,14 @@ pokeTimes val times = do
304304 loopWith times poke arr val
305305
306306{-# INLINE peek #-}
307−peek :: forall a. (Eq a, SERIALIZE_CLASS a) => a -> MutableByteArray -> IO ()
308−peek val arr = do
307+peek :: forall a. (Eq a, SERIALIZE_CLASS a) => (a, Int) -> MutableByteArray -> IO ()
309308#ifdef USE_UNBOX
310− (val1 :: a)
309+peek (val, _) arr = do
310+ (val1 :: a) <- DESERIALIZE_OP 0 arr
311311#else
312− (_, val1 :: a)
312+peek (val, n) arr = do
313+ (_, val1 :: a) <- DESERIALIZE_OP 0 arr n
313314#endif
314− <- DESERIALIZE_OP 0 arr
315315 -- Ensure that we are actually constructing the type and using it. This
316316 -- is important, otherwise the structure is created and discarded, the
317317 -- cost of creation of the structure is not accounted. Otherwise we may
@@ -328,7 +328,7 @@ peekTimes :: (Eq a, SERIALIZE_CLASS a) => Int -> a -> Int -> IO ()
328328peekTimes n val times = do
329329 arr <- newBytes n
330330 _ <- SERIALIZE_OP 0 arr val
331− loopWith times peek val arr
331+ loopWith times peek (val, n) arr
332332
333333{-# INLINE trip #-}
334334trip :: forall a. (Eq a, SERIALIZE_CLASS a) => a -> IO ()
@@ -337,11 +337,10 @@ trip val = do
337337 arr <- newBytes n
338338 _ <- SERIALIZE_OP 0 arr val
339339#ifdef USE_UNBOX
340− val1
340+ val1 <- DESERIALIZE_OP 0 arr
341341#else
342− (_, val1)
342+ (_, val1) <- DESERIALIZE_OP 0 arr n
343343#endif
344− <- DESERIALIZE_OP 0 arr
345344 -- Do not remove this, see the comments in peek.
346345 if (val1 /= val)
347346 then error "roundtrip: no match"
core/src/Streamly/Internal/Data/Serialize.hs+39−19
@@ -31,7 +31,6 @@ import Streamly.Internal.Data.Unbox
3131 ( MutableByteArray(..)
3232 , PinnedState(..)
3333 , Unbox
34− , sizeOfMutableByteArray
3534 )
3635import Streamly.Internal.Data.Array.Type (Array(..))
3736import Streamly.Internal.System.IO (unsafeInlineIO)
@@ -83,9 +82,9 @@ newtype Size a = Size (Int -> a -> Int) -- a left fold or Sum monoid
8382-- (Size f, Size g) ->
8483-- Size $ \acc obj ->
8584-- acc + f 0 (_obj1 obj) + g 0 (_obj2 obj)
86−-- deserialize i arr = do
87−-- (i1, x0) <- deserialize i arr
88−-- (i2, x1) <- deserialize i1 arr
85+-- deserialize i arr len = do
86+-- (i1, x0) <- deserialize i arr len
87+-- (i2, x1) <- deserialize i1 arr len
8988-- pure (i2, Object x0 x1)
9089-- serialize i arr (Object x0 x1) = do
9190-- i1 <- serialize i arr x0
@@ -102,12 +101,16 @@ class Serialize a where
102101 -- offset but that may require traversing the Haskell structure again to get
103102 -- the size. Therefore, this is a performance optimization.
104103
105− -- | Deserialize a value from the given byte-index in the array. Returns a
106− -- tuple of the next byte-index and the deserialized value.
107− deserialize :: Int -> MutableByteArray -> IO (Int, a)
104+ -- | @deserialize offset array arrayLen@ deserializes a value from the
105+ -- given byte-index in the array. Returns a tuple of the next byte-index
106+ -- and the deserialized value.
107+ deserialize :: Int -> MutableByteArray -> Int -> IO (Int, a)
108108
109109 -- | Write the serialized representation of the value in the array at the
110− -- given byte-index. Returns the next byte-index.
110+ -- given byte-index. Returns the next byte-index. This is an unsafe
111+ -- operation, the programmer must ensure that the array has enough space
112+ -- available in the array to serialize the value as determined by the
113+ -- @size@ operation.
111114 serialize :: Int -> MutableByteArray -> a -> IO Int
112115
113116--------------------------------------------------------------------------------
@@ -132,12 +135,29 @@ checkBounds _label _off _arr = do
132135 else return ()
133136#endif
134137
138+-- Note: Instead of passing around the size parameter, we can use
139+-- (sizeOfMutableByteArray arr) for checking the array bound, but that turns
140+-- out to be more expensive.
141+--
142+-- Another way to optimize this is to avoid the check for fixed size
143+-- structures. For fixed size structures we can do a check at the top level and
144+-- then use checkless deserialization using the Unbox type class. That will
145+-- require ConstSize and VarSize constructors in size. The programmer can
146+-- bundle all const size fields in a newtype to make serialization faster. This
147+-- can speed up the computation of size when serializing and checking size when
148+-- deserialing.
149+--
150+-- For variable size non-recursive structures a separate size validation method
151+-- could be used to validate the size before deserializing. "validate" can also
152+-- be used to collpase multiple chunks of arrays coming from network into a
153+-- single array for deserializing. But that can also be done by framing the
154+-- serialized value with a size header.
155+--
135156{-# INLINE deserializeUnsafe #-}
136−deserializeUnsafe :: forall a. Unbox a => Int -> MutableByteArray -> IO (Int, a)
137−deserializeUnsafe off arr =
157+deserializeUnsafe :: forall a. Unbox a => Int -> MutableByteArray -> Int -> IO (Int, a)
158+deserializeUnsafe off arr sz =
138159 let next = off + Unbox.sizeOf (Proxy :: Proxy a)
139160 in do
140− sz <- sizeOfMutableByteArray arr
141161 -- Keep likely path in the straight branch.
142162 if (next <= sz)
143163 then Unbox.peekByteIndex off arr >>= \val -> pure (next, val)
@@ -162,7 +182,7 @@ instance Serialize _type where \
162182; {-# INLINE size #-} \
163183; size = Size (\acc _ -> acc + Unbox.sizeOf (Proxy :: Proxy _type)) \
164184; {-# INLINE deserialize #-} \
165−; deserialize off arr = deserializeUnsafe off arr :: IO (Int, _type) \
185+; deserialize off arr end = deserializeUnsafe off arr end :: IO (Int, _type) \
166186; {-# INLINE serialize #-} \
167187; serialize = \
168188 serializeUnsafe :: Int -> MutableByteArray -> _type -> IO Int
@@ -193,19 +213,19 @@ instance forall a. Serialize a => Serialize [a] where
193213 Size f -> foldl' f (acc + (Unbox.sizeOf (Proxy :: Proxy Int))) xs
194214
195215 {-# INLINE deserialize #-}
196− deserialize off arr = do
216+ deserialize off arr sz = do
197217 len <- Unbox.peekByteIndex off arr :: IO Int
198218 let off1 = off + Unbox.sizeOf (Proxy :: Proxy Int)
199219 let
200220 peekList f o i | i >= 3 = do
201221 -- Unfold the loop three times
202− (o1, x1) <- deserialize o arr
203− (o2, x2) <- deserialize o1 arr
204− (o3, x3) <- deserialize o2 arr
222+ (o1, x1) <- deserialize o arr sz
223+ (o2, x2) <- deserialize o1 arr sz
224+ (o3, x3) <- deserialize o2 arr sz
205225 peekList (f . (\xs -> x1:x2:x3:xs)) o3 (i - 3)
206226 peekList f o 0 = pure (o, f [])
207227 peekList f o i = do
208− (o1, x) <- deserialize o arr
228+ (o1, x) <- deserialize o arr sz
209229 peekList (f . (x:)) o1 (i - 1)
210230 peekList id off1 len
211231
@@ -250,8 +270,8 @@ pinnedEncode = encodeAs Pinned
250270decode :: Serialize a => Array Word8 -> a
251271decode arr@(Array {..}) = unsafeInlineIO $ do
252272 let lenArr = Array.length arr
253− (off1, lenEncoding :: Int64) <- deserialize 0 arrContents
254− (off2, val) <- deserialize off1 arrContents
273+ (off1, lenEncoding :: Int64) <- deserialize 0 arrContents lenArr
274+ (off2, val) <- deserialize off1 arrContents lenArr
255275 assertM(fromIntegral lenEncoding + off1 == off2)
256276 assertM(lenArr == off2)
257277 pure val
core/src/Streamly/Internal/Data/Serialize/TH.hs+7−4
@@ -53,6 +53,9 @@ _tag = mkName "tag"
5353_initialOffset :: Name
5454_initialOffset = mkName "initialOffset"
5555
56+_endOffset :: Name
57+_endOffset = mkName "endOffset"
58+
5659_val :: Name
5760_val = mkName "val"
5861
@@ -194,7 +197,7 @@ mkDeserializeExprOne (DataCon cname _ _ fields) =
194197 makeBind i =
195198 bindS
196199 (tupP [varP (makeI (i + 1)), varP (makeA i)])
197− [|deserialize $(varE (makeI i)) $(varE _arr)|]
200+ [|deserialize $(varE (makeI i)) $(varE _arr) $(varE _endOffset)|]
198201
199202
200203mkDeserializeExpr :: Type -> [DataCon] -> Q Exp
@@ -217,7 +220,7 @@ mkDeserializeExpr headTy cons =
217220 doE
218221 [ bindS
219222 (tupP [varP (mkName "i0"), varP _tag])
220− [|deserialize $(varE _initialOffset) $(varE _arr)|]
223+ [|deserialize $(varE _initialOffset) $(varE _arr) $(varE _endOffset)|]
221224 , noBindS
222225 (caseE
223226 (sigE (varE _tag) (conT tagType))
@@ -351,8 +354,8 @@ deriveSerializeInternal preds headTy cons = do
351354 'deserialize
352355 [ Clause
353356 (if isUnitType cons
354− then [VarP _initialOffset, WildP]
355− else [VarP _initialOffset, VarP _arr])
357+ then [VarP _initialOffset, WildP, WildP]
358+ else [VarP _initialOffset, VarP _arr, VarP _endOffset])
356359 (NormalB peekMethod)
357360 []
358361 ]
test/Streamly/Test/Data/Serialize.hs+1−1
@@ -68,7 +68,7 @@ roundtrip val = do
6868 arr <- newBytes sz
6969
7070 off1 <- Serialize.serialize 0 arr val
71− (off2, val2) <- Serialize.deserialize 0 arr
71+ (off2, val2) <- Serialize.deserialize 0 arr sz
7272 val2 `shouldBe` val
7373 off2 `shouldBe` off1
7474
test/Streamly/Test/Data/Unbox.hs+10−8
@@ -64,13 +64,13 @@ import Test.Hspec as H
6464
6565#define MODULE_NAME "Data.Serialize.Deriving.TH"
6666#define DERIVE_UNBOX(typ) $(deriveSerialize ''typ)
67−#define PEEK(i, arr) fmap snd (deserialize i arr)
67+#define PEEK(i, arr, sz) fmap snd (deserialize i arr sz)
6868#define POKE(i, arr, val) void (serialize i arr val)
6969#define TYPE_CLASS Serialize
7070
7171#else
7272
73−#define PEEK(i, arr) peekByteIndex i arr
73+#define PEEK(i, arr, sz) peekByteIndex i arr
7474#define POKE(i, arr, val) pokeByteIndex i arr val
7575#define TYPE_CLASS Unbox
7676
@@ -189,14 +189,15 @@ testSerialization ::
189189 => a
190190 -> IO ()
191191testSerialization val = do
192− arr <- newBytes
192+ let len =
193193#ifdef USE_SERIALIZE
194194 (variableSizeOf val)
195195#else
196196 (sizeOf (Proxy :: Proxy a))
197197#endif
198+ arr <- newBytes len
198199 POKE(0, arr, val)
199− PEEK(0, arr) `shouldReturn` val
200+ PEEK(0, arr, len) `shouldReturn` val
200201
201202testGenericConsistency ::
202203 forall a.
@@ -216,12 +217,13 @@ testGenericConsistency ::
216217testGenericConsistency val = do
217218
218219 -- Test the generic sizeOf
220+ let len =
219221#ifdef USE_SERIALIZE
220− variableSizeOf val
222+ variableSizeOf val
221223#else
222− sizeOf (Proxy :: Proxy a)
224+ sizeOf (Proxy :: Proxy a)
223225#endif
224− `shouldBe` genericSizeOf (Proxy :: Proxy a)
226+ len `shouldBe` genericSizeOf (Proxy :: Proxy a)
225227
226228 -- Test the serialization and deserialization
227229 arr <- newBytes (sizeOf (Proxy :: Proxy a))
@@ -230,7 +232,7 @@ testGenericConsistency val = do
230232 genericPeekByteIndex arr 0 `shouldReturn` val
231233
232234 genericPokeByteIndex arr 0 val
233− PEEK(0, arr) `shouldReturn` val
235+ PEEK(0, arr, len) `shouldReturn` val
234236
235237
236238#ifndef USE_SERIALIZE