diff --git a/cmake/SetupChaiOptions.cmake b/cmake/SetupChaiOptions.cmake index 2b895c7d..3807603a 100644 --- a/cmake/SetupChaiOptions.cmake +++ b/cmake/SetupChaiOptions.cmake @@ -4,7 +4,7 @@ # # SPDX-License-Identifier: BSD-3-Clause ############################################################################ -option(CHAI_ENABLE_EXPERIMENTAL "Enable experimental chai features." Off) +option(CHAI_ENABLE_EXPERIMENTAL "Enable experimental features" Off) option(CHAI_ENABLE_GPU_SIMULATION_MODE "Enable GPU Simulation Mode" Off) option(CHAI_ENABLE_OPENMP "Enable OpenMP" Off) option(CHAI_ENABLE_MPI "Enable MPI (for umpire replay only)" Off) diff --git a/host-configs/lc/toss_4_x86_64_ib/nvcc_clang.cmake b/host-configs/lc/toss_4_x86_64_ib/nvcc_clang.cmake new file mode 100644 index 00000000..6cea94e6 --- /dev/null +++ b/host-configs/lc/toss_4_x86_64_ib/nvcc_clang.cmake @@ -0,0 +1,34 @@ +############################################################################## +# Copyright (c) 2020-25, Lawrence Livermore National Security, LLC and CARE +# project contributors. See the CARE LICENSE file for details. +# +# SPDX-License-Identifier: BSD-3-Clause +############################################################################## + +# Use gcc std libraries +set(GCC_VER "13.3.1" CACHE STRING "") +set(GCC_DIR "/usr/tce/packages/gcc/gcc-${GCC_VER}-magic" CACHE PATH "") + +# Use clang toolchain for host code compilers +set(CLANG_VER "14.0.6" CACHE STRING "") +set(CLANG_DIR "/usr/tce/packages/clang/clang-${CLANG_VER}-magic" CACHE PATH "") + +set(CMAKE_C_COMPILER "${CLANG_DIR}/bin/clang" CACHE PATH "") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --gcc-toolchain=${GCC_DIR}" CACHE STRING "") + +set(CMAKE_CXX_COMPILER "${CLANG_DIR}/bin/clang++" CACHE PATH "") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --gcc-toolchain=${GCC_DIR}" CACHE STRING "") + +# Use nvcc as the device code compiler +set(ENABLE_CUDA ON CACHE BOOL "") +set(CUDA_VER "12.9.1" CACHE STRING "") +set(CUDA_TOOLKIT_ROOT_DIR "/usr/tce/packages/cuda/cuda-${CUDA_VER}" CACHE PATH "") +set(CMAKE_CUDA_COMPILER "${CUDA_TOOLKIT_ROOT_DIR}/bin/nvcc" CACHE PATH "") +set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -Xcompiler=--gcc-toolchain=${GCC_DIR} -Wno-deprecated-gpu-targets -Wno-unused-command-line-argument" CACHE STRING "") +set(CMAKE_CUDA_HOST_COMPILER "${CMAKE_CXX_COMPILER}" CACHE PATH "") +set(CMAKE_CUDA_ARCHITECTURES "90" CACHE STRING "") + +# Prevent incorrect implicit libraries from being linked in +set(BLT_CMAKE_IMPLICIT_LINK_DIRECTORIES_EXCLUDE "/usr/tce/packages/gcc/gcc-10.3.1/lib/gcc/x86_64-redhat-linux/10;/usr/tce/packages/gcc/gcc-10.3.1/lib64;/lib64;/usr/lib64;/lib;/usr/lib" CACHE STRING "") + +set(UMPIRE_FMT_TARGET "fmt::fmt" CACHE STRING "") diff --git a/host-configs/lc/toss_4_x86_64_ib_cray/amdclang.cmake b/host-configs/lc/toss_4_x86_64_ib_cray/amdclang.cmake index db1dafbd..57e52a4f 100644 --- a/host-configs/lc/toss_4_x86_64_ib_cray/amdclang.cmake +++ b/host-configs/lc/toss_4_x86_64_ib_cray/amdclang.cmake @@ -6,8 +6,8 @@ ############################################################################## # Set up software versions -set(ROCM_VERSION "6.2.0" CACHE PATH "") -set(GCC_VERSION "12.2.1" CACHE PATH "") +set(ROCM_VERSION "6.4.2" CACHE PATH "") +set(GCC_VERSION "13.3.1" CACHE PATH "") # Set up compilers set(COMPILER_BASE "/usr/tce/packages/rocmcc/rocmcc-${ROCM_VERSION}-magic" CACHE PATH "") diff --git a/src/chai/CMakeLists.txt b/src/chai/CMakeLists.txt index cd8d5d8a..027c7659 100644 --- a/src/chai/CMakeLists.txt +++ b/src/chai/CMakeLists.txt @@ -39,6 +39,14 @@ if(CHAI_DISABLE_RM) ManagedArray_thin.inl) endif () +if (CHAI_ENABLE_EXPERIMENTAL) + set(chai_headers + ${chai_headers} + expt/Context.hpp + expt/ContextManager.hpp + expt/DualArray.hpp) +endif () + set (chai_sources ArrayManager.cpp) diff --git a/src/chai/expt/Array.hpp b/src/chai/expt/Array.hpp new file mode 100644 index 00000000..705ff799 --- /dev/null +++ b/src/chai/expt/Array.hpp @@ -0,0 +1,159 @@ +#ifndef CHAI_MANAGED_ARRAY_HPP +#define CHAI_MANAGED_ARRAY_HPP + +#include "chai/expt/ArrayManager.hpp" +#include + +namespace chai { +namespace expt { + template + class Array { + public: + Array() = default; + + explicit Array(const ArrayType& manager) + : m_manager{manager} + { + } + + explicit Array(ArrayType&& manager) + : m_manager{std::move(manager)} + { + } + + Array(const Array& other) : + m_data{other.m_data}, + m_size{other.m_size}, + m_manager{other.m_manager} + { + update(); + } + + void resize(std::size_t newSize) { + m_data = nullptr; + m_size = newSize; + m_manager.resize(newSize); + } + + void free() { + m_data = nullptr; + m_size = 0; + m_manager.free(); + } + + CHAI_HOST_DEVICE std::size_t size() const + { + return m_size; + } + + CHAI_HOST_DEVICE void update() const + { +#if !defined(CHAI_DEVICE_COMPILE) + m_data = m_manager.data(!std::is_const_v); +#endif + } + + CHAI_HOST_DEVICE void cupdate() const + { +#if !defined(CHAI_DEVICE_COMPILE) + m_data = m_manager.data(false); +#endif + } + + /*! + * \brief Get a pointer to the element data in the specified context. + * + * \param context The context in which to retrieve the element data. + * + * \return A pointer to the element data in the specified context. + */ + ElementType* data() const + { + update(); + return m_data; + } + + /*! + * \brief Get a const pointer to the element data in the specified context. + * + * \param context The context in which to retrieve the const element data. + * + * \return A const pointer to the element data in the specified context. + */ + CHAI_HOST_DEVICE const ElementType* cdata() const { + cupdate(); + return m_data; + } + + /*! + * \brief Get the ith element in the array. + * + * \param i The index of the element to retrieve. + * + * \pre The copy constructor has been called with the execution space + * set to CPU or GPU (e.g. by the RAJA plugin). + */ + CHAI_HOST_DEVICE ElementType& operator[](std::size_t i) const + { + return m_data[i]; + } + + /*! + * \brief Get the value of the element at the specified index. + * + * \param i The index of the element to retrieve. + * + * \return The value of the element at the specified index. + * + * \throw std::runtime_exception if unable to retrieve the element. + */ + ElementType get(std::size_t i) const + { + return *(static_cast(m_manager.get(i*sizeof(ElementType), sizeof(ElementType)))); + } + + /*! + * \brief Set a value at a specified index in the array. + * + * \param i The index where the value is to be set. + * \param value The value to set at the specified index. + * + * \throw std::runtime_exception if the array manager is not associated with the Array. + */ + void set(std::size_t i, const ElementType& value) { + m_manager.set(i*sizeof(ElementType), sizeof(ElementType), static_cast(std::addressof(value))); + } + + private: + /*! + * The array that is coherent in the current execution space. + */ + ElementType* m_data{nullptr}; + + /*! + * The number of elements in the array. + */ + std::size_t m_size{0}; + + /*! + * The array manager controls the coherence of the array. + */ + ArrayType m_manager{}; + }; // class Array + + /*! + * \brief Constructs an array by creating a new manager object. + * + * \tparam ArrayManager The type of array manager. + * \tparam Args The type of the arguments used to construct the array manager. + * + * \param args The arguments to construct an array manager. + */ + template + Array makeArray(Args&&... args) { + return Array(new ArrayManager(std::forward(args)...)); + } +} // namespace expt +} // namespace chai + +#endif // CHAI_MANAGED_ARRAY_HPP diff --git a/src/chai/expt/ArrayPointer.hpp b/src/chai/expt/ArrayPointer.hpp new file mode 100644 index 00000000..0e8ada62 --- /dev/null +++ b/src/chai/expt/ArrayPointer.hpp @@ -0,0 +1,179 @@ +#ifndef CHAI_ARRAY_POINTER_HPP +#define CHAI_ARRAY_POINTER_HPP + +#include "chai/config.hpp" +#include "chai/ChaiMacros.hpp" +#include + +namespace chai::expt +{ + template typename ManagerType> + class ArrayPointer + { + public: + using Manager = ManagerType>; + + ArrayPointer() = default; + + CHAI_HOST_DEVICE ArrayPointer(std::nullptr_t) + : ArrayPointer() + { + } + + explicit ArrayPointer(Manager* array) + : m_manager{array} + { + update(); + } + + CHAI_HOST_DEVICE ArrayPointer(const ArrayPointer& other) + : m_data{other.m_data}, + m_size{other.m_size}, + m_manager{other.m_manager} + { + update(); + } + + template >> + CHAI_HOST_DEVICE ArrayPointer(const ArrayPointer& other) + : m_data{other.m_data}, + m_size{other.m_size}, + m_manager{other.m_manager} + { + update(); + } + + CHAI_HOST_DEVICE ArrayPointer& operator=(const ArrayPointer& other) + { + if (&other != this) + { + m_data = other.m_data; + m_size = other.m_size; + m_manager = other.m_manager; + + update(); + } + + return *this; + } + + CHAI_HOST_DEVICE ArrayPointer& operator=(std::nullptr_t) + { + m_data = nullptr; + m_size = 0; + m_manager = nullptr; + + return *this; + } + + void resize(std::size_t newSize) + { + if (m_manager == nullptr) + { + m_manager = new Manager(); + } + + m_data = nullptr; + m_size = newSize; + m_manager->resize(newSize); + + update(); + } + + void free() + { + m_data = nullptr; + m_size = 0; + delete m_manager; + m_manager = nullptr; + } + + CHAI_HOST_DEVICE std::size_t size() const + { + return m_size; + } + + CHAI_HOST_DEVICE void update() const + { +#if !defined(CHAI_DEVICE_COMPILE) + if (m_manager) + { + if (ElementType* data = m_manager->data(); data) + { + m_data = data; + } + + m_size = m_manager->size(); + } +#endif + } + + CHAI_HOST_DEVICE void cupdate() const + { +#if !defined(CHAI_DEVICE_COMPILE) + if (m_manager) + { + if (ElementType* data = m_manager->data(); data) + { + m_data = data; + } + + m_size = m_manager->size(); + } +#endif + } + + CHAI_HOST_DEVICE ElementType* data() const + { + update(); + return m_data; + } + + CHAI_HOST_DEVICE const ElementType* cdata() const + { + cupdate(); + return m_data; + } + + CHAI_HOST_DEVICE ElementType& operator[](std::size_t i) const + { + return m_data[i]; + } + + ElementType get(std::size_t i) + { + if (m_manager && i < m_manager->size()) + { + return m_manager->get(i); + } + else + { + throw std::out_of_range("Manager index out of bounds"); + } + } + + void set(std::size_t i, ElementType value) + { + if (m_manager && i < m_manager->size()) + { + m_manager->set(i, value); + } + else + { + throw std::out_of_range("Manager index out of bounds"); + } + } + + private: + mutable ElementType* m_data{nullptr}; + mutable std::size_t m_size{0}; + Manager* m_manager{nullptr}; + + /// Needed for the converting constructor + template typename OtherManagerType> + friend class ArrayPointer; + }; // class ArrayPointer +} // namespace chai::expt + +#endif // CHAI_ARRAY_POINTER_HPP \ No newline at end of file diff --git a/src/chai/expt/ArrayView.hpp b/src/chai/expt/ArrayView.hpp new file mode 100644 index 00000000..2b1e10bc --- /dev/null +++ b/src/chai/expt/ArrayView.hpp @@ -0,0 +1,71 @@ +#ifndef CHAI_ARRAY_VIEW_HPP +#define CHAI_ARRAY_VIEW_HPP + +namespace chai::expt +{ + template ArrayType> + class ArrayView + { + public: + using Array = std::conditional_t, const >, ArrayType>>; + + ArrayView() = default; + + explicit ArrayView(Array& array) + : m_size{array.size()}, + m_array{std::addressof(array)} + { + } + + ArrayView(const ArrayView& other) + : m_data{other.m_data}, + m_size{other.m_size}, + m_array{other.m_array} + { + update(); + } + + ArrayView& operator=(const ArrayView& other) = default; + + void update() const + { + if (m_array) + { + m_data = m_array->data(); + } + } + + std::size_t size() const + { + return m_size; + } + + T* data() const + { + update(); + return m_data; + } + + T& operator[](std::size_t i) const + { + return m_data[i]; + } + + T get(std::size_t i) const + { + return m_array->get(i); + } + + void set(std::size_t i, T value) const + { + m_array->set(i, value); + } + + private: + mutable T* m_data{nullptr}; + std::size_t m_size{0}; + Array* m_array{nullptr}; + }; // class ArrayView +} // namespace chai::expt + +#endif // CHAI_ARRAY_VIEW_HPP \ No newline at end of file diff --git a/src/chai/expt/Context.hpp b/src/chai/expt/Context.hpp new file mode 100644 index 00000000..326c24c0 --- /dev/null +++ b/src/chai/expt/Context.hpp @@ -0,0 +1,14 @@ +#ifndef CHAI_CONTEXT_HPP +#define CHAI_CONTEXT_HPP + +namespace chai::expt +{ + enum class Context + { + NONE = 0, + HOST = 1, + DEVICE = 2 + }; +} // namespace chai::expt + +#endif // CHAI_CONTEXT_HPP \ No newline at end of file diff --git a/src/chai/expt/ContextGuard.hpp b/src/chai/expt/ContextGuard.hpp new file mode 100644 index 00000000..43345452 --- /dev/null +++ b/src/chai/expt/ContextGuard.hpp @@ -0,0 +1,33 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-25, Lawrence Livermore National Security, LLC and CHAI +// project contributors. See the CHAI LICENSE file for details. +// +// SPDX-License-Identifier: BSD-3-Clause +////////////////////////////////////////////////////////////////////////////// + +#ifndef CHAI_CONTEXT_GUARD_HPP +#define CHAI_CONTEXT_GUARD_HPP + +#include "chai/expt/Context.hpp" +#include "chai/expt/ContextManager.hpp" + +namespace chai { +namespace expt { + class ContextGuard { + public: + explicit ContextGuard(Context context) { + m_context_manager.setContext(context); + } + + ~ContextGuard() { + m_context_manager.setContext(m_saved_context); + } + + private: + ContextManager& m_context_manager{ContextManager::getInstance()}; + Context m_saved_context{m_context_manager.getContext()}; + }; +} // namespace expt +} // namespace chai + +#endif // CHAI_CONTEXT_GUARD_HPP \ No newline at end of file diff --git a/src/chai/expt/ContextManager.hpp b/src/chai/expt/ContextManager.hpp new file mode 100644 index 00000000..5b7f962d --- /dev/null +++ b/src/chai/expt/ContextManager.hpp @@ -0,0 +1,147 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-25, Lawrence Livermore National Security, LLC and CHAI +// project contributors. See the CHAI LICENSE file for details. +// +// SPDX-License-Identifier: BSD-3-Clause +////////////////////////////////////////////////////////////////////////////// + +#ifndef CHAI_CONTEXT_MANAGER_HPP +#define CHAI_CONTEXT_MANAGER_HPP + +#include "chai/config.hpp" +#include "chai/expt/Context.hpp" +#include + +#if defined(CHAI_ENABLE_CUDA) +#include +#elif defined(CHAI_ENABLE_HIP) +#include +#endif + +namespace chai::expt { + /*! + * \class ContextManager + * + * \brief Singleton class for managing the current context. + * + * This class provides a centralized way to get and set the current + * context across the application. + */ + class ContextManager + { + public: + /*! + * \brief Get the singleton instance of ContextManager. + * + * \return The singleton instance. + */ + static ContextManager& getInstance() + { + static ContextManager s_instance; + return s_instance; + } + + /*! + * \brief Deleted copy constructor to prevent copying. + */ + ContextManager(const ContextManager&) = delete; + + /*! + * \brief Deleted assignment operator to prevent assignment. + */ + ContextManager& operator=(const ContextManager&) = delete; + + /*! + * \brief Get the current context. + * + * \return The current context. + */ + Context getContext() const + { + return m_context; + } + + /*! + * \brief Set the current context. + * + * \param context The new context to set. + */ + void setContext(Context context) + { + m_context = context; + + if (context == Context::DEVICE) + { + m_device_synchronized = false; + } + } + + /*! + * \brief Synchronize the given context. + * + * \param context The context that needs synchronization. + */ + void synchronize(Context context) + { + if (context == Context::DEVICE && !m_device_synchronized) + { +#if defined(CHAI_ENABLE_CUDA) + cudaDeviceSynchronize(); +#elif defined(CHAI_ENABLE_HIP) + hipDeviceSynchronize(); +#endif + m_device_synchronized = true; + } + } + + /*! + * \brief Check if a specific context needs synchronization. + * + * \param context The context to check. + * \return True if the context needs synchronization, false otherwise. + */ + bool isSynchronized(Context context) const + { + return context == Context::DEVICE ? m_device_synchronized : true; + } + + /*! + * \brief Mark the given context as synchronized. + * + * This should only be called after synchronization has been performed. + * + * \param context The context to clear the synchronization flag for. + */ + void setSynchronized(Context context, bool synchronized) + { + if (context == Context::DEVICE) + { + m_device_synchronized = synchronized; + } + } + + void reset() + { + m_context = Context::NONE; + m_device_synchronized = true; + } + + private: + /*! + * \brief Private constructor for singleton pattern. + */ + ContextManager() = default; + + /*! + * \brief The current context. + */ + Context m_context{Context::NONE}; + + /*! + * \brief Map for tracking which contexts are synchronized. + */ + bool m_device_synchronized{true}; + }; // class ContextManager +} // namespace chai::expt + +#endif // CHAI_CONTEXT_MANAGER_HPP diff --git a/src/chai/expt/DeviceArray.hpp b/src/chai/expt/DeviceArray.hpp new file mode 100644 index 00000000..e2a3b159 --- /dev/null +++ b/src/chai/expt/DeviceArray.hpp @@ -0,0 +1,157 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-25, Lawrence Livermore National Security, LLC and CHAI +// project contributors. See the CHAI LICENSE file for details. +// +// SPDX-License-Identifier: BSD-3-Clause +////////////////////////////////////////////////////////////////////////////// +#ifndef CHAI_DEVICE_ARRAY_HPP +#define CHAI_DEVICE_ARRAY_HPP + +namespace chai::expt +{ + template + class DeviceArray { + public: + DeviceArray() = default; + + explicit DeviceArray(const umpire::Allocator& allocator) + : m_allocator{allocator} + { + } + + DeviceArray(std::size_t size, const umpire::Allocator& allocator = umpire::ResourceManager::getInstance().getAllocator("DEVICE")) + : m_allocator{allocator} + { + resize(size); + } + + DeviceArray(const DeviceArray& other) + : m_allocator{other.m_allocator} + { + resize(other.m_size); + umpire::ResourceManager::getInstance().copy(other.m_data, m_data, m_size * sizeof(T)); + } + + DeviceArray(DeviceArray&& other) + : m_data{other.m_data}, + m_size{other.m_size}, + m_allocator{other.m_allocator} + { + other.m_data = nullptr; + other.m_size = 0; + } + + ~DeviceArray() + { + m_allocator.deallocate(m_data); + } + + DeviceArray& operator=(const DeviceArray& other) + { + if (&other != this) + { + m_allocator.deallocate(m_data); + + m_allocator = other.m_allocator; + m_size = other.m_size; + m_data = static_cast(m_allocator.allocate(m_size * sizeof(T))); + umpire::ResourceManager::getInstance().copy(other.m_data, m_data, m_size * sizeof(T)); + } + + return *this; + } + + DeviceArray& operator=(DeviceArray&& other) + { + if (&other != this) + { + m_allocator.deallocate(m_data); + + m_data = other.m_data; + m_size = other.m_size; + m_allocator = other.m_allocator; + + other.m_data = nullptr; + other.m_size = 0; + } + + return *this; + } + + void resize(size_t newSize) + { + if (newSize != m_size) + { + T* newData = nullptr; + + if (newSize > 0) + { + std::size_t newSizeBytes = newSize * sizeof(T); + newData = static_cast(m_allocator.allocate(newSizeBytes)); + + if constexpr (std::is_trivially_copyable_v) + { + std::memcpy(newData, m_data, std::min(newSizeBytes, m_size * sizeof(T))); + } + else + { + std::copy_n(m_data, std::min(newSize, m_size), newData); + } + } + + m_allocator.deallocate(m_data); + m_data = newData; + m_size = newSize; + } + } + + void free() + { + m_allocator.deallocate(m_data); + m_data = nullptr; + m_size = 0; + } + + size_t size() const + { + return m_size; + } + + T* data() + { + return m_data; + } + + const T* data() const + { + return m_data; + } + + T& operator[](std::size_t i) + { + return m_data[i]; + } + + const T& operator[](std::size_t i) const + { + return m_data[i]; + } + + T get(std::size_t i) const + { + return m_data[i]; + } + + void set(std::size_t i, T value) + { + m_data[i] = value; + } + + private: + T* m_data{nullptr}; + std::size_t m_size{0}; + umpire::Allocator m_allocator{umpire::ResourceManager::getInstance().getAllocator("DEVICE")}; + }; // class DeviceArray +} // namespace chai::expt + +#endif // CHAI_DEVICE_ARRAY_HPP \ No newline at end of file diff --git a/src/chai/expt/DualArray.hpp b/src/chai/expt/DualArray.hpp new file mode 100644 index 00000000..fa0bf3e9 --- /dev/null +++ b/src/chai/expt/DualArray.hpp @@ -0,0 +1,340 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-25, Lawrence Livermore National Security, LLC and CHAI +// project contributors. See the CHAI LICENSE file for details. +// +// SPDX-License-Identifier: BSD-3-Clause +////////////////////////////////////////////////////////////////////////////// +#ifndef CHAI_DUAL_ARRAY_HPP +#define CHAI_DUAL_ARRAY_HPP + +#include "chai/expt/Context.hpp" +#include "chai/expt/ContextManager.hpp" +#include "umpire/Allocator.hpp" +#include "umpire/ResourceManager.hpp" +#include + +namespace chai::expt +{ + template + class DualArray { + public: + DualArray() = default; + + DualArray(const umpire::Allocator& host_allocator, + const umpire::Allocator& device_allocator) + : m_host_allocator{host_allocator}, + m_device_allocator{device_allocator} + { + } + + explicit DualArray(std::size_t size, + const umpire::Allocator& host_allocator = umpire::ResourceManager::getInstance().getAllocator("HOST"), + const umpire::Allocator& device_allocator = umpire::ResourceManager::getInstance().getAllocator("DEVICE")) + : m_host_allocator{host_allocator}, + m_device_allocator{device_allocator} + { + resize(size); + } + + DualArray(const DualArray& other) + : m_host_allocator{other.m_host_allocator}, + m_device_allocator{other.m_device_allocator} + { + resize(other.m_size); + umpire::ResourceManager::getInstance().copy(other.m_device_data, m_device_data, m_size * sizeof(T)); + } + + DualArray(DualArray&& other) + : m_host_data{other.m_host_data}, + m_device_data{other.m_device_data}, + m_size{other.m_size}, + m_modified{other.m_modified}, + m_host_allocator{other.m_host_allocator}, + m_device_allocator{other.m_device_allocator} + { + other.m_host_data = nullptr; + other.m_device_data = nullptr; + other.m_size = 0; + other.m_modified = Context::NONE; + } + + ~DualArray() + { + m_host_allocator.deallocate(m_host_data); + m_device_allocator.deallocate(m_device_data); + } + + DualArray& operator=(const DualArray& other) + { + if (&other != this) + { + m_host_allocator.deallocate(m_host_data); + m_host_data = nullptr; + + m_device_allocator.deallocate(m_device_data); + m_device_data = nullptr; + + m_size = 0; + + m_host_allocator = other.m_host_allocator; + m_device_allocator = other.m_device_allocator; + + resize(other.m_size); + // TODO: Fix the copy + umpire::ResourceManager::getInstance().copy(other.m_device_data, m_device_data, m_size * sizeof(T)); + } + + return *this; + } + + DualArray& operator=(DualArray&& other) + { + if (&other != this) + { + m_host_allocator.deallocate(m_host_data); + m_device_allocator.deallocate(m_device_data); + + m_host_data = other.m_host_data; + m_device_data = other.m_device_data; + m_size = other.m_size; + m_modified = other.m_modified; + m_host_allocator = other.m_host_allocator; + m_device_allocator = other.m_device_allocator; + + other.m_host_data = nullptr; + other.m_device_data = nullptr; + other.m_size = 0; + other.m_modified = Context::NONE; + } + + return *this; + } + + void resize(std::size_t new_size) + { + if (new_size != m_size) + { + std::size_t old_size_bytes = m_size * sizeof(T); + std::size_t new_size_bytes = new_size * sizeof(T); + + if (m_modified == Context::HOST || + (m_host_data && !m_device_data)) + { + if (m_device_data) + { + m_device_allocator.deallocate(m_device_data); + m_device_data = nullptr; + } + + T* new_host_data = nullptr; + + if (new_size > 0) + { + new_host_data = static_cast(m_host_allocator.allocate(new_size_bytes)); + } + + if (m_host_data) + { + umpire::ResourceManager::getInstance().copy(m_host_data, new_host_data, std::min(old_size_bytes, new_size_bytes)); + m_host_allocator.deallocate(m_host_data); + } + + m_host_data = new_host_data; + } + else + { + if (m_host_data) + { + m_host_allocator.deallocate(m_host_data); + m_host_data = nullptr; + } + + T* new_device_data = nullptr; + + if (new_size > 0) + { + new_device_data = static_cast(m_device_allocator.allocate(new_size_bytes)); + } + + if (m_device_data) + { + umpire::ResourceManager::getInstance().copy(m_device_data, new_device_data, std::min(old_size_bytes, new_size_bytes)); + m_device_allocator.deallocate(m_device_data); + } + + m_device_data = new_device_data; + } + + m_size = new_size; + } + } + + void free() + { + m_host_allocator.deallocate(m_host_data); + m_host_data = nullptr; + + m_device_allocator.deallocate(m_device_data); + m_device_data = nullptr; + + m_size = 0; + m_modified = Context::NONE; + } + + std::size_t size() const + { + return m_size; + } + + T* data() + { + Context context = + ContextManager::getInstance().getContext(); + + if (context == Context::DEVICE) + { + if (m_device_data == nullptr) + { + m_device_data = static_cast(m_device_allocator.allocate(m_size * sizeof(T))); + } + + if (m_modified == Context::HOST) + { + umpire::ResourceManager::getInstance().copy(m_host_data, m_device_data, m_size * sizeof(T)); + } + + m_modified = Context::DEVICE; + return m_device_data; + } + else if (context == Context::HOST) + { + if (m_host_data == nullptr) + { + m_host_data = static_cast(m_host_allocator.allocate(m_size * sizeof(T))); + } + + if (m_modified == Context::DEVICE) + { + umpire::ResourceManager::getInstance().copy(m_device_data, m_host_data, m_size * sizeof(T)); + } + + m_modified = Context::HOST; + return m_host_data; + } + else + { + return nullptr; + } + } + + const T* data() const + { + Context context = + ContextManager::getInstance().getContext(); + + if (context == Context::DEVICE) + { + if (m_device_data == nullptr) + { + m_device_data = static_cast(m_device_allocator.allocate(m_size * sizeof(T))); + } + + if (m_modified == Context::HOST) + { + umpire::ResourceManager::getInstance().copy(m_host_data, m_device_data, m_size * sizeof(T)); + m_modified = Context::NONE; + } + + return m_device_data; + } + else if (context == Context::HOST) + { + if (m_host_data == nullptr) + { + m_host_data = static_cast(m_host_allocator.allocate(m_size * sizeof(T))); + } + + if (m_modified == Context::DEVICE) + { + umpire::ResourceManager::getInstance().copy(m_device_data, m_host_data, m_size * sizeof(T)); + m_modified = Context::NONE; + } + + return m_host_data; + } + else + { + return nullptr; + } + } + + T get(std::size_t i) const + { + T result; + + if (m_modified == Context::DEVICE) + { + umpire::ResourceManager::getInstance().copy(m_device_data + i, &result, sizeof(T)); + } + else + { + result = m_host_data[i]; + } + + return result; + } + + void set(std::size_t i, T value) + { + if (m_modified == Context::DEVICE) + { + umpire::ResourceManager::getInstance().copy(&value, m_device_data + i, sizeof(T)); + } + else + { + if (m_host_data == nullptr) + { + m_host_data = static_cast(m_host_allocator.allocate(m_size * sizeof(T))); + } + + m_host_data[i] = value; + m_modified = Context::HOST; + } + } + + T* host_data() + { + return m_host_data; + } + + T* device_data() + { + return m_device_data; + } + + Context modified() const + { + return m_modified; + } + + umpire::Allocator host_allocator() const + { + return m_host_allocator; + } + + umpire::Allocator device_allocator() const + { + return m_device_allocator; + } + + private: + mutable T* m_host_data{nullptr}; + mutable T* m_device_data{nullptr}; + std::size_t m_size{0}; + mutable Context m_modified{Context::NONE}; + mutable umpire::Allocator m_host_allocator{umpire::ResourceManager::getInstance().getAllocator("HOST")}; + mutable umpire::Allocator m_device_allocator{umpire::ResourceManager::getInstance().getAllocator("DEVICE")}; + }; // class DualArray +} // namespace chai::expt + +#endif // CHAI_DUAL_ARRAY_HPP \ No newline at end of file diff --git a/src/chai/expt/HostArray.hpp b/src/chai/expt/HostArray.hpp new file mode 100644 index 00000000..1c1278b4 --- /dev/null +++ b/src/chai/expt/HostArray.hpp @@ -0,0 +1,173 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-25, Lawrence Livermore National Security, LLC and CHAI +// project contributors. See the CHAI LICENSE file for details. +// +// SPDX-License-Identifier: BSD-3-Clause +////////////////////////////////////////////////////////////////////////////// +#ifndef CHAI_HOST_ARRAY_HPP +#define CHAI_HOST_ARRAY_HPP + +namespace chai::expt +{ + template + class HostArray { + public: + HostArray() = default; + + explicit HostArray(const umpire::Allocator& allocator) + : m_allocator{allocator} + { + } + + HostArray(std::size_t size, const umpire::Allocator& allocator = umpire::ResourceManager::getInstance().getAllocator("HOST")) + : m_allocator{allocator} + { + resize(size); + } + + HostArray(const HostArray& other) + : m_allocator{other.m_allocator} + { + resize(other.m_size); + + if constexpr (std::is_trivially_copyable_v) + { + std::memcpy(m_data, other.m_data, m_size * sizeof(T)); + } + else + { + std::copy_n(other.m_data, m_size, m_data); + } + } + + HostArray(HostArray&& other) + : m_data{other.m_data}, + m_size{other.m_size}, + m_allocator{other.m_allocator} + { + other.m_data = nullptr; + other.m_size = 0; + } + + ~HostArray() + { + m_allocator.deallocate(m_data); + } + + HostArray& operator=(const HostArray& other) + { + if (&other != this) + { + m_allocator.deallocate(m_data); + + m_allocator = other.m_allocator; + m_size = other.m_size; + m_data = static_cast(m_allocator.allocate(m_size * sizeof(T))); + + if constexpr (std::is_trivially_copyable_v) + { + std::memcpy(m_data, other.m_data, m_size * sizeof(T)); + } + else + { + std::copy_n(other.m_data, m_size, m_data); + } + } + + return *this; + } + + HostArray& operator=(HostArray&& other) + { + if (&other != this) + { + m_allocator.deallocate(m_data); + + m_data = other.m_data; + m_size = other.m_size; + m_allocator = other.m_allocator; + + other.m_data = nullptr; + other.m_size = 0; + } + + return *this; + } + + void resize(size_t newSize) + { + if (newSize != m_size) + { + T* newData = nullptr; + + if (newSize > 0) + { + std::size_t newSizeBytes = newSize * sizeof(T); + newData = static_cast(m_allocator.allocate(newSizeBytes)); + + if constexpr (std::is_trivially_copyable_v) + { + std::memcpy(newData, m_data, std::min(newSizeBytes, m_size * sizeof(T))); + } + else + { + std::copy_n(m_data, std::min(newSize, m_size), newData); + } + } + + m_allocator.deallocate(m_data); + m_data = newData; + m_size = newSize; + } + } + + void free() + { + m_allocator.deallocate(m_data); + m_data = nullptr; + m_size = 0; + } + + size_t size() const + { + return m_size; + } + + T* data() + { + return m_data; + } + + const T* data() const + { + return m_data; + } + + T& operator[](std::size_t i) + { + return m_data[i]; + } + + const T& operator[](std::size_t i) const + { + return m_data[i]; + } + + T get(std::size_t i) const + { + return m_data[i]; + } + + void set(std::size_t i, T value) + { + m_data[i] = value; + } + + private: + T* m_data{nullptr}; + std::size_t m_size{0}; + umpire::Allocator m_allocator{umpire::ResourceManager::getInstance().getAllocator("HOST")}; + }; // class HostArray +} // namespace chai::expt + +#endif // CHAI_HOST_ARRAY_HPP \ No newline at end of file diff --git a/src/chai/expt/HostArrayPointer.hpp b/src/chai/expt/HostArrayPointer.hpp new file mode 100644 index 00000000..0f16d147 --- /dev/null +++ b/src/chai/expt/HostArrayPointer.hpp @@ -0,0 +1,87 @@ +#ifndef CHAI_HOST_ARRAY_POINTER_HPP +#define CHAI_HOST_ARRAY_POINTER_HPP + +#include "chai/expt/HostArray.hpp" + +namespace chai::expt +{ + template + class HostArrayPointer + { + public: + using HostArrayType = std::conditional_t, const HostArray>, HostArray>>; + + HostArrayPointer() = default; + + HostArrayPointer(HostArrayType* array) + : m_array{array} + { + } + + HostArrayPointer(const HostArrayPointer& other) + : m_data{other.m_data}, + m_size{other.m_size}, + m_array{other.m_array} + { + update(); + } + + HostArrayPointer& operator=(const HostArrayPointer& other) = default; + + void update() const + { + if (m_array) + { + m_data = m_array->data(); + } + } + + void resize(std::size_t newSize) + { + m_data = nullptr; + m_size = newSize; + m_array->resize(newSize); + } + + void free() + { + m_data = nullptr; + m_size = 0; + delete m_array; + m_array = nullptr; + } + + std::size_t size() const + { + return m_size; + } + + T* data() const + { + update(); + return m_data; + } + + T& operator[](std::size_t i) const + { + return m_data[i]; + } + + T get(std::size_t i) const + { + return m_array->get(i); + } + + void set(std::size_t i, T value) const + { + m_array->set(i, value); + } + + private: + T* m_data{nullptr}; + std::size_t m_size{0}; + HostArrayType* m_array{nullptr}; + }; // class HostArrayPointer +} // namespace chai::expt + +#endif // CHAI_HOST_ARRAY_POINTER_HPP \ No newline at end of file diff --git a/src/chai/expt/HostArraySharedPointer.hpp b/src/chai/expt/HostArraySharedPointer.hpp new file mode 100644 index 00000000..6a5fc0d2 --- /dev/null +++ b/src/chai/expt/HostArraySharedPointer.hpp @@ -0,0 +1,80 @@ +#ifndef CHAI_HOST_ARRAY_SHARED_POINTER_HPP +#define CHAI_HOST_ARRAY_SHARED_POINTER_HPP + +#include "chai/expt/HostArray.hpp" + +namespace chai::expt +{ + template + class HostArraySharedPointer + { + public: + using HostArrayType = std::conditional_t, const HostArray>, HostArray>>; + using SharedPointerType = std::shared_ptr; + + HostArraySharedPointer() = default; + + HostArraySharedPointer(const SharedPointerType& array) + : m_array{array} + { + } + + HostArraySharedPointer(const HostArraySharedPointer& other) + : m_data{other.m_data}, + m_size{other.m_size}, + m_array{other.m_array} + { + update(); + } + + HostArraySharedPointer& operator=(const HostArraySharedPointer& other) = default; + + void update() const + { + if (m_array) + { + m_data = m_array->data(); + } + } + + void resize(std::size_t newSize) + { + m_data = nullptr; + m_size = newSize; + m_array->resize(newSize); + } + + std::size_t size() const + { + return m_size; + } + + T* data() const + { + update(); + return m_data; + } + + T& operator[](std::size_t i) const + { + return m_data[i]; + } + + T get(std::size_t i) const + { + return m_array->get(i); + } + + void set(std::size_t i, T value) const + { + m_array->set(i, value); + } + + private: + T* m_data{nullptr}; + std::size_t m_size{0}; + SharedPointerType m_array{nullptr}; + }; // class HostArraySharedPointer +} // namespace chai::expt + +#endif // CHAI_HOST_ARRAY_SHARED_POINTER_HPP \ No newline at end of file diff --git a/src/chai/expt/HostArrayView.hpp b/src/chai/expt/HostArrayView.hpp new file mode 100644 index 00000000..510de0ae --- /dev/null +++ b/src/chai/expt/HostArrayView.hpp @@ -0,0 +1,73 @@ +#ifndef CHAI_HOST_ARRAY_VIEW_HPP +#define CHAI_HOST_ARRAY_VIEW_HPP + +#include "chai/expt/HostArray.hpp" + +namespace chai::expt +{ + template + class HostArrayView + { + public: + using HostArrayType = std::conditional_t, const HostArray>, HostArray>>; + + HostArrayView() = default; + + HostArrayView(HostArrayType& array) + : m_size{array.size()}, + m_array{std::addressof(array)} + { + } + + HostArrayView(const HostArrayView& other) + : m_data{other.m_data}, + m_size{other.m_size}, + m_array{other.m_array} + { + update(); + } + + HostArrayView& operator=(const HostArrayView& other) = default; + + void update() const + { + if (m_array) + { + m_data = m_array->data(); + } + } + + std::size_t size() const + { + return m_size; + } + + T* data() const + { + update(); + return m_data; + } + + T& operator[](std::size_t i) const + { + return m_data[i]; + } + + T get(std::size_t i) const + { + return m_array->get(i); + } + + void set(std::size_t i, T value) const + { + m_array->set(i, value); + } + + private: + mutable T* m_data{nullptr}; + std::size_t m_size{0}; + HostArrayType* m_array{nullptr}; + }; // class HostArrayView +} // namespace chai::expt + +#endif // CHAI_HOST_ARRAY_VIEW_HPP \ No newline at end of file diff --git a/src/chai/expt/HostDeviceArrayManager.hpp b/src/chai/expt/HostDeviceArrayManager.hpp new file mode 100644 index 00000000..67da5e5c --- /dev/null +++ b/src/chai/expt/HostDeviceArrayManager.hpp @@ -0,0 +1,345 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-25, Lawrence Livermore National Security, LLC and CHAI +// project contributors. See the CHAI LICENSE file for details. +// +// SPDX-License-Identifier: BSD-3-Clause +////////////////////////////////////////////////////////////////////////////// +#ifndef CHAI_HOST_DEVICE_ARRAY_MANAGER_HPP +#define CHAI_HOST_DEVICE_ARRAY_MANAGER_HPP + +#include "chai/expt/Context.hpp" +#include "chai/expt/ContextManager.hpp" +#include "umpire/Allocator.hpp" +#include "umpire/ResourceManager.hpp" +#include + +namespace chai::expt +{ + template + class HostDeviceArrayManager { + public: + HostDeviceArrayManager() = default; + + HostDeviceArrayManager(const umpire::Allocator& host_allocator, + const umpire::Allocator& device_allocator) + : m_host_allocator{host_allocator}, + m_device_allocator{device_allocator} + { + } + + explicit HostDeviceArrayManager(std::size_t size) + { + resize(size); + } + + HostDeviceArrayManager(std::size_t size, + const umpire::Allocator& host_allocator, + const umpire::Allocator& device_allocator) + : m_host_allocator{host_allocator}, + m_device_allocator{device_allocator} + { + resize(size); + } + + HostDeviceArrayManager(const HostDeviceArrayManager& other) + : m_host_allocator{other.m_host_allocator}, + m_device_allocator{other.m_device_allocator} + { + resize(other.m_size); + umpire::ResourceManager::getInstance().copy(other.m_device_data, m_device_data, m_size * sizeof(T)); + } + + HostDeviceArrayManager(HostDeviceArrayManager&& other) + : m_host_data{other.m_host_data}, + m_device_data{other.m_device_data}, + m_size{other.m_size}, + m_modified{other.m_modified}, + m_host_allocator{other.m_host_allocator}, + m_device_allocator{other.m_device_allocator} + { + other.m_host_data = nullptr; + other.m_device_data = nullptr; + other.m_size = 0; + other.m_modified = Context::NONE; + } + + ~HostDeviceArrayManager() + { + m_host_allocator.deallocate(m_host_data); + m_device_allocator.deallocate(m_device_data); + } + + HostDeviceArrayManager& operator=(const HostDeviceArrayManager& other) + { + if (&other != this) + { + m_host_allocator.deallocate(m_host_data); + m_host_data = nullptr; + + m_device_allocator.deallocate(m_device_data); + m_device_data = nullptr; + + m_size = 0; + + m_host_allocator = other.m_host_allocator; + m_device_allocator = other.m_device_allocator; + + resize(other.m_size); + // TODO: Fix the copy + umpire::ResourceManager::getInstance().copy(other.m_device_data, m_device_data, m_size * sizeof(T)); + } + + return *this; + } + + HostDeviceArrayManager& operator=(HostDeviceArrayManager&& other) + { + if (&other != this) + { + m_host_allocator.deallocate(m_host_data); + m_device_allocator.deallocate(m_device_data); + + m_host_data = other.m_host_data; + m_device_data = other.m_device_data; + m_size = other.m_size; + m_modified = other.m_modified; + m_host_allocator = other.m_host_allocator; + m_device_allocator = other.m_device_allocator; + + other.m_host_data = nullptr; + other.m_device_data = nullptr; + other.m_size = 0; + other.m_modified = Context::NONE; + } + + return *this; + } + + void resize(std::size_t new_size) + { + if (new_size != m_size) + { + std::size_t old_size_bytes = m_size * sizeof(T); + std::size_t new_size_bytes = new_size * sizeof(T); + + if (m_modified == Context::HOST || + (m_host_data && !m_device_data)) + { + if (m_device_data) + { + m_device_allocator.deallocate(m_device_data); + m_device_data = nullptr; + } + + T* new_host_data = nullptr; + + if (new_size > 0) + { + new_host_data = static_cast(m_host_allocator.allocate(new_size_bytes)); + } + + if (m_host_data) + { + umpire::ResourceManager::getInstance().copy(m_host_data, new_host_data, std::min(old_size_bytes, new_size_bytes)); + m_host_allocator.deallocate(m_host_data); + } + + m_host_data = new_host_data; + } + else + { + if (m_host_data) + { + m_host_allocator.deallocate(m_host_data); + m_host_data = nullptr; + } + + T* new_device_data = nullptr; + + if (new_size > 0) + { + new_device_data = static_cast(m_device_allocator.allocate(new_size_bytes)); + } + + if (m_device_data) + { + umpire::ResourceManager::getInstance().copy(m_device_data, new_device_data, std::min(old_size_bytes, new_size_bytes)); + m_device_allocator.deallocate(m_device_data); + } + + m_device_data = new_device_data; + } + + m_size = new_size; + } + } + + void free() + { + m_host_allocator.deallocate(m_host_data); + m_host_data = nullptr; + + m_device_allocator.deallocate(m_device_data); + m_device_data = nullptr; + + m_size = 0; + m_modified = Context::NONE; + } + + std::size_t size() const + { + return m_size; + } + + T* data() + { + Context context = + ContextManager::getInstance().getContext(); + + if (context == Context::DEVICE) + { + if (m_device_data == nullptr) + { + m_device_data = static_cast(m_device_allocator.allocate(m_size * sizeof(T))); + } + + if (m_modified == Context::HOST) + { + umpire::ResourceManager::getInstance().copy(m_host_data, m_device_data, m_size * sizeof(T)); + } + + m_modified = Context::DEVICE; + return m_device_data; + } + else if (context == Context::HOST) + { + if (m_host_data == nullptr) + { + m_host_data = static_cast(m_host_allocator.allocate(m_size * sizeof(T))); + } + + if (m_modified == Context::DEVICE) + { + umpire::ResourceManager::getInstance().copy(m_device_data, m_host_data, m_size * sizeof(T)); + } + + m_modified = Context::HOST; + return m_host_data; + } + else + { + return nullptr; + } + } + + const T* data() const + { + Context context = + ContextManager::getInstance().getContext(); + + if (context == Context::DEVICE) + { + if (m_device_data == nullptr) + { + m_device_data = static_cast(m_device_allocator.allocate(m_size * sizeof(T))); + } + + if (m_modified == Context::HOST) + { + umpire::ResourceManager::getInstance().copy(m_host_data, m_device_data, m_size * sizeof(T)); + m_modified = Context::NONE; + } + + return m_device_data; + } + else if (context == Context::HOST) + { + if (m_host_data == nullptr) + { + m_host_data = static_cast(m_host_allocator.allocate(m_size * sizeof(T))); + } + + if (m_modified == Context::DEVICE) + { + umpire::ResourceManager::getInstance().copy(m_device_data, m_host_data, m_size * sizeof(T)); + m_modified = Context::NONE; + } + + return m_host_data; + } + else + { + return nullptr; + } + } + + T get(std::size_t i) const + { + T result; + + if (m_modified == Context::DEVICE) + { + umpire::ResourceManager::getInstance().copy(m_device_data + i, &result, sizeof(T)); + } + else + { + result = m_host_data[i]; + } + + return result; + } + + void set(std::size_t i, T value) + { + if (m_modified == Context::DEVICE) + { + umpire::ResourceManager::getInstance().copy(&value, m_device_data + i, sizeof(T)); + } + else + { + if (m_host_data == nullptr) + { + m_host_data = static_cast(m_host_allocator.allocate(m_size * sizeof(T))); + } + + m_host_data[i] = value; + m_modified = Context::HOST; + } + } + + T* host_data() + { + return m_host_data; + } + + T* device_data() + { + return m_device_data; + } + + Context modified() const + { + return m_modified; + } + + umpire::Allocator host_allocator() const + { + return m_host_allocator; + } + + umpire::Allocator device_allocator() const + { + return m_device_allocator; + } + + private: + mutable T* m_host_data{nullptr}; + mutable T* m_device_data{nullptr}; + std::size_t m_size{0}; + mutable Context m_modified{Context::NONE}; + mutable umpire::Allocator m_host_allocator{umpire::ResourceManager::getInstance().getAllocator("HOST")}; + mutable umpire::Allocator m_device_allocator{umpire::ResourceManager::getInstance().getAllocator("DEVICE")}; + }; // class HostDeviceArrayManager +} // namespace chai::expt + +#endif // CHAI_HOST_DEVICE_ARRAY_MANAGER_HPP \ No newline at end of file diff --git a/src/chai/expt/UnifiedArray.hpp b/src/chai/expt/UnifiedArray.hpp new file mode 100644 index 00000000..15f5aad6 --- /dev/null +++ b/src/chai/expt/UnifiedArray.hpp @@ -0,0 +1,222 @@ +#ifndef CHAI_UNIFIED_ARRAY_HPP +#define CHAI_UNIFIED_ARRAY_HPP + +#include "chai/expt/ExecutionContext.hpp" +#include "umpire/ResourceManager.hpp" + +namespace chai { +namespace expt { + /*! + * \class UnifiedArray + * + * \brief A container for managing the lifetime and coherence of a + * unified memory array, meaning an array with a single address + * that is accessible from all processors/devices in a system. + * + * This container should be used in tandem with the ExecutionContextManager. + * Together, they provide a programming model where work (e.g. a kernel) + * is generally performed asynchronously, with synchronization occurring + * only as needed for coherence of the array. For example, if the array is + * written to in an asynchronize kernel on a GPU, then the GPU will be + * synchronized if the array needs to be accessed on the CPU. + * + * This model works well for APUs where the CPU and GPU have the same + * physical memory. It also works for pinned (i.e. page-locked) memory + * and in some cases for pageable memory, though no pre-fetching is + * performed. + * + * Example: + * + * \code + * // Create a UnifiedArray with size 100 and default allocator + * int size = 10000; + * UnifiedArray array(size); + * + * // Access elements on the device + * std::span device_view(array.data(ExecutionContext::DEVICE, array.size()); + * + * // Launch a kernel that modifies device_view. + * // Note that this example relies on c++20 and the ability to use constexpr + * // host code on the device. + * + * // Access elements on the host. This will synchronize the device. + * std::span host_view(array.data(ExecutionContext::HOST), array.size()); + * + * for (int i = 0; i < size; ++i) { + * std::cout << host_view[i] << "\n"; + * } + * + * // Access and modify individual elements in the container. + * // This should be used sparingly or it will tank performance. + * // Getting the last element after performing a scan is one use case. + * array.get(ExecutionContext::HOST, size - 1) = 10; + * \endcode + */ + template + class UnifiedArray + public: + UnifiedArray() = default; + + explicit UnifiedArray(const umpire::Allocator& allocator) : + m_allocator{allocator} + { + } + + UnifiedArray(std::size_t size, const umpire::Allocator& allocator) : + m_allocator{allocator} + { + resize(size); + } + + UnifiedArray(const UnifiedArray& other) + : m_allocator{other.m_allocator} + { + resize(other.m_size); + ExecutionContextManager::getInstance().setExecutionContext(ExecutionContext::DEVICE); + umpire::ResourceManager::getInstance().copy(other.m_data, m_data, m_size * sizeof(T)); + ExecutionContextManager::getInstance().setExecutionContext(ExecutionContext::NONE); + m_modified = ExecutionContext::DEVICE; + } + + UnifiedArray(UnifiedArray&& other) : + m_data{other.m_data}, + m_size{other.m_size}, + m_modified{other.m_modified}, + m_allocator{other.m_allocator} + { + other.m_data = nullptr; + other.m_size = 0; + other.m_modified = ExecutionContext::NONE; + } + + ~UnifiedArray() + { + m_allocator.deallocate(m_data); + } + + UnifiedArray& operator=(const UnifiedArray& other) { + if (&other != this) { // Prevent self-assignment + m_allocator.deallocate(m_data); + + m_allocator = other.m_allocator; + m_size = other.m_size; + m_data = static_cast(m_allocator.allocate(m_size * sizeof(T))); + + ExecutionContextManager::getInstance().setExecutionContext(ExecutionContext::DEVICE); + umpire::ResourceManager::getInstance().copy(other.m_data, m_data, m_size * sizeof(T)); + ExecutionContextManager::getInstance().setExecutionContext(ExecutionContext::NONE); + + m_modified = ExecutionContext::DEVICE; + } + + return *this; + } + + UnifiedArray& operator=(UnifiedArray&& other) { + if (&other != this) { + m_allocator.deallocate(m_data); + + m_data = other.m_data; + m_size = other.m_size; + m_modified = other.m_modified; + m_allocator = other.m_allocator; + + other.m_data = nullptr; + other.m_size = 0; + other.m_modified = ExecutionContext::NONE; + } + + return *this; + } + + void resize(std::size_t newSize) + { + if (newSize != m_size) + { + T* newData = nullptr; + + if (newSize > 0) + { + std::size_t newSizeBytes = newSize * sizeof(T); + ExecutionContextManager::getInstance().setExecutionContext(ExecutionContext::DEVICE); + newData = static_cast(m_allocator.allocate(newSizeBytes)); + umpire::ResourceManager::getInstance().copy(other.m_data, m_data, std::min(newSizeBytes, m_size * sizeof(T))); + ExecutionContextManager::getInstance().setExecutionContext(ExecutionContext::NONE); + m_modified = ExecutionContext::DEVICE; + } + + m_allocator.deallocate(m_data); + m_data = newData; + m_size = newSize; + } + } + + void free() + { + m_allocator.deallocate(m_data); + m_data = nullptr; + m_size = 0; + m_modified = ExecutionContext::NONE; + } + + /*! + * \brief Get the number of elements. + */ + size_t size() const + { + return m_size; + } + + T* data() + { + ExecutionContext executionContext = + ExecutionContextManager::getInstance().getExecutionContext(); + + if (executionContext != m_modified) { + ExecutionContextManager::getInstance().synchronize(m_modified); + m_modified = executionContext; + } + + return m_data; + } + + const T* data() const { + ExecutionContext executionContext = + ExecutionContextManager::getInstance().getExecutionContext(); + + if (executionContext != m_modified) { + ExecutionContextManager::getInstance().synchronize(m_modified); + m_modified = ExecutionContext::NONE; + } + + return m_data; + } + + T& get(ExecutionContext executionContext, size_t i) { + if (executionContext != m_modified) { + ExecutionContextManager::getInstance().synchronize(m_modified); + m_modified = executionContext; + } + + return m_data[i]; + } + + const T& get(ExecutionContext executionContext, size_t i) { + if (executionContext != m_modified) { + ExecutionContextManager::getInstance().synchronize(m_modified); + m_modified = ExecutionContext::NONE; + } + + return m_data[i]; + } + + private: + T* m_data{nullptr}; + size_t m_size{0}; + ExecutionContext m_modified{ExecutionContext::NONE}; + umpire::Allocator m_allocator{}; + }; // class UnifiedArray +} // namespace expt +} // namespace chai + +#endif // CHAI_UNIFIED_ARRAY_HPP diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 58c759df..584af100 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -8,3 +8,7 @@ add_subdirectory(install) add_subdirectory(unit) add_subdirectory(integration) + +if(CHAI_ENABLE_EXPERIMENTAL) + add_subdirectory(expt) +endif() \ No newline at end of file diff --git a/tests/expt/CMakeLists.txt b/tests/expt/CMakeLists.txt new file mode 100644 index 00000000..094f71e6 --- /dev/null +++ b/tests/expt/CMakeLists.txt @@ -0,0 +1,38 @@ +############################################################################## +# Copyright (c) 2016-25, Lawrence Livermore National Security, LLC and CHAI +# project contributors. See the CHAI LICENSE file for details. +# +# SPDX-License-Identifier: BSD-3-Clause +############################################################################## + +blt_add_executable( + NAME ContextManagerTests + SOURCES ContextManagerTests.cpp + INCLUDES ${PROJECT_BINARY_DIR}/include + DEPENDS_ON chai gtest) + +blt_add_test( + NAME ContextManagerTests + COMMAND ContextManagerTests) + +blt_add_executable( + NAME HostDeviceArrayPointerTests + SOURCES HostDeviceArrayPointerTests.cpp + INCLUDES ${PROJECT_BINARY_DIR}/include + DEPENDS_ON chai gtest) + +blt_add_test( + NAME HostDeviceArrayPointerTests + COMMAND HostDeviceArrayPointerTests) + +if(0) +blt_add_executable( + NAME DualArrayTests + SOURCES DualArrayTests.cpp + INCLUDES ${PROJECT_BINARY_DIR}/include + DEPENDS_ON chai gtest) + +blt_add_test( + NAME DualArrayTests + COMMAND DualArrayTests) +endif() diff --git a/tests/expt/ContextManagerTests.cpp b/tests/expt/ContextManagerTests.cpp new file mode 100644 index 00000000..38e03996 --- /dev/null +++ b/tests/expt/ContextManagerTests.cpp @@ -0,0 +1,30 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-25, Lawrence Livermore National Security, LLC and CHAI +// project contributors. See the CHAI LICENSE file for details. +// +// SPDX-License-Identifier: BSD-3-Clause +////////////////////////////////////////////////////////////////////////////// + +#include "chai/expt/ContextManager.hpp" +#include "gtest/gtest.h" + +// Test that getInstance returns the same object at the same place in memory +TEST(ContextManager, SingletonInstance) { + chai::expt::ContextManager& contextManager1 = chai::expt::ContextManager::getInstance(); + chai::expt::ContextManager& contextManager2 = chai::expt::ContextManager::getInstance(); + EXPECT_EQ(&contextManager1, &contextManager2); +} + +// Test that the default execution context is NONE +TEST(ContextManager, DefaultContext) { + chai::expt::ContextManager& contextManager = chai::expt::ContextManager::getInstance(); + EXPECT_EQ(contextManager.getContext(), chai::expt::Context::NONE); +} + +// Test setting and getting the execution context +TEST(ContextManager, Context) { + chai::expt::ContextManager& contextManager = chai::expt::ContextManager::getInstance(); + chai::expt::Context context = chai::expt::Context::HOST; + contextManager.setContext(context); + EXPECT_EQ(contextManager.getContext(), context); +} \ No newline at end of file diff --git a/tests/expt/DualArrayTests.cpp b/tests/expt/DualArrayTests.cpp new file mode 100644 index 00000000..1b7091c2 --- /dev/null +++ b/tests/expt/DualArrayTests.cpp @@ -0,0 +1,446 @@ +#include "chai/expt/DualArray.hpp" +#include "chai/expt/Context.hpp" +#include "chai/expt/ContextManager.hpp" +#include "umpire/Allocator.hpp" +#include "umpire/ResourceManager.hpp" +#include "umpire/strategy/QuickPool.hpp" +#include "gtest/gtest.h" + +enum class ContextManagerState +{ + CONTEXT_NONE_SYNCHRONIZED_DEVICE, + CONTEXT_HOST_SYNCHRONIZED_DEVICE, + CONTEXT_DEVICE_SYNCHRONIZED_DEVICE, + CONTEXT_NONE_UNSYNCHRONIZED_DEVICE, + CONTEXT_HOST_UNSYNCHRONIZED_DEVICE, + CONTEXT_DEVICE_UNSYNCHRONIZED_DEVICE +}; + +class DualArrayTest : public ::testing::Test +{ + protected: + static chai::expt::ContextManager& GetContextManager() + { + static chai::expt::ContextManager& s_context_manager = + chai::expt::ContextManager::getInstance(); + + return s_context_manager; + } + + static umpire::ResourceManager& GetResourceManager() + { + static umpire::ResourceManager& s_resource_manager = + umpire::ResourceManager::getInstance(); + + return s_resource_manager; + } + + static umpire::Allocator& GetDefaultHostAllocator() + { + static umpire::Allocator s_default_host_allocator = + GetResourceManager().getAllocator("HOST"); + + return s_default_host_allocator; + } + + static umpire::Allocator& GetDefaultDeviceAllocator() + { + static umpire::Allocator s_default_device_allocator = + GetResourceManager().getAllocator("DEVICE"); + + return s_default_device_allocator; + } + + static umpire::Allocator& GetCustomHostAllocator() + { + static umpire::Allocator s_custom_host_allocator = + GetResourceManager().makeAllocator( + "HOST_CUSTOM", GetDefaultHostAllocator()); + + return s_custom_host_allocator; + } + + static umpire::Allocator& GetCustomDeviceAllocator() + { + static umpire::Allocator s_custom_device_allocator = + GetResourceManager().makeAllocator( + "DEVICE_CUSTOM", GetDefaultDeviceAllocator()); + + return s_custom_device_allocator; + } + + static const std::array& GetContextManagerStates() + { + static constexpr std::array s_context_manager_states{ + ContextManagerState::CONTEXT_NONE_SYNCHRONIZED_DEVICE, + ContextManagerState::CONTEXT_HOST_SYNCHRONIZED_DEVICE, + ContextManagerState::CONTEXT_DEVICE_SYNCHRONIZED_DEVICE, + ContextManagerState::CONTEXT_NONE_UNSYNCHRONIZED_DEVICE, + ContextManagerState::CONTEXT_HOST_UNSYNCHRONIZED_DEVICE, + ContextManagerState::CONTEXT_DEVICE_UNSYNCHRONIZED_DEVICE + }; + + return s_context_manager_states; + } + + void SetUp() override + { + GetContextManager().reset(); + } + + void TearDown() override + { + GetContextManager().reset(); + } + + void SetContextManagerState(ContextManagerState state) + { + chai::expt::ContextManager& context_manager = GetContextManager(); + context_manager.reset(); + + if (state == ContextManagerState::CONTEXT_NONE_SYNCHRONIZED_DEVICE) + { + context_manager.setContext(chai::expt::Context::NONE); + } + else if (state == ContextManagerState::CONTEXT_HOST_SYNCHRONIZED_DEVICE) + { + context_manager.setContext(chai::expt::Context::HOST); + } + else if (state == ContextManagerState::CONTEXT_DEVICE_SYNCHRONIZED_DEVICE) + { + context_manager.setContext(chai::expt::Context::DEVICE); + context_manager.synchronize(chai::expt::Context::DEVICE); + } + else if (state == ContextManagerState::CONTEXT_NONE_UNSYNCHRONIZED_DEVICE) + { + context_manager.setContext(chai::expt::Context::DEVICE); + context_manager.setContext(chai::expt::Context::NONE); + } + else if (state == ContextManagerState::CONTEXT_HOST_UNSYNCHRONIZED_DEVICE) + { + context_manager.setContext(chai::expt::Context::DEVICE); + context_manager.setContext(chai::expt::Context::HOST); + } + else if (state == ContextManagerState::CONTEXT_DEVICE_UNSYNCHRONIZED_DEVICE) + { + context_manager.setContext(chai::expt::Context::DEVICE); + } + } + + std::size_t m_size = 10; +}; + +TEST_F(DualArrayTest, DefaultConstructor) +{ + for (ContextManagerState context_manager_state : GetContextManagerStates()) + { + SetContextManagerState(context_manager_state); + chai::expt::DualArray array; + EXPECT_EQ(array.size(), 0); + EXPECT_EQ(array.modified(), chai::expt::Context::NONE); + EXPECT_EQ(array.host_data(), nullptr); + EXPECT_EQ(array.device_data(), nullptr); + EXPECT_EQ(array.host_allocator().getId(), GetDefaultHostAllocator().getId()); + EXPECT_EQ(array.device_allocator().getId(), GetDefaultDeviceAllocator().getId()); + } +} + +TEST_F(DualArrayTest, AllocatorConstructor) +{ + for (ContextManagerState context_manager_state : GetContextManagerStates()) + { + SetContextManagerState(context_manager_state); + chai::expt::DualArray array(GetCustomHostAllocator(), GetCustomDeviceAllocator()); + EXPECT_EQ(array.size(), 0); + EXPECT_EQ(array.modified(), chai::expt::Context::NONE); + EXPECT_EQ(array.host_data(), nullptr); + EXPECT_EQ(array.device_data(), nullptr); + EXPECT_EQ(array.host_allocator().getId(), GetCustomHostAllocator().getId()); + EXPECT_EQ(array.device_allocator().getId(), GetCustomDeviceAllocator().getId()); + } +} + +TEST_F(DualArrayTest, SizeConstructor) +{ + for (ContextManagerState context_manager_state : GetContextManagerStates()) + { + SetContextManagerState(context_manager_state); + chai::expt::DualArray array(m_size); + EXPECT_EQ(array.size(), m_size); + EXPECT_EQ(array.modified(), chai::expt::Context::NONE); + + if (context_manager_state == ContextManagerState::CONTEXT_NONE_SYNCHRONIZED_DEVICE || + context_manager_state == ContextManagerState::CONTEXT_NONE_UNSYNCHRONIZED_DEVICE) + { + EXPECT_EQ(array.host_data(), nullptr); + EXPECT_EQ(array.device_data(), nullptr); + } + else if (context_manager_state == ContextManagerState::CONTEXT_HOST_SYNCHRONIZED_DEVICE || + context_manager_state == ContextManagerState::CONTEXT_HOST_UNSYNCHRONIZED_DEVICE) + { + EXPECT_NE(array.host_data(), nullptr); + ASSERT_TRUE(GetResourceManager().hasAllocator(array.host_data())); + EXPECT_EQ(GetResourceManager().getAllocator(array.host_data()).getId(), GetDefaultHostAllocator().getId()); + EXPECT_EQ(array.device_data(), nullptr); + } + else if (context_manager_state == ContextManagerState::CONTEXT_DEVICE_SYNCHRONIZED_DEVICE || + context_manager_state == ContextManagerState::CONTEXT_DEVICE_UNSYNCHRONIZED_DEVICE) + { + EXPECT_EQ(array.host_data(), nullptr); + EXPECT_NE(array.device_data(), nullptr); + ASSERT_TRUE(GetResourceManager().hasAllocator(array.device_data())); + EXPECT_EQ(GetResourceManager().getAllocator(array.device_data()).getId(), GetDefaultDeviceAllocator().getId()); + } + } +} + +TEST_F(DualArrayTest, SizeAndAllocatorConstructor) +{ + for (auto context_manager_state : GetContextManagerStates()) + { + chai::expt::DualArray array(m_size, + GetCustomHostAllocator(), + GetCustomDeviceAllocator()); + + EXPECT_EQ(array.size(), m_size); + EXPECT_EQ(array.modified(), chai::expt::Context::NONE); + + if (context_manager_state == ContextManagerState::CONTEXT_NONE_SYNCHRONIZED_DEVICE || + context_manager_state == ContextManagerState::CONTEXT_NONE_UNSYNCHRONIZED_DEVICE) + { + EXPECT_EQ(array.host_data(), nullptr); + EXPECT_EQ(array.device_data(), nullptr); + } + else if (context_manager_state == ContextManagerState::CONTEXT_HOST_SYNCHRONIZED_DEVICE || + context_manager_state == ContextManagerState::CONTEXT_HOST_UNSYNCHRONIZED_DEVICE) + { + EXPECT_NE(array.host_data(), nullptr); + ASSERT_TRUE(GetResourceManager().hasAllocator(array.host_data())); + EXPECT_EQ(GetResourceManager().getAllocator(array.host_data()).getId(), GetCustomHostAllocator().getId()); + EXPECT_EQ(array.device_data(), nullptr); + } + else if (context_manager_state == ContextManagerState::CONTEXT_DEVICE_SYNCHRONIZED_DEVICE || + context_manager_state == ContextManagerState::CONTEXT_DEVICE_UNSYNCHRONIZED_DEVICE) + { + EXPECT_EQ(array.host_data(), nullptr); + EXPECT_NE(array.device_data(), nullptr); + ASSERT_TRUE(GetResourceManager().hasAllocator(array.device_data())); + EXPECT_EQ(GetResourceManager().getAllocator(array.device_data()).getId(), GetCustomDeviceAllocator().getId()); + } + } +} + +TEST_F(DualArrayTest, CopyConstructor) { + const size_t size = 5; + chai::expt::DualArray array1(size, GetCustomHostAllocator(), GetCustomDeviceAllocator()); + + // Set some data in array1 + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); + for (size_t i = 0; i < size; ++i) { + array1.set(i, static_cast(i)); + } + + // Copy construct array2 + chai::expt::DualArray array2(array1); + + EXPECT_EQ(array2.size(), size); + + // Verify data was copied + for (size_t i = 0; i < size; ++i) { + EXPECT_EQ(array2.get(i), static_cast(i)); + } +} + +TEST_F(DualArrayTest, MoveConstructor) { + const size_t size = 5; + chai::expt::DualArray array1(size, GetCustomHostAllocator(), GetCustomDeviceAllocator()); + + // Set some data in array1 + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); + for (size_t i = 0; i < size; ++i) { + array1.set(i, static_cast(i)); + } + + // Move construct array2 + chai::expt::DualArray array2(std::move(array1)); + + EXPECT_EQ(array2.size(), size); + EXPECT_EQ(array1.size(), 0); // array1 should be empty after move + EXPECT_EQ(array1.host_data(), nullptr); + EXPECT_EQ(array1.device_data(), nullptr); + + // Verify data was moved + for (size_t i = 0; i < size; ++i) { + EXPECT_EQ(array2.get(i), static_cast(i)); + } +} + +TEST_F(DualArrayTest, CopyAssignment) { + const size_t size = 5; + chai::expt::DualArray array1(size, GetCustomHostAllocator(), GetCustomDeviceAllocator()); + + // Set some data in array1 + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); + for (size_t i = 0; i < size; ++i) { + array1.set(i, static_cast(i)); + } + + // Copy assign to array2 + chai::expt::DualArray array2; + array2 = array1; + + EXPECT_EQ(array2.size(), size); + + // Verify data was copied + for (size_t i = 0; i < size; ++i) { + EXPECT_EQ(array2.get(i), static_cast(i)); + } +} + +TEST_F(DualArrayTest, MoveAssignment) { + const size_t size = 5; + chai::expt::DualArray array1(size, GetCustomHostAllocator(), GetCustomDeviceAllocator()); + + // Set some data in array1 + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); + for (size_t i = 0; i < size; ++i) { + array1.set(i, static_cast(i)); + } + + // Move assign to array2 + chai::expt::DualArray array2; + array2 = std::move(array1); + + EXPECT_EQ(array2.size(), size); + EXPECT_EQ(array1.size(), 0); // array1 should be empty after move + EXPECT_EQ(array1.host_data(), nullptr); + EXPECT_EQ(array1.device_data(), nullptr); + + // Verify data was moved + for (size_t i = 0; i < size; ++i) { + EXPECT_EQ(array2.get(i), static_cast(i)); + } +} + +TEST_F(DualArrayTest, Resize) { + chai::expt::DualArray array(5, GetCustomHostAllocator(), GetCustomDeviceAllocator()); + EXPECT_EQ(array.size(), 5); + + // Resize larger + array.resize(10); + EXPECT_EQ(array.size(), 10); + + // Resize smaller + array.resize(3); + EXPECT_EQ(array.size(), 3); + + // Resize to zero + array.resize(0); + EXPECT_EQ(array.size(), 0); +} + +TEST_F(DualArrayTest, ResizeWithData) { + const size_t initial_size = 5; + chai::expt::DualArray array(initial_size, GetCustomHostAllocator(), GetCustomDeviceAllocator()); + + // Set some data + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); + for (size_t i = 0; i < initial_size; ++i) { + array.set(i, static_cast(i)); + } + + // Resize larger + const size_t new_size = 8; + array.resize(new_size); + + // Verify original data is preserved + for (size_t i = 0; i < initial_size; ++i) { + EXPECT_EQ(array.get(i), static_cast(i)); + } + + // Resize smaller + const size_t smaller_size = 3; + array.resize(smaller_size); + + // Verify remaining data is preserved + for (size_t i = 0; i < smaller_size; ++i) { + EXPECT_EQ(array.get(i), static_cast(i)); + } +} + +TEST_F(DualArrayTest, Free) { + chai::expt::DualArray array(5, GetCustomHostAllocator(), GetCustomDeviceAllocator()); + + array.free(); + EXPECT_EQ(array.size(), 0); + EXPECT_EQ(array.host_data(), nullptr); + EXPECT_EQ(array.device_data(), nullptr); + EXPECT_EQ(array.modified(), chai::expt::Context::NONE); +} + +TEST_F(DualArrayTest, DataAndModified) { + const size_t size = 5; + chai::expt::DualArray array(size, GetCustomHostAllocator(), GetCustomDeviceAllocator()); + + // Test host context + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); + int* host_ptr = array.data(); + EXPECT_NE(host_ptr, nullptr); + EXPECT_EQ(array.modified(), chai::expt::Context::HOST); + + // Test device context + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::DEVICE); + int* device_ptr = array.data(); + EXPECT_NE(device_ptr, nullptr); + EXPECT_EQ(array.modified(), chai::expt::Context::DEVICE); +} + +TEST_F(DualArrayTest, ConstData) { + const size_t size = 5; + chai::expt::DualArray array(size, GetCustomHostAllocator(), GetCustomDeviceAllocator()); + + // Set some data + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); + for (size_t i = 0; i < size; ++i) { + array.set(i, static_cast(i)); + } + + // Create a const reference + const chai::expt::DualArray& const_array = array; + + // Test host context + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); + const int* host_ptr = const_array.data(); + EXPECT_NE(host_ptr, nullptr); + + // Test device context + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::DEVICE); + const int* device_ptr = const_array.data(); + EXPECT_NE(device_ptr, nullptr); +} + +TEST_F(DualArrayTest, GetAndSet) { + const size_t size = 5; + chai::expt::DualArray array(size, GetCustomHostAllocator(), GetCustomDeviceAllocator()); + + // Set values in host context + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); + for (size_t i = 0; i < size; ++i) { + array.set(i, static_cast(i * 10)); + } + + // Get values in host context + for (size_t i = 0; i < size; ++i) { + EXPECT_EQ(array.get(i), static_cast(i * 10)); + } + + // Switch to device context and test data sync + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::DEVICE); + int* device_ptr = array.data(); + + // Switch back to host and verify data still accessible + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); + for (size_t i = 0; i < size; ++i) { + EXPECT_EQ(array.get(i), static_cast(i * 10)); + } +} \ No newline at end of file diff --git a/tests/expt/HostArrayTests.cpp b/tests/expt/HostArrayTests.cpp new file mode 100644 index 00000000..8748b1c2 --- /dev/null +++ b/tests/expt/HostArrayTests.cpp @@ -0,0 +1,234 @@ +#include "gtest/gtest.h" +#include "chai/expt/HostArray.hpp" +#include "umpire/ResourceManager.hpp" + +TEST(HostArrayTest, DefaultConstructor) { + chai::expt::HostArray array; + EXPECT_EQ(array.size(), 0); + EXPECT_EQ(array.data(), nullptr); +} + +TEST(HostArrayTest, SizeConstructor) { + const size_t testSize = 10; + chai::expt::HostArray array(testSize); + + EXPECT_EQ(array.size(), testSize); + EXPECT_NE(array.data(), nullptr); +} + +TEST(HostArrayTest, AllocatorConstructor) { + auto& rm = umpire::ResourceManager::getInstance(); + auto allocator = rm.getAllocator("HOST"); + + chai::expt::HostArray array(allocator); + EXPECT_EQ(array.size(), 0); + EXPECT_EQ(array.data(), nullptr); +} + +TEST(HostArrayTest, SizeAllocatorConstructor) { + const size_t testSize = 5; + auto& rm = umpire::ResourceManager::getInstance(); + auto allocator = rm.getAllocator("HOST"); + + chai::expt::HostArray array(testSize, allocator); + + EXPECT_EQ(array.size(), testSize); + EXPECT_NE(array.data(), nullptr); +} + +TEST(HostArrayTest, CopyConstructor) { + const size_t testSize = 3; + chai::expt::HostArray array1(testSize); + + // Initialize array1 + for (size_t i = 0; i < testSize; ++i) { + array1[i] = static_cast(i * 10); + } + + // Copy construct array2 + chai::expt::HostArray array2(array1); + + EXPECT_EQ(array2.size(), array1.size()); + + // Verify contents + for (size_t i = 0; i < testSize; ++i) { + EXPECT_EQ(array2[i], array1[i]); + } + + // Verify array2 is a deep copy + array1[0] = 100; + EXPECT_NE(array1[0], array2[0]); +} + +TEST(HostArrayTest, MoveConstructor) { + const size_t testSize = 4; + chai::expt::HostArray array1(testSize); + + // Initialize array1 + for (size_t i = 0; i < testSize; ++i) { + array1[i] = i * 1.5; + } + + double* originalData = array1.data(); + + // Move construct array2 + chai::expt::HostArray array2(std::move(array1)); + + // Verify array1 is empty after move + EXPECT_EQ(array1.size(), 0); + EXPECT_EQ(array1.data(), nullptr); + + // Verify array2 has the original data + EXPECT_EQ(array2.size(), testSize); + EXPECT_EQ(array2.data(), originalData); + EXPECT_DOUBLE_EQ(array2[2], 3.0); +} + +TEST(HostArrayTest, CopyAssignment) { + const size_t srcSize = 5; + const size_t destSize = 3; + + chai::expt::HostArray array1(srcSize); + chai::expt::HostArray array2(destSize); + + // Initialize arrays + for (size_t i = 0; i < srcSize; ++i) { + array1[i] = static_cast(i * 10); + } + + for (size_t i = 0; i < destSize; ++i) { + array2[i] = static_cast(i * 100); + } + + // Perform copy assignment + array2 = array1; + + // Verify array2 now matches array1 + EXPECT_EQ(array2.size(), array1.size()); + + for (size_t i = 0; i < srcSize; ++i) { + EXPECT_EQ(array2[i], array1[i]); + } +} + +TEST(HostArrayTest, MoveAssignment) { + const size_t srcSize = 4; + const size_t destSize = 2; + + chai::expt::HostArray array1(srcSize); + chai::expt::HostArray array2(destSize); + + // Initialize arrays + for (size_t i = 0; i < srcSize; ++i) { + array1[i] = static_cast(i * 2.5); + } + + float* originalData = array1.data(); + + // Perform move assignment + array2 = std::move(array1); + + // Verify array1 is empty after move + EXPECT_EQ(array1.size(), 0); + EXPECT_EQ(array1.data(), nullptr); + + // Verify array2 now has array1's original data + EXPECT_EQ(array2.size(), srcSize); + EXPECT_EQ(array2.data(), originalData); +} + +TEST(HostArrayTest, Resize) { + const size_t initialSize = 3; + chai::expt::HostArray array(initialSize); + + // Initialize array + for (size_t i = 0; i < initialSize; ++i) { + array[i] = static_cast(i + 1); + } + + // Resize larger + const size_t newLargerSize = 5; + array.resize(newLargerSize); + + EXPECT_EQ(array.size(), newLargerSize); + + // Check original data was preserved + for (size_t i = 0; i < initialSize; ++i) { + EXPECT_EQ(array[i], static_cast(i + 1)); + } + + // Resize smaller + const size_t newSmallerSize = 2; + array.resize(newSmallerSize); + + EXPECT_EQ(array.size(), newSmallerSize); + + // Check remaining data was preserved + for (size_t i = 0; i < newSmallerSize; ++i) { + EXPECT_EQ(array[i], static_cast(i + 1)); + } +} + +TEST(HostArrayTest, Free) { + chai::expt::HostArray array(10); + EXPECT_NE(array.data(), nullptr); + + array.free(); + EXPECT_EQ(array.size(), 0); + EXPECT_EQ(array.data(), nullptr); +} + +TEST(HostArrayTest, AccessOperators) { + const size_t testSize = 4; + chai::expt::HostArray array(testSize); + + // Test set and operator[] + for (size_t i = 0; i < testSize; ++i) { + array[i] = static_cast(i * 10); + } + + // Test get and const operator[] + const chai::expt::HostArray& constArray = array; + for (size_t i = 0; i < testSize; ++i) { + EXPECT_EQ(constArray[i], static_cast(i * 10)); + EXPECT_EQ(constArray.get(i), static_cast(i * 10)); + } + + // Test set method + for (size_t i = 0; i < testSize; ++i) { + array.set(i, static_cast(i * 20)); + EXPECT_EQ(array[i], static_cast(i * 20)); + } +} + +TEST(HostArrayTest, NonTrivialType) { + // Test with a non-trivial type like std::string + const size_t testSize = 3; + chai::expt::HostArray array(testSize); + + array[0] = "Hello"; + array[1] = "World"; + array[2] = "Test"; + + // Copy construction + chai::expt::HostArray arrayCopy(array); + EXPECT_EQ(arrayCopy.size(), testSize); + EXPECT_EQ(arrayCopy[0], "Hello"); + EXPECT_EQ(arrayCopy[1], "World"); + EXPECT_EQ(arrayCopy[2], "Test"); + + // Modify original, copy should be unaffected + array[0] = "Changed"; + EXPECT_EQ(arrayCopy[0], "Hello"); + + // Resize + arrayCopy.resize(4); + EXPECT_EQ(arrayCopy.size(), 4); + EXPECT_EQ(arrayCopy[0], "Hello"); + arrayCopy[3] = "New"; +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/tests/expt/HostDeviceArrayManagerTests.cpp b/tests/expt/HostDeviceArrayManagerTests.cpp new file mode 100644 index 00000000..7169e9f8 --- /dev/null +++ b/tests/expt/HostDeviceArrayManagerTests.cpp @@ -0,0 +1,484 @@ +#include "chai/expt/HostDeviceArrayManager.hpp" +#include "chai/expt/Context.hpp" +#include "chai/expt/ContextManager.hpp" +#include "umpire/Allocator.hpp" +#include "umpire/ResourceManager.hpp" +#include "umpire/strategy/QuickPool.hpp" +#include "gtest/gtest.h" + +enum class ContextManagerState +{ + CONTEXT_NONE_SYNCHRONIZED_DEVICE, + CONTEXT_HOST_SYNCHRONIZED_DEVICE, + CONTEXT_DEVICE_SYNCHRONIZED_DEVICE, + CONTEXT_NONE_UNSYNCHRONIZED_DEVICE, + CONTEXT_HOST_UNSYNCHRONIZED_DEVICE, + CONTEXT_DEVICE_UNSYNCHRONIZED_DEVICE +}; + +enum class HostDeviceArrayManagerState +{ + ALLOCATED_NONE, + ALLOCATED_HOST, + ALLOCATED_DEVICE, + ALLOCATED_HOST_MODIFIED_HOST, + ALLOCATED_DEVICE_MODIFIED_DEVICE, + ALLOCATED_BOTH, + ALLOCATED_BOTH_MODIFIED_HOST, + ALLOCATED_BOTH_MODIFIED_DEVICE +} + +class HostDeviceArrayManagerTest : public ::testing::Test +{ + protected: + static chai::expt::ContextManager& GetContextManager() + { + static chai::expt::ContextManager& s_context_manager = + chai::expt::ContextManager::getInstance(); + + return s_context_manager; + } + + static umpire::ResourceManager& GetResourceManager() + { + static umpire::ResourceManager& s_resource_manager = + umpire::ResourceManager::getInstance(); + + return s_resource_manager; + } + + static umpire::Allocator& GetDefaultHostAllocator() + { + static umpire::Allocator s_default_host_allocator = + GetResourceManager().getAllocator("HOST"); + + return s_default_host_allocator; + } + + static umpire::Allocator& GetDefaultDeviceAllocator() + { + static umpire::Allocator s_default_device_allocator = + GetResourceManager().getAllocator("DEVICE"); + + return s_default_device_allocator; + } + + static umpire::Allocator& GetCustomHostAllocator() + { + static umpire::Allocator s_custom_host_allocator = + GetResourceManager().makeAllocator( + "HOST_CUSTOM", GetDefaultHostAllocator()); + + return s_custom_host_allocator; + } + + static umpire::Allocator& GetCustomDeviceAllocator() + { + static umpire::Allocator s_custom_device_allocator = + GetResourceManager().makeAllocator( + "DEVICE_CUSTOM", GetDefaultDeviceAllocator()); + + return s_custom_device_allocator; + } + + void SetUp() override + { + GetContextManager().reset(); + } + + void TearDown() override + { + GetContextManager().reset(); + } + + void SetContextManagerState(ContextManagerState state) + { + chai::expt::ContextManager& context_manager = GetContextManager(); + context_manager.reset(); + + if (state == ContextManagerState::CONTEXT_NONE_SYNCHRONIZED_DEVICE) + { + context_manager.setContext(chai::expt::Context::NONE); + } + else if (state == ContextManagerState::CONTEXT_HOST_SYNCHRONIZED_DEVICE) + { + context_manager.setContext(chai::expt::Context::HOST); + } + else if (state == ContextManagerState::CONTEXT_DEVICE_SYNCHRONIZED_DEVICE) + { + context_manager.setContext(chai::expt::Context::DEVICE); + context_manager.synchronize(chai::expt::Context::DEVICE); + } + else if (state == ContextManagerState::CONTEXT_NONE_UNSYNCHRONIZED_DEVICE) + { + context_manager.setContext(chai::expt::Context::DEVICE); + context_manager.setContext(chai::expt::Context::NONE); + } + else if (state == ContextManagerState::CONTEXT_HOST_UNSYNCHRONIZED_DEVICE) + { + context_manager.setContext(chai::expt::Context::DEVICE); + context_manager.setContext(chai::expt::Context::HOST); + } + else if (state == ContextManagerState::CONTEXT_DEVICE_UNSYNCHRONIZED_DEVICE) + { + context_manager.setContext(chai::expt::Context::DEVICE); + } + } + + void SetArrayManagerState(ArrayManagerState) + + std::size_t m_size = 10; +}; + +class HostDeviceArrayManager_AllocatedNone_Test : public ::testing::Test +{ + protected: + chai::expt::HostDeviceArrayManager m_array{}; +}; + +class HostDeviceArrayManager_AllocatedHost_Test : public ::testing::Test +{ + protected: + void SetUp() override + { + GetContextManager().reset(); + GetContextManager().setContext(chai::expt::Context::HOST); + m_array.resize(m_size); + GetContextManager().reset(); + } + + void TearDown() override + { + GetContextManager().reset(); + m_array.free(); + } + + chai::expt::HostDeviceArrayManager m_array{}; +}; + +TEST_F(HostDeviceArrayManagerTest, DefaultConstructor) +{ + for (ContextManagerState context_manager_state : GetContextManagerStates()) + { + SetContextManagerState(context_manager_state); + chai::expt::HostDeviceArrayManager array; + EXPECT_EQ(array.size(), 0); + EXPECT_EQ(array.modified(), chai::expt::Context::NONE); + EXPECT_EQ(array.host_data(), nullptr); + EXPECT_EQ(array.device_data(), nullptr); + EXPECT_EQ(array.host_allocator().getId(), GetDefaultHostAllocator().getId()); + EXPECT_EQ(array.device_allocator().getId(), GetDefaultDeviceAllocator().getId()); + } +} + +TEST_F(HostDeviceArrayManagerTest, AllocatorConstructor) +{ + for (ContextManagerState context_manager_state : GetContextManagerStates()) + { + SetContextManagerState(context_manager_state); + chai::expt::HostDeviceArrayManager array(GetCustomHostAllocator(), GetCustomDeviceAllocator()); + EXPECT_EQ(array.size(), 0); + EXPECT_EQ(array.modified(), chai::expt::Context::NONE); + EXPECT_EQ(array.host_data(), nullptr); + EXPECT_EQ(array.device_data(), nullptr); + EXPECT_EQ(array.host_allocator().getId(), GetCustomHostAllocator().getId()); + EXPECT_EQ(array.device_allocator().getId(), GetCustomDeviceAllocator().getId()); + } +} + +TEST_F(HostDeviceArrayManagerTest, SizeConstructor) +{ + for (ContextManagerState context_manager_state : GetContextManagerStates()) + { + SetContextManagerState(context_manager_state); + chai::expt::HostDeviceArrayManager array(m_size); + EXPECT_EQ(array.size(), m_size); + EXPECT_EQ(array.modified(), chai::expt::Context::NONE); + EXPECT_EQ(array.host_allocator().getId(), GetDefaultHostAllocator().getId()); + EXPECT_EQ(array.device_allocator().getId(), GetDefaultDeviceAllocator().getId()); + + if (context_manager_state == ContextManagerState::CONTEXT_NONE_SYNCHRONIZED_DEVICE || + context_manager_state == ContextManagerState::CONTEXT_NONE_UNSYNCHRONIZED_DEVICE) + { + EXPECT_EQ(array.host_data(), nullptr); + EXPECT_EQ(array.device_data(), nullptr); + } + else if (context_manager_state == ContextManagerState::CONTEXT_HOST_SYNCHRONIZED_DEVICE || + context_manager_state == ContextManagerState::CONTEXT_HOST_UNSYNCHRONIZED_DEVICE) + { + EXPECT_NE(array.host_data(), nullptr); + ASSERT_TRUE(GetResourceManager().hasAllocator(array.host_data())); + EXPECT_EQ(GetResourceManager().getAllocator(array.host_data()).getId(), GetDefaultHostAllocator().getId()); + EXPECT_EQ(array.device_data(), nullptr); + } + else if (context_manager_state == ContextManagerState::CONTEXT_DEVICE_SYNCHRONIZED_DEVICE || + context_manager_state == ContextManagerState::CONTEXT_DEVICE_UNSYNCHRONIZED_DEVICE) + { + EXPECT_EQ(array.host_data(), nullptr); + EXPECT_NE(array.device_data(), nullptr); + ASSERT_TRUE(GetResourceManager().hasAllocator(array.device_data())); + EXPECT_EQ(GetResourceManager().getAllocator(array.device_data()).getId(), GetDefaultDeviceAllocator().getId()); + } + } +} + +TEST_F(HostDeviceArrayManagerTest, SizeAndAllocatorConstructor) +{ + for (auto context_manager_state : GetContextManagerStates()) + { + chai::expt::HostDeviceArrayManager array(m_size, + GetCustomHostAllocator(), + GetCustomDeviceAllocator()); + + EXPECT_EQ(array.size(), m_size); + EXPECT_EQ(array.modified(), chai::expt::Context::NONE); + EXPECT_EQ(array.host_allocator().getId(), GetCustomHostAllocator().getId()); + EXPECT_EQ(array.device_allocator().getId(), GetCustomDeviceAllocator().getId()); + + if (context_manager_state == ContextManagerState::CONTEXT_NONE_SYNCHRONIZED_DEVICE || + context_manager_state == ContextManagerState::CONTEXT_NONE_UNSYNCHRONIZED_DEVICE) + { + EXPECT_EQ(array.host_data(), nullptr); + EXPECT_EQ(array.device_data(), nullptr); + } + else if (context_manager_state == ContextManagerState::CONTEXT_HOST_SYNCHRONIZED_DEVICE || + context_manager_state == ContextManagerState::CONTEXT_HOST_UNSYNCHRONIZED_DEVICE) + { + EXPECT_NE(array.host_data(), nullptr); + ASSERT_TRUE(GetResourceManager().hasAllocator(array.host_data())); + EXPECT_EQ(GetResourceManager().getAllocator(array.host_data()).getId(), GetCustomHostAllocator().getId()); + EXPECT_EQ(array.device_data(), nullptr); + } + else if (context_manager_state == ContextManagerState::CONTEXT_DEVICE_SYNCHRONIZED_DEVICE || + context_manager_state == ContextManagerState::CONTEXT_DEVICE_UNSYNCHRONIZED_DEVICE) + { + EXPECT_EQ(array.host_data(), nullptr); + EXPECT_NE(array.device_data(), nullptr); + ASSERT_TRUE(GetResourceManager().hasAllocator(array.device_data())); + EXPECT_EQ(GetResourceManager().getAllocator(array.device_data()).getId(), GetCustomDeviceAllocator().getId()); + } + } +} + +TEST_F(HostDeviceArrayManagerTest, CopyConstructor) { + const size_t size = 5; + chai::expt::HostDeviceArrayManager array1(size, GetCustomHostAllocator(), GetCustomDeviceAllocator()); + + // Set some data in array1 + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); + for (size_t i = 0; i < size; ++i) { + array1.set(i, static_cast(i)); + } + + // Copy construct array2 + chai::expt::HostDeviceArrayManager array2(array1); + + EXPECT_EQ(array2.size(), size); + + // Verify data was copied + for (size_t i = 0; i < size; ++i) { + EXPECT_EQ(array2.get(i), static_cast(i)); + } +} + +TEST_F(HostDeviceArrayManagerTest, MoveConstructor) { + const size_t size = 5; + chai::expt::HostDeviceArrayManager array1(size, GetCustomHostAllocator(), GetCustomDeviceAllocator()); + + // Set some data in array1 + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); + for (size_t i = 0; i < size; ++i) { + array1.set(i, static_cast(i)); + } + + // Move construct array2 + chai::expt::HostDeviceArrayManager array2(std::move(array1)); + + EXPECT_EQ(array2.size(), size); + EXPECT_EQ(array1.size(), 0); // array1 should be empty after move + EXPECT_EQ(array1.host_data(), nullptr); + EXPECT_EQ(array1.device_data(), nullptr); + + // Verify data was moved + for (size_t i = 0; i < size; ++i) { + EXPECT_EQ(array2.get(i), static_cast(i)); + } +} + +TEST_F(HostDeviceArrayManagerTest, CopyAssignment) { + const size_t size = 5; + chai::expt::HostDeviceArrayManager array1(size, GetCustomHostAllocator(), GetCustomDeviceAllocator()); + + // Set some data in array1 + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); + for (size_t i = 0; i < size; ++i) { + array1.set(i, static_cast(i)); + } + + // Copy assign to array2 + chai::expt::HostDeviceArrayManager array2; + array2 = array1; + + EXPECT_EQ(array2.size(), size); + + // Verify data was copied + for (size_t i = 0; i < size; ++i) { + EXPECT_EQ(array2.get(i), static_cast(i)); + } +} + +TEST_F(HostDeviceArrayManagerTest, MoveAssignment) { + const size_t size = 5; + chai::expt::HostDeviceArrayManager array1(size, GetCustomHostAllocator(), GetCustomDeviceAllocator()); + + // Set some data in array1 + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); + for (size_t i = 0; i < size; ++i) { + array1.set(i, static_cast(i)); + } + + // Move assign to array2 + chai::expt::HostDeviceArrayManager array2; + array2 = std::move(array1); + + EXPECT_EQ(array2.size(), size); + EXPECT_EQ(array1.size(), 0); // array1 should be empty after move + EXPECT_EQ(array1.host_data(), nullptr); + EXPECT_EQ(array1.device_data(), nullptr); + + // Verify data was moved + for (size_t i = 0; i < size; ++i) { + EXPECT_EQ(array2.get(i), static_cast(i)); + } +} + +TEST_F(HostDeviceArrayManagerTest, Resize) { + chai::expt::HostDeviceArrayManager array(5, GetCustomHostAllocator(), GetCustomDeviceAllocator()); + EXPECT_EQ(array.size(), 5); + + // Resize larger + array.resize(10); + EXPECT_EQ(array.size(), 10); + + // Resize smaller + array.resize(3); + EXPECT_EQ(array.size(), 3); + + // Resize to zero + array.resize(0); + EXPECT_EQ(array.size(), 0); +} + +TEST_F(HostDeviceArrayManagerTest, ResizeWithData) { + const size_t initial_size = 5; + chai::expt::HostDeviceArrayManager array(initial_size, GetCustomHostAllocator(), GetCustomDeviceAllocator()); + + // Set some data + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); + for (size_t i = 0; i < initial_size; ++i) { + array.set(i, static_cast(i)); + } + + // Resize larger + const size_t new_size = 8; + array.resize(new_size); + + // Verify original data is preserved + for (size_t i = 0; i < initial_size; ++i) { + EXPECT_EQ(array.get(i), static_cast(i)); + } + + // Resize smaller + const size_t smaller_size = 3; + array.resize(smaller_size); + + // Verify remaining data is preserved + for (size_t i = 0; i < smaller_size; ++i) { + EXPECT_EQ(array.get(i), static_cast(i)); + } +} + +TEST_F(HostDeviceArrayManagerTest, Free) { + chai::expt::HostDeviceArrayManager array(5, GetCustomHostAllocator(), GetCustomDeviceAllocator()); + + array.free(); + EXPECT_EQ(array.size(), 0); + EXPECT_EQ(array.host_data(), nullptr); + EXPECT_EQ(array.device_data(), nullptr); + EXPECT_EQ(array.modified(), chai::expt::Context::NONE); +} + +TEST_F(HostDeviceArrayManagerTest, DataAndModified) { + const size_t size = 5; + chai::expt::HostDeviceArrayManager array(size, GetCustomHostAllocator(), GetCustomDeviceAllocator()); + + // Test host context + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); + int* host_ptr = array.data(); + EXPECT_NE(host_ptr, nullptr); + EXPECT_EQ(array.modified(), chai::expt::Context::HOST); + + // Test device context + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::DEVICE); + int* device_ptr = array.data(); + EXPECT_NE(device_ptr, nullptr); + EXPECT_EQ(array.modified(), chai::expt::Context::DEVICE); +} + +TEST_F(HostDeviceArrayManagerTest, ConstData) { + const size_t size = 5; + chai::expt::HostDeviceArrayManager array(size, GetCustomHostAllocator(), GetCustomDeviceAllocator()); + + // Set some data + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); + for (size_t i = 0; i < size; ++i) { + array.set(i, static_cast(i)); + } + + // Create a const reference + const chai::expt::HostDeviceArrayManager& const_array = array; + + // Test host context + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); + const int* host_ptr = const_array.data(); + EXPECT_NE(host_ptr, nullptr); + + // Test device context + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::DEVICE); + const int* device_ptr = const_array.data(); + EXPECT_NE(device_ptr, nullptr); +} + +TEST_F(HostDeviceArrayManagerTest, GetAndSet) { + const size_t size = 5; + chai::expt::HostDeviceArrayManager array(size, GetCustomHostAllocator(), GetCustomDeviceAllocator()); + + // Set values in host context + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); + for (size_t i = 0; i < size; ++i) { + array.set(i, static_cast(i * 10)); + } + + // Get values in host context + for (size_t i = 0; i < size; ++i) { + EXPECT_EQ(array.get(i), static_cast(i * 10)); + } + + // Switch to device context and test data sync + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::DEVICE); + int* device_ptr = array.data(); + + // Switch back to host and verify data still accessible + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); + for (size_t i = 0; i < size; ++i) { + EXPECT_EQ(array.get(i), static_cast(i * 10)); + } +} + +TEST(HostDeviceArrayManager, DefaultConstructor_ContextNone_DeviceSynchronized) +{ + + HostDeviceArrayManager manager; + + +} \ No newline at end of file diff --git a/tests/expt/HostDeviceArrayPointerTests.cpp b/tests/expt/HostDeviceArrayPointerTests.cpp new file mode 100644 index 00000000..cbe271cf --- /dev/null +++ b/tests/expt/HostDeviceArrayPointerTests.cpp @@ -0,0 +1,210 @@ +#include "chai/config.hpp" +#include "chai/expt/ArrayPointer.hpp" +#include "chai/expt/HostDeviceArrayManager.hpp" +#include + +namespace { + +template +using HostDeviceArrayPointer = + chai::expt::ArrayPointer; + +class HostDeviceArrayPointerTest : public ::testing::Test { +protected: + void SetUp() override {} + void TearDown() override {} +}; + +TEST_F(HostDeviceArrayPointerTest, DefaultConstructor) { + HostDeviceArrayPointer ptr; + EXPECT_EQ(ptr.size(), 0); + EXPECT_EQ(ptr.data(), nullptr); +} + +TEST_F(HostDeviceArrayPointerTest, NullptrConstructor) { + HostDeviceArrayPointer ptr(nullptr); + EXPECT_EQ(ptr.size(), 0); + EXPECT_EQ(ptr.data(), nullptr); +} + +TEST_F(HostDeviceArrayPointerTest, ManagerConstructor) { + auto* manager = new chai::expt::HostDeviceArrayManager(5); + HostDeviceArrayPointer ptr(manager); + + EXPECT_EQ(ptr.size(), 5); + EXPECT_NE(ptr.data(), nullptr); + + ptr.free(); +} + +TEST_F(HostDeviceArrayPointerTest, CopyConstructor) { + auto* manager = new chai::expt::HostDeviceArrayManager(5); + HostDeviceArrayPointer ptr1(manager); + HostDeviceArrayPointer ptr2(ptr1); + + EXPECT_EQ(ptr2.size(), 5); + EXPECT_NE(ptr2.data(), nullptr); + + ptr1.free(); +} + +TEST_F(HostDeviceArrayPointerTest, ConvertingConstructor) { + auto* manager = new chai::expt::HostDeviceArrayManager(5); + HostDeviceArrayPointer ptr1(manager); + HostDeviceArrayPointer ptr2(ptr1); + + EXPECT_EQ(ptr2.size(), 5); + EXPECT_NE(ptr2.data(), nullptr); + + ptr2.free(); +} + +TEST_F(HostDeviceArrayPointerTest, CopyAssignment) { + auto* manager1 = new chai::expt::HostDeviceArrayManager(5); + HostDeviceArrayPointer ptr1(manager1); + + auto* manager2 = new chai::expt::HostDeviceArrayManager(10); + HostDeviceArrayPointer ptr2(manager2); + + ptr2.free(); + ptr2 = ptr1; + + EXPECT_EQ(ptr2.size(), 5); + EXPECT_NE(ptr2.data(), nullptr); + + ptr2.free(); +} + +TEST_F(HostDeviceArrayPointerTest, NullptrAssignment) { + auto* manager = new chai::expt::HostDeviceArrayManager(5); + HostDeviceArrayPointer ptr(manager); + + ptr = nullptr; + + EXPECT_EQ(ptr.size(), 0); + EXPECT_EQ(ptr.data(), nullptr); + + delete manager; +} + +TEST_F(HostDeviceArrayPointerTest, Resize) { + HostDeviceArrayPointer ptr; + + ptr.resize(10); + EXPECT_EQ(ptr.size(), 10); + EXPECT_NE(ptr.data(), nullptr); + + ptr.resize(5); + EXPECT_EQ(ptr.size(), 5); + EXPECT_NE(ptr.data(), nullptr); + + ptr.free(); +} + +TEST_F(HostDeviceArrayPointerTest, Free) { + HostDeviceArrayPointer ptr; + + ptr.resize(10); + ptr.free(); + EXPECT_EQ(ptr.size(), 0); + EXPECT_EQ(ptr.data(), nullptr); +} + +TEST_F(HostDeviceArrayPointerTest, DataAccess) { + HostDeviceArrayPointer ptr; + ptr.resize(5); + + auto* data = ptr.data(); + EXPECT_NE(data, nullptr); + + // Test cdata() too + auto* cdata = ptr.cdata(); + EXPECT_NE(cdata, nullptr); + + ptr.free(); +} + +TEST_F(HostDeviceArrayPointerTest, Update) { + auto* manager = new chai::expt::HostDeviceArrayManager(5); + HostDeviceArrayPointer ptr(manager); + + manager->resize(10); + EXPECT_EQ(ptr.size(), 5); // Size hasn't been updated yet + + ptr.update(); + EXPECT_EQ(ptr.size(), 10); // Now size should be updated + + ptr.free(); +} + +TEST_F(HostDeviceArrayPointerTest, ElementAccess) { + HostDeviceArrayPointer ptr; + ptr.resize(5); + + // Initialize array + for (std::size_t i = 0; i < ptr.size(); ++i) { + ptr.set(i, static_cast(i * 10)); + } + + // Test get() + for (std::size_t i = 0; i < ptr.size(); ++i) { + EXPECT_EQ(ptr.get(i), static_cast(i * 10)); + } + + // Test operator[] + ptr.update(); + + for (std::size_t i = 0; i < ptr.size(); ++i) { + EXPECT_EQ(ptr[i], static_cast(i * 10)); + } + + ptr.free(); +} + +TEST_F(HostDeviceArrayPointerTest, GetSetMethods) { + HostDeviceArrayPointer ptr; + ptr.resize(5); + + // Test set() + ptr.set(2, 42); + + // Test get() + EXPECT_EQ(ptr.get(2), 42); + + ptr.free(); +} + +TEST_F(HostDeviceArrayPointerTest, ExceptionHandling) { + HostDeviceArrayPointer ptr; + + // Test out-of-bounds access with no underlying array + EXPECT_THROW(ptr.get(0), std::out_of_range); + EXPECT_THROW(ptr.set(0, 42), std::out_of_range); + + // Now allocate memory + ptr.resize(5); + + // Test out-of-bounds access with get() + EXPECT_THROW(ptr.get(10), std::out_of_range); + + // Test out-of-bounds access with set() + EXPECT_THROW(ptr.set(10, 42), std::out_of_range); + + ptr.free(); +} + +TEST_F(HostDeviceArrayPointerTest, LambdaCapture) { + HostDeviceArrayPointer ptr; + ptr.resize(5); + + // Initialize array + auto f = [=] (std::size_t i) { ptr[i] = i; }; + + for (std::size_t i = 0; i < ptr.size(); ++i) { + f(i); + } + + ptr.free(); +} + +} // namespace \ No newline at end of file