halide-haskell-0.0.2.0: src/Language/Halide/Buffer.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE UndecidableInstances #-}
-- |
-- Module : Language.Halide.Buffer
-- Description : Buffers
-- Copyright : (c) Tom Westerhout, 2021-2023
--
-- A buffer in Halide is a __view__ of some multidimensional array. Buffers can reference data that's
-- located on a CPU, GPU, or another device. Halide pipelines use buffers for both input and output arguments.
module Language.Halide.Buffer
( -- * Buffers
--
-- | In the C interface of Halide, buffers are described by the C struct
-- [@halide_buffer_t@](https://halide-lang.org/docs/structhalide__buffer__t.html). On the Haskell side,
-- we have 'HalideBuffer'.
HalideBuffer (..)
-- | To easily test out your pipeline, there are helper functions to create 'HalideBuffer's without
-- worrying about the low-level representation.
, allocaCpuBuffer
, allocaBuffer
-- | Buffers can also be converted to lists to easily print them for debugging.
, IsListPeek (..)
, peekScalar
-- | For production usage however, you don't want to work with lists. Instead, you probably want Halide
-- to work with your existing array data types. For this, we define 'IsHalideBuffer' typeclass that
-- teaches Halide how to convert your data into a 'HalideBuffer'. Depending on how you implement the
-- instance, this can be very efficient, because it need not involve any memory copying.
, IsHalideBuffer (..)
, withHalideBuffer
-- | There are also helper functions to simplify writing instances of 'IsHalideBuffer'.
, bufferFromPtrShapeStrides
, bufferFromPtrShape
-- * Internals
, RawHalideBuffer (..)
, HalideDimension (..)
, HalideDeviceInterface
, rowMajorStrides
, colMajorStrides
, isDeviceDirty
, isHostDirty
, getBufferExtent
, bufferCopyToHost
, withCopiedToHost
, withCropped
)
where
import Control.Exception (bracket_)
import Control.Monad (forM, unless, when)
import Control.Monad.ST (RealWorld)
import Data.Int
import Data.Kind (Type)
import Data.List qualified as List
import Data.Proxy
import Data.Vector.Storable qualified as S
import Data.Vector.Storable.Mutable qualified as SM
import Data.Word
import Foreign.Marshal.Alloc (alloca, free, mallocBytes)
import Foreign.Marshal.Array
import Foreign.Marshal.Utils
import Foreign.Ptr
import Foreign.Storable
import GHC.Stack (HasCallStack)
import GHC.TypeNats
import Language.C.Inline qualified as C
import Language.C.Inline.Cpp.Exception qualified as C
import Language.C.Inline.Unsafe qualified as CU
import Language.Halide.Context
import Language.Halide.Target
import Language.Halide.Type
import Prelude hiding (min)
-- | Information about a dimension in a buffer.
--
-- It is the Haskell analogue of [@halide_dimension_t@](https://halide-lang.org/docs/structhalide__dimension__t.html).
data HalideDimension = HalideDimension
{ halideDimensionMin :: {-# UNPACK #-} !Int32
-- ^ Starting index.
, halideDimensionExtent :: {-# UNPACK #-} !Int32
-- ^ Length of the dimension.
, halideDimensionStride :: {-# UNPACK #-} !Int32
-- ^ Stride along this dimension.
, halideDimensionFlags :: {-# UNPACK #-} !Word32
-- ^ Extra flags.
}
deriving stock (Read, Show, Eq)
instance Storable HalideDimension where
sizeOf _ = 16
{-# INLINE sizeOf #-}
alignment _ = 4
{-# INLINE alignment #-}
peek p =
HalideDimension
<$> peekByteOff p 0
<*> peekByteOff p 4
<*> peekByteOff p 8
<*> peekByteOff p 12
{-# INLINE peek #-}
poke p x = do
pokeByteOff p 0 (halideDimensionMin x)
pokeByteOff p 4 (halideDimensionExtent x)
pokeByteOff p 8 (halideDimensionStride x)
pokeByteOff p 12 (halideDimensionFlags x)
{-# INLINE poke #-}
-- | @simpleDimension extent stride@ creates a @HalideDimension@ of size @extent@ separated by
-- @stride@.
simpleDimension :: Int -> Int -> HalideDimension
simpleDimension extent stride = HalideDimension 0 (fromIntegral extent) (fromIntegral stride) 0
{-# INLINE simpleDimension #-}
-- | Get strides corresponding to row-major ordering
rowMajorStrides
:: Integral a
=> [a]
-- ^ Extents
-> [a]
rowMajorStrides = drop 1 . scanr (*) 1
-- | Get strides corresponding to column-major ordering.
colMajorStrides
:: Integral a
=> [a]
-- ^ Extents
-> [a]
colMajorStrides = scanl (*) 1 . init
-- | Haskell analogue of [@halide_device_interface_t@](https://halide-lang.org/docs/structhalide__device__interface__t.html).
data HalideDeviceInterface
-- | The low-level untyped Haskell analogue of [@halide_buffer_t@](https://halide-lang.org/docs/structhalide__buffer__t.html).
--
-- It's quite difficult to use 'RawHalideBuffer' correctly, and misusage can result in crashes and
-- segmentation faults. Hence, prefer the higher-level 'HalideBuffer' wrapper for all your code
data RawHalideBuffer = RawHalideBuffer
{ halideBufferDevice :: !Word64
, halideBufferDeviceInterface :: !(Ptr HalideDeviceInterface)
, halideBufferHost :: !(Ptr Word8)
, halideBufferFlags :: !Word64
, halideBufferType :: !HalideType
, halideBufferDimensions :: !Int32
, halideBufferDim :: !(Ptr HalideDimension)
, halideBufferPadding :: !(Ptr ())
}
deriving stock (Show, Eq)
-- | An @n@-dimensional buffer of elements of type @a@.
--
-- Most pipelines use @'Ptr' ('HalideBuffer' n a)@ for input and output array arguments.
newtype HalideBuffer (n :: Nat) (a :: Type) = HalideBuffer {unHalideBuffer :: RawHalideBuffer}
deriving stock (Show, Eq)
importHalide
instance Storable RawHalideBuffer where
sizeOf _ = 56
alignment _ = 8
peek p =
RawHalideBuffer
<$> peekByteOff p 0 -- device
<*> peekByteOff p 8 -- interface
<*> peekByteOff p 16 -- host
<*> peekByteOff p 24 -- flags
<*> peekByteOff p 32 -- type
<*> peekByteOff p 36 -- dimensions
<*> peekByteOff p 40 -- dim
<*> peekByteOff p 48 -- padding
poke p x = do
pokeByteOff p 0 (halideBufferDevice x)
pokeByteOff p 8 (halideBufferDeviceInterface x)
pokeByteOff p 16 (halideBufferHost x)
pokeByteOff p 24 (halideBufferFlags x)
pokeByteOff p 32 (halideBufferType x)
pokeByteOff p 36 (halideBufferDimensions x)
pokeByteOff p 40 (halideBufferDim x)
pokeByteOff p 48 (halideBufferPadding x)
-- | Construct a 'HalideBuffer' from a pointer to the data, a list of extents,
-- and a list of strides, and use it in an 'IO' action.
--
-- This function throws a runtime error if the number of dimensions does not
-- match @n@.
bufferFromPtrShapeStrides
:: forall n a b
. (HasCallStack, KnownNat n, IsHalideType a)
=> Ptr a
-- ^ CPU pointer to the data
-> [Int]
-- ^ Extents (in number of elements, __not__ in bytes)
-> [Int]
-- ^ Strides (in number of elements, __not__ in bytes)
-> (Ptr (HalideBuffer n a) -> IO b)
-- ^ Action to run
-> IO b
bufferFromPtrShapeStrides p shape stride action =
withArrayLen (zipWith simpleDimension shape stride) $ \n dim -> do
unless (n == fromIntegral (natVal (Proxy @n))) $
error $
"specified wrong number of dimensions: "
<> show n
<> "; expected "
<> show (natVal (Proxy @n))
<> " from the type declaration"
let !buffer =
RawHalideBuffer
{ halideBufferDevice = 0
, halideBufferDeviceInterface = nullPtr
, halideBufferHost = castPtr p
, halideBufferFlags = 0
, halideBufferType = halideTypeFor (Proxy :: Proxy a)
, halideBufferDimensions = fromIntegral n
, halideBufferDim = dim
, halideBufferPadding = nullPtr
}
with buffer $ \bufferPtr -> do
r <- action (castPtr bufferPtr)
hasDataOnDevice <-
toEnum . fromIntegral
<$> [CU.exp| bool { $(halide_buffer_t* bufferPtr)->device } |]
when hasDataOnDevice $
error "the Buffer still references data on the device; did you forget to call copyToHost?"
pure r
-- | Similar to 'bufferFromPtrShapeStrides', but assumes column-major ordering of data.
bufferFromPtrShape
:: (HasCallStack, KnownNat n, IsHalideType a)
=> Ptr a
-- ^ CPU pointer to the data
-> [Int]
-- ^ Extents (in number of elements, __not__ in bytes)
-> (Ptr (HalideBuffer n a) -> IO b)
-> IO b
bufferFromPtrShape p shape = bufferFromPtrShapeStrides p shape (colMajorStrides shape)
-- | Specifies that a type @t@ can be used as an @n@-dimensional Halide buffer with elements of type @a@.
class (KnownNat n, IsHalideType a) => IsHalideBuffer t n a where
withHalideBufferImpl :: t -> (Ptr (HalideBuffer n a) -> IO b) -> IO b
-- | Treat a type @t@ as a 'HalideBuffer' and use it in an 'IO' action.
--
-- This function is a simple wrapper around 'withHalideBufferImpl', except that the order of type parameters
-- is reversed. If you have @TypeApplications@ extension enabled, this allows you to write
-- @withHalideBuffer @3 @Float yourBuffer@ to specify that you want a 3-dimensional buffer of @Float@.
withHalideBuffer :: forall n a t b. IsHalideBuffer t n a => t -> (Ptr (HalideBuffer n a) -> IO b) -> IO b
withHalideBuffer = withHalideBufferImpl @t @n @a
-- | Storable vectors are one-dimensional buffers. This involves no copying.
instance IsHalideType a => IsHalideBuffer (S.Vector a) 1 a where
withHalideBufferImpl v f =
S.unsafeWith v $ \dataPtr ->
bufferFromPtrShape dataPtr [S.length v] f
-- | Storable vectors are one-dimensional buffers. This involves no copying.
instance IsHalideType a => IsHalideBuffer (S.MVector RealWorld a) 1 a where
withHalideBufferImpl v f =
SM.unsafeWith v $ \dataPtr ->
bufferFromPtrShape dataPtr [SM.length v] f
-- | Lists can also act as Halide buffers. __Use for testing only.__
instance IsHalideType a => IsHalideBuffer [a] 1 a where
withHalideBufferImpl v = withHalideBuffer (S.fromList v)
-- | Lists can also act as Halide buffers. __Use for testing only.__
instance IsHalideType a => IsHalideBuffer [[a]] 2 a where
withHalideBufferImpl xs f = do
let d0 = length xs
d1 = if d0 == 0 then 0 else length (head xs)
-- we want column-major ordering, so transpose first
v = S.fromList (List.concat (List.transpose xs))
when (S.length v /= d0 * d1) $
error "list doesn't have a regular shape (i.e. rows have varying number of elements)"
S.unsafeWith v $ \cpuPtr ->
bufferFromPtrShape cpuPtr [d0, d1] f
-- | Lists can also act as Halide buffers. __Use for testing only.__
instance IsHalideType a => IsHalideBuffer [[[a]]] 3 a where
withHalideBufferImpl xs f = do
let d0 = length xs
d1 = if d0 == 0 then 0 else length (head xs)
d2 = if d1 == 0 then 0 else length (head (head xs))
-- we want column-major ordering, so transpose first
v =
S.fromList
. List.concat
. List.concatMap List.transpose
. List.transpose
. fmap List.transpose
$ xs
when (S.length v /= d0 * d1 * d2) $
error "list doesn't have a regular shape (i.e. rows have varying number of elements)"
S.unsafeWith v $ \cpuPtr ->
bufferFromPtrShape cpuPtr [d0, d1, d2] f
-- | Lists can also act as Halide buffers. __Use for testing only.__
instance IsHalideType a => IsHalideBuffer [[[[a]]]] 4 a where
withHalideBufferImpl xs f = do
let d0 = length xs
d1 = if d0 == 0 then 0 else length (head xs)
d2 = if d1 == 0 then 0 else length (head (head xs))
d3 = if d2 == 0 then 0 else length (head (head (head xs)))
-- we want column-major ordering, so transpose first
v =
S.fromList
. concat
. concat
. concatMap (fmap List.transpose . List.transpose . fmap List.transpose)
. List.transpose
. fmap (List.transpose . fmap List.transpose)
$ xs
when (S.length v /= d0 * d1 * d2 * d3) $
error "list doesn't have a regular shape (i.e. rows have varying number of elements)"
S.unsafeWith v $ \cpuPtr ->
bufferFromPtrShape cpuPtr [d0, d1, d2, d3] f
whenM :: Monad m => m Bool -> m () -> m ()
whenM cond f =
cond >>= \case
True -> f
False -> pure ()
-- | Temporary allocate a CPU buffer.
--
-- This is useful for testing and debugging when you need to allocate an output buffer for your pipeline. E.g.
--
-- @
-- 'allocaCpuBuffer' [3, 3] $ \out -> do
-- myKernel out -- fill the buffer
-- print =<< 'peekToList' out -- print it for debugging
-- @
allocaCpuBuffer
:: forall n a b
. (HasCallStack, KnownNat n, IsHalideType a)
=> [Int]
-> (Ptr (HalideBuffer n a) -> IO b)
-> IO b
allocaCpuBuffer = allocaBuffer hostTarget
getTotalBytes :: Ptr RawHalideBuffer -> IO Int
getTotalBytes buf = do
fromIntegral
<$> [CU.block| size_t {
auto const& b = *$(const halide_buffer_t* buf);
auto const n = std::accumulate(b.dim, b.dim + b.dimensions, size_t{1},
[](auto acc, auto const& dim) { return acc * dim.extent; });
return n * (b.type.bits * b.type.lanes / 8);
} |]
allocateHostMemory :: Ptr RawHalideBuffer -> IO ()
allocateHostMemory buf = do
ptr <- mallocBytes =<< getTotalBytes buf
[CU.block| void { $(halide_buffer_t* buf)->host = $(uint8_t* ptr); } |]
freeHostMemory :: Ptr RawHalideBuffer -> IO ()
freeHostMemory buf = do
ptr <-
[CU.block| uint8_t* {
auto& b = *$(halide_buffer_t* buf);
auto const p = b.host;
b.host = nullptr;
return p;
} |]
free ptr
allocateDeviceMemory :: Ptr HalideDeviceInterface -> Ptr RawHalideBuffer -> IO ()
allocateDeviceMemory interface buf = do
[CU.block| void {
auto const* interface = $(const halide_device_interface_t* interface);
interface->device_malloc(nullptr, $(halide_buffer_t* buf), interface);
} |]
freeDeviceMemory :: HasCallStack => Ptr RawHalideBuffer -> IO ()
freeDeviceMemory buf = do
deviceInterface <-
[CU.exp| const halide_device_interface_t* { $(const halide_buffer_t* buf)->device_interface } |]
when (deviceInterface == nullPtr) $
error "cannot free device memory: device_interface is NULL"
[CU.block| void {
$(halide_buffer_t* buf)->device_interface->device_free(nullptr, $(halide_buffer_t* buf));
$(halide_buffer_t* buf)->device = 0;
} |]
allocaBuffer
:: forall n a b
. (HasCallStack, KnownNat n, IsHalideType a)
=> Target
-> [Int]
-> (Ptr (HalideBuffer n a) -> IO b)
-> IO b
allocaBuffer target shape action = do
deviceInterface <- getDeviceInterface target
let onHost = deviceInterface == nullPtr
withArrayLen (zipWith simpleDimension shape (colMajorStrides shape)) $ \n dim -> do
unless (n == fromIntegral (natVal (Proxy @n))) $
error $
"specified wrong number of dimensions: "
<> show n
<> "; expected "
<> show (natVal (Proxy @n))
<> " from the type declaration"
let rawBuffer =
RawHalideBuffer
{ halideBufferDevice = 0
, halideBufferDeviceInterface = nullPtr
, halideBufferHost = nullPtr
, halideBufferFlags = 0
, halideBufferType = halideTypeFor (Proxy :: Proxy a)
, halideBufferDimensions = fromIntegral n
, halideBufferDim = dim
, halideBufferPadding = nullPtr
}
with rawBuffer $ \buf -> do
let allocate
| onHost = allocateHostMemory
| otherwise = allocateDeviceMemory deviceInterface
let deallocate
| onHost = freeHostMemory
| otherwise = freeDeviceMemory
bracket_ (allocate buf) (deallocate buf) $ do
r <- action (castPtr buf)
isHostNull <- toBool <$> [CU.exp| bool { $(halide_buffer_t* buf)->host == nullptr } |]
isDeviceNull <- toBool <$> [CU.exp| bool { $(halide_buffer_t* buf)->device == 0 } |]
when (onHost && not isDeviceNull) . error $
"buffer was allocated on host, but its device pointer is not NULL"
<> "; did you forget a copyToHost in your pipeline?"
when (not onHost && not isHostNull) . error $
"buffer was allocated on device, but its host pointer is not NULL"
<> "; did you add an extra copyToHost?"
pure r
getDeviceInterface :: Target -> IO (Ptr HalideDeviceInterface)
getDeviceInterface target =
case device of
DeviceNone -> pure nullPtr
DeviceHost -> pure nullPtr
_ ->
withCxxTarget target $ \target' ->
[C.throwBlock| const halide_device_interface_t* {
return handle_halide_exceptions([=](){
auto const device = static_cast<Halide::DeviceAPI>($(int api));
auto const& target = *$(const Halide::Target* target');
return Halide::get_device_interface_for_device_api(device, target, "getDeviceInterface");
});
} |]
where
device@(fromIntegral . fromEnum -> api) = deviceAPIForTarget target
-- | Do we have changes on the device the have not been copied to the host?
isDeviceDirty :: Ptr RawHalideBuffer -> IO Bool
isDeviceDirty p =
toBool <$> [CU.exp| bool { $(const halide_buffer_t* p)->device_dirty() } |]
-- | Set the @device_dirty@ flag to the given value.
setDeviceDirty :: Bool -> Ptr RawHalideBuffer -> IO ()
setDeviceDirty (fromIntegral . fromEnum -> b) p =
[CU.exp| void { $(halide_buffer_t* p)->set_device_dirty($(bool b)) } |]
-- | Do we have changes on the device the have not been copied to the host?
isHostDirty :: Ptr RawHalideBuffer -> IO Bool
isHostDirty p =
toBool <$> [CU.exp| bool { $(const halide_buffer_t* p)->host_dirty() } |]
-- | Set the @host_dirty@ flag to the given value.
setHostDirty :: Bool -> Ptr RawHalideBuffer -> IO ()
setHostDirty (fromIntegral . fromEnum -> b) p =
[CU.exp| void { $(halide_buffer_t* p)->set_host_dirty($(bool b)) } |]
-- | Copy the underlying memory from device to host.
bufferCopyToHost :: HasCallStack => Ptr RawHalideBuffer -> IO ()
bufferCopyToHost p = whenM (isDeviceDirty p) $ do
raw <- peek p
when (raw.halideBufferDeviceInterface == nullPtr) . error $
"device_dirty is set, but device_interface is NULL"
when (raw.halideBufferHost == nullPtr) . error $
"host is NULL, did you forget to allocate memory?"
[CU.block| void {
auto& buf = *$(halide_buffer_t* p);
buf.device_interface->copy_to_host(nullptr, &buf);
} |]
whenM (isDeviceDirty p) . error $
"device_dirty is set right after a copy_to_host; something went wrong..."
checkNumberOfDimensions :: forall n. (HasCallStack, KnownNat n) => RawHalideBuffer -> IO ()
checkNumberOfDimensions raw = do
unless (fromIntegral (natVal (Proxy @n)) == raw.halideBufferDimensions) $
error $
"type-level and runtime number of dimensions do not match: "
<> show (natVal (Proxy @n))
<> " != "
<> show raw.halideBufferDimensions
-- | Perform an action on a cropped buffer.
withCropped
:: Ptr (HalideBuffer n a)
-- ^ buffer
-> Int
-- ^ dimension
-> Int
-- ^ min
-> Int
-- ^ extent
-> (Ptr (HalideBuffer n a) -> IO b)
-- ^ what to do
-> IO b
withCropped
(castPtr -> src)
(fromIntegral -> d)
(fromIntegral -> min)
(fromIntegral -> extent)
action = do
rank <- fromIntegral <$> [CU.exp| int { $(const halide_buffer_t* src)->dimensions } |]
alloca $ \dst ->
allocaArray rank $ \dstDim -> do
[CU.block| void {
auto const& src = *$(const halide_buffer_t* src);
auto& dst = *$(halide_buffer_t* dst);
auto const d = $(int d);
dst = src;
dst.dim = $(halide_dimension_t* dstDim);
memcpy(dst.dim, src.dim, src.dimensions * sizeof(halide_dimension_t));
if (dst.host != nullptr) {
auto const shift = $(int min) - src.dim[d].min;
dst.host += (shift * src.dim[d].stride) * ((src.type.bits + 7) / 8);
}
dst.dim[d].min = $(int min);
dst.dim[d].extent = $(int extent);
if (src.device != 0 && src.device_interface != nullptr) {
src.device_interface->device_crop(nullptr, &src, &dst);
}
} |]
action (castPtr dst)
getBufferExtent :: forall n a. KnownNat n => Ptr (HalideBuffer n a) -> Int -> IO Int
getBufferExtent (castPtr -> buf) (fromIntegral -> d)
| d < fromIntegral (natVal (Proxy @n)) =
fromIntegral <$> [CU.exp| int { $(const halide_buffer_t* buf)->dim[$(int d)].extent } |]
| otherwise = error "index out of bounds"
peekScalar :: forall a. (HasCallStack, IsHalideType a) => Ptr (HalideBuffer 0 a) -> IO a
peekScalar p = withCopiedToHost p $ do
raw <- peek (castPtr @_ @RawHalideBuffer p)
checkNumberOfDimensions @0 raw
when (raw.halideBufferHost == nullPtr) . error $ "host is NULL"
peek $ castPtr @_ @a raw.halideBufferHost
-- | Specifies that @a@ can be converted to a list. This is very similar to 'GHC.Exts.IsList' except that
-- we read the list from a @'Ptr'@ rather than converting directly.
-- class IsListPeek a where
-- type ListPeekElem a :: Type
-- peekToList :: HasCallStack => Ptr a -> IO [ListPeekElem a]
type family NestedList (n :: Nat) (a :: Type) where
NestedList 0 a = a
NestedList 1 a = [a]
NestedList 2 a = [[a]]
NestedList 3 a = [[[a]]]
NestedList 4 a = [[[[a]]]]
NestedList 5 a = [[[[[a]]]]]
type family NestedListLevel (a :: Type) :: Nat where
NestedListLevel [a] = 1 + NestedListLevel a
NestedListLevel a = 0
type family NestedListType (a :: Type) :: Type where
NestedListType [a] = NestedListType a
NestedListType a = a
class
( KnownNat n
, IsHalideType a
, NestedList n a ~ b
, NestedListLevel b ~ n
, NestedListType b ~ a
) =>
IsListPeek n a b
| n a -> b
, n b -> a
, a b -> n
where
peekToList :: HasCallStack => Ptr (HalideBuffer n a) -> IO b
instance
(IsHalideType a, NestedListLevel [a] ~ 1, NestedListType [a] ~ a)
=> IsListPeek 1 a [a]
where
peekToList p = withCopiedToHost p $ do
raw <- peek (castPtr @_ @RawHalideBuffer p)
(HalideDimension min0 extent0 stride0 _) <- peekElemOff (halideBufferDim raw) 0
let ptr0 = castPtr @_ @a (halideBufferHost raw)
when (ptr0 == nullPtr) . error $ "host is NULL"
forM [0 .. extent0 - 1] $ \i0 ->
peekElemOff ptr0 (fromIntegral (min0 + stride0 * i0))
instance
(IsHalideType a, NestedListLevel [[a]] ~ 2, NestedListType [[a]] ~ a)
=> IsListPeek 2 a [[a]]
where
peekToList p = withCopiedToHost p $ do
raw <- peek (castPtr @_ @RawHalideBuffer p)
(HalideDimension min0 extent0 stride0 _) <- peekElemOff (halideBufferDim raw) 0
(HalideDimension min1 extent1 stride1 _) <- peekElemOff (halideBufferDim raw) 1
let ptr0 = castPtr @_ @a (halideBufferHost raw)
when (ptr0 == nullPtr) . error $ "host is NULL"
forM [0 .. extent0 - 1] $ \i0 -> do
let ptr1 = ptr0 `advancePtr` fromIntegral (min0 + stride0 * i0)
forM [0 .. extent1 - 1] $ \i1 ->
peekElemOff ptr1 (fromIntegral (min1 + stride1 * i1))
instance
(IsHalideType a, NestedListLevel [[[a]]] ~ 3, NestedListType [[[a]]] ~ a)
=> IsListPeek 3 a [[[a]]]
where
peekToList p = withCopiedToHost p $ do
raw <- peek (castPtr @_ @RawHalideBuffer p)
(HalideDimension min0 extent0 stride0 _) <- peekElemOff (halideBufferDim raw) 0
(HalideDimension min1 extent1 stride1 _) <- peekElemOff (halideBufferDim raw) 1
(HalideDimension min2 extent2 stride2 _) <- peekElemOff (halideBufferDim raw) 2
let ptr0 = castPtr @_ @a (halideBufferHost raw)
when (ptr0 == nullPtr) . error $ "host is NULL"
forM [0 .. extent0 - 1] $ \i0 -> do
let ptr1 = ptr0 `advancePtr` fromIntegral (min0 + stride0 * i0)
forM [0 .. extent1 - 1] $ \i1 -> do
let ptr2 = ptr1 `advancePtr` fromIntegral (min1 + stride1 * i1)
forM [0 .. extent2 - 1] $ \i2 ->
peekElemOff ptr2 (fromIntegral (min2 + stride2 * i2))
instance
(IsHalideType a, NestedListLevel [[[[a]]]] ~ 4, NestedListType [[[[a]]]] ~ a)
=> IsListPeek 4 a [[[[a]]]]
where
peekToList p = withCopiedToHost p $ do
raw <- peek (castPtr @_ @RawHalideBuffer p)
(HalideDimension min0 extent0 stride0 _) <- peekElemOff (halideBufferDim raw) 0
(HalideDimension min1 extent1 stride1 _) <- peekElemOff (halideBufferDim raw) 1
(HalideDimension min2 extent2 stride2 _) <- peekElemOff (halideBufferDim raw) 2
(HalideDimension min3 extent3 stride3 _) <- peekElemOff (halideBufferDim raw) 3
let ptr0 = castPtr @_ @a (halideBufferHost raw)
when (ptr0 == nullPtr) . error $ "host is NULL"
forM [0 .. extent0 - 1] $ \i0 -> do
let ptr1 = ptr0 `advancePtr` fromIntegral (min0 + stride0 * i0)
forM [0 .. extent1 - 1] $ \i1 -> do
let ptr2 = ptr1 `advancePtr` fromIntegral (min1 + stride1 * i1)
forM [0 .. extent2 - 1] $ \i2 -> do
let ptr3 = ptr2 `advancePtr` fromIntegral (min2 + stride2 * i2)
forM [0 .. extent3 - 1] $ \i3 ->
peekElemOff ptr3 (fromIntegral (min3 + stride3 * i3))
-- | @withCopiedToHost buf action@ performs the action @action@ ensuring that @buf@ has been
-- copied to the host beforehand. If @buf@ is already on the host, no copying is performed.
withCopiedToHost :: Ptr (HalideBuffer n a) -> IO b -> IO b
withCopiedToHost (castPtr @_ @RawHalideBuffer -> buf) action = do
raw <- peek buf
let allocate = when (raw.halideBufferDevice /= 0) $ allocateHostMemory buf
deallocate = when (raw.halideBufferDevice /= 0) $ freeHostMemory buf
bracket_ allocate deallocate $ do
when (raw.halideBufferDevice /= 0) $ do
setDeviceDirty True buf
bufferCopyToHost buf
action
-- instance IsHalideType a => IsListPeek (HalideBuffer 0 a) where
-- type ListPeekElem (HalideBuffer 0 a) = a
-- peekToList p = withCopiedToHost p $ do
-- raw <- peek (castPtr @_ @RawHalideBuffer p)
-- checkNumberOfDimensions @0 raw
-- when (raw.halideBufferHost == nullPtr) . error $ "host is NULL"
-- fmap pure . peek $ castPtr @_ @a raw.halideBufferHost
-- instance IsHalideType a => IsListPeek (HalideBuffer 1 a) where
-- type ListPeekElem (HalideBuffer 1 a) = a
-- peekToList p = withCopiedToHost p $ do
-- raw <- peek (castPtr @_ @RawHalideBuffer p)
-- (HalideDimension min0 extent0 stride0 _) <- peekElemOff (halideBufferDim raw) 0
-- let ptr0 = castPtr @_ @a (halideBufferHost raw)
-- when (ptr0 == nullPtr) . error $ "host is NULL"
-- forM [0 .. extent0 - 1] $ \i0 ->
-- peekElemOff ptr0 (fromIntegral (min0 + stride0 * i0))
-- instance IsHalideType a => IsListPeek (HalideBuffer 2 a) where
-- type ListPeekElem (HalideBuffer 2 a) = [a]
-- peekToList p = withCopiedToHost p $ do
-- raw <- peek (castPtr @_ @RawHalideBuffer p)
-- (HalideDimension min0 extent0 stride0 _) <- peekElemOff (halideBufferDim raw) 0
-- (HalideDimension min1 extent1 stride1 _) <- peekElemOff (halideBufferDim raw) 1
-- let ptr0 = castPtr @_ @a (halideBufferHost raw)
-- when (ptr0 == nullPtr) . error $ "host is NULL"
-- forM [0 .. extent0 - 1] $ \i0 -> do
-- let ptr1 = ptr0 `advancePtr` fromIntegral (min0 + stride0 * i0)
-- forM [0 .. extent1 - 1] $ \i1 ->
-- peekElemOff ptr1 (fromIntegral (min1 + stride1 * i1))
-- instance IsHalideType a => IsListPeek (HalideBuffer 3 a) where
-- type ListPeekElem (HalideBuffer 3 a) = [[a]]
-- peekToList p = withCopiedToHost p $ do
-- raw <- peek (castPtr @_ @RawHalideBuffer p)
-- (HalideDimension min0 extent0 stride0 _) <- peekElemOff (halideBufferDim raw) 0
-- (HalideDimension min1 extent1 stride1 _) <- peekElemOff (halideBufferDim raw) 1
-- (HalideDimension min2 extent2 stride2 _) <- peekElemOff (halideBufferDim raw) 2
-- let ptr0 = castPtr @_ @a (halideBufferHost raw)
-- when (ptr0 == nullPtr) . error $ "host is NULL"
-- forM [0 .. extent0 - 1] $ \i0 -> do
-- let ptr1 = ptr0 `advancePtr` fromIntegral (min0 + stride0 * i0)
-- forM [0 .. extent1 - 1] $ \i1 -> do
-- let ptr2 = ptr1 `advancePtr` fromIntegral (min1 + stride1 * i1)
-- forM [0 .. extent2 - 1] $ \i2 ->
-- peekElemOff ptr2 (fromIntegral (min2 + stride2 * i2))
-- instance IsHalideType a => IsListPeek (HalideBuffer 4 a) where
-- type ListPeekElem (HalideBuffer 4 a) = [[[a]]]
-- peekToList p = withCopiedToHost p $ do
-- raw <- peek (castPtr @_ @RawHalideBuffer p)
-- (HalideDimension min0 extent0 stride0 _) <- peekElemOff (halideBufferDim raw) 0
-- (HalideDimension min1 extent1 stride1 _) <- peekElemOff (halideBufferDim raw) 1
-- (HalideDimension min2 extent2 stride2 _) <- peekElemOff (halideBufferDim raw) 2
-- (HalideDimension min3 extent3 stride3 _) <- peekElemOff (halideBufferDim raw) 3
-- let ptr0 = castPtr @_ @a (halideBufferHost raw)
-- when (ptr0 == nullPtr) . error $ "host is NULL"
-- forM [0 .. extent0 - 1] $ \i0 -> do
-- let ptr1 = ptr0 `advancePtr` fromIntegral (min0 + stride0 * i0)
-- forM [0 .. extent1 - 1] $ \i1 -> do
-- let ptr2 = ptr1 `advancePtr` fromIntegral (min1 + stride1 * i1)
-- forM [0 .. extent2 - 1] $ \i2 -> do
-- let ptr3 = ptr2 `advancePtr` fromIntegral (min2 + stride2 * i2)
-- forM [0 .. extent3 - 1] $ \i3 ->
-- peekElemOff ptr3 (fromIntegral (min3 + stride3 * i3))