packages feed

sdl3-bindgen-sys-0.0.0.1: runtime/HsBindgen/Runtime/Marshal.hs

-- | Marshaling and serialization
--
-- Generalizes 'Storable'. For details, see
-- https://github.com/well-typed/hs-bindgen/issues/649.
--
-- This module is intended to be imported qualified.
--
-- > import HsBindgen.Runtime.Prelude
-- > import HsBindgen.Runtime.Marshal qualified as Marshal
module HsBindgen.Runtime.Marshal (
  -- * Type Classes
  StaticSize (..),
  ReadRaw (..),
  WriteRaw (..),
  EquivStorable (..),

  -- * Utility Functions
  readRawByteOff,
  writeRawByteOff,
  readRawElemOff,
  writeRawElemOff,
  maybeReadRaw,
  with,
  withZero,
  new,
  newZero,
) where

import Data.Complex (Complex ((:+)))
import Data.Int (Int16, Int32, Int64, Int8)
import Data.Proxy (Proxy (Proxy))
import Data.Word (Word16, Word32, Word64, Word8)
import Foreign.C qualified as C
import Foreign.ForeignPtr (ForeignPtr, withForeignPtr)
import Foreign.Marshal.Alloc qualified as Alloc
import Foreign.Marshal.Utils qualified as Utils
import Foreign.Ptr (FunPtr, Ptr)
import Foreign.Ptr qualified as Ptr
import Foreign.StablePtr (StablePtr)
import Foreign.Storable (Storable)
import Foreign.Storable qualified as Storable
import GHC.ForeignPtr (mallocForeignPtrAlignedBytes)

import HsBindgen.Runtime.PtrConst (PtrConst)

{-------------------------------------------------------------------------------
  Type Classes
-------------------------------------------------------------------------------}

-- | Size and alignment for values that have a static size in memory
--
-- Types that are instances of 'Storable' can derive this instance.
class StaticSize a where
  -- | Storage requirements (bytes)
  staticSizeOf :: Proxy a -> Int
  default staticSizeOf :: (Storable a) => Proxy a -> Int
  staticSizeOf _proxy = Storable.sizeOf @a undefined

  -- | Alignment (bytes)
  staticAlignment :: Proxy a -> Int
  default staticAlignment :: (Storable a) => Proxy a -> Int
  staticAlignment _proxy = Storable.alignment @a undefined

-- | Values that can be read from memory
--
-- Types that are instances of 'Storable' can derive this instance.
class ReadRaw a where
  -- | Read a value from the given memory location
  --
  -- This function might require a properly aligned address to function
  -- correctly, depending on the architecture.
  readRaw :: Ptr a -> IO a
  default readRaw :: (Storable a) => Ptr a -> IO a
  readRaw = Storable.peek

-- | Values that can be written to memory
--
-- Types that are instances of 'Storable' can derive this instance.
class WriteRaw a where
  -- | Write a value to the given memory location
  --
  -- This function might require a properly aligned address to function
  -- correctly, depending on the architecture.
  writeRaw :: Ptr a -> a -> IO ()
  default writeRaw :: (Storable a) => Ptr a -> a -> IO ()
  writeRaw = Storable.poke

