packages feed

libclang-bindings-0.1.0.0: src/Clang/Internal/ByValue.hs

{-# LANGUAGE AllowAmbiguousTypes #-}

-- | Dealing with structs-by-value
module Clang.Internal.ByValue (
    OnHaskellHeap(..)
    -- * Construction
  , HasKnownSize(..)
  , copyToHaskellHeap
    -- * Access
  , R(..)
  , LivesOnHaskellHeap -- opaque
  , onHaskellHeap
    -- * Preallocation
  , W(..)
  , Preallocate(..)
  , preallocate_
  , preallocatePair
  , preallocatePair_
    -- * Arrays of values
  , ArrOnHaskellHeap(..)
  , preallocateArray
  , indexArrOnHaskellHeap
  ) where

import Data.Array.Byte (ByteArray (..))
import Foreign
import GHC.Exts
import GHC.IO

{-------------------------------------------------------------------------------
  Definition
-------------------------------------------------------------------------------}

data OnHaskellHeap tag = OnHaskellHeap ByteArray#

instance Eq (OnHaskellHeap tag) where
  OnHaskellHeap a == OnHaskellHeap b = ByteArray a == ByteArray b

instance Ord (OnHaskellHeap tag) where
  compare (OnHaskellHeap a) (OnHaskellHeap b) = compare (ByteArray a) (ByteArray b)

instance Show (OnHaskellHeap tag) where
  showsPrec d (OnHaskellHeap a) = showsPrec d (ByteArray a)

{-------------------------------------------------------------------------------
  Construction
-------------------------------------------------------------------------------}

-- | Structs with known size
--
-- Intended for use with a type argument:
--
-- > knownSize @CXToken_
class HasKnownSize tag where
  knownSize :: Int

copyToHaskellHeap :: forall tag.
     HasKnownSize tag
  => Ptr tag -> IO (OnHaskellHeap tag)
copyToHaskellHeap src = fmap fst $
    mkByteArray (knownSize @tag) OnHaskellHeap $ \arr -> do
      let dest :: Ptr tag
          dest = Ptr (mutableByteArrayContents# arr)
      copyBytes dest src (knownSize @tag)

{-------------------------------------------------------------------------------
  Access
-------------------------------------------------------------------------------}

-- | A read-only byte array
--
-- This type is used to hold the bytes of a C struct that is passed by value as
-- an argument to a C function through the Haskell FFI.
--
-- === Example
--
-- Let's say we want to generate Haskell bindings for this C code:
--
-- > struct S { int x; };
-- > void foo (struct S x);
--
-- The Haskell FFI does not support passing structs by value, so we generate a C
-- wrapper function that instead takes the struct argument by a pointer,
-- dereferences the pointer, and passes the struct by-value on to the original
-- @foo@.
--
-- > void foo_wrapper (struct S * x) { foo(*x); };
--
-- For the struct, we create a Haskell datatype. We add a foreign import that
-- binds to the C wrapper function, using 'R'.
--
-- > data S {-# CType "struct S" #-} = S { x :: CInt }
-- > foreign import capi unsafe "foo_wrapper" foo :: R S -> IO ()
--
-- We can use 'R' here even though the wrapper function takes a struct pointer,
-- not a struct value. Any value of type 'R' is passed through the Haskell FFI
-- as a pointer to the array payload because 'R' is an unlifted FFI type.
--
-- <https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/ffi.html#unlifted-ffi-types>
type R :: k -> UnliftedType
newtype R tag = R ByteArray#

-- | Heap-allocated structs
--
-- The definition of this class is not exported; instances are expected to be
-- derived using newtype deriving.
class LivesOnHaskellHeap a where
  type Reading a :: UnliftedType

  -- | Get a pointer to a heap-allocated struct
  --
  -- This essentially just unwraps the lifted 'OnHaskellHeap' type, but then
  -- rewrapping it as the unlifted 'R' newtype, to avoid losing type info.
  onHaskellHeap :: a -> (Reading a -> IO r) -> IO r

instance LivesOnHaskellHeap (OnHaskellHeap tag) where
  type Reading (OnHaskellHeap tag) = R tag
  onHaskellHeap (OnHaskellHeap arr) f = f (R arr)

{-------------------------------------------------------------------------------
  Preallocation
-------------------------------------------------------------------------------}

-- | A read-write byte array
--
-- This type is used to hold the bytes of a C struct that is passed by value as
-- a result from a C function through the Haskell FFI.
--
-- === Example
--
-- Let's say we want to generate Haskell bindings for this C code:
--
-- > struct S { int x; };
-- > struct S foo ();
--
-- The Haskell FFI does not support passing structs by value, so we generate a C
-- wrapper function that instead takes an extra struct argument pointer that is
-- used to hold the struct result value from calling the original @foo@.
--
-- > void foo_wrapper (struct S * result) { *result = foo(); };
--
-- For the struct, we create a Haskell datatype. We add a foreign import that
-- binds to the C wrapper function, using 'W'.
--
-- > data S {-# CType "struct S" #-} = S { x :: CInt }
-- > foreign import capi unsafe "foo_wrapper" foo :: W S -> IO ()
--
-- We can use 'W' here even though the wrapper function takes a struct pointer,
-- not a struct value. Any value of type 'W' is passed through the Haskell FFI
-- as a pointer to the array payload because 'W' is an unlifted FFI type.
-- Moreover, 'W' can be mutated by the C wrapper function, and the mutation will
-- be visible in Haskell-land as well.
--
-- <https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/ffi.html#unlifted-ffi-types>
type W :: k -> UnliftedType
newtype W tag = W (MutableByteArray# RealWorld)

-- | Preallocate a buffer
--
-- NOTE: Although we only define one instance of 'Preallocate' here, the
-- intention is that other instances are defined through newtype deriving, e.g.
--
-- > newtype CXType = CXType (OnHaskellHeap CXType_)
-- >   deriving newtype (LivesOnHaskellHeap, Preallocate, Show)
--
-- "Clang.Internal.CXString" also provides another instance.
class Preallocate a where
  type Writing a :: UnliftedType

  -- | Preallocate a buffer
  --
  -- See 'onHaskellHeap' for rationale.
  preallocate :: (Writing a -> IO r) -> IO (a, r)

preallocate_ :: Preallocate a => (Writing a -> IO ()) -> IO a
preallocate_ = fmap fst . preallocate

instance HasKnownSize tag => Preallocate (OnHaskellHeap tag) where
  type Writing (OnHaskellHeap tag) = W tag

  preallocate :: (W tag -> IO r) -> IO (OnHaskellHeap tag, r)
  preallocate f =
      mkByteArray (knownSize @tag) OnHaskellHeap $ \arr ->
        f (W arr)

-- | Preallocate two values
--
-- TODO <https://github.com/well-typed/libclang-bindings/issues/74>
--
-- It would be nice to generalize this, but I can't quite figure out how without
-- introducing a ton of machinery.
preallocatePair :: forall a b r.
     (Preallocate a, Preallocate b)
  => (Writing a -> Writing b -> IO r)
  -> IO ((a, b), r)
preallocatePair k = fmap reassoc $
    preallocate $ \wa ->
    preallocate $ \wb ->
      k wa wb
  where
    reassoc :: (a, (b, r)) -> ((a, b), r)
    reassoc (a, (b, r)) = ((a, b), r)

preallocatePair_ ::
     (Preallocate a, Preallocate b)
  => (Writing a -> Writing b -> IO ())
  -> IO (a, b)
preallocatePair_ = fmap fst . preallocatePair

{-------------------------------------------------------------------------------
  Arrays of values
-------------------------------------------------------------------------------}

data ArrOnHaskellHeap tag = ArrOnHaskellHeap ByteArray#

preallocateArray :: forall tag.
     HasKnownSize tag
  => Int
  -> (W tag -> IO ())
  -> IO (ArrOnHaskellHeap tag)
preallocateArray n k = fmap (\(a, ()) -> a) $
    mkByteArray (n * knownSize @tag) ArrOnHaskellHeap $ \arr ->
      k (W arr)

indexArrOnHaskellHeap :: forall tag.
     HasKnownSize tag
  => ArrOnHaskellHeap tag
  -> Int
  -> IO (OnHaskellHeap tag)
indexArrOnHaskellHeap (ArrOnHaskellHeap src) i = fmap (\(a, ()) -> a) $
    mkByteArray (knownSize @tag) OnHaskellHeap $ \dst ->
      copyByteArray src (i * knownSize @tag) dst 0 (knownSize @tag)

{-------------------------------------------------------------------------------
  Internal auxiliary
-------------------------------------------------------------------------------}

mkByteArray ::
     Int
  -> (ByteArray# -> a)
  -> (MutableByteArray# RealWorld -> IO b)
  -> IO (a, b)
mkByteArray (I# sz) wrap fill = IO $ \w0 ->
    let !(# w1, arr  #) = newPinnedByteArray# sz     w0
        !(# w2, b    #) = unIO (fill arr)            w1
        !(# w3, arr' #) = unsafeFreezeByteArray# arr w2
    in (# w3, (wrap arr', b) #)

copyByteArray ::
     ByteArray#                   -- ^ source
  -> Int                          -- ^ source offset
  -> MutableByteArray# RealWorld  -- ^ destination
  -> Int                          -- ^ destination offset
  -> Int                          -- ^ length
  -> IO ()
copyByteArray src (I# src_ofs) dst (I# dst_ofs) (I# len) = IO $ \w ->
    (# copyByteArray# src src_ofs dst dst_ofs len w, () #)