on master help sign in
Implement recordSyntaxWithHeader for the case of 1 constructor
commita309bf77e0bec9c48255a615e758e75f00fecf41 authorAdithya Kumar <adithya@composewell.com> dateTue 12 Sep 2023, 21:58 (3 years ago) parentde89f36
4 files changed+377−7
core/src/Streamly/Internal/Data/Serialize/TH.hs+39−7 core/src/Streamly/Internal/Data/Serialize/TH/Bottom.hs+9 core/src/Streamly/Internal/Data/Serialize/TH/RecHeader.hs+328 core/streamly-core.cabal+1
core/src/Streamly/Internal/Data/Serialize/TH.hs+39−7
@@ -33,6 +33,8 @@ import Streamly.Internal.Data.Unbox.TH
3333 , reifyDataType
3434 )
3535
36+import qualified Streamly.Internal.Data.Serialize.TH.RecHeader as RecHeader
37+
3638import Streamly.Internal.Data.Serialize.TH.Bottom
3739import Streamly.Internal.Data.Serialize.TH.Common
3840
@@ -146,6 +148,8 @@ mkSizeOfExpr False False tyOfTy =
146148 [varP _acc, varP _x]
147149 (caseE (varE _x) (fmap (matchCons acc) cons))
148150
151+mkSizeOfExpr False True (TheType con) = RecHeader.mkSizeOfExpr con
152+
149153mkSizeOfExpr _ _ _ = errorUnimplemented
150154
151155mkSizeDec :: Config -> Type -> [DataCon] -> Q [Dec]
@@ -238,6 +242,30 @@ mkDeserializeExpr False False headTy tyOfTy =
238242 $(lift (pprint headTy)) ++ ")")|])
239243 []
240244
245+mkDeserializeExpr False True headTy (TheType con@(SimpleDataCon _ fields)) = do
246+ deserializeWithKeys <- newName "deserializeWithKeys"
247+ updateFunc <- newName "updateFunc"
248+ hOff <- newName "hOff"
249+ finalOff <- newName "finalOff"
250+ deserializeWithKeysMethod <-
251+ RecHeader.mkDeserializeKeysExpr
252+ hOff finalOff _arr _endOffset updateFunc con
253+ updateFuncDec <- RecHeader.conUpdateFuncExpr updateFunc headTy fields
254+ letE
255+ ([ pure $ FunD
256+ deserializeWithKeys
257+ [ Clause
258+ [VarP hOff, VarP finalOff]
259+ (NormalB deserializeWithKeysMethod)
260+ []
261+ ]
262+ ] ++ (pure <$> updateFuncDec))
263+ (RecHeader.mkDeserializeExpr
264+ _initialOffset
265+ _endOffset
266+ deserializeWithKeys
267+ con)
268+
241269mkDeserializeExpr _ _ _ _ = errorUnimplemented
242270
243271mkDeserializeDec :: Config -> Type -> [DataCon] -> Q [Dec]
@@ -340,6 +368,9 @@ mkSerializeExpr False False tyOfTy =
340368 ]))
341369 (zip [0 ..] cons))
342370
371+mkSerializeExpr False True (TheType con) =
372+ RecHeader.mkSerializeExpr _initialOffset con
373+
343374mkSerializeExpr _ _ _ = errorUnimplemented
344375
345376mkSerializeDec :: Config -> Type -> [DataCon] -> Q [Dec]
@@ -418,15 +449,16 @@ deriveSerializeWith :: Config -> Q [Dec] -> Q [Dec]
418449deriveSerializeWith conf mDecs = do
419450 dec <- mDecs
420451 case dec of
421− [inst@(InstanceD _ _ headTy [])] -> do
452+ [inst@(InstanceD _ _ headTyWC [])] -> do
453+ let headTy = unwrap dec headTyWC
422454 dt <- reifyDataType (getMainTypeName dec headTy)
423455 let cons = dtCons dt
424456 deriveSerializeInternal conf headTy cons (next inst)
425457 _ -> errorUnsupported
426458 where
427459
428− next (InstanceD mo preds headTy []) methods =
429− pure [InstanceD mo preds headTy methods]
460+ next (InstanceD mo preds headTyWC []) methods =
461+ pure [InstanceD mo preds headTyWC methods]
430462 next _ _ = errorUnsupported
431463
432464 errorMessage dec =
@@ -444,12 +476,12 @@ deriveSerializeWith conf mDecs = do
444476 , "instance Serialize (TableT Identity)"
445477 ]
446478
447− getMainTypeName dec = go . unwrap
479+ unwrap _ (AppT (ConT _) r) = r
480+ unwrap dec _ = errorMessage dec
448481
449− where
482+ getMainTypeName dec = go
450483
451− unwrap (AppT (ConT _) r) = r
452− unwrap _ = errorMessage dec
484+ where
453485
454486 go (ConT nm) = nm
455487 go (AppT l _) = go l
core/src/Streamly/Internal/Data/Serialize/TH/Bottom.hs+9
@@ -18,6 +18,7 @@ module Streamly.Internal.Data.Serialize.TH.Bottom
1818 , Field
1919 , mkFieldName
2020 , isUnitType
21+ , isRecordSyntax
2122 , c2w
2223 , wListToString
2324 , xorCmp
@@ -32,6 +33,7 @@ module Streamly.Internal.Data.Serialize.TH.Bottom
3233 , int_w8
3334 , int_w32
3435 , w32_int
36+ , w8_int
3537 , _acc
3638 , _arr
3739 , _endOffset
@@ -43,6 +45,7 @@ module Streamly.Internal.Data.Serialize.TH.Bottom
4345 , errorUnimplemented
4446 ) where
4547
48+import Data.Maybe (isJust)
4649import Data.Char (chr, ord)
4750import Data.List (foldl')
4851import Data.Word (Word16, Word32, Word64, Word8)
@@ -174,6 +177,9 @@ isUnitType :: [DataCon] -> Bool
174177isUnitType [DataCon _ _ _ []] = True
175178isUnitType _ = False
176179
180+isRecordSyntax :: SimpleDataCon -> Bool
181+isRecordSyntax (SimpleDataCon _ fields) = and (isJust . fst <$> fields)
182+
177183--------------------------------------------------------------------------------
178184-- Type casting
179185--------------------------------------------------------------------------------
@@ -193,6 +199,9 @@ w8_w32 = fromIntegral
193199w8_w64 :: Word8 -> Word64
194200w8_w64 = fromIntegral
195201
202+w8_int :: Word8 -> Int
203+w8_int = fromIntegral
204+
196205w32_int :: Word32 -> Int
197206w32_int = fromIntegral
198207
core/src/Streamly/Internal/Data/Serialize/TH/RecHeader.hsnew file+328
@@ -0,0 +1,328 @@
1+{-# LANGUAGE TemplateHaskell #-}
2+
3+-- |
4+-- Module : Streamly.Internal.Data.Serialize.TH.RecHeader
5+-- Copyright : (c) 2023 Composewell Technologies
6+-- License : BSD3-3-Clause
7+-- Maintainer : streamly@composewell.com
8+-- Stability : experimental
9+-- Portability : GHC
10+--
11+module Streamly.Internal.Data.Serialize.TH.RecHeader
12+ ( mkSerializeExpr
13+ , mkDeserializeExpr
14+ , mkSizeOfExpr
15+ , conUpdateFuncExpr
16+ , mkDeserializeKeysExpr
17+ ) where
18+
19+--------------------------------------------------------------------------------
20+-- Imports
21+--------------------------------------------------------------------------------
22+
23+import Control.Monad (void)
24+import Data.List (foldl')
25+import Data.Word (Word16, Word32, Word8)
26+import Data.Maybe (fromJust)
27+import Language.Haskell.TH
28+import Streamly.Internal.Data.Serialize.Type (Serialize(..))
29+import Data.Foldable (foldlM)
30+import Streamly.Internal.Data.Unbox (MutableByteArray)
31+import Data.Proxy (Proxy(..))
32+
33+import qualified Streamly.Internal.Data.Unbox as Unbox
34+
35+import Streamly.Internal.Data.Serialize.TH.Bottom
36+import Streamly.Internal.Data.Serialize.TH.Common
37+
38+--------------------------------------------------------------------------------
39+-- Notes
40+--------------------------------------------------------------------------------
41+
42+-- Encoding the header length is required to check if we want to compare the
43+-- header. We should not compare the headers if the lengths of the headers are
44+-- not equal as this might lead to a runtime error.
45+
46+--------------------------------------------------------------------------------
47+-- Compact lists
48+--------------------------------------------------------------------------------
49+
50+-- Like haskell list but the maximum length of the list is 255
51+newtype CompactList a =
52+ CompactList
53+ { unCompactList :: [a]
54+ }
55+
56+-- We use 'Word8' to encode the length, hence the maximim number of elements in
57+-- the list is 255.
58+instance forall a. Serialize a => Serialize (CompactList a) where
59+
60+ -- {-# INLINE size #-}
61+ size acc (CompactList xs) =
62+ foldl' size (acc + (Unbox.sizeOf (Proxy :: Proxy Word8))) xs
63+
64+ -- Inlining this causes large compilation times for tests
65+ {-# INLINABLE deserialize #-}
66+ deserialize off arr sz = do
67+ (off1, len8) <- deserialize off arr sz :: IO (Int, Word8)
68+ let len = w8_int len8
69+ peekList f o i | i >= 3 = do
70+ -- Unfold the loop three times
71+ (o1, x1) <- deserialize o arr sz
72+ (o2, x2) <- deserialize o1 arr sz
73+ (o3, x3) <- deserialize o2 arr sz
74+ peekList (f . (\xs -> x1:x2:x3:xs)) o3 (i - 3)
75+ peekList f o 0 = pure (o, f [])
76+ peekList f o i = do
77+ (o1, x) <- deserialize o arr sz
78+ peekList (f . (x:)) o1 (i - 1)
79+ (nextOff, lst) <- peekList id off1 len
80+ pure (nextOff, CompactList lst)
81+
82+ -- Inlining this causes large compilation times for tests
83+ {-# INLINABLE serialize #-}
84+ serialize off arr (CompactList val) = do
85+ void $ serialize off arr (int_w8 (length val) :: Word8)
86+ let off1 = off + Unbox.sizeOf (Proxy :: Proxy Word8)
87+ let pokeList o [] = pure o
88+ pokeList o (x:xs) = do
89+ o1 <- serialize o arr x
90+ pokeList o1 xs
91+ pokeList off1 val
92+
93+--------------------------------------------------------------------------------
94+-- Helpers
95+--------------------------------------------------------------------------------
96+
97+fieldToNameBase :: Field -> String
98+fieldToNameBase = nameBase . fromJust . fst
99+
100+isMaybeType :: Type -> Bool
101+isMaybeType (AppT (ConT m) _) = m == ''Maybe
102+isMaybeType _ = False
103+
104+--------------------------------------------------------------------------------
105+-- Size
106+--------------------------------------------------------------------------------
107+
108+-- We add 4 here because we use 'serializeWithSize' for serializing.
109+exprGetSize :: Q Exp -> (Int, Type) -> Q Exp
110+exprGetSize acc (i, _) =
111+ [|size $(acc) $(varE (mkFieldName i)) + 4|]
112+
113+sizeOfHeader :: SimpleDataCon -> Int
114+sizeOfHeader (SimpleDataCon _ fields) =
115+ sizeForFinalOff + sizeForHeaderLength + sizeForNumFields
116+ + sum (map ((+ sizeForFieldLen) . length . fieldToNameBase) fields)
117+
118+ where
119+
120+ sizeForFinalOff = 4
121+ sizeForHeaderLength = 2 -- Max header length is (255 * (255 + 1) + 1) and
122+ -- hence 2 bytes is enough to store it.
123+ sizeForNumFields = 1 -- At max 255 fields in the record constructor
124+ sizeForFieldLen = 1 -- At max 255 letters in the key
125+
126+mkSizeOfExpr :: SimpleDataCon -> Q Exp
127+mkSizeOfExpr con = do
128+ n_acc <- newName "acc"
129+ n_x <- newName "x"
130+ (lamE
131+ [varP n_acc, varP n_x]
132+ [|$(litIntegral hlen) +
133+ $(caseE (varE n_x) [matchCons (varE n_acc) con])|])
134+
135+ where
136+
137+ hlen = sizeOfHeader con
138+ sizeOfFields acc fields = foldl' exprGetSize acc $ zip [0 ..] fields
139+ matchCons acc (SimpleDataCon cname fields) =
140+ let expr = sizeOfFields acc (map snd fields)
141+ in matchConstructor cname (length fields) expr
142+
143+--------------------------------------------------------------------------------
144+-- Header
145+--------------------------------------------------------------------------------
146+
147+headerValue :: SimpleDataCon -> [Word8]
148+headerValue (SimpleDataCon _ fields) =
149+ int_w8 numFields : concat (fmap lengthPrependedFieldEncoding fields)
150+
151+ where
152+
153+ -- Error out if the number of fields or the length of key is >= 256. We use
154+ -- Word8 for encoding the info and hence the max value is 255.
155+ numFields =
156+ let lenFields = length fields
157+ in if lenFields <= 255
158+ then lenFields
159+ else errorUnsupported
160+ lengthPrependedFieldEncoding field =
161+ let fEnc =
162+ let fEnc_ = map c2w (fieldToNameBase field)
163+ lenFEnc = length fEnc_
164+ in if lenFEnc <= 255
165+ then fEnc_
166+ else errorUnsupported
167+ in (int_w8 (length fEnc)) : fEnc
168+
169+--------------------------------------------------------------------------------
170+-- Peek
171+--------------------------------------------------------------------------------
172+
173+-- Encoding the size is required if we want to skip the field.
174+-- We encode the size as 'Word32' hence there is a 4 bytes increase in size.
175+{-# INLINE serializeWithSize #-}
176+serializeWithSize :: Serialize a => Int -> MutableByteArray -> a -> IO Int
177+serializeWithSize off arr val = do
178+ off1 <- serialize (off + 4) arr val
179+ Unbox.pokeByteIndex off arr (int_w32 (off1 - off - 4) :: Word32)
180+ pure off1
181+
182+mkSerializeExpr :: Name -> SimpleDataCon -> Q Exp
183+mkSerializeExpr initialOffset (con@(SimpleDataCon cname fields)) = do
184+ afterHLen <- newName "afterHLen"
185+ [|do $(varP afterHLen) <-
186+ serialize
187+ ($(varE initialOffset) + 4)
188+ $(varE _arr)
189+ ($(litIntegral hlen) :: Word16)
190+ $(varP (makeI 0)) <- $(serializeW8List afterHLen _arr hval)
191+ let $(openConstructor cname (length fields)) = $(varE _val)
192+ finalOff <- $(mkSerializeExprFields 'serializeWithSize fields)
193+ Unbox.pokeByteIndex
194+ $(varE initialOffset)
195+ $(varE _arr)
196+ ((fromIntegral :: Int -> Word32)
197+ (finalOff - $(varE initialOffset)))
198+ pure finalOff|]
199+
200+ where
201+
202+ hval = headerValue con
203+ hlen = length hval
204+
205+--------------------------------------------------------------------------------
206+-- Poke
207+--------------------------------------------------------------------------------
208+
209+{-# INLINE deserializeWithSize #-}
210+deserializeWithSize ::
211+ Serialize a => Int -> MutableByteArray -> Int -> IO (Int, a)
212+deserializeWithSize off arr endOff = deserialize (off + 4) arr endOff
213+
214+conUpdateFuncExpr :: Name -> Type -> [Field] -> Q [Dec]
215+conUpdateFuncExpr funcName headTy fields = do
216+ prevRec <- newName "prevRec"
217+ curOff <- newName "curOff"
218+ endOff <- newName "endOff"
219+ arr <- newName "arr"
220+ key <- newName "key"
221+ method <-
222+ (caseE
223+ (varE key)
224+ (concat
225+ [ map (matchField arr endOff (prevRec, curOff)) fnames
226+ , [ match
227+ wildP
228+ (normalB
229+ [|do (valOff, valLen :: Word32) <-
230+ deserialize
231+ $(varE curOff)
232+ $(varE arr)
233+ $(varE endOff)
234+ pure
235+ ( $(varE prevRec)
236+ , valOff + w32_int valLen)|])
237+ []
238+ ]
239+ ]))
240+ pure
241+ [ PragmaD (InlineP funcName NoInline FunLike AllPhases)
242+ , FunD
243+ funcName
244+ [ Clause
245+ [ VarP arr
246+ , VarP endOff
247+ , TupP [VarP prevRec, VarP curOff]
248+ , VarP key
249+ ]
250+ (NormalB method)
251+ []
252+ ]
253+ ]
254+
255+ where
256+
257+ fnames = fmap (fromJust . fst) fields
258+ matchField :: Name -> Name -> (Name, Name) -> Name -> Q Match
259+ matchField arr endOff (prevRec, currOff) fname = do
260+ val <- newName "val"
261+ newOff <- newName "newOff"
262+ match
263+ (litP (StringL (nameBase fname)))
264+ (normalB
265+ [|do ($(varP newOff), $(varP val)) <-
266+ deserializeWithSize
267+ $(varE currOff)
268+ $(varE arr)
269+ $(varE endOff)
270+ pure
271+ ( $(recUpdE (varE prevRec) [pure (fname, VarE val)])
272+ :: $(pure headTy)
273+ , $(varE newOff))|])
274+ []
275+
276+mkDeserializeKeysExpr ::
277+ Name -> Name -> Name -> Name -> Name -> SimpleDataCon -> Q Exp
278+mkDeserializeKeysExpr
279+ hOff finalOff arr endOff updateFunc (SimpleDataCon cname fields) = do
280+
281+ [|do (dataOff, hlist :: CompactList (CompactList Word8)) <-
282+ deserialize $(varE hOff) $(varE arr) $(varE endOff)
283+ let keys = wListToString . unCompactList <$> unCompactList hlist
284+ (finalRec, _) <-
285+ foldlM
286+ ($(varE updateFunc) $(varE arr) $(varE endOff))
287+ ($(emptyConExpr), dataOff)
288+ keys
289+ pure ($(varE finalOff), finalRec)|]
290+
291+ where
292+
293+ emptyTy k ty =
294+ if isMaybeType ty
295+ then [|Nothing|]
296+ else [|error $(litE (StringL (nameBase k ++ " is not found.")))|]
297+ fieldToEmptyRecC (Just name, ty) = (name, ) <$> emptyTy name ty
298+ fieldToEmptyRecC _ = errorUnsupported
299+ emptyConExpr = recConE cname (fmap fieldToEmptyRecC fields)
300+
301+mkDeserializeExpr :: Name -> Name -> Name -> SimpleDataCon -> Q Exp
302+mkDeserializeExpr initialOff endOff deserializeWithKeys con = do
303+ hOff <- newName "hOff"
304+ let sizeForFinalOff = 4 -- Word32
305+ sizeForHeaderLength = 2 -- Word16
306+ sizePreData = sizeForFinalOff + sizeForHeaderLength + hlen
307+ [|do (hlenOff, encLen :: Word32) <-
308+ deserialize $(varE initialOff) $(varE _arr) $(varE endOff)
309+ ($(varP hOff), hlen1 :: Word16) <-
310+ deserialize hlenOff $(varE _arr) $(varE endOff)
311+ if hlen1 == $(litIntegral hlen)
312+ then if $(xorCmp hval hOff _arr)
313+ then do
314+ let $(varP (makeI 0)) =
315+ $(varE initialOff) +
316+ $(litIntegral sizePreData)
317+ $(mkDeserializeExprOne 'deserializeWithSize con)
318+ else $(varE deserializeWithKeys)
319+ $(varE hOff)
320+ ($(varE initialOff) + w32_int encLen)
321+ else $(varE deserializeWithKeys)
322+ $(varE hOff)
323+ ($(varE initialOff) + w32_int encLen)|]
324+
325+ where
326+
327+ hval = headerValue con
328+ hlen = length hval
core/streamly-core.cabal+1
@@ -289,6 +289,7 @@ library
289289 , Streamly.Internal.Data.Serialize
290290 , Streamly.Internal.Data.Unbox.TH
291291 , Streamly.Internal.Data.Serialize.TH
292+ , Streamly.Internal.Data.Serialize.TH.RecHeader
292293 , Streamly.Internal.Data.Serialize.TH.Common
293294 , Streamly.Internal.Data.Serialize.TH.Bottom
294295