vtk-m2/vtkm/cont/internal/DeviceAdapterMemoryManager.h
Kenneth Moreland f598656d53 Enable reallocation of ArrayHandleBasic with moved std::vector
It is possible to create an `ArrayHandleBasic` from a `std::vector`. It
is possible to move the `vector` into the `ArrayHandle` such that the
`ArrayHandle` takes over the memory. When you do this, the `ArrayHandle`
should be able to resize the data if necessary. However, this was not
working.

There were actually 3 problems that were colluding to lead to this
incorrect behavior.

1. The `Buffer` object was not allowing the reallocation of pinned data.
Pinned data means that the `Buffer`'s memory is pointing to some user
data that should stay where it is. Instead, the `Buffer` should attempt
to reallocate the pinned data using its registered realloc method. This
registered realloc method should be the think to throw the exception if
reallocation is not supported. (Technically, the memory doesn't really
need to be pinned since the data is moved and the user no longer has
direct access to it. But for implementation reasons, moved `vector` data
is pinned.)

2. The `InvalidRealloc` function was not properly throwing an exception.
This was not noticed since `Buffer` was inappropriately throwing an
exception for it.

3. The reallocation method `StdVectorReallocater` had a bad assert that
crashed the program during reallocation.
2022-07-08 11:49:16 -06:00

287 lines
11 KiB
C++

