on master help sign in
Pass the array size to deserialize for bound check
7bc450dHarendra Kumar
benchmark/Streamly/Benchmark/Data/Serialize.hs+8−9
@@ -304,14 +304,14 @@ pokeTimes val times = do
304 loopWith times poke arr val
304 loopWith times poke arr val
305
305
306{-# INLINE peek #-}
306{-# INLINE peek #-}
307−peek :: forall a. (Eq a, SERIALIZE_CLASS a) => a -> MutableByteArray -> IO ()
307+peek :: forall a. (Eq a, SERIALIZE_CLASS a) => (a, Int) -> MutableByteArray -> IO ()
308−peek val arr = do
309#ifdef USE_UNBOX
308#ifdef USE_UNBOX
310− (val1 :: a)
309+peek (val, _) arr = do
310+ (val1 :: a) <- DESERIALIZE_OP 0 arr
311#else
311#else
312− (_, val1 :: a)
312+peek (val, n) arr = do
313+ (_, val1 :: a) <- DESERIALIZE_OP 0 arr n
313#endif
314#endif
314− <- DESERIALIZE_OP 0 arr
315 -- Ensure that we are actually constructing the type and using it. This
315 -- Ensure that we are actually constructing the type and using it. This
316 -- is important, otherwise the structure is created and discarded, the
316 -- is important, otherwise the structure is created and discarded, the
317 -- cost of creation of the structure is not accounted. Otherwise we may
317 -- 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 ()
328peekTimes n val times = do
328peekTimes n val times = do
329 arr <- newBytes n
329 arr <- newBytes n
330 _ <- SERIALIZE_OP 0 arr val
330 _ <- SERIALIZE_OP 0 arr val
331− loopWith times peek val arr
331+ loopWith times peek (val, n) arr
332
332
333{-# INLINE trip #-}
333{-# INLINE trip #-}
334trip :: forall a. (Eq a, SERIALIZE_CLASS a) => a -> IO ()
334trip :: forall a. (Eq a, SERIALIZE_CLASS a) => a -> IO ()
@@ -337,11 +337,10 @@ trip val = do
337 arr <- newBytes n
337 arr <- newBytes n
338 _ <- SERIALIZE_OP 0 arr val
338 _ <- SERIALIZE_OP 0 arr val
339#ifdef USE_UNBOX
339#ifdef USE_UNBOX
340− val1
340+ val1 <- DESERIALIZE_OP 0 arr
341#else
341#else
342− (_, val1)
342+ (_, val1) <- DESERIALIZE_OP 0 arr n
343#endif
343#endif
344− <- DESERIALIZE_OP 0 arr
345 -- Do not remove this, see the comments in peek.
344 -- Do not remove this, see the comments in peek.
346 if (val1 /= val)
345 if (val1 /= val)
347 then error "roundtrip: no match"
346 then error "roundtrip: no match"
core/src/Streamly/Internal/Data/Serialize.hs+39−19
@@ -31,7 +31,6 @@ import Streamly.Internal.Data.Unbox
31 ( MutableByteArray(..)
31 ( MutableByteArray(..)
32 , PinnedState(..)
32 , PinnedState(..)
33 , Unbox
33 , Unbox
34− , sizeOfMutableByteArray
35 )
34 )
36import Streamly.Internal.Data.Array.Type (Array(..))
35import Streamly.Internal.Data.Array.Type (Array(..))
37import Streamly.Internal.System.IO (unsafeInlineIO)
36import Streamly.Internal.System.IO (unsafeInlineIO)
@@ -83,9 +82,9 @@ newtype Size a = Size (Int -> a -> Int) -- a left fold or Sum monoid
83-- (Size f, Size g) ->
82-- (Size f, Size g) ->
84-- Size $ \acc obj ->
83-- Size $ \acc obj ->
85-- acc + f 0 (_obj1 obj) + g 0 (_obj2 obj)
84-- acc + f 0 (_obj1 obj) + g 0 (_obj2 obj)
86−-- deserialize i arr = do
85+-- deserialize i arr len = do
87−-- (i1, x0) <- deserialize i arr
86+-- (i1, x0) <- deserialize i arr len
88−-- (i2, x1) <- deserialize i1 arr
87+-- (i2, x1) <- deserialize i1 arr len
89-- pure (i2, Object x0 x1)
88-- pure (i2, Object x0 x1)
90-- serialize i arr (Object x0 x1) = do
89-- serialize i arr (Object x0 x1) = do
91-- i1 <- serialize i arr x0
90-- i1 <- serialize i arr x0
@@ -102,12 +101,16 @@ class Serialize a where
102 -- offset but that may require traversing the Haskell structure again to get
101 -- offset but that may require traversing the Haskell structure again to get
103 -- the size. Therefore, this is a performance optimization.
102 -- the size. Therefore, this is a performance optimization.
104
103
104+ -- | @deserialize offset array arrayLen@ deserializes a value from the
105− -- | Deserialize a value from the given byte-index in the array. Returns a
105+ -- given byte-index in the array. Returns a tuple of the next byte-index
106− -- tuple of the next byte-index and the deserialized value.
106+ -- and the deserialized value.
107− deserialize :: Int -> MutableByteArray -> IO (Int, a)
107+ deserialize :: Int -> MutableByteArray -> Int -> IO (Int, a)
108
108
109 -- | Write the serialized representation of the value in the array at the
109 -- | 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.
111 serialize :: Int -> MutableByteArray -> a -> IO Int
114 serialize :: Int -> MutableByteArray -> a -> IO Int
112
115
113--------------------------------------------------------------------------------
116--------------------------------------------------------------------------------
@@ -132,12 +135,29 @@ checkBounds _label _off _arr = do
132 else return ()
135 else return ()
133#endif
136#endif
134
137
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+--
135{-# INLINE deserializeUnsafe #-}
156{-# INLINE deserializeUnsafe #-}
136−deserializeUnsafe :: forall a. Unbox a => Int -> MutableByteArray -> IO (Int, a)
157+deserializeUnsafe :: forall a. Unbox a => Int -> MutableByteArray -> Int -> IO (Int, a)
137−deserializeUnsafe off arr =
158+deserializeUnsafe off arr sz =
138 let next = off + Unbox.sizeOf (Proxy :: Proxy a)
159 let next = off + Unbox.sizeOf (Proxy :: Proxy a)
139 in do
160 in do
140− sz <- sizeOfMutableByteArray arr
141 -- Keep likely path in the straight branch.
161 -- Keep likely path in the straight branch.
142 if (next <= sz)
162 if (next <= sz)
143 then Unbox.peekByteIndex off arr >>= \val -> pure (next, val)
163 then Unbox.peekByteIndex off arr >>= \val -> pure (next, val)
@@ -162,7 +182,7 @@ instance Serialize _type where \
162; {-# INLINE size #-} \
182; {-# INLINE size #-} \
163; size = Size (\acc _ -> acc + Unbox.sizeOf (Proxy :: Proxy _type)) \
183; size = Size (\acc _ -> acc + Unbox.sizeOf (Proxy :: Proxy _type)) \
164; {-# INLINE deserialize #-} \
184; {-# INLINE deserialize #-} \
165−; deserialize off arr = deserializeUnsafe off arr :: IO (Int, _type) \
185+; deserialize off arr end = deserializeUnsafe off arr end :: IO (Int, _type) \
166; {-# INLINE serialize #-} \
186; {-# INLINE serialize #-} \
167; serialize = \
187; serialize = \
168 serializeUnsafe :: Int -> MutableByteArray -> _type -> IO Int
188 serializeUnsafe :: Int -> MutableByteArray -> _type -> IO Int
@@ -193,19 +213,19 @@ instance forall a. Serialize a => Serialize [a] where
193 Size f -> foldl' f (acc + (Unbox.sizeOf (Proxy :: Proxy Int))) xs
213 Size f -> foldl' f (acc + (Unbox.sizeOf (Proxy :: Proxy Int))) xs
194
214
195 {-# INLINE deserialize #-}
215 {-# INLINE deserialize #-}
196− deserialize off arr = do
216+ deserialize off arr sz = do
197 len <- Unbox.peekByteIndex off arr :: IO Int
217 len <- Unbox.peekByteIndex off arr :: IO Int
198 let off1 = off + Unbox.sizeOf (Proxy :: Proxy Int)
218 let off1 = off + Unbox.sizeOf (Proxy :: Proxy Int)
199 let
219 let
200 peekList f o i | i >= 3 = do
220 peekList f o i | i >= 3 = do
201 -- Unfold the loop three times
221 -- Unfold the loop three times
202− (o1, x1) <- deserialize o arr
222+ (o1, x1) <- deserialize o arr sz
203− (o2, x2) <- deserialize o1 arr
223+ (o2, x2) <- deserialize o1 arr sz
204− (o3, x3) <- deserialize o2 arr
224+ (o3, x3) <- deserialize o2 arr sz
205 peekList (f . (\xs -> x1:x2:x3:xs)) o3 (i - 3)
225 peekList (f . (\xs -> x1:x2:x3:xs)) o3 (i - 3)
206 peekList f o 0 = pure (o, f [])
226 peekList f o 0 = pure (o, f [])
207 peekList f o i = do
227 peekList f o i = do
208− (o1, x) <- deserialize o arr
228+ (o1, x) <- deserialize o arr sz
209 peekList (f . (x:)) o1 (i - 1)
229 peekList (f . (x:)) o1 (i - 1)
210 peekList id off1 len
230 peekList id off1 len
211
231
@@ -250,8 +270,8 @@ pinnedEncode = encodeAs Pinned
250decode :: Serialize a => Array Word8 -> a
270decode :: Serialize a => Array Word8 -> a
251decode arr@(Array {..}) = unsafeInlineIO $ do
271decode arr@(Array {..}) = unsafeInlineIO $ do
252 let lenArr = Array.length arr
272 let lenArr = Array.length arr
253− (off1, lenEncoding :: Int64) <- deserialize 0 arrContents
273+ (off1, lenEncoding :: Int64) <- deserialize 0 arrContents lenArr
254− (off2, val) <- deserialize off1 arrContents
274+ (off2, val) <- deserialize off1 arrContents lenArr
255 assertM(fromIntegral lenEncoding + off1 == off2)
275 assertM(fromIntegral lenEncoding + off1 == off2)
256 assertM(lenArr == off2)
276 assertM(lenArr == off2)
257 pure val
277 pure val
core/src/Streamly/Internal/Data/Serialize/TH.hs+7−4
@@ -53,6 +53,9 @@ _tag = mkName "tag"
53_initialOffset :: Name
53_initialOffset :: Name
54_initialOffset = mkName "initialOffset"
54_initialOffset = mkName "initialOffset"
55
55
56+_endOffset :: Name
57+_endOffset = mkName "endOffset"
58+
56_val :: Name
59_val :: Name
57_val = mkName "val"
60_val = mkName "val"
58
61
@@ -194,7 +197,7 @@ mkDeserializeExprOne (DataCon cname _ _ fields) =
194 makeBind i =
197 makeBind i =
195 bindS
198 bindS
196 (tupP [varP (makeI (i + 1)), varP (makeA i)])
199 (tupP [varP (makeI (i + 1)), varP (makeA i)])
197− [|deserialize $(varE (makeI i)) $(varE _arr)|]
200+ [|deserialize $(varE (makeI i)) $(varE _arr) $(varE _endOffset)|]
198
201
199
202
200mkDeserializeExpr :: Type -> [DataCon] -> Q Exp
203mkDeserializeExpr :: Type -> [DataCon] -> Q Exp
@@ -217,7 +220,7 @@ mkDeserializeExpr headTy cons =
217 doE
220 doE
218 [ bindS
221 [ bindS
219 (tupP [varP (mkName "i0"), varP _tag])
222 (tupP [varP (mkName "i0"), varP _tag])
220− [|deserialize $(varE _initialOffset) $(varE _arr)|]
223+ [|deserialize $(varE _initialOffset) $(varE _arr) $(varE _endOffset)|]
221 , noBindS
224 , noBindS
222 (caseE
225 (caseE
223 (sigE (varE _tag) (conT tagType))
226 (sigE (varE _tag) (conT tagType))
@@ -351,8 +354,8 @@ deriveSerializeInternal preds headTy cons = do
351 'deserialize
354 'deserialize
352 [ Clause
355 [ Clause
353 (if isUnitType cons
356 (if isUnitType cons
354− then [VarP _initialOffset, WildP]
357+ then [VarP _initialOffset, WildP, WildP]
355− else [VarP _initialOffset, VarP _arr])
358+ else [VarP _initialOffset, VarP _arr, VarP _endOffset])
356 (NormalB peekMethod)
359 (NormalB peekMethod)
357 []
360 []
358 ]
361 ]
test/Streamly/Test/Data/Serialize.hs+1−1
@@ -68,7 +68,7 @@ roundtrip val = do
68 arr <- newBytes sz
68 arr <- newBytes sz
69
69
70 off1 <- Serialize.serialize 0 arr val
70 off1 <- Serialize.serialize 0 arr val
71− (off2, val2) <- Serialize.deserialize 0 arr
71+ (off2, val2) <- Serialize.deserialize 0 arr sz
72 val2 `shouldBe` val
72 val2 `shouldBe` val
73 off2 `shouldBe` off1
73 off2 `shouldBe` off1
74
74
test/Streamly/Test/Data/Unbox.hs+10−8
@@ -64,13 +64,13 @@ import Test.Hspec as H
64
64
65#define MODULE_NAME "Data.Serialize.Deriving.TH"
65#define MODULE_NAME "Data.Serialize.Deriving.TH"
66#define DERIVE_UNBOX(typ) $(deriveSerialize ''typ)
66#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)
68#define POKE(i, arr, val) void (serialize i arr val)
68#define POKE(i, arr, val) void (serialize i arr val)
69#define TYPE_CLASS Serialize
69#define TYPE_CLASS Serialize
70
70
71#else
71#else
72
72
73−#define PEEK(i, arr) peekByteIndex i arr
73+#define PEEK(i, arr, sz) peekByteIndex i arr
74#define POKE(i, arr, val) pokeByteIndex i arr val
74#define POKE(i, arr, val) pokeByteIndex i arr val
75#define TYPE_CLASS Unbox
75#define TYPE_CLASS Unbox
76
76
@@ -189,14 +189,15 @@ testSerialization ::
189 => a
189 => a
190 -> IO ()
190 -> IO ()
191testSerialization val = do
191testSerialization val = do
192− arr <- newBytes
192+ let len =
193#ifdef USE_SERIALIZE
193#ifdef USE_SERIALIZE
194 (variableSizeOf val)
194 (variableSizeOf val)
195#else
195#else
196 (sizeOf (Proxy :: Proxy a))
196 (sizeOf (Proxy :: Proxy a))
197#endif
197#endif
198+ arr <- newBytes len
198 POKE(0, arr, val)
199 POKE(0, arr, val)
199− PEEK(0, arr) `shouldReturn` val
200+ PEEK(0, arr, len) `shouldReturn` val
200
201
201testGenericConsistency ::
202testGenericConsistency ::
202 forall a.
203 forall a.
@@ -216,12 +217,13 @@ testGenericConsistency ::
216testGenericConsistency val = do
217testGenericConsistency val = do
217
218
218 -- Test the generic sizeOf
219 -- Test the generic sizeOf
220+ let len =
219#ifdef USE_SERIALIZE
221#ifdef USE_SERIALIZE
220− variableSizeOf val
222+ variableSizeOf val
221#else
223#else
222− sizeOf (Proxy :: Proxy a)
224+ sizeOf (Proxy :: Proxy a)
223#endif
225#endif
224− `shouldBe` genericSizeOf (Proxy :: Proxy a)
226+ len `shouldBe` genericSizeOf (Proxy :: Proxy a)
225
227
226 -- Test the serialization and deserialization
228 -- Test the serialization and deserialization
227 arr <- newBytes (sizeOf (Proxy :: Proxy a))
229 arr <- newBytes (sizeOf (Proxy :: Proxy a))
@@ -230,7 +232,7 @@ testGenericConsistency val = do
230 genericPeekByteIndex arr 0 `shouldReturn` val
232 genericPeekByteIndex arr 0 `shouldReturn` val
231
233
232 genericPokeByteIndex arr 0 val
234 genericPokeByteIndex arr 0 val
233− PEEK(0, arr) `shouldReturn` val
235+ PEEK(0, arr, len) `shouldReturn` val
234
236
235
237
236#ifndef USE_SERIALIZE
238#ifndef USE_SERIALIZE