packages feed

fb-util-0.1.0.1: Foreign/CPP/HsStruct/HsStdTuple.hs

{-
  Copyright (c) Meta Platforms, Inc. and affiliates.
  All rights reserved.

  This source code is licensed under the BSD-style license found in the
  LICENSE file in the root directory of this source tree.
-}

{-# LANGUAGE TemplateHaskell #-}
module Foreign.CPP.HsStruct.HsStdTuple
  ( deriveHsStdTupleUnsafe
  , HsStdTuple(..)
  ) where

import Data.List (foldl')
import Control.Monad (forM, unless)
import Foreign
import Language.Haskell.TH

import Foreign.CPP.Addressable
import Foreign.CPP.Marshallable


newtype HsStdTuple a = HsStdTuple { unHsStdTuple :: a }

deriveHsStdTupleUnsafe
  :: String -> Int -> Int -> TypeQ -> Q [Dec]
deriveHsStdTupleUnsafe cppType sizeVal alignmentVal hsType = do
  hsStdTupleType <- [t| HsStdTuple |]
  hsTRaw <- hsType
  let
    (hsT, tupleType) = case hsTRaw of
      (AppT a b) | a == hsStdTupleType -> (b, hsType)
      _ -> (hsTRaw, [t| HsStdTuple $hsType |])
    (numTypes, hsTypes) = unfoldTupleT hsT

  unless (numTypes > 0) $
    fail "StdTuple must be a tuple with 1 or more underlying types"

  hsTypesDestructible <-
    forM hsTypes $ \t -> isInstance (mkName "Destructible") [t]

  addressableInst <- instanceD
    (cxt [])
    [t| Addressable $tupleType |]
    []

  let
    sizeOfFn = funD (mkName "sizeOf")
      [ clause [wildP] (normalB $ litE $ integerL $ fromIntegral sizeVal) [] ]

    alignmentFn = funD (mkName "alignment")
      [ clause [wildP] (normalB $
          litE $ integerL $ fromIntegral alignmentVal) []
      ]

    ptrN = mkName "ptr"
    pNames = take numTypes $ idxNames "p"
    vNames = take numTypes $ idxNames "v"
    pvZipped = zip pNames vNames
    pCasted = map (\p -> parensE $ varE castPtrN `appE` varE p) pNames

    hsPokeName = mkName $ "c_poke_" ++ cppName
    cppPokeName = "std_tuple_poke_" ++ cppName

    nestedPokeFns = nestPokeFns (zip3 vNames pNames hsTypesDestructible) $ doE
      -- c_poke* FFI call
      [ noBindS (foldl' appE
          (varE hsPokeName `appE` [| castPtr ptr|] `appE` [|nullPtr|])
          pCasted
        )
      ]

    pokeFn = funD (mkName "poke")
      [ clause [varP ptrN, conP (mkName "HsStdTuple") [tupP (map varP vNames)]]
          (normalB nestedPokeFns) []
      ]

    hsPeekName = mkName $ "c_peek_" ++ cppName
    cppPeekName = "std_tuple_peek_" ++ cppName

    nestedPeekFns = nestPeekFns (zip pNames hsTypesDestructible) $
      doE $
        -- c_peek* FFI call
        [noBindS (foldl' appE
            (varE hsPeekName `appE` [| castPtr ptr|] `appE` [|nullPtr|])
            pCasted
          )
        ] ++
        -- N peeks
        map (\(p, v) -> bindS (varP v) (varE (mkName "peek") `appE` varE p))
          pvZipped ++
        -- tuple construction
        [noBindS (varE (mkName "return") `appE`
          parensE (conE (mkName "HsStdTuple") `appE` tupE (map varE vNames)))
        ]

    peekFn = funD (mkName "peek")
      [ clause [varP ptrN] (normalB nestedPeekFns) []
      ]

  storableInst <- instanceD
    (cxt [])
    [t| Storable $tupleType |]
    [sizeOfFn, alignmentFn, pokeFn, peekFn]

  peekImport <- forImpD cCall unsafe cppPeekName hsPeekName $
    -- Ptr () -> Ptr () -> <<< all tuple types >>> -> IO ()
    foldl' (\b a -> [t| $a -> $b |]) [t| IO () |] $
      map (\a -> [t| Ptr $a |]) ([t|()|] : [t| ()|] : map return hsTypes)

  pokeImport <- forImpD cCall unsafe cppPokeName hsPokeName $
    -- Ptr () -> Ptr () -> <<< all tuple types >>> -> IO ()
    foldl' (\b a -> [t| $a -> $b |]) [t| IO () |] $
      map (\a -> [t| Ptr $a |]) ([t|()|] : [t| ()|] : map return hsTypes)

  constructibleInst <- instanceD
    (cxt [])
    [t| Constructible $tupleType |]
    []

  return
    [ addressableInst
    , storableInst
    , constructibleInst
    , peekImport
    , pokeImport
    ]
  where
    unfoldTupleT :: Type -> (Int, [Type])
    unfoldTupleT (AppT a b) = let
      (i, ts) = unfoldTupleT a
      in (i, ts ++ [b])
    unfoldTupleT (TupleT i) = (i, [])
    unfoldTupleT n = (1, [n])

    castPtrN = mkName "castPtr"
    cppName = cppType

    idxNames c = map (\i -> mkName (c ++ show i)) [0::Int ..]

    nestPeekFns :: [(Name, Bool)] -> ExpQ -> ExpQ
    nestPeekFns [] base = base
    nestPeekFns ((p, isDestructible):ts) base =
      varE (mkName allocFn) `appE` lamE [varP p] (nestPeekFns ts base)
      where
        allocFn = if isDestructible then "withDefaultCxxObject" else "alloca"

    nestPokeFns :: [(Name, Name, Bool)] -> ExpQ -> ExpQ
    nestPokeFns [] base = base
    nestPokeFns ((v, p, isDestructible):ts) base =
      varE (mkName withObjFn) `appE` varE v `appE`
        lamE [varP p] (nestPokeFns ts base)
      where
        withObjFn = if isDestructible then "withCxxObject" else "with"