//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#ifndef vtk_m_cont_internal_DeviceAdapterMemoryManager_h
#define vtk_m_cont_internal_DeviceAdapterMemoryManager_h
#include <vtkm/cont/vtkm_cont_export.h>
#include <vtkm/Flags.h>
#include <vtkm/Types.h>
#include <vtkm/cont/DeviceAdapterTag.h>
#include <cstring>
#include <memory>
#include <vector>
namespace vtkm
{
using BufferSizeType = vtkm::Int64;
namespace cont
{
namespace internal
{
struct TransferredBuffer;
namespace detail
{
struct BufferInfoInternals;
} // namespace detail
class VTKM_CONT_EXPORT BufferInfo
{
public:
/// Returns a pointer to the memory that is allocated. This pointer may only be referenced on
/// the associated device.
///
VTKM_CONT void* GetPointer() const;
/// Returns the size of the buffer in bytes.
///
VTKM_CONT vtkm::BufferSizeType GetSize() const;
/// \brief Returns the device on which this buffer is allocated.
///
/// If the buffer is not on a device (i.e. it is on the host), then DeviceAdapterIdUndefined
/// is returned.
///
VTKM_CONT vtkm::cont::DeviceAdapterId GetDevice() const;
VTKM_CONT BufferInfo();
VTKM_CONT ~BufferInfo();
VTKM_CONT BufferInfo(const BufferInfo& src);
VTKM_CONT BufferInfo(BufferInfo&& src);
VTKM_CONT BufferInfo& operator=(const BufferInfo& src);
VTKM_CONT BufferInfo& operator=(BufferInfo&& src);
/// Shallow copy buffer from one host/device to another host/device. Make sure that these
/// two devices share the same memory space. (This is not checked and will cause badness
/// if not correct.)
///
VTKM_CONT BufferInfo(const BufferInfo& src, vtkm::cont::DeviceAdapterId device);
VTKM_CONT BufferInfo(BufferInfo&& src, vtkm::cont::DeviceAdapterId device);
/// A function callback for deleting the memory.
///
using Deleter = void(void* container);
/// A function callback for reallocating the memory.
///
using Reallocater = void(void*& memory,
void*& container,
vtkm::BufferSizeType oldSize,
vtkm::BufferSizeType newSize);
/// Creates a BufferInfo with the given memory, some (unknown) container holding that memory, a
/// deletion function, and a reallocation function. The deleter will be called with the pointer
/// to the container when the buffer is released.
///
VTKM_CONT BufferInfo(vtkm::cont::DeviceAdapterId device,
void* memory,
void* container,
vtkm::BufferSizeType size,
Deleter deleter,
Reallocater reallocater);
/// Reallocates the buffer to a new size.
///
VTKM_CONT void Reallocate(vtkm::BufferSizeType newSize);
/// Transfers ownership of the underlying allocation and Deleter and Reallocater to the caller.
/// After ownership has been transferred this buffer will be equivalant to one that was passed
/// to VTK-m as `view` only.
///
/// This means that the Deleter will do nothing, and the Reallocater will throw an `ErrorBadAllocation`.
///
VTKM_CONT TransferredBuffer TransferOwnership();
private:
detail::BufferInfoInternals* Internals;
vtkm::cont::DeviceAdapterId Device;
};
/// Represents the buffer being transferred to external ownership
///
/// The Memory pointer represents the actual data allocation to
/// be used for access and execution
///
/// The container represents what needs to be deleted. This might
/// not be equivalent to \c Memory when we have transferred things
/// such as std::vector
///
struct TransferredBuffer
{
void* Memory;
void* Container;
BufferInfo::Deleter* Delete;
BufferInfo::Reallocater* Reallocate;
vtkm::BufferSizeType Size;
};
/// Allocates a `BufferInfo` object for the host.
///
VTKM_CONT_EXPORT VTKM_CONT vtkm::cont::internal::BufferInfo AllocateOnHost(
vtkm::BufferSizeType size);
/// \brief The base class for device adapter memory managers.
///
/// Every device adapter is expected to define a specialization of `DeviceAdapterMemoryManager`,
/// and they are all expected to subclass this base class.
///
class VTKM_CONT_EXPORT DeviceAdapterMemoryManagerBase
{
public:
VTKM_CONT virtual ~DeviceAdapterMemoryManagerBase();
/// Allocates a buffer of the specified size in bytes and returns a BufferInfo object
/// containing information about it.
VTKM_CONT virtual vtkm::cont::internal::BufferInfo Allocate(vtkm::BufferSizeType size) const = 0;
/// Reallocates the provided buffer to a new size. The passed in `BufferInfo` should be
/// modified to reflect the changes.
VTKM_CONT void Reallocate(vtkm::cont::internal::BufferInfo& buffer,
vtkm::BufferSizeType newSize) const;
/// Manages the provided array. Returns a `BufferInfo` object that contains the data.
VTKM_CONT BufferInfo ManageArray(void* memory,
void* container,
vtkm::BufferSizeType size,
vtkm::cont::internal::BufferInfo::Deleter deleter,
vtkm::cont::internal::BufferInfo::Reallocater reallocater) const;
/// Returns the device that this manager is associated with.
VTKM_CONT virtual vtkm::cont::DeviceAdapterId GetDevice() const = 0;
/// Copies data from the provided host buffer provided onto the device and returns a buffer info
/// object holding the pointer for the device.
VTKM_CONT virtual vtkm::cont::internal::BufferInfo CopyHostToDevice(
const vtkm::cont::internal::BufferInfo& src) const = 0;
/// Copies data from the provided host buffer into the provided pre-allocated device buffer. The
/// `BufferInfo` object for the device was created by a previous call to this object.
VTKM_CONT virtual void CopyHostToDevice(const vtkm::cont::internal::BufferInfo& src,
const vtkm::cont::internal::BufferInfo& dest) const = 0;
/// Copies data from the device buffer provided to the host. The passed in `BufferInfo` object
/// was created by a previous call to this object.
VTKM_CONT virtual vtkm::cont::internal::BufferInfo CopyDeviceToHost(
const vtkm::cont::internal::BufferInfo& src) const = 0;
/// Copies data from the device buffer provided into the provided pre-allocated host buffer. The
/// `BufferInfo` object for the device was created by a previous call to this object.
VTKM_CONT virtual void CopyDeviceToHost(const vtkm::cont::internal::BufferInfo& src,
const vtkm::cont::internal::BufferInfo& dest) const = 0;
/// Deep copies data from one device buffer to another device buffer. The passed in `BufferInfo`
/// object was created by a previous call to this object.
VTKM_CONT virtual vtkm::cont::internal::BufferInfo CopyDeviceToDevice(
const vtkm::cont::internal::BufferInfo& src) const = 0;
/// Deep copies data from one device buffer to another device buffer. The passed in `BufferInfo`
/// objects were created by a previous call to this object.
VTKM_CONT virtual void CopyDeviceToDevice(const vtkm::cont::internal::BufferInfo& src,
const vtkm::cont::internal::BufferInfo& dest) const = 0;
};
/// \brief The device adapter memory manager.
///
/// Every device adapter is expected to define a specialization of `DeviceAdapterMemoryManager`.
/// This class must be a (perhaps indirect) subclass of `DeviceAdapterMemoryManagerBase`. All
/// abstract methods must be implemented.
///
template <typename DeviceAdapterTag>
class DeviceAdapterMemoryManager;
VTKM_CONT_EXPORT VTKM_CONT void InvalidRealloc(void*&,
void*&,
vtkm::BufferSizeType,
vtkm::BufferSizeType);
// Deletes a container object by casting it to a pointer of a given type (the template argument)
// and then using delete[] on the object.
template <typename T>
VTKM_CONT inline void SimpleArrayDeleter(void* container_)
{
T* container = reinterpret_cast<T*>(container_);
delete[] container;
}
// Reallocates a standard C array. Note that the allocation method is different than the default
// host allocation of vtkm::cont::internal::BufferInfo and may be less efficient.
template <typename T>
VTKM_CONT inline void SimpleArrayReallocater(void*& memory,
void*& container,
vtkm::BufferSizeType oldSize,
vtkm::BufferSizeType newSize)
{
VTKM_ASSERT(memory == container);
VTKM_ASSERT(static_cast<std::size_t>(newSize) % sizeof(T) == 0);
// If the new size is not much smaller than the old size, just reuse the buffer (and waste a
// little memory).
if ((newSize > ((3 * oldSize) / 4)) && (newSize <= oldSize))
{
return;
}
void* newBuffer = new T[static_cast<std::size_t>(newSize) / sizeof(T)];
std::memcpy(newBuffer, memory, static_cast<std::size_t>(newSize < oldSize ? newSize : oldSize));
if (memory != nullptr)
{
SimpleArrayDeleter<T>(memory);
}
memory = container = newBuffer;
}
// Deletes a container object by casting it to a pointer of a given type (the template argument)
// and then using delete on the object.
template <typename T>
VTKM_CONT inline void CastDeleter(void* container_)
{
T* container = reinterpret_cast<T*>(container_);
delete container;
}
template <typename T, typename Allocator>
VTKM_CONT inline void StdVectorDeleter(void* container)
{
CastDeleter<std::vector<T, Allocator>>(container);
}
template <typename T, typename Allocator>
VTKM_CONT inline void StdVectorReallocater(void*& memory,
void*& container,
vtkm::BufferSizeType oldSize,
vtkm::BufferSizeType newSize)
{
using vector_type = std::vector<T, Allocator>;
vector_type* vector = reinterpret_cast<vector_type*>(container);
VTKM_ASSERT(vector->empty() || (memory == vector->data()));
VTKM_ASSERT(oldSize == static_cast<vtkm::BufferSizeType>(vector->size() * sizeof(T)));
vector->resize(static_cast<std::size_t>(newSize));
memory = vector->data();
}
}
}
} // namespace vtkm::cont::internal
#endif //vtk_m_cont_internal_DeviceAdapterMemoryManager_h