@@ -0,0 +1,487 @@
1+{-# LANGUAGE TemplateHaskell #-}
3+module Streamly.Internal.Data.Unbox.TH
8+--------------------------------------------------------------------------------
10+--------------------------------------------------------------------------------
12+import Data.Word (Word16, Word32, Word64, Word8)
13+import Data.Proxy (Proxy(..))
15+import Language.Haskell.TH
16+import Language.Haskell.TH.Syntax
17+import Streamly.Internal.Data.Unbox
19+--------------------------------------------------------------------------------
21+--------------------------------------------------------------------------------
23+-- The following are copied to remove the dependency on th-utilities.
24+-- The code has been copied from th-abstraction and th-utilities.
26+-- Some CPP macros in the following code are not required but are kept
27+-- anyway. They can be removed if deemed as a maintainance burden.
29+#if MIN_VERSION_template_haskell(2,17,0)
30+type TyVarBndr_ flag = TyVarBndr flag
32+type TyVarBndr_ flag = TyVarBndr
35+-- | Case analysis for a 'TyVarBndr'. If the value is a @'PlainTV' n _@, apply
36+-- the first function to @n@; if it is @'KindedTV' n _ k@, apply the second
37+-- function to @n@ and @k@.
38+elimTV :: (Name -> r) -> (Name -> Kind -> r) -> TyVarBndr_ flag -> r
39+#if MIN_VERSION_template_haskell(2,17,0)
40+elimTV ptv _ktv (PlainTV n _) = ptv n
41+elimTV _ptv ktv (KindedTV n _ k) = ktv n k
43+elimTV ptv _ktv (PlainTV n) = ptv n
44+elimTV _ptv ktv (KindedTV n k) = ktv n k
47+-- | Extract the type variable name from a 'TyVarBndr', ignoring the
48+-- kind signature if one exists.
49+tvName :: TyVarBndr_ flag -> Name
50+tvName = elimTV id (\n _ -> n)
52+-- | Get the 'Name' of a 'TyVarBndr'
53+tyVarBndrName :: TyVarBndr_ flag -> Name
54+tyVarBndrName = tvName
56+appsT :: Type -> [Type] -> Type
58+appsT x (y:xs) = appsT (AppT x y) xs
60+-- | Utility to conveniently handle change to 'InstanceD' API in
61+-- template-haskell-2.11.0
62+plainInstanceD :: Cxt -> Type -> [Dec] -> Dec
64+#if MIN_VERSION_template_haskell(2,11,0)
70+-- | Simplified info about a 'DataD'. Omits deriving, strictness,
71+-- kind info, and whether it's @data@ or @newtype@.
72+data DataType = DataType
76+ , dtCons :: [DataCon]
77+ } deriving (Eq, Show, Ord) --, Data, Typeable, Generic)
79+-- | Simplified info about a 'Con'. Omits deriving, strictness, and kind
80+-- info. This is much nicer than consuming 'Con' directly, because it
81+-- unifies all the constructors into one.
82+data DataCon = DataCon
86+ , dcFields :: [(Maybe Name, Type)]
87+ } deriving (Eq, Show, Ord) --, Data, Typeable, Generic)
90+-- | Convert a 'Con' to a list of 'DataCon'. The result is a list
91+-- because 'GadtC' and 'RecGadtC' can define multiple constructors.
92+conToDataCons :: Con -> [DataCon]
94+ NormalC name slots ->
95+ [DataCon name [] [] (map (\(_, ty) -> (Nothing, ty)) slots)]
97+ [DataCon name [] [] (map (\(n, _, ty) -> (Just n, ty)) fields)]
98+ InfixC (_, ty1) name (_, ty2) ->
99+ [DataCon name [] [] [(Nothing, ty1), (Nothing, ty2)]]
100+ ForallC tvs preds con ->
101+ map (\(DataCon name tvs0 preds0 fields) ->
102+ DataCon name (tvs0 ++ map tyVarBndrName tvs) (preds0 ++ preds) fields) (conToDataCons con)
103+#if MIN_VERSION_template_haskell(2,11,0)
105+ map (\dn -> DataCon dn [] [] (map (\(_, ty) -> (Nothing, ty)) slots)) ns
106+ RecGadtC ns fields _ ->
107+ map (\dn -> DataCon dn [] [] (map (\(fn, _, ty) -> (Just fn, ty)) fields)) ns
110+-- | Reify the given data or newtype declaration, and yields its
111+-- 'DataType' representation.
112+reifyDataType :: Name -> Q DataType
113+reifyDataType name = do
115+ case infoToDataType info of
116+ Nothing -> fail $ "Expected to reify a datatype. Instead got:\n" ++ pprint info
119+infoToDataType :: Info -> Maybe DataType
120+infoToDataType info = case info of
121+#if MIN_VERSION_template_haskell(2,11,0)
122+ TyConI (DataD preds name tvs _kind cons _deriving) ->
124+ TyConI (DataD preds name tvs cons _deriving) ->
126+ Just $ DataType name (map tyVarBndrName tvs) preds (concatMap conToDataCons cons)
127+#if MIN_VERSION_template_haskell(2,11,0)
128+ TyConI (NewtypeD preds name tvs _kind con _deriving) ->
130+ TyConI (NewtypeD preds name tvs con _deriving) ->
132+ Just $ DataType name (map tyVarBndrName tvs) preds (conToDataCons con)
135+--------------------------------------------------------------------------------
137+--------------------------------------------------------------------------------
139+type Field = (Maybe Name, Type)
147+_initialOffset :: Name
148+_initialOffset = mkName "initialOffset"
153+mkOffsetName :: Int -> Name
154+mkOffsetName i = mkName ("offset" ++ show i)
156+mkFieldName :: Int -> Name
157+mkFieldName i = mkName ("field" ++ show i)
159+--------------------------------------------------------------------------------
160+-- Domain specific helpers
161+--------------------------------------------------------------------------------
163+exprGetSize :: Type -> Q Exp
164+exprGetSize ty = appE (varE 'sizeOf) [|Proxy :: Proxy $(pure ty)|]
166+getTagSize :: Int -> Int
167+getTagSize numConstructors
168+ | numConstructors == 1 = 0
169+ | fromIntegral (maxBound :: Word8) >= numConstructors = 1
170+ | fromIntegral (maxBound :: Word16) >= numConstructors = 2
171+ | fromIntegral (maxBound :: Word32) >= numConstructors = 4
172+ | fromIntegral (maxBound :: Word64) >= numConstructors = 8
173+ | otherwise = error "Too many constructors"
175+getTagType :: Int -> Name
176+getTagType numConstructors
177+ | numConstructors == 1 = error "No tag for 1 constructor"
178+ | fromIntegral (maxBound :: Word8) >= numConstructors = ''Word8
179+ | fromIntegral (maxBound :: Word16) >= numConstructors = ''Word16
180+ | fromIntegral (maxBound :: Word32) >= numConstructors = ''Word32
181+ | fromIntegral (maxBound :: Word64) >= numConstructors = ''Word64
182+ | otherwise = error "Too many constructors"
184+mkOffsetDecls :: Int -> [Field] -> [Q Dec]
185+mkOffsetDecls tagSize fields =
188+ (varP (mkOffsetName 0))
190+ [|$(litE (IntegerL (fromIntegral tagSize))) +
191+ $(varE _initialOffset)|])
193+ (map mkOffsetExpr (zip [1 ..] fields)))
197+ mkOffsetExpr (i, (_, ty)) =
199+ (varP (mkOffsetName i))
200+ (normalB [|$(varE (mkOffsetName (i - 1))) + $(exprGetSize ty)|])
203+--------------------------------------------------------------------------------
205+--------------------------------------------------------------------------------
207+isUnitType :: [DataCon] -> Bool
208+isUnitType [DataCon _ _ _ []] = True
211+mkSizeOfExpr :: Type -> [DataCon] -> Q Exp
212+mkSizeOfExpr headTy constructors =
213+ case constructors of
216+ ("Attempting to get size with no constructors (" ++
217+ $(lift (pprint headTy)) ++ ")")|]
218+ -- One constructor with no fields is a unit type. Size of a unit type is
220+ [con@(DataCon _ _ _ fields)] ->
222+ [] -> litE (IntegerL 1)
223+ _ -> [|$(sizeOfConstructor con)|]
224+ _ -> [|$(litE (IntegerL (fromIntegral tagSize))) + $(sizeOfHeadDt)|]
228+ tagSize = getTagSize (length constructors)
229+ sizeOfField (_, ty) = exprGetSize ty
230+ sizeOfConstructor (DataCon _ _ _ fields) =
231+ appE (varE 'sum) (listE (map sizeOfField fields))
232+ -- The size of any Unbox type is atleast 1
234+ appE (varE 'maximum) (listE (map sizeOfConstructor constructors))
236+--------------------------------------------------------------------------------
238+--------------------------------------------------------------------------------
240+mkPeekExprOne :: Int -> DataCon -> Q Exp
241+mkPeekExprOne tagSize (DataCon cname _ _ fields) =
243+ [] -> [|pure $(conE cname)|]
246+ (mkOffsetDecls tagSize fields)
248+ (\acc i -> [|$(acc) <*> $(peekField i)|])
249+ [|$(conE cname) <$> $(peekField 0)|]
250+ [1 .. (length fields - 1)])
254+ peekField i = [|peekByteIndex $(varE (mkOffsetName i)) $(varE _arr)|]
256+mkPeekExpr :: Type -> [DataCon] -> Q Exp
257+mkPeekExpr headTy cons =
261+ ("Attempting to peek type with no constructors (" ++
262+ $(lift (pprint headTy)) ++ ")")|]
263+ [con] -> mkPeekExprOne 0 con
268+ [|peekByteIndex $(varE _initialOffset) $(varE _arr)|]
271+ (sigE (varE _tag) (conT tagType))
272+ (map peekMatch (zip [0 ..] cons) ++ [peekErr]))
277+ lenCons = length cons
278+ tagType = getTagType lenCons
279+ tagSize = getTagSize lenCons
280+ peekMatch (i, con) =
281+ match (litP (IntegerL i)) (normalB (mkPeekExprOne tagSize con)) []
287+ ("Found invalid tag while peeking (" ++
288+ $(lift (pprint headTy)) ++ ")")|])
291+--------------------------------------------------------------------------------
293+--------------------------------------------------------------------------------
295+mkPokeExprTag :: Name -> Int -> Q Exp
296+mkPokeExprTag tagType tagVal = pokeTag
302+ $(varE _initialOffset)
304+ $((sigE (litE (IntegerL (fromIntegral tagVal))) (conT tagType)))|]
306+mkPokeExprFields :: Int -> [Field] -> Q Exp
307+mkPokeExprFields tagSize fields = do
312+ (mkOffsetDecls tagSize fields)
313+ (doE $ map (noBindS . pokeField) [0 .. (numFields - 1)])
317+ numFields = length fields
320+ $(varE (mkOffsetName i))
322+ $(varE (mkFieldName i))|]
324+mkPokeMatch :: Name -> Int -> Q Exp -> Q Match
325+mkPokeMatch cname numFields exp0 =
327+ (conP cname (map varP (map mkFieldName [0 .. (numFields - 1)])))
331+mkPokeExpr :: Type -> [DataCon] -> Q Exp
332+mkPokeExpr headTy cons =
336+ ("Attempting to poke type with no constructors (" ++
337+ $(lift (pprint headTy)) ++ ")")|]
338+ [(DataCon _ _ _ [])] -> [|pure ()|]
339+ [(DataCon cname _ _ fields)] ->
342+ [mkPokeMatch cname (length fields) (mkPokeExprFields 0 fields)]
346+ (map (\(tagVal, (DataCon cname _ _ fields)) ->
350+ (doE [ noBindS $ mkPokeExprTag tagType tagVal
351+ , noBindS $ mkPokeExprFields tagSize fields
357+ lenCons = length cons
358+ tagType = getTagType lenCons
359+ tagSize = getTagSize lenCons
361+--------------------------------------------------------------------------------
363+--------------------------------------------------------------------------------
365+-- | A general function to derive Unbox instances where you can control which
366+-- Constructors of the datatype to consider and what the Context for the 'Unbox'
367+-- instance would be.
369+-- Consider the datatype:
371+-- data CustomDataType a b
373+-- | CDTConstructor2 Bool
374+-- | CDTConstructor3 Bool b
375+-- deriving (Show, Eq)
380+-- $(deriveUnboxInternal
381+-- [AppT (ConT ''Unbox) (VarT (mkName "b"))]
383+-- (AppT (ConT ''CustomDataType) (VarT (mkName "a")))
384+-- (VarT (mkName "b")))
385+-- [ DataCon 'CDTConstructor1 [] [] []
386+-- , DataCon 'CDTConstructor2 [] [] [(Nothing, (ConT ''Bool))]
391+-- [(Nothing, (ConT ''Bool)), (Nothing, (VarT (mkName "b")))]
394+deriveUnboxInternal :: Cxt -> Type -> [DataCon] -> Q [Dec]
395+deriveUnboxInternal preds headTy cons = do
396+ sizeOfMethod <- mkSizeOfExpr headTy cons
397+ peekMethod <- mkPeekExpr headTy cons
398+ pokeMethod <- mkPokeExpr headTy cons
400+ [ FunD 'sizeOf [Clause [WildP] (NormalB sizeOfMethod) []]
406+ else [VarP _initialOffset, VarP _arr])
407+ (NormalB peekMethod)
414+ then [WildP, WildP, WildP]
415+ else [VarP _initialOffset, VarP _arr, VarP _val])
416+ (NormalB pokeMethod)
420+ return [plainInstanceD preds (AppT (ConT ''Unbox) headTy) methods]
422+-- | Template haskell helper to create instances of 'Unbox' automatically.
424+-- Consider the datatype:
426+-- data CustomDataType a b c = ...
429+-- Usage: @$(deriveUnbox ''CustomDataType)@
431+-- Note: All type variables automatcally get an "Unbox" constraint.
432+-- The derived code will look like the following,
434+-- instance (Unbox a, Unbox b, Unbox c) => Unbox (CustomDataType a b c) where
438+-- To control which type variables get the Unbox constraint, use
439+-- 'deriveUnboxWith'.
440+deriveUnbox :: Name -> Q [Dec]
441+deriveUnbox name = do
442+ dt <- reifyDataType name
443+ let preds = map (unboxPred . VarT) (dtTvs dt)
444+ headTy = appsT (ConT name) (map VarT (dtTvs dt))
446+ deriveUnboxInternal preds headTy cons
451+#if MIN_VERSION_template_haskell(2,10,0)
452+ AppT (ConT ''Unbox) ty
457+-- | Like 'deriveUnbox' but control which type variables get the 'Unbox'
460+-- Consider the datatype:
462+-- data CustomDataType a b c = ...
465+-- Usage: @$(deriveUnboxWith ["a", "c"] ''CustomDataType)@
468+-- instance (Unbox a, Unbox c) => Unbox (CustomDataType a b c) where
472+deriveUnboxWith :: [String] -> Name -> Q [Dec]
473+deriveUnboxWith vars name = do
474+ dt <- reifyDataType name
475+ let preds = map (unboxPred . VarT) (fmap mkName vars)
476+ headTy = appsT (ConT name) (map VarT (dtTvs dt))
478+ deriveUnboxInternal preds headTy cons
483+#if MIN_VERSION_template_haskell(2,10,0)
484+ AppT (ConT ''Unbox) ty