-- | Type used to derive a 'Storable' instance when the type has 'StaticSize',
-- 'ReadRaw', and 'WriteRaw' instances
--
-- Use the @DerivingVia@ GHC extension as follows:
--
-- @
-- {-# LANGUAGE DerivingVia #-}
--
-- data Foo = Foo { ... }
--   deriving Storable via EquivStorable Foo
-- @
newtype EquivStorable a = EquivStorable a

instance
  (ReadRaw a, StaticSize a, WriteRaw a)
  => Storable (EquivStorable a)
  where
  sizeOf _ = staticSizeOf @a undefined
  alignment _ = staticAlignment @a undefined

  peek ptr = EquivStorable <$> readRaw (Ptr.castPtr ptr)

  poke ptr (EquivStorable x) = writeRaw (Ptr.castPtr ptr) x

{-------------------------------------------------------------------------------
  Utility Functions
-------------------------------------------------------------------------------}

-- | Read a value from the given memory location, given by a base address and an
-- offset
readRawByteOff :: (ReadRaw a) => Ptr b -> Int -> IO a
readRawByteOff ptr off = readRaw (ptr `Ptr.plusPtr` off)

-- | Write a value to the given memory location, given by a base address and an
-- offset
writeRawByteOff :: (WriteRaw a) => Ptr b -> Int -> a -> IO ()
writeRawByteOff ptr off = writeRaw (ptr `Ptr.plusPtr` off)

-- | Read a value from a memory area regarded as an array of values of the same
-- kind
--
-- The first argument specifies the start address of the array.  The second
-- specifies the (zero-based) index into the array.
readRawElemOff :: forall a. (ReadRaw a, StaticSize a) => Ptr a -> Int -> IO a
readRawElemOff ptr off = readRawByteOff ptr $ off * staticSizeOf @a Proxy

-- | Write a value to a memory area regarded as an array of values of the same
-- kind
--
-- The first argument specifies the start address of the array.  The second
-- specifies the (zero-based) index into the array.
writeRawElemOff
  :: forall a
   . (StaticSize a, WriteRaw a)
  => Ptr a
  -> Int
  -> a
  -> IO ()
writeRawElemOff ptr off = writeRawByteOff ptr $ off * staticSizeOf @a Proxy

-- | Read a value from memory when passed a non-null pointer
maybeReadRaw :: (ReadRaw a) => Ptr a -> IO (Maybe a)
maybeReadRaw ptr
  | ptr == Ptr.nullPtr = return Nothing
  | otherwise = Just <$> readRaw ptr

-- | Allocate local memory, write the specified value, and call a function with
-- the pointer
--
-- The allocated memory is aligned.
--
-- Memory that is not written to by 'Storable.poke' may contain arbitrary data.
--
-- The allocated memory is freed when the function terminates, either normally
-- or via an exception.  The passed pointer must therefore /not/ be used after
-- this.
with
  :: forall a b
   . (StaticSize a, WriteRaw a)
  => a
  -> (Ptr a -> IO b)
  -> IO b
with x f = Alloc.allocaBytesAligned size align $ \ptr -> do
  writeRaw ptr x
  f ptr
 where
  size, align :: Int
  size = staticSizeOf @a Proxy
  align = staticAlignment @a Proxy

-- | Allocate local memory, write the specified value, and call a function with
-- the pointer
--
-- The allocated memory is aligned.
--
-- The memory is filled with bytes of value zero before the value is written.
-- Memory that is not written to by 'Storable.poke' contains zeros, not
-- arbitrary data.
--
-- The allocated memory is freed when the function terminates, either normally
-- or via an exception.  The passed pointer must therefore /not/ be used after
-- this.
withZero
  :: forall a b
   . (StaticSize a, WriteRaw a)
  => a
  -> (Ptr a -> IO b)
  -> IO b
withZero x f = Alloc.allocaBytesAligned size align $ \ptr -> do
  Utils.fillBytes ptr 0 size
  writeRaw ptr x
  f ptr
 where
  size, align :: Int
  size = staticSizeOf @a Proxy
  align = staticAlignment @a Proxy

-- | Allocate memory, write the specified value, and the 'ForeignPtr'
--
-- The allocated memory is aligned.
--
-- Memory that is not written to by 'writeRaw' may contain arbitrary data.
new
  :: forall a
   . (StaticSize a, WriteRaw a)
  => a
  -> IO (ForeignPtr a)
new x = do
  fptr <- mallocForeignPtrAlignedBytes size align
  withForeignPtr fptr $ \ptr -> writeRaw ptr x
  return fptr
 where
  size, align :: Int
  size = staticSizeOf @a Proxy
  align = staticAlignment @a Proxy

-- | Allocate memory, write the specified value, and the 'ForeignPtr'
--
-- The allocated memory is aligned.
--
-- The memory is filled with bytes of value zero before the value is written.
-- Memory that is not written to by 'writeRaw' contains zeros, not arbitrary
-- data.
newZero
  :: forall a
   . (StaticSize a, WriteRaw a)
  => a
  -> IO (ForeignPtr a)
newZero x = do
  fptr <- mallocForeignPtrAlignedBytes size align
  withForeignPtr fptr $ \ptr -> do
    Utils.fillBytes ptr 0 size
    writeRaw ptr x
  return fptr
 where
  size, align :: Int
  size = staticSizeOf @a Proxy
  align = staticAlignment @a Proxy

{-------------------------------------------------------------------------------
  Instances
-------------------------------------------------------------------------------}

instance StaticSize C.CChar
instance ReadRaw C.CChar
instance WriteRaw C.CChar

instance StaticSize C.CSChar
instance ReadRaw C.CSChar
instance WriteRaw C.CSChar

instance StaticSize C.CUChar
instance ReadRaw C.CUChar
instance WriteRaw C.CUChar

instance StaticSize C.CShort
instance ReadRaw C.CShort
instance WriteRaw C.CShort

instance StaticSize C.CUShort
instance ReadRaw C.CUShort
instance WriteRaw C.CUShort

instance StaticSize C.CInt
instance ReadRaw C.CInt
instance WriteRaw C.CInt

instance StaticSize C.CUInt
instance ReadRaw C.CUInt
instance WriteRaw C.CUInt

instance StaticSize C.CLong
instance ReadRaw C.CLong
instance WriteRaw C.CLong

instance StaticSize C.CULong
instance ReadRaw C.CULong
instance WriteRaw C.CULong

instance StaticSize C.CPtrdiff
instance ReadRaw C.CPtrdiff
instance WriteRaw C.CPtrdiff

instance StaticSize C.CSize
instance ReadRaw C.CSize
instance WriteRaw C.CSize

instance StaticSize C.CWchar
instance ReadRaw C.CWchar
instance WriteRaw C.CWchar

instance StaticSize C.CSigAtomic
instance ReadRaw C.CSigAtomic
instance WriteRaw C.CSigAtomic

instance StaticSize C.CLLong
instance ReadRaw C.CLLong
instance WriteRaw C.CLLong

instance StaticSize C.CULLong
instance ReadRaw C.CULLong
instance WriteRaw C.CULLong

instance StaticSize C.CBool
instance ReadRaw C.CBool
instance WriteRaw C.CBool

instance StaticSize C.CIntPtr
instance ReadRaw C.CIntPtr
instance WriteRaw C.CIntPtr

instance StaticSize C.CUIntPtr
instance ReadRaw C.CUIntPtr
instance WriteRaw C.CUIntPtr

instance StaticSize C.CIntMax
instance ReadRaw C.CIntMax
instance WriteRaw C.CIntMax

instance StaticSize C.CUIntMax
instance ReadRaw C.CUIntMax
instance WriteRaw C.CUIntMax

instance StaticSize C.CClock
instance ReadRaw C.CClock
instance WriteRaw C.CClock

instance StaticSize C.CTime
instance ReadRaw C.CTime
instance WriteRaw C.CTime

instance StaticSize C.CUSeconds
instance ReadRaw C.CUSeconds
instance WriteRaw C.CUSeconds

instance StaticSize C.CSUSeconds
instance ReadRaw C.CSUSeconds
instance WriteRaw C.CSUSeconds

instance StaticSize C.CFloat
instance ReadRaw C.CFloat
instance WriteRaw C.CFloat

instance StaticSize C.CDouble
instance ReadRaw C.CDouble
instance WriteRaw C.CDouble

instance StaticSize (Ptr a)
instance ReadRaw (Ptr a)
instance WriteRaw (Ptr a)

instance StaticSize (PtrConst a)
instance ReadRaw (PtrConst a)
instance WriteRaw (PtrConst a)

instance StaticSize (FunPtr a)
instance ReadRaw (FunPtr a)
instance WriteRaw (FunPtr a)

instance StaticSize (StablePtr a)
instance ReadRaw (StablePtr a)
instance WriteRaw (StablePtr a)

instance StaticSize Int8
instance ReadRaw Int8
instance WriteRaw Int8

instance StaticSize Int16
instance ReadRaw Int16
instance WriteRaw Int16

instance StaticSize Int32
instance ReadRaw Int32
instance WriteRaw Int32

instance StaticSize Int64
instance ReadRaw Int64
instance WriteRaw Int64

instance StaticSize Word8
instance ReadRaw Word8
instance WriteRaw Word8

instance StaticSize Word16
instance ReadRaw Word16
instance WriteRaw Word16

instance StaticSize Word32
instance ReadRaw Word32
instance WriteRaw Word32

instance StaticSize Word64
instance ReadRaw Word64
instance WriteRaw Word64

instance StaticSize Int
instance ReadRaw Int
instance WriteRaw Int

instance StaticSize Word
instance ReadRaw Word
instance WriteRaw Word

instance StaticSize Float
instance ReadRaw Float
instance WriteRaw Float

instance StaticSize Double
instance ReadRaw Double
instance WriteRaw Double

instance StaticSize Char
instance ReadRaw Char
instance WriteRaw Char

instance StaticSize Bool
instance ReadRaw Bool
instance WriteRaw Bool

instance StaticSize ()
instance ReadRaw ()
instance WriteRaw ()

--------------------------------------------------------------------------------

-- The instances for 'Complex' follow the 'Storable' instance.  It is rewritten
-- so that they only rely on the classes defined in this module, not 'Storable'.

instance (StaticSize a) => StaticSize (Complex a) where
  staticSizeOf _ = 2 * staticSizeOf (Proxy @a)
  staticAlignment _ = staticAlignment (Proxy @a)

instance (ReadRaw a, StaticSize a) => ReadRaw (Complex a) where
  readRaw ptrComplex =
    let ptrPart = Ptr.castPtr ptrComplex
     in (:+) <$> readRaw ptrPart <*> readRawElemOff ptrPart 1

instance (StaticSize a, WriteRaw a) => WriteRaw (Complex a) where
  writeRaw ptrComplex (r :+ i) = do
    let ptrPart = Ptr.castPtr ptrComplex
    writeRaw ptrPart r
    writeRawElemOff ptrPart 1 i