diff --git a/cmake/SetupChaiOptions.cmake b/cmake/SetupChaiOptions.cmake index 2b895c7d..4de2b249 100644 --- a/cmake/SetupChaiOptions.cmake +++ b/cmake/SetupChaiOptions.cmake @@ -16,6 +16,7 @@ option(CHAI_ENABLE_UM "Use CUDA unified (managed) memory" Off) option(CHAI_THIN_GPU_ALLOCATE "Single memory space model" Off) option(CHAI_ENABLE_PINNED "Use pinned host memory" Off) option(CHAI_ENABLE_RAJA_PLUGIN "Build plugin to set RAJA execution spaces" On) +option(CHAI_ENABLE_EXPERIMENTAL_RAJA_PLUGIN "Build experimental plugin to integrate CHAI context with RAJA" Off) option(CHAI_ENABLE_GPU_ERROR_CHECKING "Enable GPU error checking" On) option(CHAI_ENABLE_MANAGED_PTR "Enable managed_ptr" On) option(CHAI_DEBUG "Enable Debug Logging." Off) diff --git a/docs/sphinx/expt/design.rst b/docs/sphinx/expt/design.rst new file mode 100644 index 00000000..de097913 --- /dev/null +++ b/docs/sphinx/expt/design.rst @@ -0,0 +1,96 @@ +.. + # Copyright (c) 2016-26, Lawrence Livermore National Security, LLC and CHAI + # project contributors. See the CHAI LICENSE file for details. + # + # SPDX-License-Identifier: BSD-3-Clause + +.. _experimental_design: + +******************* +Experimental Design +******************* + +CHAI provides data structures that implicitly manage coherence across multiple execution contexts. + +------- +Context +------- + +Currently, there are two execution contexts that are handled by CHAI. These are represented in the `Context` enum class. +The `HOST` enum value represents synchronous execution on a CPU. The `DEVICE` enum value represents asynchronous execution on a GPU. +Both NVIDIA and AMD GPUs are supported. + +-------------- +ContextManager +-------------- + +Implicitly managing data coherence requires managing some global state. This is handled by a singleton called `ContextManager`. +When an application enters an execution context, it uses `ContextManager` to set the current context. `ContextManager` also +tracks which contexts may need synchronization. CHAI data structures can query `ContextManager` to update data coherence and +inform `ContextManager` of needed synchronization or synchronization that has been performed. + +Note: It is much faster for `ContextManager` to track synchronization than to repeatedly call `cudaDeviceSynchronize()` or `hipDeviceSynchronize()`. + +.. code-block:: cpp + + #include "chai/expt/ContextManager.hpp" + + ::chai::expt::ContextManager& contextManager = ::chai::expt::ContextManager::getInstance(); + + contextManager.setContext(::chai::expt::Context::HOST); + // Use CHAI data structures in the HOST context... + contextManager.setContext(::chai::expt::Context::NONE); + + contextManager.setContext(::chai::expt::Context::DEVICE); + // Use CHAI data structures in the DEVICE context... + contextManager.setContext(::chai::expt::Context::NONE); + +------------ +ContextGuard +------------ + +It is easy to forget to reset the current context or even to forget the current context +when writing code. Similar to `std::lock_guard`, CHAI provides `ContextGuard` that sets +the active context and then resets it upon destruction. This is the recommended approach. + +.. code-block:: cpp + + #include "chai/expt/ContextGuard.hpp" + + { + ::chai::expt::ContextGuard contextGuard{::chai::expt::Context::HOST}; + // Use CHAI data structures in the HOST context... + } + + { + ::chai::expt::ContextGuard contextGuard{::chai::expt::Context::DEVICE}; + // Use CHAI data structures in the DEVICE context... + } + +----------------- +ContextRAJAPlugin +----------------- + +In an application that also uses RAJA, CHAI provides a RAJA plugin, `ContextRAJAPlugin`, +that implicitly manages the context in calls to RAJA. To enable this plugin, configure with +`-DCHAI_ENABLE_EXPERIMENTAL_RAJA_PLUGIN=ON` and register the plugin. In the future, registration +may be handled by CHAI. + +.. code-block:: cpp + + #include "chai/expt/ContextRAJAPlugin.hpp" + #include "RAJA/RAJA.hpp" + + static ::RAJA::util::PluginRegistry::add P( + "CHAIContextPlugin", + "Plugin that integrates CHAI context management with RAJA."); + + ::RAJA::forall<::RAJA::seq_exec>(::RAJA::TypedRangeSegment(0, N), [=] (int i) { + // Use CHAI data structures in the HOST context... + }); + + constexpr int BLOCK_SIZE = 256; + + ::RAJA::forall<::RAJA::cuda_exec_async>(::RAJA::TypedRangeSegment(0, N), [=] __device__ (int i) { + // Use CHAI data structures in the DEVICE context... + }); 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..7a4a7343 --- /dev/null +++ b/host-configs/lc/toss_4_x86_64_ib/nvcc_clang.cmake @@ -0,0 +1,35 @@ +############################################################################## +# Copyright (c) 2016-26, Lawrence Livermore National Security, LLC and CHAI +# project contributors. See the CHAI 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 "19.1.3" 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} --expt-relaxed-constexpr" 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 (if needed) +set(BLT_CMAKE_IMPLICIT_LINK_DIRECTORIES_EXCLUDE "" CACHE STRING "") + +# The header only version of fmt in umpire has issues with nvcc +set(UMPIRE_FMT_TARGET "fmt::fmt" CACHE STRING "") diff --git a/host-configs/lc/toss_4_x86_64_ib_cray/amdclang.cmake b/host-configs/lc/toss_4_x86_64_ib_cray/amdclang.cmake index db1dafbd..492b5ccf 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.3" CACHE PATH "") +set(GCC_VERSION "13.3.1" CACHE PATH "") # Set up compilers set(COMPILER_BASE "/usr/tce/packages/rocmcc/rocmcc-${ROCM_VERSION}-magic" CACHE PATH "") diff --git a/src/chai/CMakeLists.txt b/src/chai/CMakeLists.txt index cd8d5d8a..5b8889bf 100644 --- a/src/chai/CMakeLists.txt +++ b/src/chai/CMakeLists.txt @@ -23,30 +23,31 @@ set (chai_headers PointerRecord.hpp Types.hpp) +set (chai_sources + ArrayManager.cpp) + +if(CHAI_DISABLE_RM) + set(chai_headers + ${chai_headers} + ManagedArray_thin.inl) +endif () + if(CHAI_ENABLE_EXPERIMENTAL) set(chai_headers ${chai_headers} + expt/Context.hpp + expt/ContextGuard.hpp + expt/ContextManager.hpp ManagedSharedPtr.hpp SharedPtrCounter.hpp SharedPtrManager.hpp SharedPtrManager.inl SharedPointerRecord.hpp) -endif() -if(CHAI_DISABLE_RM) - set(chai_headers - ${chai_headers} - ManagedArray_thin.inl) -endif () - -set (chai_sources - ArrayManager.cpp) - -if(CHAI_ENABLE_EXPERIMENTAL) set (chai_sources ${chai_sources} SharedPtrManager.cpp) -endif () +endif() set (chai_depends umpire) @@ -84,6 +85,16 @@ if (CHAI_ENABLE_RAJA_PLUGIN) endif () endif () +if (CHAI_ENABLE_EXPERIMENTAL_RAJA_PLUGIN) + set (chai_headers + ${chai_headers} + expt/ContextRAJAPlugin.hpp) + + set (chai_sources + ${chai_sources} + expt/ContextRAJAPlugin.cpp) +endif() + blt_add_library( NAME chai SOURCES ${chai_sources} diff --git a/src/chai/expt/Context.hpp b/src/chai/expt/Context.hpp new file mode 100644 index 00000000..ce37022f --- /dev/null +++ b/src/chai/expt/Context.hpp @@ -0,0 +1,24 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-26, 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_HPP +#define CHAI_CONTEXT_HPP + +namespace chai::expt +{ + /*! + * \brief Execution context identifier. + */ + enum class Context + { + NONE = 0, /*!< No context. */ + HOST = 1, /*!< Host (CPU) context. */ + DEVICE = 2 /*!< Device (GPU/accelerator) context. */ + }; // enum class Context +} // namespace chai::expt + +#endif // CHAI_CONTEXT_HPP diff --git a/src/chai/expt/ContextGuard.hpp b/src/chai/expt/ContextGuard.hpp new file mode 100644 index 00000000..b4313de7 --- /dev/null +++ b/src/chai/expt/ContextGuard.hpp @@ -0,0 +1,49 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-26, 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::expt { + /*! + * \brief RAII guard that temporarily sets the active Context and restores the + * previously active Context upon destruction. + */ + class ContextGuard { + public: + /*! + * \brief Sets the active Context for the lifetime of this guard. + * \param context The Context to set as active. + */ + explicit ContextGuard(Context context) { + m_context_manager.setContext(context); + } + + /*! + * \brief Restores the Context that was active when this guard was created. + */ + ~ContextGuard() { + m_context_manager.setContext(m_saved_context); + } + + private: + /*! + * \brief Reference to the global ContextManager instance. + */ + ContextManager& m_context_manager{ContextManager::getInstance()}; + + /*! + * Context that was active at guard construction time. + */ + Context m_saved_context{m_context_manager.getContext()}; + }; // class ContextGuard +} // namespace chai::expt + +#endif // CHAI_CONTEXT_GUARD_HPP diff --git a/src/chai/expt/ContextManager.hpp b/src/chai/expt/ContextManager.hpp new file mode 100644 index 00000000..34cb89cb --- /dev/null +++ b/src/chai/expt/ContextManager.hpp @@ -0,0 +1,141 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-26, Lawrence Livermore National Security, LLC and CHAI +// project contributors. See the CHAI LICENSE file for details. +// +// SPDX-License-Identifier: BSD-3-Clause +////////////////////////////////////////////////////////////////////////////// + +#ifndef CHAI_CONTEXT_MANAGER_HPP +#define CHAI_CONTEXT_MANAGER_HPP + +#include "chai/config.hpp" +#include "chai/expt/Context.hpp" +#include "camp/helpers.hpp" + +#if defined(CHAI_ENABLE_CUDA) +#include +#elif defined(CHAI_ENABLE_HIP) +#include +#endif + +namespace chai::expt { + /*! + * \brief Singleton class for managing the current context + * and context synchronization across the application. + */ + class ContextManager + { + public: + /*! + * \brief Get the singleton instance. + */ + static ContextManager& getInstance() + { + static ContextManager s_instance; + return s_instance; + } + + /*! + * \brief Disable copy construction. + * + * ContextManager is a singleton and must not be copied. + */ + ContextManager(const ContextManager&) = delete; + + /*! + * \brief Disable copy assignment. + * + * ContextManager is a singleton and must not be assigned. + */ + ContextManager& operator=(const ContextManager&) = delete; + + /*! + * \brief Get the current context. + */ + Context getContext() const + { + return m_context; + } + + /*! + * \brief Set the current context. + * + * Setting the context to DEVICE marks the device as not synchronized. + */ + void setContext(Context context) + { + m_context = context; + + if (context == Context::DEVICE) + { + m_device_synchronized = false; + } + } + + /*! + * \brief Synchronize the requested context (no-op if already synchronized). + */ + void synchronize(Context context) + { + if (context == Context::DEVICE && !m_device_synchronized) + { +#if defined(CHAI_ENABLE_CUDA) + CAMP_CUDA_API_INVOKE_AND_CHECK(cudaDeviceSynchronize); +#elif defined(CHAI_ENABLE_HIP) + CAMP_HIP_API_INVOKE_AND_CHECK(hipDeviceSynchronize); +#endif + m_device_synchronized = true; + } + } + + /*! + * \brief Query whether the requested context is synchronized. + */ + bool isSynchronized(Context context) const + { + return context == Context::DEVICE ? m_device_synchronized : true; + } + + /*! + * \brief Explicitly set the synchronization state for the DEVICE context. + */ + void setDeviceSynchronized(bool synchronized) + { + m_device_synchronized = synchronized; + } + + /*! + * \brief Reset manager state to defaults. + */ + void reset() + { + m_context = Context::NONE; + m_device_synchronized = true; + } + + private: + /*! + * \brief Default constructor. + * + * Private to enforce singleton access via getInstance(). + */ + ContextManager() = default; + + /*! + * \brief Current context for the application. + * + * Defaults to NONE until explicitly set. + */ + Context m_context{Context::NONE}; + + /*! + * \brief Device synchronization state. + * + * True if the device context has been synchronized since the last time the + * context was set to DEVICE. + */ + bool m_device_synchronized{true}; + }; // class ContextManager +} // namespace chai::expt + +#endif // CHAI_CONTEXT_MANAGER_HPP diff --git a/src/chai/expt/ContextRAJAPlugin.cpp b/src/chai/expt/ContextRAJAPlugin.cpp new file mode 100644 index 00000000..b9a00c27 --- /dev/null +++ b/src/chai/expt/ContextRAJAPlugin.cpp @@ -0,0 +1,42 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-26, Lawrence Livermore National Security, LLC and CHAI +// project contributors. See the CHAI LICENSE file for details. +// +// SPDX-License-Identifier: BSD-3-Clause +////////////////////////////////////////////////////////////////////////////// + +#include "chai/config.hpp" +#include "chai/expt/Context.hpp" +#include "chai/expt/ContextManager.hpp" +#include "chai/expt/ContextRAJAPlugin.hpp" + +namespace chai::expt { + void ContextRAJAPlugin::preCapture(const ::RAJA::util::PluginContext& p) { + Context context = Context::NONE; + + switch (p.platform) { + case ::RAJA::Platform::host: + context = Context::HOST; + break; +#if defined(CHAI_ENABLE_CUDA) + case ::RAJA::Platform::cuda: + context = Context::DEVICE; + break; +#endif +#if defined(CHAI_ENABLE_HIP) + case ::RAJA::Platform::hip: + context = Context::DEVICE; + break; +#endif + default: + context = Context::NONE; + break; + } + + ContextManager::getInstance().setContext(context); + } + + void ContextRAJAPlugin::postCapture(const ::RAJA::util::PluginContext&) { + ContextManager::getInstance().setContext(Context::NONE); + } +} // namespace chai::expt diff --git a/src/chai/expt/ContextRAJAPlugin.hpp b/src/chai/expt/ContextRAJAPlugin.hpp new file mode 100644 index 00000000..89abf02c --- /dev/null +++ b/src/chai/expt/ContextRAJAPlugin.hpp @@ -0,0 +1,45 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-26, 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_RAJA_PLUGIN_HPP +#define CHAI_CONTEXT_RAJA_PLUGIN_HPP + +#include "RAJA/util/PluginStrategy.hpp" + +namespace chai::expt { + /*! + * \brief Plugin that integrates CHAI context management with RAJA. + * + * CHAI data structures rely on being copy constructed in the correct context. + * Their typical usage is to capture them by copy into a lambda expression that + * is passed to RAJA. The lambda capture happens before the context is set, so + * RAJA calls the `preCapture` method, which sets the current execution context. + * Then RAJA copies the lambda, which triggers the copy constructors of the CHAI + * data structures, making their data coherent in the current execution context. + * Then RAJA calls the `postCapture` method, which unsets the current execution + * context so that the CHAI data structures do not update data coherence in an + * unexpected or unnecessary way. Finally, RAJA executes the lambda. + */ + class ContextRAJAPlugin : + public ::RAJA::util::PluginStrategy + { + public: + /*! + * \brief Sets the current context to match the RAJA execution context. + * \param p RAJA plugin context. + */ + void preCapture(const ::RAJA::util::PluginContext& p) override; + + /*! + * \brief Resets the current context. + * \param p RAJA plugin context for the capture. + */ + void postCapture(const ::RAJA::util::PluginContext& p) override; + }; // class ContextRAJAPlugin +} // namespace chai::expt + +#endif // CHAI_CONTEXT_RAJA_PLUGIN_HPP diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 58c759df..df7f3dd6 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() diff --git a/tests/expt/CMakeLists.txt b/tests/expt/CMakeLists.txt new file mode 100644 index 00000000..f0f31376 --- /dev/null +++ b/tests/expt/CMakeLists.txt @@ -0,0 +1,60 @@ +############################################################################## +# Copyright (c) 2016-26, Lawrence Livermore National Security, LLC and CHAI +# project contributors. See the CHAI LICENSE file for details. +# +# SPDX-License-Identifier: BSD-3-Clause +############################################################################## + +set(chai_expt_test_headers + TestHelpers.hpp) + +set(chai_expt_test_depends + chai + gtest) + +if(ENABLE_CUDA) + set(chai_expt_test_depends + ${chai_expt_test_depends} + cuda) +endif() + +if(ENABLE_HIP) + set(chai_expt_test_depends + ${chai_expt_test_depends} + blt::hip) +endif() + +blt_add_executable( + NAME ContextManagerTests + SOURCES ContextManagerTests.cpp + HEADERS ${chai_expt_test_headers} + INCLUDES ${PROJECT_BINARY_DIR}/include + DEPENDS_ON ${chai_expt_test_depends}) + +blt_add_test( + NAME ContextManagerTests + COMMAND ContextManagerTests) + +blt_add_executable( + NAME ContextGuardTests + SOURCES ContextGuardTests.cpp + HEADERS ${chai_expt_test_headers} + INCLUDES ${PROJECT_BINARY_DIR}/include + DEPENDS_ON ${chai_expt_test_depends}) + +blt_add_test( + NAME ContextGuardTests + COMMAND ContextGuardTests) + +if(CHAI_ENABLE_EXPERIMENTAL_RAJA_PLUGIN) + blt_add_executable( + NAME ContextRAJAPluginTests + SOURCES ContextRAJAPluginTests.cpp + HEADERS ${chai_expt_test_headers} + INCLUDES ${PROJECT_BINARY_DIR}/include + DEPENDS_ON ${chai_expt_test_depends}) + + blt_add_test( + NAME ContextRAJAPluginTests + COMMAND ContextRAJAPluginTests) +endif() diff --git a/tests/expt/ContextGuardTests.cpp b/tests/expt/ContextGuardTests.cpp new file mode 100644 index 00000000..6e246600 --- /dev/null +++ b/tests/expt/ContextGuardTests.cpp @@ -0,0 +1,39 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-26, 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/ContextGuard.hpp" +#include "gtest/gtest.h" + +// Test that ContextGuard updates the current context in scope +// and restores the previous context on destruction. +TEST(ContextGuard, HOST) { + ::chai::expt::ContextManager& contextManager = ::chai::expt::ContextManager::getInstance(); + ::chai::expt::Context context = contextManager.getContext(); + + { + ::chai::expt::Context tempContext = ::chai::expt::Context::HOST; + ::chai::expt::ContextGuard contextGuard(tempContext); + EXPECT_EQ(contextManager.getContext(), tempContext); + } + + EXPECT_EQ(contextManager.getContext(), context); +} + +// Test that ContextGuard updates the current context in scope +// and restores the previous context on destruction. +TEST(ContextGuard, DEVICE) { + ::chai::expt::ContextManager& contextManager = ::chai::expt::ContextManager::getInstance(); + ::chai::expt::Context context = contextManager.getContext(); + + { + ::chai::expt::Context tempContext = ::chai::expt::Context::DEVICE; + ::chai::expt::ContextGuard contextGuard(tempContext); + EXPECT_EQ(contextManager.getContext(), tempContext); + } + + EXPECT_EQ(contextManager.getContext(), context); +} diff --git a/tests/expt/ContextManagerTests.cpp b/tests/expt/ContextManagerTests.cpp new file mode 100644 index 00000000..2be686e6 --- /dev/null +++ b/tests/expt/ContextManagerTests.cpp @@ -0,0 +1,45 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-26, Lawrence Livermore National Security, LLC and CHAI +// project contributors. See the CHAI LICENSE file for details. +// +// SPDX-License-Identifier: BSD-3-Clause +////////////////////////////////////////////////////////////////////////////// + +#include "chai/config.hpp" +#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 context is NONE +TEST(ContextManager, DefaultContext) { + ::chai::expt::ContextManager& contextManager = ::chai::expt::ContextManager::getInstance(); + EXPECT_EQ(contextManager.getContext(), ::chai::expt::Context::NONE); +} + +// Test setting the HOST context +TEST(ContextManager, HOST) { + ::chai::expt::ContextManager& contextManager = ::chai::expt::ContextManager::getInstance(); + ::chai::expt::Context context = ::chai::expt::Context::HOST; + contextManager.setContext(context); + EXPECT_EQ(contextManager.getContext(), context); + EXPECT_EQ(contextManager.isSynchronized(context), true); + contextManager.setContext(::chai::expt::Context::NONE); +} + +// Test setting the DEVICE context +TEST(ContextManager, DEVICE) { + ::chai::expt::ContextManager& contextManager = ::chai::expt::ContextManager::getInstance(); + ::chai::expt::Context context = ::chai::expt::Context::DEVICE; + contextManager.setContext(context); + EXPECT_EQ(contextManager.getContext(), context); + EXPECT_EQ(contextManager.isSynchronized(context), false); + contextManager.setDeviceSynchronized(true); + EXPECT_EQ(contextManager.isSynchronized(context), true); + contextManager.setContext(::chai::expt::Context::NONE); +} diff --git a/tests/expt/ContextRAJAPluginTests.cpp b/tests/expt/ContextRAJAPluginTests.cpp new file mode 100644 index 00000000..8b160258 --- /dev/null +++ b/tests/expt/ContextRAJAPluginTests.cpp @@ -0,0 +1,119 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-26, Lawrence Livermore National Security, LLC and CHAI +// project contributors. See the CHAI LICENSE file for details. +// +// SPDX-License-Identifier: BSD-3-Clause +////////////////////////////////////////////////////////////////////////////// + +#include "chai/config.hpp" +#include "chai/ChaiMacros.hpp" +#include "chai/expt/Context.hpp" +#include "chai/expt/ContextManager.hpp" +#include "chai/expt/ContextRAJAPlugin.hpp" +#include "RAJA/RAJA.hpp" +#include "gtest/gtest.h" +#include "TestHelpers.hpp" + +// Pre-main registration of plugin with RAJA +static ::RAJA::util::PluginRegistry::add<::chai::expt::ContextRAJAPlugin> P( + "CHAIContextPlugin", + "Plugin that integrates CHAI context management with RAJA."); + +/*! + * \brief Tests whether the plugin was actually called. + */ +class ContextRAJAPluginTester { + public: + /*! + * @brief Construct a tester with an initial context of NONE. + */ + ContextRAJAPluginTester() = default; + + /*! + * @brief Copy-construct and capture the current ContextManager context. + */ + CHAI_HOST_DEVICE ContextRAJAPluginTester(const ContextRAJAPluginTester& other) + : m_context{other.m_context} + { +#if !defined(CHAI_DEVICE_COMPILE) + ::chai::expt::Context context = ::chai::expt::ContextManager::getInstance().getContext(); + + if (context != ::chai::expt::Context::NONE) { + m_context = context; + } +#endif + } + + /*! + * @brief Get the stored context. + * + * @return The stored ::chai::expt::Context value. + */ + CHAI_HOST_DEVICE ::chai::expt::Context getContext() const { + return m_context; + } + + private: + /*! + * @brief Stored context value. + */ + ::chai::expt::Context m_context{::chai::expt::Context::NONE}; +}; + +// Test that the tester object got the updated context and that the current context +// is NONE inside the loop. +TEST(ContextRAJAPlugin, HOST) { + ContextRAJAPluginTester tester{}; + EXPECT_EQ(tester.getContext(), ::chai::expt::Context::NONE); + + ::RAJA::forall<::RAJA::seq_exec>(::RAJA::TypedRangeSegment(0, 1), [=] (int) { + EXPECT_EQ(tester.getContext(), ::chai::expt::Context::HOST); + EXPECT_EQ(::chai::expt::ContextManager::getInstance().getContext(), ::chai::expt::Context::NONE); + }); + + EXPECT_EQ(tester.getContext(), ::chai::expt::Context::NONE); +} + +#if defined(CHAI_ENABLE_CUDA) +// Test that the tester object got the updated context. +CUDA_TEST(ContextRAJAPlugin, CUDA) { + ContextRAJAPluginTester tester{}; + EXPECT_EQ(tester.getContext(), ::chai::expt::Context::NONE); + + ::chai::expt::Context* result = nullptr; + CAMP_CUDA_API_INVOKE_AND_CHECK(cudaMallocManaged, (void**)&result, sizeof(::chai::expt::Context)); + + ::RAJA::forall<::RAJA::cuda_exec_async<256>>(::RAJA::TypedRangeSegment(0, 1), [=] __device__ (int) { + *result = tester.getContext(); + }); + + CAMP_CUDA_API_INVOKE_AND_CHECK(cudaDeviceSynchronize); + + EXPECT_EQ(*result, ::chai::expt::Context::DEVICE); + EXPECT_EQ(tester.getContext(), ::chai::expt::Context::NONE); + + CAMP_CUDA_API_INVOKE_AND_CHECK(cudaFree, (void*) result); +} +#endif + +#if defined(CHAI_ENABLE_HIP) +// Test that the tester object got the updated context. +TEST(ContextRAJAPlugin, HIP) { + ContextRAJAPluginTester tester{}; + EXPECT_EQ(tester.getContext(), ::chai::expt::Context::NONE); + + ::chai::expt::Context* result = nullptr; + CAMP_HIP_API_INVOKE_AND_CHECK(hipMallocManaged, (void**)&result, sizeof(::chai::expt::Context)); + + ::RAJA::forall<::RAJA::hip_exec_async<256>>(::RAJA::TypedRangeSegment(0, 1), [=] __device__ (int) { + *result = tester.getContext(); + }); + + CAMP_HIP_API_INVOKE_AND_CHECK(hipDeviceSynchronize); + + EXPECT_EQ(*result, ::chai::expt::Context::DEVICE); + EXPECT_EQ(tester.getContext(), ::chai::expt::Context::NONE); + + CAMP_HIP_API_INVOKE_AND_CHECK(hipFree, (void*) result); +} +#endif diff --git a/tests/expt/TestHelpers.hpp b/tests/expt/TestHelpers.hpp new file mode 100644 index 00000000..4ef94dfb --- /dev/null +++ b/tests/expt/TestHelpers.hpp @@ -0,0 +1,16 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-26, Lawrence Livermore National Security, LLC and CHAI +// project contributors. See the CHAI LICENSE file for details. +// +// SPDX-License-Identifier: BSD-3-Clause +////////////////////////////////////////////////////////////////////////////// + +#ifndef CHAI_TEST_HELPERS_HPP +#define CHAI_TEST_HELPERS_HPP + +#define CUDA_TEST(X, Y) \ + static void cuda_test_##X##Y(); \ + TEST(X, Y) { cuda_test_##X##Y(); } \ + static void cuda_test_##X##Y() + +#endif // CHAI_TEST_HELPERS_HPP