packages feed

heph-aligned-storable-0.1.0.0: src/Foreign/GPU/Marshal/Aligned.hs

{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RoleAnnotations #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE NoMonomorphismRestriction #-}

-- |
-- Description : Zero-initialized allocation for GPU-aligned types
-- Copyright   : (c) Jeremy Nuttall, 2025
-- License     : BSD-3-Clause
-- Maintainer  : jeremy@jeremy-nuttall.com
-- Stability   : experimental
--
-- Allocation helpers with guaranteed zero-initialized padding.
-- Use these instead of 'alloca' or 'malloc' to avoid garbage in padding bytes.
module Foreign.GPU.Marshal.Aligned (
  -- * Utilities for 'Packed' values
  PackedPtr,
  withPacked,
  allocaPacked,

  -- * Utilities for 'Strided' values
  StridedPtr,
  withStrided,
  allocaStrided,

  -- * Utilities for runtime length arrays
  alignedCopyVector,
) where

import qualified Data.Vector.Storable as SV
import UnliftIO (MonadIO, MonadUnliftIO, liftIO)
import UnliftIO.Foreign

import Foreign.GPU.Storable.Aligned

-- | Convenience type for 'Packed' 'AlignedPtr'
type PackedPtr layout a = AlignedPtr layout (Packed layout a)

-- | Temporarily allocates a zero-initialized block of memory and pokes a
-- 'Packed' value into it, providing a pointer to the result.
-- The storage is freed automatically. The pointer is only valid within the continuation.
withPacked
  :: forall layout a m b
   . (MonadUnliftIO m, AlignedStorable layout a)
  => a
  -> (PackedPtr layout a -> m b)
  -> m b
withPacked a f = withZeroed (Packed a) (f . AlignedPtr)
{-# INLINEABLE withPacked #-}

-- | Allocates temporary, zero-initialized storage for a 'Packed' value on the stack.
-- The pointer is only valid within the continuation.
allocaPacked
  :: forall layout a b m
   . (MonadUnliftIO m, AlignedStorable layout a)
  => (PackedPtr layout a -> m b)
  -> m b
allocaPacked f = allocaZeroed (f . AlignedPtr)
{-# INLINEABLE allocaPacked #-}

-- | Convenience type for 'Strided' 'AlignedPtr'
type StridedPtr layout a = AlignedPtr layout (Strided layout a)

-- | Temporarily allocates a zero-initialized block of memory and pokes a
-- 'Strided' value into it, providing a pointer to the result.
-- The storage is freed automatically. The pointer is only valid within the continuation.
withStrided
  :: (MonadUnliftIO m, AlignedStorable layout a) => a -> (StridedPtr layout a -> m b) -> m b
withStrided a f = withZeroed (Strided a) (f . AlignedPtr)
{-# INLINEABLE withStrided #-}

-- | Allocates temporary, zero-initialized storage for a 'Strided' value on the stack.
-- The pointer is only valid within the continuation.
allocaStrided :: (MonadUnliftIO m, AlignedStorable layout a) => (StridedPtr layout a -> m b) -> m b
allocaStrided f = allocaZeroed (f . AlignedPtr)
{-# INLINEABLE allocaStrided #-}

-- | Performs a straight 'copyBytes' on the underlying pointer of 'SV.Vector'
alignedCopyVector
  :: forall layout a m
   . (MonadIO m, AlignedStorable layout a)
  => AlignedPtr layout a
  -> SV.Vector (Strided layout a)
  -> m ()
alignedCopyVector (AlignedPtr dest) v = liftIO $ SV.unsafeWith v \src ->
  copyBytes
    dest
    (castPtr src)
    (sizeOf @(Strided layout a) undefined * SV.length v)
{-# INLINE alignedCopyVector #-}

--------------------------------------------------------------------------------
-- Utilities
--------------------------------------------------------------------------------

zeroPtr :: forall a m. (MonadIO m, Storable a) => Ptr a -> m ()
zeroPtr ptr = fillBytes ptr 0 (sizeOf @a undefined)
{-# INLINE zeroPtr #-}

allocaZeroed :: (MonadUnliftIO m, Storable a) => (Ptr a -> m b) -> m b
allocaZeroed f = alloca \ptr -> zeroPtr ptr >> f ptr
{-# INLINE allocaZeroed #-}

withZeroed :: (MonadUnliftIO m, Storable a) => a -> (Ptr a -> m b) -> m b
withZeroed a f = allocaZeroed \ptr -> do
  liftIO $ poke ptr a
  f ptr
{-# INLINE withZeroed #-}