From 9bca6bd977a021e034b5098d37edd3a1a21a6497 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Fri, 12 Sep 2025 09:05:34 -0700 Subject: [PATCH 01/29] Add separate host array classes --- src/chai/expt/HostArray.hpp | 174 +++++++++++++++++++++++ src/chai/expt/HostArrayPointer.hpp | 87 ++++++++++++ src/chai/expt/HostArraySharedPointer.hpp | 80 +++++++++++ src/chai/expt/HostArrayView.hpp | 72 ++++++++++ 4 files changed, 413 insertions(+) create mode 100644 src/chai/expt/HostArray.hpp create mode 100644 src/chai/expt/HostArrayPointer.hpp create mode 100644 src/chai/expt/HostArraySharedPointer.hpp create mode 100644 src/chai/expt/HostArrayView.hpp diff --git a/src/chai/expt/HostArray.hpp b/src/chai/expt/HostArray.hpp new file mode 100644 index 00000000..2557767c --- /dev/null +++ b/src/chai/expt/HostArray.hpp @@ -0,0 +1,174 @@ +////////////////////////////////////////////////////////////////////////////// +// 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; + + 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..f0e7937f --- /dev/null +++ b/src/chai/expt/HostArrayPointer.hpp @@ -0,0 +1,87 @@ +#ifndef CHAI_HOST_ARRAY_POINTER_HPP +#define CHAI_HOST_ARRAY_POINTER_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..5c8d0f5c --- /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 + +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..d963c434 --- /dev/null +++ b/src/chai/expt/HostArrayView.hpp @@ -0,0 +1,72 @@ +#ifndef CHAI_HOST_ARRAY_VIEW_HPP +#define CHAI_HOST_ARRAY_VIEW_HPP + +namespace chai::expt +{ + +template +class HostArrayView +{ + public: + using HostArrayType = std::conditional_t, const HostArray>, HostArray>>; + + HostArrayView() = default; + + HostArrayView(HostArrayType& array) + : 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: + 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 From 016a6c3851a59c2db658502744ce02e305b613af Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Fri, 12 Sep 2025 11:27:52 -0700 Subject: [PATCH 02/29] Clean up --- src/chai/expt/HostArray.hpp | 251 +++++++++++------------ src/chai/expt/HostArrayPointer.hpp | 132 ++++++------ src/chai/expt/HostArraySharedPointer.hpp | 116 +++++------ src/chai/expt/HostArrayView.hpp | 103 +++++----- 4 files changed, 301 insertions(+), 301 deletions(-) diff --git a/src/chai/expt/HostArray.hpp b/src/chai/expt/HostArray.hpp index 2557767c..5106a282 100644 --- a/src/chai/expt/HostArray.hpp +++ b/src/chai/expt/HostArray.hpp @@ -7,62 +7,28 @@ #ifndef CHAI_HOST_ARRAY_HPP #define CHAI_HOST_ARRAY_HPP -namespace chai::expt { - -template -class HostArray { - public: - HostArray() = default; - - 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) +namespace chai::expt +{ + template + class HostArray { + public: + HostArray() = default; + + HostArray(const umpire::Allocator& allocator) + : m_allocator{allocator} { - std::memcpy(m_data, other.m_data, m_size * sizeof(T)); } - else + + HostArray(std::size_t size, const umpire::Allocator& allocator = umpire::ResourceManager::getInstance().getAllocator("HOST")) + : m_allocator{allocator} { - std::copy_n(other.m_data, m_size, m_data); + resize(size); } - } - - 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) + HostArray(const HostArray& other) + : m_allocator{other.m_allocator} { - 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))); + resize(other.m_size); if constexpr (std::is_trivially_copyable_v) { @@ -74,101 +40,134 @@ class HostArray { } } - return *this; - } - - HostArray& operator=(HostArray&& other) - { - if (&other != this) + HostArray(HostArray&& other) + : m_data{other.m_data}, + m_size{other.m_size}, + m_allocator{other.m_allocator} { - 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) + ~HostArray() { - T* newData = nullptr; + m_allocator.deallocate(m_data); + } - if (newSize > 0) + HostArray& operator=(const HostArray& other) + { + if (&other != this) { - std::size_t newSizeBytes = newSize * sizeof(T); - newData = static_cast(m_allocator.allocate(newSizeBytes)); + 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(newData, m_data, std::min(newSizeBytes, m_size * sizeof(T)); + std::memcpy(m_data, other.m_data, m_size * sizeof(T)); } else { - std::copy_n(m_data, std::min(newSize, m_size), newData); + 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 = 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 + 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 index f0e7937f..0f16d147 100644 --- a/src/chai/expt/HostArrayPointer.hpp +++ b/src/chai/expt/HostArrayPointer.hpp @@ -1,87 +1,87 @@ #ifndef CHAI_HOST_ARRAY_POINTER_HPP #define CHAI_HOST_ARRAY_POINTER_HPP -namespace chai::expt -{ +#include "chai/expt/HostArray.hpp" -template -class HostArrayPointer +namespace chai::expt { - public: - using HostArrayType = std::conditional_t, const HostArray>, HostArray>>; - - HostArrayPointer() = default; - - HostArrayPointer(HostArrayType* array) - : m_array{array} - { - } + 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(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; + HostArrayPointer& operator=(const HostArrayPointer& other) = default; - void update() const - { - if (m_array) + void update() const { - m_data = m_array->data(); + 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; - } + void resize(std::size_t newSize) + { + m_data = nullptr; + m_size = newSize; + m_array->resize(newSize); + } - std::size_t size() const - { - return m_size; - } + void free() + { + m_data = nullptr; + m_size = 0; + delete m_array; + m_array = nullptr; + } - T* data() const - { - update(); - return m_data; - } + std::size_t size() const + { + return m_size; + } - T& operator[](std::size_t i) const - { - return m_data[i]; - } + T* data() const + { + update(); + return m_data; + } - T get(std::size_t i) const - { - return m_array->get(i); - } + T& operator[](std::size_t i) const + { + return m_data[i]; + } - void set(std::size_t i, T value) const - { - m_array->set(i, value); - } + T get(std::size_t i) const + { + return m_array->get(i); + } - private: - T* m_data{nullptr}; - std::size_t m_size{0}; - HostArrayType* m_array{nullptr}; -}; // class HostArrayPointer + 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 index 5c8d0f5c..6a5fc0d2 100644 --- a/src/chai/expt/HostArraySharedPointer.hpp +++ b/src/chai/expt/HostArraySharedPointer.hpp @@ -1,80 +1,80 @@ #ifndef CHAI_HOST_ARRAY_SHARED_POINTER_HPP #define CHAI_HOST_ARRAY_SHARED_POINTER_HPP -namespace chai::expt -{ +#include "chai/expt/HostArray.hpp" -template -class HostArraySharedPointer +namespace chai::expt { - public: - using HostArrayType = std::conditional_t, const HostArray>, HostArray>>; - using SharedPointerType = std::shared_ptr; + template + class HostArraySharedPointer + { + public: + using HostArrayType = std::conditional_t, const HostArray>, HostArray>>; + using SharedPointerType = std::shared_ptr; - HostArraySharedPointer() = default; + HostArraySharedPointer() = default; - HostArraySharedPointer(const SharedPointerType& array) - : m_array{array} - { - } + 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(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; + HostArraySharedPointer& operator=(const HostArraySharedPointer& other) = default; - void update() const - { - if (m_array) + void update() const { - m_data = m_array->data(); + 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; - } + void resize(std::size_t newSize) + { + m_data = nullptr; + m_size = newSize; + m_array->resize(newSize); + } - T* data() const - { - update(); - return m_data; - } + std::size_t size() const + { + return m_size; + } - T& operator[](std::size_t i) const - { - return m_data[i]; - } + T* data() const + { + update(); + return m_data; + } - T get(std::size_t i) const - { - return m_array->get(i); - } + T& operator[](std::size_t i) const + { + return m_data[i]; + } - void set(std::size_t i, T value) const - { - m_array->set(i, value); - } + T get(std::size_t i) const + { + return m_array->get(i); + } - private: - T* m_data{nullptr}; - std::size_t m_size{0}; - SharedPointerType m_array{nullptr}; -}; // class HostArraySharedPointer + 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 index d963c434..510de0ae 100644 --- a/src/chai/expt/HostArrayView.hpp +++ b/src/chai/expt/HostArrayView.hpp @@ -1,72 +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>>; -template -class HostArrayView -{ - public: - using HostArrayType = std::conditional_t, const HostArray>, HostArray>>; - - HostArrayView() = default; + HostArrayView() = default; - HostArrayView(HostArrayType& array) - : m_array{std::addressof(array)} - { - } + 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(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; + HostArrayView& operator=(const HostArrayView& other) = default; - void update() const - { - if (m_array) + void update() const { - m_data = m_array->data(); + if (m_array) + { + m_data = m_array->data(); + } } - } - - std::size_t size() const - { - return m_size; - } - T* data() const - { - update(); - return m_data; - } + std::size_t size() const + { + return m_size; + } - T& operator[](std::size_t i) const - { - return m_data[i]; - } + T* data() const + { + update(); + return m_data; + } - T get(std::size_t i) const - { - return m_array->get(i); - } + T& operator[](std::size_t i) const + { + return m_data[i]; + } - void set(std::size_t i, T value) const - { - m_array->set(i, value); - } + T get(std::size_t i) const + { + return m_array->get(i); + } - private: - T* m_data{nullptr}; - std::size_t m_size{0}; - HostArrayType* m_array{nullptr}; -}; // class HostArrayView + 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 From 8bb4f566926bfeaf59c501e4c6ee46d79245b699 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Mon, 15 Sep 2025 14:06:41 -0700 Subject: [PATCH 03/29] Fixes and add tests --- src/chai/expt/HostArray.hpp | 2 +- tests/expt/HostArrayTests.cpp | 0 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 tests/expt/HostArrayTests.cpp diff --git a/src/chai/expt/HostArray.hpp b/src/chai/expt/HostArray.hpp index 5106a282..6913c4c0 100644 --- a/src/chai/expt/HostArray.hpp +++ b/src/chai/expt/HostArray.hpp @@ -107,7 +107,7 @@ namespace chai::expt if constexpr (std::is_trivially_copyable_v) { - std::memcpy(newData, m_data, std::min(newSizeBytes, m_size * sizeof(T)); + std::memcpy(newData, m_data, std::min(newSizeBytes, m_size * sizeof(T))); } else { diff --git a/tests/expt/HostArrayTests.cpp b/tests/expt/HostArrayTests.cpp new file mode 100644 index 00000000..e69de29b From 5ae28c14ffc451990fdc0cff4b09573b29a6ca05 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Mon, 15 Sep 2025 14:07:28 -0700 Subject: [PATCH 04/29] Clean up --- tests/expt/HostArrayTests.cpp | 234 ++++++++++++++++++++++++++++++++++ 1 file changed, 234 insertions(+) diff --git a/tests/expt/HostArrayTests.cpp b/tests/expt/HostArrayTests.cpp index e69de29b..8748b1c2 100644 --- a/tests/expt/HostArrayTests.cpp +++ 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 From eedce00f9e3ea27c937c4d4bd6114afcf9be877f Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Tue, 16 Sep 2025 16:06:15 -0700 Subject: [PATCH 05/29] Make host array constructor explicit --- src/chai/expt/HostArray.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chai/expt/HostArray.hpp b/src/chai/expt/HostArray.hpp index 6913c4c0..1c1278b4 100644 --- a/src/chai/expt/HostArray.hpp +++ b/src/chai/expt/HostArray.hpp @@ -14,7 +14,7 @@ namespace chai::expt public: HostArray() = default; - HostArray(const umpire::Allocator& allocator) + explicit HostArray(const umpire::Allocator& allocator) : m_allocator{allocator} { } From 4fd51a0e3153329fe6a10a1d9f6b886168c25333 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Tue, 16 Sep 2025 16:52:49 -0700 Subject: [PATCH 06/29] Add generic ArrayPointer and ArrayView classes --- src/chai/expt/ArrayPointer.hpp | 85 ++++++++++++++++++++++++++++++++++ src/chai/expt/ArrayView.hpp | 71 ++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 src/chai/expt/ArrayPointer.hpp create mode 100644 src/chai/expt/ArrayView.hpp diff --git a/src/chai/expt/ArrayPointer.hpp b/src/chai/expt/ArrayPointer.hpp new file mode 100644 index 00000000..f52fa077 --- /dev/null +++ b/src/chai/expt/ArrayPointer.hpp @@ -0,0 +1,85 @@ +#ifndef CHAI_ARRAY_POINTER_HPP +#define CHAI_ARRAY_POINTER_HPP + +namespace chai::expt +{ + template ArrayType> + class ArrayPointer + { + public: + using Array = std::conditional_t, const >, ArrayType>>; + + ArrayPointer() = default; + + explicit ArrayPointer(Array* array) + : m_array{array} + { + } + + ArrayPointer(const ArrayPointer& other) + : m_data{other.m_data}, + m_size{other.m_size}, + m_array{other.m_array} + { + update(); + } + + ArrayPointer& operator=(const ArrayPointer& 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}; + Array* m_array{nullptr}; + }; // 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 From 9f7f647bf25b6f2f55d824f148f2a49e1c90ecc7 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Tue, 16 Sep 2025 17:16:29 -0700 Subject: [PATCH 07/29] Clean up ArrayPointer --- src/chai/expt/ArrayPointer.hpp | 38 +++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/chai/expt/ArrayPointer.hpp b/src/chai/expt/ArrayPointer.hpp index f52fa077..9f105946 100644 --- a/src/chai/expt/ArrayPointer.hpp +++ b/src/chai/expt/ArrayPointer.hpp @@ -1,6 +1,8 @@ #ifndef CHAI_ARRAY_POINTER_HPP #define CHAI_ARRAY_POINTER_HPP +#include "chai/config.hpp" + namespace chai::expt { template ArrayType> @@ -16,7 +18,7 @@ namespace chai::expt { } - ArrayPointer(const ArrayPointer& other) + CHAI_HOST_DEVICE ArrayPointer(const ArrayPointer& other) : m_data{other.m_data}, m_size{other.m_size}, m_array{other.m_array} @@ -24,18 +26,24 @@ namespace chai::expt update(); } + template * = nullptr> + CHAI_HOST_DEVICE Array(const Array& other) + : m_data{other.m_data}, + m_size{other.m_size}, + m_array{other.m_array} + { + } + ArrayPointer& operator=(const ArrayPointer& other) = default; - void update() const + void resize(std::size_t newSize) { - if (m_array) + if (m_array == nullptr) { - m_data = m_array->data(); + m_array = new Array(); } - } - void resize(std::size_t newSize) - { m_data = nullptr; m_size = newSize; m_array->resize(newSize); @@ -49,18 +57,28 @@ namespace chai::expt m_array = nullptr; } - std::size_t size() const + CHAI_HOST_DEVICE std::size_t size() const { return m_size; } - T* data() const + CHAI_HOST_DEVICE void update() const + { +#if !defined(CHAI_DEVICE_COMPILE) + if (m_array) + { + m_data = m_array->data(); + } +#endif + } + + CHAI_HOST_DEVICE T* data() const { update(); return m_data; } - T& operator[](std::size_t i) const + CHAI_HOST_DEVICE T& operator[](std::size_t i) const { return m_data[i]; } From 27f9e76c6c2ca0a30eaee629fa2b9f1c8627460e Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Wed, 17 Sep 2025 16:40:18 -0700 Subject: [PATCH 08/29] Update ArrayPointer --- src/chai/expt/ArrayPointer.hpp | 99 +++++++++++++++++++++++++++++----- 1 file changed, 86 insertions(+), 13 deletions(-) diff --git a/src/chai/expt/ArrayPointer.hpp b/src/chai/expt/ArrayPointer.hpp index 9f105946..f31696a2 100644 --- a/src/chai/expt/ArrayPointer.hpp +++ b/src/chai/expt/ArrayPointer.hpp @@ -2,20 +2,27 @@ #define CHAI_ARRAY_POINTER_HPP #include "chai/config.hpp" +#include namespace chai::expt { - template ArrayType> + template typename ArrayType> class ArrayPointer { public: - using Array = std::conditional_t, const >, ArrayType>>; + using Array = std::conditional_t, const >, ArrayType>>; ArrayPointer() = default; + CHAI_HOST_DEVICE ArrayPointer(std::nullptr_t) + : ArrayPointer() + { + } + explicit ArrayPointer(Array* array) : m_array{array} { + update(); } CHAI_HOST_DEVICE ArrayPointer(const ArrayPointer& other) @@ -27,15 +34,37 @@ namespace chai::expt } template * = nullptr> - CHAI_HOST_DEVICE Array(const Array& other) + std::enable_if_t* = nullptr> + CHAI_HOST_DEVICE ArrayPointer(const ArrayPointer& other) : m_data{other.m_data}, m_size{other.m_size}, m_array{other.m_array} { + update(); + } + + CHAI_HOST_DEVICE ArrayPointer& operator=(const ArrayPointer& other) + { + if (&other != this) + { + m_data = other.m_data; + m_size = other.m_size; + m_array = other.m_array; + + update(); + } + + return *this; } - ArrayPointer& operator=(const ArrayPointer& other) = default; + CHAI_HOST_DEVICE ArrayPointer& operator=(std::nullptr_t) + { + m_data = nullptr; + m_size = 0; + m_array = nullptr; + + return *this; + } void resize(std::size_t newSize) { @@ -47,6 +76,8 @@ namespace chai::expt m_data = nullptr; m_size = newSize; m_array->resize(newSize); + + update(); } void free() @@ -67,34 +98,76 @@ namespace chai::expt #if !defined(CHAI_DEVICE_COMPILE) if (m_array) { - m_data = m_array->data(); + if (ElementType* data = m_array->data(); data) + { + m_data = data; + } + + m_size = m_array->size(); } #endif } - CHAI_HOST_DEVICE T* data() const + CHAI_HOST_DEVICE void cupdate() const + { +#if !defined(CHAI_DEVICE_COMPILE) + if (m_array) + { + const Array* array = m_array; + + if (ElementType* data = array->data(); data) + { + m_data = data; + } + + m_size = array->size(); + } +#endif + } + + CHAI_HOST_DEVICE ElementType* data() const { update(); return m_data; } - CHAI_HOST_DEVICE T& operator[](std::size_t i) const + CHAI_HOST_DEVICE ElementType* cdata() const + { + cupdate(); + return m_data; + } + + CHAI_HOST_DEVICE ElementType& operator[](std::size_t i) const { return m_data[i]; } - T get(std::size_t i) const + ElementType get(std::size_t i) const { - return m_array->get(i); + if (m_array && i < m_array->size()) + { + return m_array->get(i); + } + else + { + throw std::out_of_range("Array index out of bounds"); + } } - void set(std::size_t i, T value) const + void set(std::size_t i, ElementType value) const { - m_array->set(i, value); + if (m_array && i < m_array->size()) + { + m_array->set(i, value); + } + else + { + throw std::out_of_range("Array index out of bounds"); + } } private: - T* m_data{nullptr}; + ElementType* m_data{nullptr}; std::size_t m_size{0}; Array* m_array{nullptr}; }; // class ArrayPointer From eee025616b0ad3cd5a62166a78b9d4f6726c2d7f Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Thu, 18 Sep 2025 13:53:26 -0700 Subject: [PATCH 09/29] Add DualArray implementation --- src/chai/expt/DualArray.hpp | 334 ++++++++++++++++++++++++++++++++++++ 1 file changed, 334 insertions(+) create mode 100644 src/chai/expt/DualArray.hpp diff --git a/src/chai/expt/DualArray.hpp b/src/chai/expt/DualArray.hpp new file mode 100644 index 00000000..415dbb67 --- /dev/null +++ b/src/chai/expt/DualArray.hpp @@ -0,0 +1,334 @@ +////////////////////////////////////////////////////////////////////////////// +// 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 + +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& allocator = umpire::ResourceManager::getInstance().getAllocator("HOST"), + const umpire::Allocator& 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_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 = 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_data, m_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 = ExecutionContext::NONE; + } + + return *this; + } + + void resize(std::size_t new_size) + { + if (new_size != m_size) + { + std::size_t old_size_bytes = old_size * sizeof(T); + std::size_t new_size_bytes = new_size * sizeof(T); + + if (m_modified == ExecutionContext::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 = ExecutionContext::NONE; + } + + std::size_t size() const + { + return m_size; + } + + T* data() + { + ExecutionContext execution_context = + ExecutionContextManager::getInstance()::getExecutionContext(); + + if (execution_context == ExecutionContext::DEVICE) + { + if (m_device_data == nullptr) + { + m_device_data = static_cast(m_device_allocator.allocate(m_size * sizeof(T))); + } + + if (m_modified == ExecutionContext::HOST) + { + umpire::ResourceManager::getInstance().copy(m_host_data, m_device_data, m_size * sizeof(T)); + } + + m_modified = ExecutionContext::DEVICE; + return m_device_data; + } + else if (execution_context == ExecutionContext::HOST) + { + if (m_host_data == nullptr) + { + m_host_data = static_cast(m_host_allocator.allocate(m_size * sizeof(T))); + } + + if (m_modified == ExecutionContext::DEVICE) + { + umpire::ResourceManager::getInstance().copy(m_device_data, m_host_data, m_size * sizeof(T)); + } + + m_modified = ExecutionContext::HOST; + return m_host_data; + } + else + { + return nullptr; + } + } + + const T* data() const + { + ExecutionContext execution_context = + ExecutionContextManager::getInstance()::getExecutionContext(); + + if (execution_context == ExecutionContext::DEVICE) + { + if (m_device_data == nullptr) + { + m_device_data = static_cast(m_device_allocator.allocate(m_size * sizeof(T))); + } + + if (m_modified == ExecutionContext::HOST) + { + umpire::ResourceManager::getInstance().copy(m_host_data, m_device_data, m_size * sizeof(T)); + m_modified = ExecutionContext::NONE; + } + + return m_device_data; + } + else if (execution_context == ExecutionContext::HOST) + { + if (m_host_data == nullptr) + { + m_host_data = static_cast(m_host_allocator.allocate(m_size * sizeof(T))); + } + + if (m_modified == ExecutionContext::DEVICE) + { + umpire::ResourceManager::getInstance().copy(m_device_data, m_host_data, m_size * sizeof(T)); + m_modified = ExecutionContext::NONE; + } + + return m_host_data; + } + else + { + return nullptr; + } + } + + T get(std::size_t i) const + { + T result; + + if (m_modified == ExecutionContext::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 == ExecutionContext::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 = ExecutionContext::HOST; + } + } + + const T* host_data() const + { + return m_host_data; + } + + const T* device_data() const + { + 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: + T* m_host_data{nullptr}; + T* m_device_data{nullptr}; + std::size_t m_size{0}; + ExecutionContext m_execution_context{ExecutionContext::NONE}; + umpire::Allocator m_host_allocator{umpire::ResourceManager::getInstance().getAllocator("HOST")}; + 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 From 8445b141b78549b697e01c76df5b096494bed18f Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Thu, 18 Sep 2025 14:02:25 -0700 Subject: [PATCH 10/29] Add Context and ContextManager --- src/chai/expt/Context.hpp | 14 +++ src/chai/expt/ContextManager.hpp | 145 +++++++++++++++++++++++++++++++ src/chai/expt/DualArray.hpp | 49 ++++++----- 3 files changed, 185 insertions(+), 23 deletions(-) create mode 100644 src/chai/expt/Context.hpp create mode 100644 src/chai/expt/ContextManager.hpp 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/ContextManager.hpp b/src/chai/expt/ContextManager.hpp new file mode 100644 index 00000000..73322a9f --- /dev/null +++ b/src/chai/expt/ContextManager.hpp @@ -0,0 +1,145 @@ +////////////////////////////////////////////////////////////////////////////// +// 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/expt/Context.hpp" + +namespace chai { +namespace 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; + m_synchronized[context] = false; + } + + /*! + * \brief Synchronize the given context. + * + * \param context The context that needs synchronization. + */ + void synchronize(Context context) { + auto it = m_synchronized.find(context); + + if (it != m_synchronized.end()) { + #if defined(CHAI_ENABLE_DEVICE) + if (context == Context::DEVICE) { +#if defined(CHAI_ENABLE_CUDA) + cudaDeviceSynchronize(); +#elif defined(CHAI_ENABLE_HIP) + hipDeviceSynchronize(); +#endif + } + } + bool& unsynchronized = m_unsynchronized[context]; + + if (unsynchronized) { +#if defined(CHAI_ENABLE_DEVICE) + if (context == Context::DEVICE) { +#if defined(CHAI_ENABLE_CUDA) + cudaDeviceSynchronize(); +#elif defined(CHAI_ENABLE_HIP) + hipDeviceSynchronize(); +#endif + } + + unsynchronized = false; + } + } + + /*! + * \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 { + auto it = m_synchronized.find(context); + + if (it == m_synchronized.end()) { + return true; + } + else { + return it->second; + } + } + + /*! + * \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 markSynchronized(Context context) { + m_synchronized[context] = true; + } + + private: + /*! + * \brief Private constructor for singleton pattern. + */ + constexpr ContextManager() noexcept = default; + + /*! + * \brief The current context. + */ + Context m_context = Context::NONE; + + /*! + * \brief Map for tracking which contexts are synchronized. + */ + std::unordered_map m_synchronized; + }; // class ContextManager +} // namespace expt +} // namespace chai + +#endif // CHAI_CONTEXT_MANAGER_HPP diff --git a/src/chai/expt/DualArray.hpp b/src/chai/expt/DualArray.hpp index 415dbb67..044e35b7 100644 --- a/src/chai/expt/DualArray.hpp +++ b/src/chai/expt/DualArray.hpp @@ -7,6 +7,9 @@ #ifndef CHAI_DUAL_ARRAY_HPP #define CHAI_DUAL_ARRAY_HPP +#include "chai/expt/Context.hpp" +#include "chai/expt/ContextManager.hpp" + namespace chai::expt { template @@ -98,7 +101,7 @@ namespace chai::expt other.m_host_data = nullptr; other.m_device_data = nullptr; other.m_size = 0; - other.m_modified = ExecutionContext::NONE; + other.m_modified = Context::NONE; } return *this; @@ -111,7 +114,7 @@ namespace chai::expt std::size_t old_size_bytes = old_size * sizeof(T); std::size_t new_size_bytes = new_size * sizeof(T); - if (m_modified == ExecutionContext::HOST || + if (m_modified == Context::HOST || (m_host_data && !m_device_data)) { if (m_device_data) @@ -172,7 +175,7 @@ namespace chai::expt m_device_data = nullptr; m_size = 0; - m_modified = ExecutionContext::NONE; + m_modified = Context::NONE; } std::size_t size() const @@ -182,37 +185,37 @@ namespace chai::expt T* data() { - ExecutionContext execution_context = - ExecutionContextManager::getInstance()::getExecutionContext(); + Context execution_context = + ContextManager::getInstance()::getContext(); - if (execution_context == ExecutionContext::DEVICE) + if (execution_context == Context::DEVICE) { if (m_device_data == nullptr) { m_device_data = static_cast(m_device_allocator.allocate(m_size * sizeof(T))); } - if (m_modified == ExecutionContext::HOST) + if (m_modified == Context::HOST) { umpire::ResourceManager::getInstance().copy(m_host_data, m_device_data, m_size * sizeof(T)); } - m_modified = ExecutionContext::DEVICE; + m_modified = Context::DEVICE; return m_device_data; } - else if (execution_context == ExecutionContext::HOST) + else if (execution_context == Context::HOST) { if (m_host_data == nullptr) { m_host_data = static_cast(m_host_allocator.allocate(m_size * sizeof(T))); } - if (m_modified == ExecutionContext::DEVICE) + if (m_modified == Context::DEVICE) { umpire::ResourceManager::getInstance().copy(m_device_data, m_host_data, m_size * sizeof(T)); } - m_modified = ExecutionContext::HOST; + m_modified = Context::HOST; return m_host_data; } else @@ -223,35 +226,35 @@ namespace chai::expt const T* data() const { - ExecutionContext execution_context = - ExecutionContextManager::getInstance()::getExecutionContext(); + Context execution_context = + ContextManager::getInstance()::getContext(); - if (execution_context == ExecutionContext::DEVICE) + if (execution_context == Context::DEVICE) { if (m_device_data == nullptr) { m_device_data = static_cast(m_device_allocator.allocate(m_size * sizeof(T))); } - if (m_modified == ExecutionContext::HOST) + if (m_modified == Context::HOST) { umpire::ResourceManager::getInstance().copy(m_host_data, m_device_data, m_size * sizeof(T)); - m_modified = ExecutionContext::NONE; + m_modified = Context::NONE; } return m_device_data; } - else if (execution_context == ExecutionContext::HOST) + else if (execution_context == Context::HOST) { if (m_host_data == nullptr) { m_host_data = static_cast(m_host_allocator.allocate(m_size * sizeof(T))); } - if (m_modified == ExecutionContext::DEVICE) + if (m_modified == Context::DEVICE) { umpire::ResourceManager::getInstance().copy(m_device_data, m_host_data, m_size * sizeof(T)); - m_modified = ExecutionContext::NONE; + m_modified = Context::NONE; } return m_host_data; @@ -266,7 +269,7 @@ namespace chai::expt { T result; - if (m_modified == ExecutionContext::DEVICE) + if (m_modified == Context::DEVICE) { umpire::ResourceManager::getInstance().copy(m_device_data + i, &result, sizeof(T)); } @@ -280,7 +283,7 @@ namespace chai::expt void set(std::size_t i, T value) { - if (m_modified == ExecutionContext::DEVICE) + if (m_modified == Context::DEVICE) { umpire::ResourceManager::getInstance().copy(&value, m_device_data + i, sizeof(T)); } @@ -292,7 +295,7 @@ namespace chai::expt } m_host_data[i] = value; - m_modified = ExecutionContext::HOST; + m_modified = Context::HOST; } } @@ -325,7 +328,7 @@ namespace chai::expt T* m_host_data{nullptr}; T* m_device_data{nullptr}; std::size_t m_size{0}; - ExecutionContext m_execution_context{ExecutionContext::NONE}; + Context m_execution_context{Context::NONE}; umpire::Allocator m_host_allocator{umpire::ResourceManager::getInstance().getAllocator("HOST")}; umpire::Allocator m_device_allocator{umpire::ResourceManager::getInstance().getAllocator("DEVICE")}; }; // class DualArray From de9d95479f1b8df272018ab0bf633f17536417fd Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Thu, 18 Sep 2025 14:12:55 -0700 Subject: [PATCH 11/29] Add ContextManager tests --- tests/expt/ContextManagerTests.cpp | 30 +++++++++++++++++++ .../expt/ExecutionContextManagerTests.cpp | 30 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 tests/expt/ContextManagerTests.cpp create mode 100644 tests/unit/expt/ExecutionContextManagerTests.cpp 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/unit/expt/ExecutionContextManagerTests.cpp b/tests/unit/expt/ExecutionContextManagerTests.cpp new file mode 100644 index 00000000..35802a66 --- /dev/null +++ b/tests/unit/expt/ExecutionContextManagerTests.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/ExecutionContextManager.hpp" +#include "gtest/gtest.h" + +// Test that getInstance returns the same object at the same place in memory +TEST(ExecutionContextManager, SingletonInstance) { + chai::expt::ExecutionContextManager& executionContextManager1 = chai::expt::ExecutionContextManager::getInstance(); + chai::expt::ExecutionContextManager& executionContextManager2 = chai::expt::ExecutionContextManager::getInstance(); + EXPECT_EQ(&executionContextManager1, &executionContextManager2); +} + +// Test that the default execution context is NONE +TEST(ExecutionContextManager, DefaultExecutionContext) { + chai::expt::ExecutionContextManager& executionContextManager = chai::expt::ExecutionContextManager::getInstance(); + EXPECT_EQ(executionContextManager.getExecutionContext(), chai::expt::ExecutionContext::NONE); +} + +// Test setting and getting the execution context +TEST(ExecutionContextManager, ExecutionContext) { + chai::expt::ExecutionContextManager& executionContextManager = chai::expt::ExecutionContextManager::getInstance(); + chai::expt::ExecutionContext executionContext = chai::expt::ExecutionContext::HOST; + executionContextManager.setExecutionContext(executionContext); + EXPECT_EQ(executionContextManager.getExecutionContext(), executionContext); +} \ No newline at end of file From 383796b6f19018e4fe4fa772c14bcda34ce4e2c8 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Thu, 18 Sep 2025 14:13:45 -0700 Subject: [PATCH 12/29] Remove accidentally committed changes --- .../expt/ExecutionContextManagerTests.cpp | 30 ------------------- 1 file changed, 30 deletions(-) delete mode 100644 tests/unit/expt/ExecutionContextManagerTests.cpp diff --git a/tests/unit/expt/ExecutionContextManagerTests.cpp b/tests/unit/expt/ExecutionContextManagerTests.cpp deleted file mode 100644 index 35802a66..00000000 --- a/tests/unit/expt/ExecutionContextManagerTests.cpp +++ /dev/null @@ -1,30 +0,0 @@ -////////////////////////////////////////////////////////////////////////////// -// 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/ExecutionContextManager.hpp" -#include "gtest/gtest.h" - -// Test that getInstance returns the same object at the same place in memory -TEST(ExecutionContextManager, SingletonInstance) { - chai::expt::ExecutionContextManager& executionContextManager1 = chai::expt::ExecutionContextManager::getInstance(); - chai::expt::ExecutionContextManager& executionContextManager2 = chai::expt::ExecutionContextManager::getInstance(); - EXPECT_EQ(&executionContextManager1, &executionContextManager2); -} - -// Test that the default execution context is NONE -TEST(ExecutionContextManager, DefaultExecutionContext) { - chai::expt::ExecutionContextManager& executionContextManager = chai::expt::ExecutionContextManager::getInstance(); - EXPECT_EQ(executionContextManager.getExecutionContext(), chai::expt::ExecutionContext::NONE); -} - -// Test setting and getting the execution context -TEST(ExecutionContextManager, ExecutionContext) { - chai::expt::ExecutionContextManager& executionContextManager = chai::expt::ExecutionContextManager::getInstance(); - chai::expt::ExecutionContext executionContext = chai::expt::ExecutionContext::HOST; - executionContextManager.setExecutionContext(executionContext); - EXPECT_EQ(executionContextManager.getExecutionContext(), executionContext); -} \ No newline at end of file From 3ec3666593d845501a1ab65b930185c627e4b652 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Thu, 18 Sep 2025 14:27:21 -0700 Subject: [PATCH 13/29] More changes --- cmake/SetupChaiOptions.cmake | 1 + src/chai/CMakeLists.txt | 7 ++ src/chai/expt/Array.hpp | 159 +++++++++++++++++++++++ src/chai/expt/DeviceArray.hpp | 157 +++++++++++++++++++++++ src/chai/expt/UnifiedArray.hpp | 222 +++++++++++++++++++++++++++++++++ tests/CMakeLists.txt | 4 + tests/expt/CMakeLists.txt | 16 +++ 7 files changed, 566 insertions(+) create mode 100644 src/chai/expt/Array.hpp create mode 100644 src/chai/expt/DeviceArray.hpp create mode 100644 src/chai/expt/UnifiedArray.hpp create mode 100644 tests/expt/CMakeLists.txt diff --git a/cmake/SetupChaiOptions.cmake b/cmake/SetupChaiOptions.cmake index d7b69ac6..3807603a 100644 --- a/cmake/SetupChaiOptions.cmake +++ b/cmake/SetupChaiOptions.cmake @@ -4,6 +4,7 @@ # # SPDX-License-Identifier: BSD-3-Clause ############################################################################ +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/src/chai/CMakeLists.txt b/src/chai/CMakeLists.txt index faab5481..0293fea9 100644 --- a/src/chai/CMakeLists.txt +++ b/src/chai/CMakeLists.txt @@ -28,6 +28,13 @@ if(CHAI_DISABLE_RM) ManagedArray_thin.inl) endif () +if (CHAI_ENABLE_EXPERIMENTAL) + set(chai_headers + ${chai_headers} + expt/Context.hpp + expt/ContextManager.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/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/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..bae75c49 --- /dev/null +++ b/tests/expt/CMakeLists.txt @@ -0,0 +1,16 @@ +############################################################################## +# 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) \ No newline at end of file From 4bdf2f82c11bae27cc8cea40d23dc030d106d32e Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Thu, 18 Sep 2025 14:42:02 -0700 Subject: [PATCH 14/29] Fix ContextManager --- src/chai/expt/ContextManager.hpp | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/chai/expt/ContextManager.hpp b/src/chai/expt/ContextManager.hpp index 73322a9f..ce578e61 100644 --- a/src/chai/expt/ContextManager.hpp +++ b/src/chai/expt/ContextManager.hpp @@ -8,7 +8,15 @@ #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 { namespace expt { @@ -70,18 +78,6 @@ namespace expt { auto it = m_synchronized.find(context); if (it != m_synchronized.end()) { - #if defined(CHAI_ENABLE_DEVICE) - if (context == Context::DEVICE) { -#if defined(CHAI_ENABLE_CUDA) - cudaDeviceSynchronize(); -#elif defined(CHAI_ENABLE_HIP) - hipDeviceSynchronize(); -#endif - } - } - bool& unsynchronized = m_unsynchronized[context]; - - if (unsynchronized) { #if defined(CHAI_ENABLE_DEVICE) if (context == Context::DEVICE) { #if defined(CHAI_ENABLE_CUDA) @@ -90,8 +86,7 @@ namespace expt { hipDeviceSynchronize(); #endif } - - unsynchronized = false; +#endif } } @@ -127,7 +122,7 @@ namespace expt { /*! * \brief Private constructor for singleton pattern. */ - constexpr ContextManager() noexcept = default; + ContextManager() = default; /*! * \brief The current context. From c01e50202cbe1df3f8939fd6610f598df9f5bb8b Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Thu, 18 Sep 2025 15:29:29 -0700 Subject: [PATCH 15/29] Add DualArray tests and fixes --- src/chai/CMakeLists.txt | 3 +- src/chai/expt/DualArray.hpp | 41 +++--- tests/expt/CMakeLists.txt | 12 +- tests/expt/DualArrayTests.cpp | 258 ++++++++++++++++++++++++++++++++++ 4 files changed, 293 insertions(+), 21 deletions(-) create mode 100644 tests/expt/DualArrayTests.cpp diff --git a/src/chai/CMakeLists.txt b/src/chai/CMakeLists.txt index 0293fea9..e83a93c3 100644 --- a/src/chai/CMakeLists.txt +++ b/src/chai/CMakeLists.txt @@ -32,7 +32,8 @@ if (CHAI_ENABLE_EXPERIMENTAL) set(chai_headers ${chai_headers} expt/Context.hpp - expt/ContextManager.hpp) + expt/ContextManager.hpp + expt/DualArray.hpp) endif () set (chai_sources diff --git a/src/chai/expt/DualArray.hpp b/src/chai/expt/DualArray.hpp index 044e35b7..01c06841 100644 --- a/src/chai/expt/DualArray.hpp +++ b/src/chai/expt/DualArray.hpp @@ -9,6 +9,9 @@ #include "chai/expt/Context.hpp" #include "chai/expt/ContextManager.hpp" +#include "umpire/Allocator.hpp" +#include "umpire/ResourceManager.hpp" +#include namespace chai::expt { @@ -25,8 +28,8 @@ namespace chai::expt } explicit DualArray(std::size_t size, - const umpire::Allocator& allocator = umpire::ResourceManager::getInstance().getAllocator("HOST"), - const umpire::Allocator& allocator = umpire::ResourceManager::getInstance().getAllocator("DEVICE")) + 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} { @@ -38,7 +41,7 @@ namespace chai::expt m_device_allocator{other.m_device_allocator} { resize(other.m_size); - umpire::ResourceManager::getInstance().copy(other.m_data, m_device_data, m_size * sizeof(T)); + umpire::ResourceManager::getInstance().copy(other.m_device_data, m_device_data, m_size * sizeof(T)); } DualArray(DualArray&& other) @@ -52,7 +55,7 @@ namespace chai::expt other.m_host_data = nullptr; other.m_device_data = nullptr; other.m_size = 0; - other.m_modified = NONE; + other.m_modified = Context::NONE; } ~DualArray() @@ -78,7 +81,7 @@ namespace chai::expt resize(other.m_size); // TODO: Fix the copy - umpire::ResourceManager::getInstance().copy(other.m_data, m_data, m_size * sizeof(T)); + umpire::ResourceManager::getInstance().copy(other.m_device_data, m_device_data, m_size * sizeof(T)); } return *this; @@ -111,7 +114,7 @@ namespace chai::expt { if (new_size != m_size) { - std::size_t old_size_bytes = old_size * sizeof(T); + 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 || @@ -185,10 +188,10 @@ namespace chai::expt T* data() { - Context execution_context = - ContextManager::getInstance()::getContext(); + Context context = + ContextManager::getInstance().getContext(); - if (execution_context == Context::DEVICE) + if (context == Context::DEVICE) { if (m_device_data == nullptr) { @@ -203,7 +206,7 @@ namespace chai::expt m_modified = Context::DEVICE; return m_device_data; } - else if (execution_context == Context::HOST) + else if (context == Context::HOST) { if (m_host_data == nullptr) { @@ -226,10 +229,10 @@ namespace chai::expt const T* data() const { - Context execution_context = - ContextManager::getInstance()::getContext(); + Context context = + ContextManager::getInstance().getContext(); - if (execution_context == Context::DEVICE) + if (context == Context::DEVICE) { if (m_device_data == nullptr) { @@ -244,7 +247,7 @@ namespace chai::expt return m_device_data; } - else if (execution_context == Context::HOST) + else if (context == Context::HOST) { if (m_host_data == nullptr) { @@ -325,12 +328,12 @@ namespace chai::expt } private: - T* m_host_data{nullptr}; - T* m_device_data{nullptr}; + mutable T* m_host_data{nullptr}; + mutable T* m_device_data{nullptr}; std::size_t m_size{0}; - Context m_execution_context{Context::NONE}; - umpire::Allocator m_host_allocator{umpire::ResourceManager::getInstance().getAllocator("HOST")}; - umpire::Allocator m_device_allocator{umpire::ResourceManager::getInstance().getAllocator("DEVICE")}; + 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 diff --git a/tests/expt/CMakeLists.txt b/tests/expt/CMakeLists.txt index bae75c49..f10ba9c8 100644 --- a/tests/expt/CMakeLists.txt +++ b/tests/expt/CMakeLists.txt @@ -13,4 +13,14 @@ blt_add_executable( blt_add_test( NAME ContextManagerTests - COMMAND ContextManagerTests) \ No newline at end of file + COMMAND ContextManagerTests) + +blt_add_executable( + NAME DualArrayTests + SOURCES DualArrayTests.cpp + INCLUDES ${PROJECT_BINARY_DIR}/include + DEPENDS_ON chai gtest) + +blt_add_test( + NAME DualArrayTests + COMMAND DualArrayTests) \ No newline at end of file diff --git a/tests/expt/DualArrayTests.cpp b/tests/expt/DualArrayTests.cpp new file mode 100644 index 00000000..2fd09a19 --- /dev/null +++ b/tests/expt/DualArrayTests.cpp @@ -0,0 +1,258 @@ +#include "gtest/gtest.h" +#include "chai/expt/DualArray.hpp" +#include "chai/expt/Context.hpp" +#include "chai/expt/ContextManager.hpp" +#include "umpire/ResourceManager.hpp" + +namespace chai::expt { + +class DualArrayTest : public ::testing::Test { +protected: + void SetUp() override { + m_host_allocator = umpire::ResourceManager::getInstance().getAllocator("HOST"); + m_device_allocator = umpire::ResourceManager::getInstance().getAllocator("DEVICE"); + } + + umpire::Allocator m_host_allocator; + umpire::Allocator m_device_allocator; +}; + +TEST_F(DualArrayTest, DefaultConstructor) { + DualArray array; + EXPECT_EQ(array.size(), 0); + EXPECT_EQ(array.modified(), Context::NONE); + EXPECT_EQ(array.host_data(), nullptr); + EXPECT_EQ(array.device_data(), nullptr); +} + +TEST_F(DualArrayTest, AllocatorConstructor) { + DualArray array(m_host_allocator, m_device_allocator); + EXPECT_EQ(array.size(), 0); + EXPECT_EQ(array.modified(), Context::NONE); + EXPECT_EQ(array.host_data(), nullptr); + EXPECT_EQ(array.device_data(), nullptr); +} + +TEST_F(DualArrayTest, SizeAndAllocatorConstructor) { + const size_t size = 10; + DualArray array(size, m_host_allocator, m_device_allocator); + EXPECT_EQ(array.size(), size); + EXPECT_EQ(array.modified(), Context::NONE); +} + +TEST_F(DualArrayTest, CopyConstructor) { + const size_t size = 5; + DualArray array1(size, m_host_allocator, m_device_allocator); + + // Set some data in array1 + ContextManager::getInstance().setContext(Context::HOST); + for (size_t i = 0; i < size; ++i) { + array1.set(i, static_cast(i)); + } + + // Copy construct array2 + 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; + DualArray array1(size, m_host_allocator, m_device_allocator); + + // Set some data in array1 + ContextManager::getInstance().setContext(Context::HOST); + for (size_t i = 0; i < size; ++i) { + array1.set(i, static_cast(i)); + } + + // Move construct array2 + 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; + DualArray array1(size, m_host_allocator, m_device_allocator); + + // Set some data in array1 + ContextManager::getInstance().setContext(Context::HOST); + for (size_t i = 0; i < size; ++i) { + array1.set(i, static_cast(i)); + } + + // Copy assign to array2 + 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; + DualArray array1(size, m_host_allocator, m_device_allocator); + + // Set some data in array1 + ContextManager::getInstance().setContext(Context::HOST); + for (size_t i = 0; i < size; ++i) { + array1.set(i, static_cast(i)); + } + + // Move assign to array2 + 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) { + DualArray array(5, m_host_allocator, m_device_allocator); + 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; + DualArray array(initial_size, m_host_allocator, m_device_allocator); + + // Set some data + ContextManager::getInstance().setContext(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) { + DualArray array(5, m_host_allocator, m_device_allocator); + + array.free(); + EXPECT_EQ(array.size(), 0); + EXPECT_EQ(array.host_data(), nullptr); + EXPECT_EQ(array.device_data(), nullptr); + EXPECT_EQ(array.modified(), Context::NONE); +} + +TEST_F(DualArrayTest, DataAndModified) { + const size_t size = 5; + DualArray array(size, m_host_allocator, m_device_allocator); + + // Test host context + ContextManager::getInstance().setContext(Context::HOST); + int* host_ptr = array.data(); + EXPECT_NE(host_ptr, nullptr); + EXPECT_EQ(array.modified(), Context::HOST); + + // Test device context + ContextManager::getInstance().setContext(Context::DEVICE); + int* device_ptr = array.data(); + EXPECT_NE(device_ptr, nullptr); + EXPECT_EQ(array.modified(), Context::DEVICE); +} + +TEST_F(DualArrayTest, ConstData) { + const size_t size = 5; + DualArray array(size, m_host_allocator, m_device_allocator); + + // Set some data + ContextManager::getInstance().setContext(Context::HOST); + for (size_t i = 0; i < size; ++i) { + array.set(i, static_cast(i)); + } + + // Create a const reference + const DualArray& const_array = array; + + // Test host context + ContextManager::getInstance().setContext(Context::HOST); + const int* host_ptr = const_array.data(); + EXPECT_NE(host_ptr, nullptr); + + // Test device context + ContextManager::getInstance().setContext(Context::DEVICE); + const int* device_ptr = const_array.data(); + EXPECT_NE(device_ptr, nullptr); +} + +TEST_F(DualArrayTest, GetAndSet) { + const size_t size = 5; + DualArray array(size, m_host_allocator, m_device_allocator); + + // Set values in host context + ContextManager::getInstance().setContext(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 + ContextManager::getInstance().setContext(Context::DEVICE); + int* device_ptr = array.data(); + + // Switch back to host and verify data still accessible + ContextManager::getInstance().setContext(Context::HOST); + for (size_t i = 0; i < size; ++i) { + EXPECT_EQ(array.get(i), static_cast(i * 10)); + } +} + +} // namespace chai::expt \ No newline at end of file From 8acff16c5327531ea1c9c5ab1ca96f29159c4420 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Thu, 18 Sep 2025 16:03:16 -0700 Subject: [PATCH 16/29] Add ContextGuard class --- src/chai/expt/ContextGuard.hpp | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/chai/expt/ContextGuard.hpp 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 From fa423b9b82026a014bd5e1273c2da2e783a1bc1e Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Fri, 19 Sep 2025 09:18:52 -0700 Subject: [PATCH 17/29] Clean up ContextManager --- src/chai/expt/ContextManager.hpp | 73 +++++++++++++++++--------------- 1 file changed, 40 insertions(+), 33 deletions(-) diff --git a/src/chai/expt/ContextManager.hpp b/src/chai/expt/ContextManager.hpp index ce578e61..bb168b22 100644 --- a/src/chai/expt/ContextManager.hpp +++ b/src/chai/expt/ContextManager.hpp @@ -18,8 +18,7 @@ #include #endif -namespace chai { -namespace expt { +namespace chai::expt { /*! * \class ContextManager * @@ -28,14 +27,16 @@ namespace expt { * This class provides a centralized way to get and set the current * context across the application. */ - class ContextManager { + class ContextManager + { public: /*! * \brief Get the singleton instance of ContextManager. * * \return The singleton instance. */ - static ContextManager& getInstance() { + static ContextManager& getInstance() + { static ContextManager s_instance; return s_instance; } @@ -55,7 +56,8 @@ namespace expt { * * \return The current context. */ - Context getContext() const { + Context getContext() const + { return m_context; } @@ -64,29 +66,31 @@ namespace expt { * * \param context The new context to set. */ - void setContext(Context context) { + void setContext(Context context) + { m_context = context; - m_synchronized[context] = false; + + if (context == Context::DEVICE) + { + m_device_synchronized = false; + } } /*! - * \brief Synchronize the given context. + * \brief Synchronize the given context. * * \param context The context that needs synchronization. */ - void synchronize(Context context) { - auto it = m_synchronized.find(context); - - if (it != m_synchronized.end()) { -#if defined(CHAI_ENABLE_DEVICE) - if (context == Context::DEVICE) { + void synchronize(Context context) + { + if (context == Context::DEVICE && !m_device_synchronized) + { #if defined(CHAI_ENABLE_CUDA) - cudaDeviceSynchronize(); + cudaDeviceSynchronize(); #elif defined(CHAI_ENABLE_HIP) - hipDeviceSynchronize(); -#endif - } + hipDeviceSynchronize(); #endif + m_device_synchronized = true; } } @@ -96,15 +100,9 @@ namespace expt { * \param context The context to check. * \return True if the context needs synchronization, false otherwise. */ - bool isSynchronized(Context context) const { - auto it = m_synchronized.find(context); - - if (it == m_synchronized.end()) { - return true; - } - else { - return it->second; - } + bool isSynchronized(Context context) const + { + return context == Context::DEVICE ? m_device_synchronized : true; } /*! @@ -114,8 +112,18 @@ namespace expt { * * \param context The context to clear the synchronization flag for. */ - void markSynchronized(Context context) { - m_synchronized[context] = true; + 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: @@ -127,14 +135,13 @@ namespace expt { /*! * \brief The current context. */ - Context m_context = Context::NONE; + Context m_context{Context::NONE} /*! * \brief Map for tracking which contexts are synchronized. */ - std::unordered_map m_synchronized; + bool m_device_synchronized{true}; }; // class ContextManager -} // namespace expt -} // namespace chai +} // namespace chai::expt #endif // CHAI_CONTEXT_MANAGER_HPP From bf60814d1d2490a132099444f9bd5642d7367eec Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Fri, 19 Sep 2025 09:24:42 -0700 Subject: [PATCH 18/29] Improve tests --- tests/expt/DualArrayTests.cpp | 188 ++++++++++++++++++++++++++++------ 1 file changed, 158 insertions(+), 30 deletions(-) diff --git a/tests/expt/DualArrayTests.cpp b/tests/expt/DualArrayTests.cpp index 2fd09a19..dc7cadf9 100644 --- a/tests/expt/DualArrayTests.cpp +++ b/tests/expt/DualArrayTests.cpp @@ -1,43 +1,173 @@ -#include "gtest/gtest.h" #include "chai/expt/DualArray.hpp" #include "chai/expt/Context.hpp" #include "chai/expt/ContextManager.hpp" #include "umpire/ResourceManager.hpp" +#include "gtest/gtest.h" -namespace chai::expt { +enum class ContextState +{ + CONTEXT_NONE_DEVICE_SYNCHRONIZED, + CONTEXT_HOST_DEVICE_SYNCHRONIZED, + CONTEXT_DEVICE_DEVICE_SYNCHRONIZED, + CONTEXT_NONE_DEVICE_UNSYNCHRONIZED, + CONTEXT_HOST_DEVICE_UNSYNCHRONIZED, + CONTEXT_DEVICE_DEVICE_UNSYNCHRONIZED +}; -class DualArrayTest : public ::testing::Test { -protected: - void SetUp() override { - m_host_allocator = umpire::ResourceManager::getInstance().getAllocator("HOST"); - m_device_allocator = umpire::ResourceManager::getInstance().getAllocator("DEVICE"); - } +class ContextIterator +{ + public: + + private: + chai::expt::ContextManager& m_context_manager{chai::expt::ContextManager::getInstance()}; + ContextState m_context_state = CONTEXT_NONE_DEVICE_SYNCHRONIZED; - umpire::Allocator m_host_allocator; - umpire::Allocator m_device_allocator; }; -TEST_F(DualArrayTest, DefaultConstructor) { - DualArray array; - EXPECT_EQ(array.size(), 0); - EXPECT_EQ(array.modified(), Context::NONE); - EXPECT_EQ(array.host_data(), nullptr); - EXPECT_EQ(array.device_data(), nullptr); +class DualArrayTest : public ::testing::Test +{ + protected: + static void SetUpTestSuite() + { + auto& rm = umpire::ResourceManager::getInstance(); + + m_default_host_allocator = rm.getAllocator("HOST"); + m_default_device_allocator = rm.getAllocator("DEVICE"); + + m_custom_host_allocator = + rm.makeAllocator( + "HOST_CUSTOM", m_default_host_allocator); + + m_custom_device_allocator = + rm.makeAllocator( + "DEVICE_CUSTOM", m_default_device_allocator); + } + + void SetUp() override { + m_context_manager.reset(); + } + + void SetContext(chai::expt::Context context, bool device_synchronized) + { + m_context_manager.setContext(context); + m_context_manager.setSynchronized(chai::expt::Context::DEVICE, device_synchronized); + } + + umpire::Allocator m_default_host_allocator; + umpire::Allocator m_default_device_allocator; + + umpire::Allocator m_custom_host_allocator; + umpire::Allocator m_custom_device_allocator; + + chai::expt::ContextManager& m_context_manager{chai::expt::ContextManager::getInstance()}; + + m_size = 10; + + std::array, 6> m_context_states = {{ + {chai::expt::Context::NONE, false}, + {chai::expt::Context::HOST, false}, + {chai::expt::Context::DEVICE, false} + {chai::expt::Context::NONE, true}, + {chai::expt::Context::HOST, true}, + {chai::expt::Context::DEVICE, true} + }}; +}; + +TEST_F(DualArrayTest, DefaultConstructor) +{ + for (auto context_state : m_context_states) + { + chai::expt::DualArray array; + EXPECT_EQ(array.size(), 0); + EXPECT_EQ(array.modified(), Context::NONE); + EXPECT_EQ(array.host_data(), nullptr); + EXPECT_EQ(array.device_data(), nullptr); + EXPECT_EQ(array.host_allocator.getId(), m_default_host_allocator.getId()); + EXPECT_EQ(array.device_allocator.getId(), m_default_device_allocator.getId()); + } } -TEST_F(DualArrayTest, AllocatorConstructor) { - DualArray array(m_host_allocator, m_device_allocator); - EXPECT_EQ(array.size(), 0); - EXPECT_EQ(array.modified(), Context::NONE); - EXPECT_EQ(array.host_data(), nullptr); - EXPECT_EQ(array.device_data(), nullptr); +TEST_F(DualArrayTest, AllocatorConstructor) +{ + for (auto context_state : m_context_states) + { + chai::expt::DualArray array(m_custom_host_allocator, m_custom_device_allocator); + EXPECT_EQ(array.size(), 0); + EXPECT_EQ(array.modified(), Context::NONE); + EXPECT_EQ(array.host_data(), nullptr); + EXPECT_EQ(array.device_data(), nullptr); + EXPECT_EQ(array.host_allocator.getId(), m_custom_host_allocator.getId()); + EXPECT_EQ(array.device_allocator.getId(), m_custom_device_allocator.getId()); + } } -TEST_F(DualArrayTest, SizeAndAllocatorConstructor) { - const size_t size = 10; - DualArray array(size, m_host_allocator, m_device_allocator); - EXPECT_EQ(array.size(), size); - EXPECT_EQ(array.modified(), Context::NONE); +TEST_F(DualArrayTest, SizeConstructor) +{ + for (auto context_state : m_context_states) + { + chai::expt::DualArray array(m_size); + EXPECT_EQ(array.size(), m_size); + EXPECT_EQ(array.modified(), Context::NONE); + + if (context_state == ContextState::CONTEXT_NONE_DEVICE_SYNCHRONIZED || + context_state == ContextState::CONTEXT_NONE_DEVICE_UNSYNCHRONIZED) + { + EXPECT_EQ(array.host_data(), nullptr); + EXPECT_EQ(array.device_data(), nullptr); + } + else if (context_state == ContextState::CONTEXT_HOST_DEVICE_SYNCHRONIZED || + context_state == ContextState::CONTEXT_HOST_DEVICE_UNSYNCHRONIZED) + { + EXPECT_NE(array.host_data(), nullptr); + ASSERT_TRUE(m_resource_manager.hasAllocator(array.host_data())); + EXPECT_EQ(m_resource_manager.getAllocator(array.host_data().getID(), m_default_host_allocator.getID())); + EXPECT_EQ(array.device_data(), nullptr); + } + else if (context_state == ContextState::CONTEXT_DEVICE_DEVICE_SYNCHRONIZED || + context_state == ContextState::CONTEXT_DEVICE_DEVICE_UNSYNCHRONIZED) + { + EXPECT_EQ(array.host_data(), nullptr); + EXPECT_NE(array.device_data(), nullptr); + ASSERT_TRUE(m_resource_manager.hasAllocator(array.device_data())); + EXPECT_EQ(m_resource_manager.getAllocator(array.device_data().getID(), m_default_device_allocator.getID())); + } + } +} + +TEST_F(DualArrayTest, SizeAndAllocatorConstructor) +{ + for (auto context_state : m_context_states) + { + chai::expt::DualArray array(m_size, + m_custom_host_allocator, + m_custom_device_allocator); + + EXPECT_EQ(array.size(), m_size); + EXPECT_EQ(array.modified(), Context::NONE); + + if (context_state == ContextState::CONTEXT_NONE_DEVICE_SYNCHRONIZED || + context_state == ContextState::CONTEXT_NONE_DEVICE_UNSYNCHRONIZED) + { + EXPECT_EQ(array.host_data(), nullptr); + EXPECT_EQ(array.device_data(), nullptr); + } + else if (context_state == ContextState::CONTEXT_HOST_DEVICE_SYNCHRONIZED || + context_state == ContextState::CONTEXT_HOST_DEVICE_UNSYNCHRONIZED) + { + EXPECT_NE(array.host_data(), nullptr); + ASSERT_TRUE(m_resource_manager.hasAllocator(array.host_data())); + EXPECT_EQ(m_resource_manager.getAllocator(array.host_data().getID(), m_default_host_allocator.getID())); + EXPECT_EQ(array.device_data(), nullptr); + } + else if (context_state == ContextState::CONTEXT_DEVICE_DEVICE_SYNCHRONIZED || + context_state == ContextState::CONTEXT_DEVICE_DEVICE_UNSYNCHRONIZED) + { + EXPECT_EQ(array.host_data(), nullptr); + EXPECT_NE(array.device_data(), nullptr); + ASSERT_TRUE(m_resource_manager.hasAllocator(array.device_data())); + EXPECT_EQ(m_resource_manager.getAllocator(array.device_data().getID(), m_default_device_allocator.getID())); + } + } } TEST_F(DualArrayTest, CopyConstructor) { @@ -253,6 +383,4 @@ TEST_F(DualArrayTest, GetAndSet) { for (size_t i = 0; i < size; ++i) { EXPECT_EQ(array.get(i), static_cast(i * 10)); } -} - -} // namespace chai::expt \ No newline at end of file +} \ No newline at end of file From f445792b7a736547a240a96fdb99451292352142 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Fri, 19 Sep 2025 12:54:24 -0700 Subject: [PATCH 19/29] Fix up testing --- src/chai/expt/ContextManager.hpp | 2 +- src/chai/expt/DualArray.hpp | 4 +- tests/expt/DualArrayTests.cpp | 251 +++++++++++++++++-------------- 3 files changed, 144 insertions(+), 113 deletions(-) diff --git a/src/chai/expt/ContextManager.hpp b/src/chai/expt/ContextManager.hpp index bb168b22..5b7f962d 100644 --- a/src/chai/expt/ContextManager.hpp +++ b/src/chai/expt/ContextManager.hpp @@ -135,7 +135,7 @@ namespace chai::expt { /*! * \brief The current context. */ - Context m_context{Context::NONE} + Context m_context{Context::NONE}; /*! * \brief Map for tracking which contexts are synchronized. diff --git a/src/chai/expt/DualArray.hpp b/src/chai/expt/DualArray.hpp index 01c06841..fa0bf3e9 100644 --- a/src/chai/expt/DualArray.hpp +++ b/src/chai/expt/DualArray.hpp @@ -302,12 +302,12 @@ namespace chai::expt } } - const T* host_data() const + T* host_data() { return m_host_data; } - const T* device_data() const + T* device_data() { return m_device_data; } diff --git a/tests/expt/DualArrayTests.cpp b/tests/expt/DualArrayTests.cpp index dc7cadf9..00e7bd94 100644 --- a/tests/expt/DualArrayTests.cpp +++ b/tests/expt/DualArrayTests.cpp @@ -1,187 +1,218 @@ #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 ContextState +enum class ContextManagerState { - CONTEXT_NONE_DEVICE_SYNCHRONIZED, - CONTEXT_HOST_DEVICE_SYNCHRONIZED, - CONTEXT_DEVICE_DEVICE_SYNCHRONIZED, - CONTEXT_NONE_DEVICE_UNSYNCHRONIZED, - CONTEXT_HOST_DEVICE_UNSYNCHRONIZED, - CONTEXT_DEVICE_DEVICE_UNSYNCHRONIZED -}; - -class ContextIterator -{ - public: - - private: - chai::expt::ContextManager& m_context_manager{chai::expt::ContextManager::getInstance()}; - ContextState m_context_state = CONTEXT_NONE_DEVICE_SYNCHRONIZED; - + 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 void SetUpTestSuite() + void SetUp() override { - auto& rm = umpire::ResourceManager::getInstance(); - - m_default_host_allocator = rm.getAllocator("HOST"); - m_default_device_allocator = rm.getAllocator("DEVICE"); - - m_custom_host_allocator = - rm.makeAllocator( - "HOST_CUSTOM", m_default_host_allocator); - - m_custom_device_allocator = - rm.makeAllocator( - "DEVICE_CUSTOM", m_default_device_allocator); - } - - void SetUp() override { m_context_manager.reset(); } - void SetContext(chai::expt::Context context, bool device_synchronized) + void SetContextManagerState(ContextManagerState state) { - m_context_manager.setContext(context); - m_context_manager.setSynchronized(chai::expt::Context::DEVICE, device_synchronized); + m_context_manager.reset(); + + if (state == ContextManagerState::CONTEXT_NONE_SYNCHRONIZED_DEVICE) + { + m_context_manager.setContext(chai::expt::Context::NONE); + m_context_manager.setSynchronized(chai::expt::Context::DEVICE, true); + } + else if (state == ContextManagerState::CONTEXT_HOST_SYNCHRONIZED_DEVICE) + { + m_context_manager.setContext(chai::expt::Context::HOST); + m_context_manager.setSynchronized(chai::expt::Context::DEVICE, true); + } + else if (state == ContextManagerState::CONTEXT_DEVICE_SYNCHRONIZED_DEVICE) + { + m_context_manager.setContext(chai::expt::Context::DEVICE); + m_context_manager.setSynchronized(chai::expt::Context::DEVICE, true); + } + else if (state == ContextManagerState::CONTEXT_NONE_UNSYNCHRONIZED_DEVICE) + { + m_context_manager.setContext(chai::expt::Context::NONE); + m_context_manager.setSynchronized(chai::expt::Context::DEVICE, false); + } + else if (state == ContextManagerState::CONTEXT_HOST_UNSYNCHRONIZED_DEVICE) + { + m_context_manager.setContext(chai::expt::Context::HOST); + m_context_manager.setSynchronized(chai::expt::Context::DEVICE, false); + } + else if (state == ContextManagerState::CONTEXT_DEVICE_UNSYNCHRONIZED_DEVICE) + { + m_context_manager.setContext(chai::expt::Context::DEVICE); + m_context_manager.setSynchronized(chai::expt::Context::DEVICE, false); + } } - umpire::Allocator m_default_host_allocator; - umpire::Allocator m_default_device_allocator; - - umpire::Allocator m_custom_host_allocator; - umpire::Allocator m_custom_device_allocator; + static chai::expt::ContextManager& m_context_manager; + static umpire::ResourceManager& m_resource_manager; - chai::expt::ContextManager& m_context_manager{chai::expt::ContextManager::getInstance()}; + static umpire::Allocator m_default_host_allocator; + static umpire::Allocator m_default_device_allocator; + + static umpire::Allocator m_custom_host_allocator; + static umpire::Allocator m_custom_device_allocator; - m_size = 10; + std::size_t m_size = 10; - std::array, 6> m_context_states = {{ - {chai::expt::Context::NONE, false}, - {chai::expt::Context::HOST, false}, - {chai::expt::Context::DEVICE, false} - {chai::expt::Context::NONE, true}, - {chai::expt::Context::HOST, true}, - {chai::expt::Context::DEVICE, true} - }}; + static constexpr std::array m_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 + }; }; +chai::expt::ContextManager& DualArrayTest::m_context_manager = + chai::expt::ContextManager::getInstance(); + +umpire::ResourceManager& DualArrayTest::m_resource_manager = + umpire::ResourceManager::getInstance(); + +umpire::Allocator DualArrayTest::m_default_host_allocator = + umpire::ResourceManager::getInstance().getAllocator("HOST"); + +umpire::Allocator DualArrayTest::m_default_device_allocator = + umpire::ResourceManager::getInstance().getAllocator("DEVICE"); + +umpire::Allocator DualArrayTest::m_custom_host_allocator = + umpire::ResourceManager::getInstance().makeAllocator( + "HOST_CUSTOM", umpire::ResourceManager::getInstance().getAllocator("HOST")); + +umpire::Allocator DualArrayTest::m_custom_device_allocator = + umpire::ResourceManager::getInstance().makeAllocator( + "DEVICE_CUSTOM", umpire::ResourceManager::getInstance().getAllocator("DEVICE")); + TEST_F(DualArrayTest, DefaultConstructor) { - for (auto context_state : m_context_states) + for (ContextManagerState context_manager_state : m_context_manager_states) { + SetContextManagerState(context_manager_state); chai::expt::DualArray array; EXPECT_EQ(array.size(), 0); - EXPECT_EQ(array.modified(), Context::NONE); + 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(), m_default_host_allocator.getId()); - EXPECT_EQ(array.device_allocator.getId(), m_default_device_allocator.getId()); + EXPECT_EQ(array.host_allocator().getId(), m_default_host_allocator.getId()); + EXPECT_EQ(array.device_allocator().getId(), m_default_device_allocator.getId()); } } TEST_F(DualArrayTest, AllocatorConstructor) { - for (auto context_state : m_context_states) + for (ContextManagerState context_manager_state : m_context_manager_states) { + SetContextManagerState(context_manager_state); chai::expt::DualArray array(m_custom_host_allocator, m_custom_device_allocator); EXPECT_EQ(array.size(), 0); - EXPECT_EQ(array.modified(), Context::NONE); + 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(), m_custom_host_allocator.getId()); - EXPECT_EQ(array.device_allocator.getId(), m_custom_device_allocator.getId()); + EXPECT_EQ(array.host_allocator().getId(), m_custom_host_allocator.getId()); + EXPECT_EQ(array.device_allocator().getId(), m_custom_device_allocator.getId()); } } TEST_F(DualArrayTest, SizeConstructor) { - for (auto context_state : m_context_states) + for (ContextManagerState context_manager_state : m_context_manager_states) { + SetContextManagerState(context_manager_state); chai::expt::DualArray array(m_size); EXPECT_EQ(array.size(), m_size); - EXPECT_EQ(array.modified(), Context::NONE); + EXPECT_EQ(array.modified(), chai::expt::Context::NONE); - if (context_state == ContextState::CONTEXT_NONE_DEVICE_SYNCHRONIZED || - context_state == ContextState::CONTEXT_NONE_DEVICE_UNSYNCHRONIZED) + 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_state == ContextState::CONTEXT_HOST_DEVICE_SYNCHRONIZED || - context_state == ContextState::CONTEXT_HOST_DEVICE_UNSYNCHRONIZED) + 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(m_resource_manager.hasAllocator(array.host_data())); - EXPECT_EQ(m_resource_manager.getAllocator(array.host_data().getID(), m_default_host_allocator.getID())); + EXPECT_EQ(m_resource_manager.getAllocator(array.host_data()).getId(), m_default_host_allocator.getId()); EXPECT_EQ(array.device_data(), nullptr); } - else if (context_state == ContextState::CONTEXT_DEVICE_DEVICE_SYNCHRONIZED || - context_state == ContextState::CONTEXT_DEVICE_DEVICE_UNSYNCHRONIZED) + 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(m_resource_manager.hasAllocator(array.device_data())); - EXPECT_EQ(m_resource_manager.getAllocator(array.device_data().getID(), m_default_device_allocator.getID())); + EXPECT_EQ(m_resource_manager.getAllocator(array.device_data()).getId(), m_default_device_allocator.getId()); } } } TEST_F(DualArrayTest, SizeAndAllocatorConstructor) { - for (auto context_state : m_context_states) + for (auto context_manager_state : m_context_manager_states) { chai::expt::DualArray array(m_size, m_custom_host_allocator, m_custom_device_allocator); EXPECT_EQ(array.size(), m_size); - EXPECT_EQ(array.modified(), Context::NONE); + EXPECT_EQ(array.modified(), chai::expt::Context::NONE); - if (context_state == ContextState::CONTEXT_NONE_DEVICE_SYNCHRONIZED || - context_state == ContextState::CONTEXT_NONE_DEVICE_UNSYNCHRONIZED) + 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_state == ContextState::CONTEXT_HOST_DEVICE_SYNCHRONIZED || - context_state == ContextState::CONTEXT_HOST_DEVICE_UNSYNCHRONIZED) + 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(m_resource_manager.hasAllocator(array.host_data())); - EXPECT_EQ(m_resource_manager.getAllocator(array.host_data().getID(), m_default_host_allocator.getID())); + EXPECT_EQ(m_resource_manager.getAllocator(array.host_data()).getId(), m_custom_host_allocator.getId()); EXPECT_EQ(array.device_data(), nullptr); } - else if (context_state == ContextState::CONTEXT_DEVICE_DEVICE_SYNCHRONIZED || - context_state == ContextState::CONTEXT_DEVICE_DEVICE_UNSYNCHRONIZED) + 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(m_resource_manager.hasAllocator(array.device_data())); - EXPECT_EQ(m_resource_manager.getAllocator(array.device_data().getID(), m_default_device_allocator.getID())); + EXPECT_EQ(m_resource_manager.getAllocator(array.device_data()).getId(), m_custom_device_allocator.getId()); } } } TEST_F(DualArrayTest, CopyConstructor) { const size_t size = 5; - DualArray array1(size, m_host_allocator, m_device_allocator); + chai::expt::DualArray array1(size, m_custom_host_allocator, m_custom_device_allocator); // Set some data in array1 - ContextManager::getInstance().setContext(Context::HOST); + 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 - DualArray array2(array1); + chai::expt::DualArray array2(array1); EXPECT_EQ(array2.size(), size); @@ -193,16 +224,16 @@ TEST_F(DualArrayTest, CopyConstructor) { TEST_F(DualArrayTest, MoveConstructor) { const size_t size = 5; - DualArray array1(size, m_host_allocator, m_device_allocator); + chai::expt::DualArray array1(size, m_custom_host_allocator, m_custom_device_allocator); // Set some data in array1 - ContextManager::getInstance().setContext(Context::HOST); + 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 - DualArray array2(std::move(array1)); + chai::expt::DualArray array2(std::move(array1)); EXPECT_EQ(array2.size(), size); EXPECT_EQ(array1.size(), 0); // array1 should be empty after move @@ -217,16 +248,16 @@ TEST_F(DualArrayTest, MoveConstructor) { TEST_F(DualArrayTest, CopyAssignment) { const size_t size = 5; - DualArray array1(size, m_host_allocator, m_device_allocator); + chai::expt::DualArray array1(size, m_custom_host_allocator, m_custom_device_allocator); // Set some data in array1 - ContextManager::getInstance().setContext(Context::HOST); + 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 - DualArray array2; + chai::expt::DualArray array2; array2 = array1; EXPECT_EQ(array2.size(), size); @@ -239,16 +270,16 @@ TEST_F(DualArrayTest, CopyAssignment) { TEST_F(DualArrayTest, MoveAssignment) { const size_t size = 5; - DualArray array1(size, m_host_allocator, m_device_allocator); + chai::expt::DualArray array1(size, m_custom_host_allocator, m_custom_device_allocator); // Set some data in array1 - ContextManager::getInstance().setContext(Context::HOST); + 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 - DualArray array2; + chai::expt::DualArray array2; array2 = std::move(array1); EXPECT_EQ(array2.size(), size); @@ -263,7 +294,7 @@ TEST_F(DualArrayTest, MoveAssignment) { } TEST_F(DualArrayTest, Resize) { - DualArray array(5, m_host_allocator, m_device_allocator); + chai::expt::DualArray array(5, m_custom_host_allocator, m_custom_device_allocator); EXPECT_EQ(array.size(), 5); // Resize larger @@ -281,10 +312,10 @@ TEST_F(DualArrayTest, Resize) { TEST_F(DualArrayTest, ResizeWithData) { const size_t initial_size = 5; - DualArray array(initial_size, m_host_allocator, m_device_allocator); + chai::expt::DualArray array(initial_size, m_custom_host_allocator, m_custom_device_allocator); // Set some data - ContextManager::getInstance().setContext(Context::HOST); + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); for (size_t i = 0; i < initial_size; ++i) { array.set(i, static_cast(i)); } @@ -309,62 +340,62 @@ TEST_F(DualArrayTest, ResizeWithData) { } TEST_F(DualArrayTest, Free) { - DualArray array(5, m_host_allocator, m_device_allocator); + chai::expt::DualArray array(5, m_custom_host_allocator, m_custom_device_allocator); array.free(); EXPECT_EQ(array.size(), 0); EXPECT_EQ(array.host_data(), nullptr); EXPECT_EQ(array.device_data(), nullptr); - EXPECT_EQ(array.modified(), Context::NONE); + EXPECT_EQ(array.modified(), chai::expt::Context::NONE); } TEST_F(DualArrayTest, DataAndModified) { const size_t size = 5; - DualArray array(size, m_host_allocator, m_device_allocator); + chai::expt::DualArray array(size, m_custom_host_allocator, m_custom_device_allocator); // Test host context - ContextManager::getInstance().setContext(Context::HOST); + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); int* host_ptr = array.data(); EXPECT_NE(host_ptr, nullptr); - EXPECT_EQ(array.modified(), Context::HOST); + EXPECT_EQ(array.modified(), chai::expt::Context::HOST); // Test device context - ContextManager::getInstance().setContext(Context::DEVICE); + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::DEVICE); int* device_ptr = array.data(); EXPECT_NE(device_ptr, nullptr); - EXPECT_EQ(array.modified(), Context::DEVICE); + EXPECT_EQ(array.modified(), chai::expt::Context::DEVICE); } TEST_F(DualArrayTest, ConstData) { const size_t size = 5; - DualArray array(size, m_host_allocator, m_device_allocator); + chai::expt::DualArray array(size, m_custom_host_allocator, m_custom_device_allocator); // Set some data - ContextManager::getInstance().setContext(Context::HOST); + 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 DualArray& const_array = array; + const chai::expt::DualArray& const_array = array; // Test host context - ContextManager::getInstance().setContext(Context::HOST); + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); const int* host_ptr = const_array.data(); EXPECT_NE(host_ptr, nullptr); // Test device context - ContextManager::getInstance().setContext(Context::DEVICE); + 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; - DualArray array(size, m_host_allocator, m_device_allocator); + chai::expt::DualArray array(size, m_custom_host_allocator, m_custom_device_allocator); // Set values in host context - ContextManager::getInstance().setContext(Context::HOST); + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); for (size_t i = 0; i < size; ++i) { array.set(i, static_cast(i * 10)); } @@ -375,11 +406,11 @@ TEST_F(DualArrayTest, GetAndSet) { } // Switch to device context and test data sync - ContextManager::getInstance().setContext(Context::DEVICE); + chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::DEVICE); int* device_ptr = array.data(); // Switch back to host and verify data still accessible - ContextManager::getInstance().setContext(Context::HOST); + 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)); } From 50dc7493183b353107def3170219e797c8253a1b Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Fri, 19 Sep 2025 14:16:27 -0700 Subject: [PATCH 20/29] More clean up of tests --- tests/expt/DualArrayTests.cpp | 191 ++++++++++++++++++++-------------- 1 file changed, 110 insertions(+), 81 deletions(-) diff --git a/tests/expt/DualArrayTests.cpp b/tests/expt/DualArrayTests.cpp index 00e7bd94..1b7091c2 100644 --- a/tests/expt/DualArrayTests.cpp +++ b/tests/expt/DualArrayTests.cpp @@ -19,91 +19,120 @@ enum class ContextManagerState 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 { - m_context_manager.reset(); + GetContextManager().reset(); + } + + void TearDown() override + { + GetContextManager().reset(); } void SetContextManagerState(ContextManagerState state) { - m_context_manager.reset(); + chai::expt::ContextManager& context_manager = GetContextManager(); + context_manager.reset(); if (state == ContextManagerState::CONTEXT_NONE_SYNCHRONIZED_DEVICE) { - m_context_manager.setContext(chai::expt::Context::NONE); - m_context_manager.setSynchronized(chai::expt::Context::DEVICE, true); + context_manager.setContext(chai::expt::Context::NONE); } else if (state == ContextManagerState::CONTEXT_HOST_SYNCHRONIZED_DEVICE) { - m_context_manager.setContext(chai::expt::Context::HOST); - m_context_manager.setSynchronized(chai::expt::Context::DEVICE, true); + context_manager.setContext(chai::expt::Context::HOST); } else if (state == ContextManagerState::CONTEXT_DEVICE_SYNCHRONIZED_DEVICE) { - m_context_manager.setContext(chai::expt::Context::DEVICE); - m_context_manager.setSynchronized(chai::expt::Context::DEVICE, true); + context_manager.setContext(chai::expt::Context::DEVICE); + context_manager.synchronize(chai::expt::Context::DEVICE); } else if (state == ContextManagerState::CONTEXT_NONE_UNSYNCHRONIZED_DEVICE) { - m_context_manager.setContext(chai::expt::Context::NONE); - m_context_manager.setSynchronized(chai::expt::Context::DEVICE, false); + context_manager.setContext(chai::expt::Context::DEVICE); + context_manager.setContext(chai::expt::Context::NONE); } else if (state == ContextManagerState::CONTEXT_HOST_UNSYNCHRONIZED_DEVICE) { - m_context_manager.setContext(chai::expt::Context::HOST); - m_context_manager.setSynchronized(chai::expt::Context::DEVICE, false); + context_manager.setContext(chai::expt::Context::DEVICE); + context_manager.setContext(chai::expt::Context::HOST); } else if (state == ContextManagerState::CONTEXT_DEVICE_UNSYNCHRONIZED_DEVICE) { - m_context_manager.setContext(chai::expt::Context::DEVICE); - m_context_manager.setSynchronized(chai::expt::Context::DEVICE, false); + context_manager.setContext(chai::expt::Context::DEVICE); } } - static chai::expt::ContextManager& m_context_manager; - static umpire::ResourceManager& m_resource_manager; - - static umpire::Allocator m_default_host_allocator; - static umpire::Allocator m_default_device_allocator; - - static umpire::Allocator m_custom_host_allocator; - static umpire::Allocator m_custom_device_allocator; - std::size_t m_size = 10; - - static constexpr std::array m_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 - }; }; -chai::expt::ContextManager& DualArrayTest::m_context_manager = - chai::expt::ContextManager::getInstance(); - -umpire::ResourceManager& DualArrayTest::m_resource_manager = - umpire::ResourceManager::getInstance(); - -umpire::Allocator DualArrayTest::m_default_host_allocator = - umpire::ResourceManager::getInstance().getAllocator("HOST"); - -umpire::Allocator DualArrayTest::m_default_device_allocator = - umpire::ResourceManager::getInstance().getAllocator("DEVICE"); - -umpire::Allocator DualArrayTest::m_custom_host_allocator = - umpire::ResourceManager::getInstance().makeAllocator( - "HOST_CUSTOM", umpire::ResourceManager::getInstance().getAllocator("HOST")); - -umpire::Allocator DualArrayTest::m_custom_device_allocator = - umpire::ResourceManager::getInstance().makeAllocator( - "DEVICE_CUSTOM", umpire::ResourceManager::getInstance().getAllocator("DEVICE")); - TEST_F(DualArrayTest, DefaultConstructor) { - for (ContextManagerState context_manager_state : m_context_manager_states) + for (ContextManagerState context_manager_state : GetContextManagerStates()) { SetContextManagerState(context_manager_state); chai::expt::DualArray array; @@ -111,29 +140,29 @@ TEST_F(DualArrayTest, DefaultConstructor) 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(), m_default_host_allocator.getId()); - EXPECT_EQ(array.device_allocator().getId(), m_default_device_allocator.getId()); + 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 : m_context_manager_states) + for (ContextManagerState context_manager_state : GetContextManagerStates()) { SetContextManagerState(context_manager_state); - chai::expt::DualArray array(m_custom_host_allocator, m_custom_device_allocator); + 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(), m_custom_host_allocator.getId()); - EXPECT_EQ(array.device_allocator().getId(), m_custom_device_allocator.getId()); + 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 : m_context_manager_states) + for (ContextManagerState context_manager_state : GetContextManagerStates()) { SetContextManagerState(context_manager_state); chai::expt::DualArray array(m_size); @@ -150,8 +179,8 @@ TEST_F(DualArrayTest, SizeConstructor) context_manager_state == ContextManagerState::CONTEXT_HOST_UNSYNCHRONIZED_DEVICE) { EXPECT_NE(array.host_data(), nullptr); - ASSERT_TRUE(m_resource_manager.hasAllocator(array.host_data())); - EXPECT_EQ(m_resource_manager.getAllocator(array.host_data()).getId(), m_default_host_allocator.getId()); + 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 || @@ -159,19 +188,19 @@ TEST_F(DualArrayTest, SizeConstructor) { EXPECT_EQ(array.host_data(), nullptr); EXPECT_NE(array.device_data(), nullptr); - ASSERT_TRUE(m_resource_manager.hasAllocator(array.device_data())); - EXPECT_EQ(m_resource_manager.getAllocator(array.device_data()).getId(), m_default_device_allocator.getId()); + 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 : m_context_manager_states) + for (auto context_manager_state : GetContextManagerStates()) { chai::expt::DualArray array(m_size, - m_custom_host_allocator, - m_custom_device_allocator); + GetCustomHostAllocator(), + GetCustomDeviceAllocator()); EXPECT_EQ(array.size(), m_size); EXPECT_EQ(array.modified(), chai::expt::Context::NONE); @@ -186,8 +215,8 @@ TEST_F(DualArrayTest, SizeAndAllocatorConstructor) context_manager_state == ContextManagerState::CONTEXT_HOST_UNSYNCHRONIZED_DEVICE) { EXPECT_NE(array.host_data(), nullptr); - ASSERT_TRUE(m_resource_manager.hasAllocator(array.host_data())); - EXPECT_EQ(m_resource_manager.getAllocator(array.host_data()).getId(), m_custom_host_allocator.getId()); + 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 || @@ -195,15 +224,15 @@ TEST_F(DualArrayTest, SizeAndAllocatorConstructor) { EXPECT_EQ(array.host_data(), nullptr); EXPECT_NE(array.device_data(), nullptr); - ASSERT_TRUE(m_resource_manager.hasAllocator(array.device_data())); - EXPECT_EQ(m_resource_manager.getAllocator(array.device_data()).getId(), m_custom_device_allocator.getId()); + 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, m_custom_host_allocator, m_custom_device_allocator); + chai::expt::DualArray array1(size, GetCustomHostAllocator(), GetCustomDeviceAllocator()); // Set some data in array1 chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); @@ -224,7 +253,7 @@ TEST_F(DualArrayTest, CopyConstructor) { TEST_F(DualArrayTest, MoveConstructor) { const size_t size = 5; - chai::expt::DualArray array1(size, m_custom_host_allocator, m_custom_device_allocator); + chai::expt::DualArray array1(size, GetCustomHostAllocator(), GetCustomDeviceAllocator()); // Set some data in array1 chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); @@ -248,7 +277,7 @@ TEST_F(DualArrayTest, MoveConstructor) { TEST_F(DualArrayTest, CopyAssignment) { const size_t size = 5; - chai::expt::DualArray array1(size, m_custom_host_allocator, m_custom_device_allocator); + chai::expt::DualArray array1(size, GetCustomHostAllocator(), GetCustomDeviceAllocator()); // Set some data in array1 chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); @@ -270,7 +299,7 @@ TEST_F(DualArrayTest, CopyAssignment) { TEST_F(DualArrayTest, MoveAssignment) { const size_t size = 5; - chai::expt::DualArray array1(size, m_custom_host_allocator, m_custom_device_allocator); + chai::expt::DualArray array1(size, GetCustomHostAllocator(), GetCustomDeviceAllocator()); // Set some data in array1 chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); @@ -294,7 +323,7 @@ TEST_F(DualArrayTest, MoveAssignment) { } TEST_F(DualArrayTest, Resize) { - chai::expt::DualArray array(5, m_custom_host_allocator, m_custom_device_allocator); + chai::expt::DualArray array(5, GetCustomHostAllocator(), GetCustomDeviceAllocator()); EXPECT_EQ(array.size(), 5); // Resize larger @@ -312,7 +341,7 @@ TEST_F(DualArrayTest, Resize) { TEST_F(DualArrayTest, ResizeWithData) { const size_t initial_size = 5; - chai::expt::DualArray array(initial_size, m_custom_host_allocator, m_custom_device_allocator); + chai::expt::DualArray array(initial_size, GetCustomHostAllocator(), GetCustomDeviceAllocator()); // Set some data chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); @@ -340,7 +369,7 @@ TEST_F(DualArrayTest, ResizeWithData) { } TEST_F(DualArrayTest, Free) { - chai::expt::DualArray array(5, m_custom_host_allocator, m_custom_device_allocator); + chai::expt::DualArray array(5, GetCustomHostAllocator(), GetCustomDeviceAllocator()); array.free(); EXPECT_EQ(array.size(), 0); @@ -351,7 +380,7 @@ TEST_F(DualArrayTest, Free) { TEST_F(DualArrayTest, DataAndModified) { const size_t size = 5; - chai::expt::DualArray array(size, m_custom_host_allocator, m_custom_device_allocator); + chai::expt::DualArray array(size, GetCustomHostAllocator(), GetCustomDeviceAllocator()); // Test host context chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); @@ -368,7 +397,7 @@ TEST_F(DualArrayTest, DataAndModified) { TEST_F(DualArrayTest, ConstData) { const size_t size = 5; - chai::expt::DualArray array(size, m_custom_host_allocator, m_custom_device_allocator); + chai::expt::DualArray array(size, GetCustomHostAllocator(), GetCustomDeviceAllocator()); // Set some data chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); @@ -392,7 +421,7 @@ TEST_F(DualArrayTest, ConstData) { TEST_F(DualArrayTest, GetAndSet) { const size_t size = 5; - chai::expt::DualArray array(size, m_custom_host_allocator, m_custom_device_allocator); + chai::expt::DualArray array(size, GetCustomHostAllocator(), GetCustomDeviceAllocator()); // Set values in host context chai::expt::ContextManager::getInstance().setContext(chai::expt::Context::HOST); From fb1940b04a613208c1570df56028abde524c8e2e Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Tue, 28 Oct 2025 17:02:34 -0700 Subject: [PATCH 21/29] Add HostDeviceArrayManager and tests --- src/chai/expt/HostDeviceArrayManager.hpp | 345 +++++++++++++++ tests/expt/HostDeviceArrayManagerTests.cpp | 484 +++++++++++++++++++++ 2 files changed, 829 insertions(+) create mode 100644 src/chai/expt/HostDeviceArrayManager.hpp create mode 100644 tests/expt/HostDeviceArrayManagerTests.cpp 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/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 From 354af283cf48e58fcf6ab9bc89aef3c921e4d7e8 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Wed, 29 Oct 2025 10:54:25 -0700 Subject: [PATCH 22/29] Add tests integrating ArrayPointer and HostDeviceArrayManager --- tests/expt/HostDeviceArrayPointerTests.cpp | 195 +++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 tests/expt/HostDeviceArrayPointerTests.cpp diff --git a/tests/expt/HostDeviceArrayPointerTests.cpp b/tests/expt/HostDeviceArrayPointerTests.cpp new file mode 100644 index 00000000..3a2e0632 --- /dev/null +++ b/tests/expt/HostDeviceArrayPointerTests.cpp @@ -0,0 +1,195 @@ +#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(array); + 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(); +} + +} // namespace \ No newline at end of file From 39af4818cfc440497fcc8f252c8ceaed9463a7f6 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Wed, 29 Oct 2025 10:56:54 -0700 Subject: [PATCH 23/29] Add new tests to build --- tests/expt/CMakeLists.txt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/expt/CMakeLists.txt b/tests/expt/CMakeLists.txt index f10ba9c8..094f71e6 100644 --- a/tests/expt/CMakeLists.txt +++ b/tests/expt/CMakeLists.txt @@ -15,6 +15,17 @@ 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 @@ -23,4 +34,5 @@ blt_add_executable( blt_add_test( NAME DualArrayTests - COMMAND DualArrayTests) \ No newline at end of file + COMMAND DualArrayTests) +endif() From 5b7418cd5c5bc64a967fc8491bd7d361d962342f Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Wed, 29 Oct 2025 15:07:14 -0700 Subject: [PATCH 24/29] Update hip host config --- host-configs/lc/toss_4_x86_64_ib_cray/amdclang.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 "") From 3c7b2a275daeb8a6e632361aa65dbb4e73d6593a Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Wed, 29 Oct 2025 15:08:14 -0700 Subject: [PATCH 25/29] Build fixes --- src/chai/expt/ArrayPointer.hpp | 77 +++++++++++----------- tests/expt/HostDeviceArrayPointerTests.cpp | 4 +- 2 files changed, 42 insertions(+), 39 deletions(-) diff --git a/src/chai/expt/ArrayPointer.hpp b/src/chai/expt/ArrayPointer.hpp index f31696a2..5c8f32e5 100644 --- a/src/chai/expt/ArrayPointer.hpp +++ b/src/chai/expt/ArrayPointer.hpp @@ -2,15 +2,16 @@ #define CHAI_ARRAY_POINTER_HPP #include "chai/config.hpp" +#include "chai/ChaiMacros.hpp" #include namespace chai::expt { - template typename ArrayType> + template typename ManagerType> class ArrayPointer { public: - using Array = std::conditional_t, const >, ArrayType>>; + using Manager = ManagerType>; ArrayPointer() = default; @@ -19,8 +20,8 @@ namespace chai::expt { } - explicit ArrayPointer(Array* array) - : m_array{array} + explicit ArrayPointer(Manager* array) + : m_manager{array} { update(); } @@ -28,20 +29,22 @@ namespace chai::expt CHAI_HOST_DEVICE ArrayPointer(const ArrayPointer& other) : m_data{other.m_data}, m_size{other.m_size}, - m_array{other.m_array} + m_manager{other.m_manager} { update(); } - template * = nullptr> - CHAI_HOST_DEVICE ArrayPointer(const ArrayPointer& other) +#if 0 + template >> + CHAI_HOST_DEVICE ArrayPointer(const ArrayPointer& other) : m_data{other.m_data}, m_size{other.m_size}, - m_array{other.m_array} + m_manager{other.m_manager} { update(); } +#endif CHAI_HOST_DEVICE ArrayPointer& operator=(const ArrayPointer& other) { @@ -49,7 +52,7 @@ namespace chai::expt { m_data = other.m_data; m_size = other.m_size; - m_array = other.m_array; + m_manager = other.m_manager; update(); } @@ -61,21 +64,21 @@ namespace chai::expt { m_data = nullptr; m_size = 0; - m_array = nullptr; + m_manager = nullptr; return *this; } void resize(std::size_t newSize) { - if (m_array == nullptr) + if (m_manager == nullptr) { - m_array = new Array(); + m_manager = new Manager(); } m_data = nullptr; m_size = newSize; - m_array->resize(newSize); + m_manager->resize(newSize); update(); } @@ -84,8 +87,8 @@ namespace chai::expt { m_data = nullptr; m_size = 0; - delete m_array; - m_array = nullptr; + delete m_manager; + m_manager = nullptr; } CHAI_HOST_DEVICE std::size_t size() const @@ -93,83 +96,81 @@ namespace chai::expt return m_size; } - CHAI_HOST_DEVICE void update() const + CHAI_HOST_DEVICE void update() { #if !defined(CHAI_DEVICE_COMPILE) - if (m_array) + if (m_manager) { - if (ElementType* data = m_array->data(); data) + if (ElementType* data = m_manager->data(); data) { m_data = data; } - m_size = m_array->size(); + m_size = m_manager->size(); } #endif } - CHAI_HOST_DEVICE void cupdate() const + CHAI_HOST_DEVICE void cupdate() { #if !defined(CHAI_DEVICE_COMPILE) - if (m_array) + if (m_manager) { - const Array* array = m_array; - - if (ElementType* data = array->data(); data) + if (ElementType* data = m_manager->data(); data) { m_data = data; } - m_size = array->size(); + m_size = m_manager->size(); } #endif } - CHAI_HOST_DEVICE ElementType* data() const + CHAI_HOST_DEVICE ElementType* data() { update(); return m_data; } - CHAI_HOST_DEVICE ElementType* cdata() const + CHAI_HOST_DEVICE ElementType* cdata() { cupdate(); return m_data; } - CHAI_HOST_DEVICE ElementType& operator[](std::size_t i) const + CHAI_HOST_DEVICE ElementType& operator[](std::size_t i) { return m_data[i]; } - ElementType get(std::size_t i) const + ElementType get(std::size_t i) { - if (m_array && i < m_array->size()) + if (m_manager && i < m_manager->size()) { - return m_array->get(i); + return m_manager->get(i); } else { - throw std::out_of_range("Array index out of bounds"); + throw std::out_of_range("Manager index out of bounds"); } } - void set(std::size_t i, ElementType value) const + void set(std::size_t i, ElementType value) { - if (m_array && i < m_array->size()) + if (m_manager && i < m_manager->size()) { - m_array->set(i, value); + m_manager->set(i, value); } else { - throw std::out_of_range("Array index out of bounds"); + throw std::out_of_range("Manager index out of bounds"); } } private: ElementType* m_data{nullptr}; std::size_t m_size{0}; - Array* m_array{nullptr}; + Manager* m_manager{nullptr}; }; // class ArrayPointer } // namespace chai::expt diff --git a/tests/expt/HostDeviceArrayPointerTests.cpp b/tests/expt/HostDeviceArrayPointerTests.cpp index 3a2e0632..90bb7f7a 100644 --- a/tests/expt/HostDeviceArrayPointerTests.cpp +++ b/tests/expt/HostDeviceArrayPointerTests.cpp @@ -47,9 +47,10 @@ TEST_F(HostDeviceArrayPointerTest, CopyConstructor) { ptr1.free(); } +#if 0 TEST_F(HostDeviceArrayPointerTest, ConvertingConstructor) { auto* manager = new chai::expt::HostDeviceArrayManager(5); - HostDeviceArrayPointer ptr1(array); + HostDeviceArrayPointer ptr1(manager); HostDeviceArrayPointer ptr2(ptr1); EXPECT_EQ(ptr2.size(), 5); @@ -57,6 +58,7 @@ TEST_F(HostDeviceArrayPointerTest, ConvertingConstructor) { ptr2.free(); } +#endif TEST_F(HostDeviceArrayPointerTest, CopyAssignment) { auto* manager1 = new chai::expt::HostDeviceArrayManager(5); From 389c0053091bf9835f66f56f1c4c32b043295f2a Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Wed, 29 Oct 2025 15:46:15 -0700 Subject: [PATCH 26/29] Fix for using methods in lambdas --- src/chai/expt/ArrayPointer.hpp | 2 +- tests/expt/HostDeviceArrayPointerTests.cpp | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/chai/expt/ArrayPointer.hpp b/src/chai/expt/ArrayPointer.hpp index 5c8f32e5..a61cf7ea 100644 --- a/src/chai/expt/ArrayPointer.hpp +++ b/src/chai/expt/ArrayPointer.hpp @@ -138,7 +138,7 @@ namespace chai::expt return m_data; } - CHAI_HOST_DEVICE ElementType& operator[](std::size_t i) + CHAI_HOST_DEVICE ElementType& operator[](std::size_t i) const { return m_data[i]; } diff --git a/tests/expt/HostDeviceArrayPointerTests.cpp b/tests/expt/HostDeviceArrayPointerTests.cpp index 90bb7f7a..4ece1ad9 100644 --- a/tests/expt/HostDeviceArrayPointerTests.cpp +++ b/tests/expt/HostDeviceArrayPointerTests.cpp @@ -1,3 +1,4 @@ +#include "chai/config.hpp" #include "chai/expt/ArrayPointer.hpp" #include "chai/expt/HostDeviceArrayManager.hpp" #include @@ -194,4 +195,18 @@ TEST_F(HostDeviceArrayPointerTest, ExceptionHandling) { 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 From 56fd8640a6b8c5ff5c456e4941dde5e460fad2b8 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Wed, 29 Oct 2025 15:55:09 -0700 Subject: [PATCH 27/29] Mark more ArrayPointer functions as const --- src/chai/expt/ArrayPointer.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/chai/expt/ArrayPointer.hpp b/src/chai/expt/ArrayPointer.hpp index a61cf7ea..bef31345 100644 --- a/src/chai/expt/ArrayPointer.hpp +++ b/src/chai/expt/ArrayPointer.hpp @@ -96,7 +96,7 @@ namespace chai::expt return m_size; } - CHAI_HOST_DEVICE void update() + CHAI_HOST_DEVICE void update() const { #if !defined(CHAI_DEVICE_COMPILE) if (m_manager) @@ -111,7 +111,7 @@ namespace chai::expt #endif } - CHAI_HOST_DEVICE void cupdate() + CHAI_HOST_DEVICE void cupdate() const { #if !defined(CHAI_DEVICE_COMPILE) if (m_manager) @@ -126,13 +126,13 @@ namespace chai::expt #endif } - CHAI_HOST_DEVICE ElementType* data() + CHAI_HOST_DEVICE ElementType* data() const { update(); return m_data; } - CHAI_HOST_DEVICE ElementType* cdata() + CHAI_HOST_DEVICE const ElementType* cdata() const { cupdate(); return m_data; @@ -168,8 +168,8 @@ namespace chai::expt } private: - ElementType* m_data{nullptr}; - std::size_t m_size{0}; + mutable ElementType* m_data{nullptr}; + mutable std::size_t m_size{0}; Manager* m_manager{nullptr}; }; // class ArrayPointer } // namespace chai::expt From dd57fcec579b27d5e04fa384c6b8b69481430283 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Wed, 29 Oct 2025 16:03:55 -0700 Subject: [PATCH 28/29] Fix converting constructor --- src/chai/expt/ArrayPointer.hpp | 6 ++++-- tests/expt/HostDeviceArrayPointerTests.cpp | 2 -- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/chai/expt/ArrayPointer.hpp b/src/chai/expt/ArrayPointer.hpp index bef31345..0e8ada62 100644 --- a/src/chai/expt/ArrayPointer.hpp +++ b/src/chai/expt/ArrayPointer.hpp @@ -34,7 +34,6 @@ namespace chai::expt update(); } -#if 0 template >> CHAI_HOST_DEVICE ArrayPointer(const ArrayPointer& other) @@ -44,7 +43,6 @@ namespace chai::expt { update(); } -#endif CHAI_HOST_DEVICE ArrayPointer& operator=(const ArrayPointer& other) { @@ -171,6 +169,10 @@ namespace chai::expt 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 diff --git a/tests/expt/HostDeviceArrayPointerTests.cpp b/tests/expt/HostDeviceArrayPointerTests.cpp index 4ece1ad9..cbe271cf 100644 --- a/tests/expt/HostDeviceArrayPointerTests.cpp +++ b/tests/expt/HostDeviceArrayPointerTests.cpp @@ -48,7 +48,6 @@ TEST_F(HostDeviceArrayPointerTest, CopyConstructor) { ptr1.free(); } -#if 0 TEST_F(HostDeviceArrayPointerTest, ConvertingConstructor) { auto* manager = new chai::expt::HostDeviceArrayManager(5); HostDeviceArrayPointer ptr1(manager); @@ -59,7 +58,6 @@ TEST_F(HostDeviceArrayPointerTest, ConvertingConstructor) { ptr2.free(); } -#endif TEST_F(HostDeviceArrayPointerTest, CopyAssignment) { auto* manager1 = new chai::expt::HostDeviceArrayManager(5); From 80233099b910c53a6044fdd1249273b1823704ce Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Fri, 31 Oct 2025 08:39:46 -0700 Subject: [PATCH 29/29] Add cuda host config --- .../lc/toss_4_x86_64_ib/nvcc_clang.cmake | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 host-configs/lc/toss_4_x86_64_ib/nvcc_clang.cmake 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 "")