From 403dd461d7830f9550acec26e5462b15d553569a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20P=C3=B6schel?= Date: Thu, 7 Aug 2025 13:25:03 +0200 Subject: [PATCH 01/11] Add memory selection task to backend --- include/openPMD/Dataset.hpp | 6 ++++ include/openPMD/IO/ADIOS/ADIOS2File.hpp | 2 ++ include/openPMD/IO/ADIOS/ADIOS2IOHandler.hpp | 15 ++++++++ include/openPMD/IO/ADIOS/macros.hpp | 23 +++++++++++++ include/openPMD/IO/IOTask.hpp | 1 + src/IO/ADIOS/ADIOS2File.cpp | 36 ++++++++++++++++++++ src/IO/ADIOS/ADIOS2IOHandler.cpp | 2 ++ src/IO/AbstractIOHandlerImpl.cpp | 24 ++++++++++--- src/IO/HDF5/HDF5IOHandler.cpp | 7 ++++ src/IO/JSON/JSONIOHandlerImpl.cpp | 7 ++++ 10 files changed, 119 insertions(+), 4 deletions(-) diff --git a/include/openPMD/Dataset.hpp b/include/openPMD/Dataset.hpp index 80513683f9..f35c9a0247 100644 --- a/include/openPMD/Dataset.hpp +++ b/include/openPMD/Dataset.hpp @@ -34,6 +34,12 @@ namespace openPMD using Extent = std::vector; using Offset = std::vector; +struct MemorySelection +{ + Offset offset; + Extent extent; +}; + class Dataset { friend class RecordComponent; diff --git a/include/openPMD/IO/ADIOS/ADIOS2File.hpp b/include/openPMD/IO/ADIOS/ADIOS2File.hpp index d34cc8ebe5..0b7aa6f314 100644 --- a/include/openPMD/IO/ADIOS/ADIOS2File.hpp +++ b/include/openPMD/IO/ADIOS/ADIOS2File.hpp @@ -20,6 +20,7 @@ */ #pragma once +#include "openPMD/Dataset.hpp" #include "openPMD/IO/ADIOS/ADIOS2Auxiliary.hpp" #include "openPMD/IO/ADIOS/ADIOS2PreloadAttributes.hpp" #include "openPMD/IO/ADIOS/ADIOS2PreloadVariables.hpp" @@ -112,6 +113,7 @@ struct BufferedUniquePtrPut std::string name; Offset offset; Extent extent; + std::optional memorySelection; UniquePtrWithLambda data; Datatype dtype = Datatype::UNDEFINED; diff --git a/include/openPMD/IO/ADIOS/ADIOS2IOHandler.hpp b/include/openPMD/IO/ADIOS/ADIOS2IOHandler.hpp index 6c3d499779..46332f277e 100644 --- a/include/openPMD/IO/ADIOS/ADIOS2IOHandler.hpp +++ b/include/openPMD/IO/ADIOS/ADIOS2IOHandler.hpp @@ -20,6 +20,7 @@ */ #pragma once +#include "openPMD/Dataset.hpp" #include "openPMD/Error.hpp" #include "openPMD/IO/ADIOS/ADIOS2Auxiliary.hpp" #include "openPMD/IO/ADIOS/ADIOS2FilePosition.hpp" @@ -508,6 +509,7 @@ class ADIOS2IOHandlerImpl adios2::Variable verifyDataset( Offset const &offset, Extent const &extent, + std::optional const &memorySelection, adios2::IO &IO, adios2::Engine &engine, std::string const &varName, @@ -621,6 +623,18 @@ class ADIOS2IOHandlerImpl var.SetSelection( {adios2::Dims(offset.begin(), offset.end()), adios2::Dims(extent.begin(), extent.end())}); + + if (memorySelection.has_value()) + { + var.SetMemorySelection( + {adios2::Dims( + memorySelection->offset.begin(), + memorySelection->offset.end()), + adios2::Dims( + memorySelection->extent.begin(), + memorySelection->extent.end())}); + } + return var; } @@ -628,6 +642,7 @@ class ADIOS2IOHandlerImpl { bool noGroupBased = false; bool blosc2bp5 = false; + bool memorySelection = false; } printedWarningsAlready; }; // ADIOS2IOHandlerImpl diff --git a/include/openPMD/IO/ADIOS/macros.hpp b/include/openPMD/IO/ADIOS/macros.hpp index 656a7aec35..c044a691b9 100644 --- a/include/openPMD/IO/ADIOS/macros.hpp +++ b/include/openPMD/IO/ADIOS/macros.hpp @@ -21,6 +21,29 @@ #define openPMD_HAVE_ADIOS2_BP5 0 #endif +namespace openPMD +{ +namespace detail +{ + template + struct CanTheMemorySelectionBeReset + { + static constexpr bool value = false; + }; + + template + struct CanTheMemorySelectionBeReset< + Variable, + decltype(std::declval().SetMemorySelection())> + { + static constexpr bool value = true; + }; +} // namespace detail + +constexpr bool CanTheMemorySelectionBeReset = + detail::CanTheMemorySelectionBeReset>::value; +} // namespace openPMD + #else #define openPMD_HAS_ADIOS_2_8 0 diff --git a/include/openPMD/IO/IOTask.hpp b/include/openPMD/IO/IOTask.hpp index 4c82cee174..1af318329e 100644 --- a/include/openPMD/IO/IOTask.hpp +++ b/include/openPMD/IO/IOTask.hpp @@ -494,6 +494,7 @@ struct OPENPMDAPI_EXPORT Extent extent = {}; Offset offset = {}; + std::optional memorySelection = std::nullopt; Datatype dtype = Datatype::UNDEFINED; auxiliary::WriteBuffer data; }; diff --git a/src/IO/ADIOS/ADIOS2File.cpp b/src/IO/ADIOS/ADIOS2File.cpp index a50bccd943..dbed07febf 100644 --- a/src/IO/ADIOS/ADIOS2File.cpp +++ b/src/IO/ADIOS/ADIOS2File.cpp @@ -23,6 +23,7 @@ #include "openPMD/Error.hpp" #include "openPMD/IO/ADIOS/ADIOS2Auxiliary.hpp" #include "openPMD/IO/ADIOS/ADIOS2IOHandler.hpp" +#include "openPMD/IO/ADIOS/macros.hpp" #include "openPMD/IO/AbstractIOHandler.hpp" #include "openPMD/IterationEncoding.hpp" #include "openPMD/auxiliary/Environment.hpp" @@ -67,6 +68,7 @@ void DatasetReader::call( adios2::Variable var = impl->verifyDataset( bp.param.offset, bp.param.extent, + std::nullopt, IO, engine, bp.name, @@ -85,6 +87,12 @@ void DatasetReader::call( template inline constexpr bool always_false_v = false; +static constexpr char const *warningMemorySelection = + "[Warning] Using a version of ADIOS2 that cannot reset memory selections " + "on a variable, once specified. When using memory selections, then please " + "specify it explicitly on all storeChunk() calls. Further info: " + "https://github.com/ornladios/ADIOS2/pull/4169."; + template void WriteDataset::call(ADIOS2File &ba, detail::BufferedPut &bp) { @@ -103,6 +111,7 @@ void WriteDataset::call(ADIOS2File &ba, detail::BufferedPut &bp) adios2::Variable var = ba.m_impl->verifyDataset( bp.param.offset, bp.param.extent, + bp.param.memorySelection, ba.m_IO, engine, bp.name, @@ -110,6 +119,19 @@ void WriteDataset::call(ADIOS2File &ba, detail::BufferedPut &bp) ba.variables()); engine.Put(var, ptr); + if (bp.param.memorySelection.has_value()) + { + if constexpr (openPMD::CanTheMemorySelectionBeReset) + { + var.SetMemorySelection(); + } + else if (!ba.m_impl->printedWarningsAlready.memorySelection) + { + std::cerr << warningMemorySelection << std::endl; + ba.m_impl->printedWarningsAlready.memorySelection = + true; + } + } } else if constexpr (std::is_same_v< ptr_type, @@ -119,6 +141,7 @@ void WriteDataset::call(ADIOS2File &ba, detail::BufferedPut &bp) bput.name = std::move(bp.name); bput.offset = std::move(bp.param.offset); bput.extent = std::move(bp.param.extent); + bput.memorySelection = std::move(bp.param.memorySelection); /* * Note: Moving is required here since it's a unique_ptr. * std::forward<>() would theoretically work, but it @@ -175,12 +198,25 @@ struct RunUniquePtrPut adios2::Variable var = ba.m_impl->verifyDataset( bufferedPut.offset, bufferedPut.extent, + bufferedPut.memorySelection, ba.m_IO, engine, bufferedPut.name, std::nullopt, ba.variables()); engine.Put(var, ptr); + if (bufferedPut.memorySelection.has_value()) + { + if constexpr (openPMD::CanTheMemorySelectionBeReset) + { + var.SetMemorySelection(); + } + else if (!ba.m_impl->printedWarningsAlready.memorySelection) + { + std::cerr << warningMemorySelection << std::endl; + ba.m_impl->printedWarningsAlready.memorySelection = true; + } + } } static constexpr char const *errorMsg = "RunUniquePtrPut"; diff --git a/src/IO/ADIOS/ADIOS2IOHandler.cpp b/src/IO/ADIOS/ADIOS2IOHandler.cpp index 027a1038ad..a0b104fa86 100644 --- a/src/IO/ADIOS/ADIOS2IOHandler.cpp +++ b/src/IO/ADIOS/ADIOS2IOHandler.cpp @@ -50,6 +50,7 @@ #include #include #include +#include #include #include #include @@ -1188,6 +1189,7 @@ namespace detail adios2::Variable variable = impl->verifyDataset( params.offset, params.extent, + std::nullopt, IO, engine, varName, diff --git a/src/IO/AbstractIOHandlerImpl.cpp b/src/IO/AbstractIOHandlerImpl.cpp index 6766546a69..053dcbaf5f 100644 --- a/src/IO/AbstractIOHandlerImpl.cpp +++ b/src/IO/AbstractIOHandlerImpl.cpp @@ -269,10 +269,26 @@ std::future AbstractIOHandlerImpl::flush() i.writable->parent, "->", i.writable, - "] WRITE_DATASET, offset=", - [¶meter]() { return vec_as_string(parameter.offset); }, - ", extent=", - [¶meter]() { return vec_as_string(parameter.extent); }); + "] WRITE_DATASET: ", + [&]() { + std::stringstream stream; + stream << "offset: " << vec_as_string(parameter.offset) + << " extent: " << vec_as_string(parameter.extent) + << " mem-selection: "; + if (parameter.memorySelection.has_value()) + { + stream << vec_as_string( + parameter.memorySelection->offset) + << "--" + << vec_as_string( + parameter.memorySelection->extent); + } + else + { + stream << "NONE"; + } + return stream.str(); + }); writeDataset(i.writable, parameter); break; } diff --git a/src/IO/HDF5/HDF5IOHandler.cpp b/src/IO/HDF5/HDF5IOHandler.cpp index dc05bed0f1..d3d44539ee 100644 --- a/src/IO/HDF5/HDF5IOHandler.cpp +++ b/src/IO/HDF5/HDF5IOHandler.cpp @@ -1555,6 +1555,13 @@ void HDF5IOHandlerImpl::writeDataset( "[HDF5] Writing into a dataset in a file opened as read only is " "not possible."); + if (parameters.memorySelection.has_value()) + { + throw error::OperationUnsupportedInBackend( + "HDF5", + "Non-contiguous memory selections not supported in HDF5 backend."); + } + auto res = getFile(writable); File file = res ? res.value() : getFile(writable->parent).value(); diff --git a/src/IO/JSON/JSONIOHandlerImpl.cpp b/src/IO/JSON/JSONIOHandlerImpl.cpp index 0a80e53321..e24929765d 100644 --- a/src/IO/JSON/JSONIOHandlerImpl.cpp +++ b/src/IO/JSON/JSONIOHandlerImpl.cpp @@ -1149,6 +1149,13 @@ void JSONIOHandlerImpl::writeDataset( access::write(m_handler->m_backendAccess), "[JSON] Cannot write data in read-only mode."); + if (parameters.memorySelection.has_value()) + { + throw error::OperationUnsupportedInBackend( + "JSON", + "Non-contiguous memory selections not supported in JSON backend."); + } + auto pos = setAndGetFilePosition(writable); auto file = refreshFileFromParent(writable); auto &j = obtainJsonContents(writable); From 2e70bc1319949787344c528ce99c43f8778e18e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20P=C3=B6schel?= Date: Thu, 7 Aug 2025 13:34:49 +0200 Subject: [PATCH 02/11] Add future helper --- CMakeLists.txt | 1 + include/openPMD/auxiliary/Future.hpp | 23 ++++++++++++ src/auxiliary/Future.cpp | 53 ++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 include/openPMD/auxiliary/Future.hpp create mode 100644 src/auxiliary/Future.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ddd2cfd716..92cfd11391 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -407,6 +407,7 @@ set(CORE_SOURCE src/version.cpp src/auxiliary/Date.cpp src/auxiliary/Filesystem.cpp + src/auxiliary/Future.cpp src/auxiliary/JSON.cpp src/auxiliary/JSONMatcher.cpp src/auxiliary/Memory.cpp diff --git a/include/openPMD/auxiliary/Future.hpp b/include/openPMD/auxiliary/Future.hpp new file mode 100644 index 0000000000..d40f7372fb --- /dev/null +++ b/include/openPMD/auxiliary/Future.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "openPMD/auxiliary/TypeTraits.hpp" + +#include + +namespace openPMD::auxiliary +{ +template +class DeferredComputation +{ + using task_type = std::function; + task_type m_task; + bool m_valid = false; + +public: + DeferredComputation(task_type); + + auto get() -> T; + + [[nodiscard]] auto valid() const noexcept -> bool; +}; +} // namespace openPMD::auxiliary diff --git a/src/auxiliary/Future.cpp b/src/auxiliary/Future.cpp new file mode 100644 index 0000000000..d55ab3f2d7 --- /dev/null +++ b/src/auxiliary/Future.cpp @@ -0,0 +1,53 @@ +#include "openPMD/auxiliary/Future.hpp" +#include "openPMD/RecordComponent.hpp" + +#include +#include +#include + +// comment + +#include "openPMD/DatatypeMacros.hpp" + +namespace openPMD::auxiliary +{ + +template +DeferredComputation::DeferredComputation(task_type task) + : m_task([wrapped_task = std::move(task), this]() { + if (!this->m_valid) + { + throw std::runtime_error( + "[DeferredComputation] No valid state. Probably already " + "computed."); + } + this->m_valid = false; + return std::move(wrapped_task)(); + }) + , m_valid(true) +{} + +template +auto DeferredComputation::get() -> T +{ + return m_task(); +} + +template +auto DeferredComputation::valid() const noexcept -> bool +{ + return m_valid; +} + +template class DeferredComputation; +template class DeferredComputation; +// clang-format off +#define INSTANTIATE_FUTURE(dtype) \ + /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ + template class DeferredComputation>; +OPENPMD_FOREACH_DATASET_DATATYPE(INSTANTIATE_FUTURE) +#undef INSTANTIATE_FUTURE +// clang-format on +} // namespace openPMD::auxiliary + +#include "openPMD/UndefDatatypeMacros.hpp" From eb0c69d7fe81601757be156f0232099546abca80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20P=C3=B6schel?= Date: Thu, 7 Aug 2025 13:36:00 +0200 Subject: [PATCH 03/11] Fixes for UniquePtr.hpp --- include/openPMD/auxiliary/UniquePtr.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/include/openPMD/auxiliary/UniquePtr.hpp b/include/openPMD/auxiliary/UniquePtr.hpp index 87f3261b45..ee17794d3e 100644 --- a/include/openPMD/auxiliary/UniquePtr.hpp +++ b/include/openPMD/auxiliary/UniquePtr.hpp @@ -176,10 +176,11 @@ template UniquePtrWithLambda UniquePtrWithLambda::static_cast_() && { using other_type = std::remove_extent_t; + auto original_ptr = this->release(); return UniquePtrWithLambda{ - static_cast(this->release()), - [deleter = std::move(this->get_deleter())](other_type *ptr) { - deleter(static_cast(ptr)); + static_cast(original_ptr), + [deleter = std::move(this->get_deleter()), original_ptr](other_type *) { + deleter(original_ptr); }}; } } // namespace openPMD From e04dd51b3ceb2c3c00cf979d5a9a8aa2c593db75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20P=C3=B6schel?= Date: Thu, 7 Aug 2025 13:36:14 +0200 Subject: [PATCH 04/11] Fixes for Memory.hpp --- include/openPMD/auxiliary/Memory.hpp | 9 +++++---- src/auxiliary/Memory.cpp | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/include/openPMD/auxiliary/Memory.hpp b/include/openPMD/auxiliary/Memory.hpp index 75199f0985..9a8ed3bda7 100644 --- a/include/openPMD/auxiliary/Memory.hpp +++ b/include/openPMD/auxiliary/Memory.hpp @@ -54,9 +54,10 @@ namespace auxiliary WriteBuffer(); - template - explicit WriteBuffer(Args &&...args) - : m_buffer(std::forward(args)...) + WriteBuffer(std::shared_ptr ptr) : m_buffer(std::move(ptr)) + {} + + WriteBuffer(UniquePtrWithLambda ptr) : m_buffer(std::move(ptr)) {} WriteBuffer(WriteBuffer &&) noexcept( @@ -69,7 +70,7 @@ namespace auxiliary WriteBuffer const &operator=(std::shared_ptr ptr); - WriteBuffer const &operator=(UniquePtrWithLambda ptr); + WriteBuffer const &operator=(UniquePtrWithLambda ptr); void const *get() const; }; diff --git a/src/auxiliary/Memory.cpp b/src/auxiliary/Memory.cpp index 0d37c8c2ca..6934f8410e 100644 --- a/src/auxiliary/Memory.cpp +++ b/src/auxiliary/Memory.cpp @@ -171,7 +171,7 @@ WriteBuffer const &WriteBuffer::operator=(std::shared_ptr ptr) return *this; } -WriteBuffer const &WriteBuffer::operator=(UniquePtrWithLambda ptr) +WriteBuffer const &WriteBuffer::operator=(UniquePtrWithLambda ptr) { m_buffer = std::move(ptr); return *this; From 1008dfdc20904eaf46771ec596651a4ffcc60bcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20P=C3=B6schel?= Date: Thu, 7 Aug 2025 13:49:36 +0200 Subject: [PATCH 05/11] Main implementation of LoadStoreChunk.hpp --- CMakeLists.txt | 1 + include/openPMD/LoadStoreChunk.hpp | 298 ++++++++++++++++++++ include/openPMD/LoadStoreChunk.tpp | 64 +++++ include/openPMD/RecordComponent.hpp | 50 +++- include/openPMD/RecordComponent.tpp | 56 ++++ src/LoadStoreChunk.cpp | 368 +++++++++++++++++++++++++ src/RecordComponent.cpp | 190 ++++++++++++- src/auxiliary/Future.cpp | 2 +- src/auxiliary/UniquePtr.cpp | 2 +- src/binding/python/RecordComponent.cpp | 4 +- test/SerialIOTest.cpp | 2 +- 11 files changed, 1020 insertions(+), 17 deletions(-) create mode 100644 include/openPMD/LoadStoreChunk.hpp create mode 100644 include/openPMD/LoadStoreChunk.tpp create mode 100644 src/LoadStoreChunk.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 92cfd11391..5f2d72fa09 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -396,6 +396,7 @@ set(CORE_SOURCE src/Format.cpp src/Iteration.cpp src/IterationEncoding.cpp + src/LoadStoreChunk.cpp src/Mesh.cpp src/ParticlePatches.cpp src/ParticleSpecies.cpp diff --git a/include/openPMD/LoadStoreChunk.hpp b/include/openPMD/LoadStoreChunk.hpp new file mode 100644 index 0000000000..8d6dee401f --- /dev/null +++ b/include/openPMD/LoadStoreChunk.hpp @@ -0,0 +1,298 @@ +#pragma once + +#include "openPMD/Dataset.hpp" +#include "openPMD/auxiliary/Future.hpp" +#include "openPMD/auxiliary/ShareRawInternal.hpp" +#include "openPMD/auxiliary/UniquePtr.hpp" + +// comment to prevent this include from being moved by clang-format +#include "openPMD/DatatypeMacros.hpp" + +#include +#include +#include +#include + +namespace openPMD +{ +class RecordComponent; +template +class ConfigureStoreChunkFromBuffer; +template +class ConfigureLoadStoreFromBuffer; +template +class DynamicMemoryView; + +namespace internal +{ + struct LoadStoreConfig + { + Offset offset; + Extent extent; + }; + struct LoadStoreConfigWithBuffer + { + Offset offset; + Extent extent; + std::optional memorySelection; + }; + +} // namespace internal + +namespace auxiliary::detail +{ +#define OPENPMD_ENUMERATE_TYPES(type) , std::shared_ptr + using shared_ptr_dataset_types = auxiliary::detail::variant_tail_t< + auxiliary::detail::bottom OPENPMD_FOREACH_DATASET_DATATYPE( + OPENPMD_ENUMERATE_TYPES)>; +#undef OPENPMD_ENUMERATE_TYPES +} // namespace auxiliary::detail + +namespace compose +{ + template + class ConfigureLoadStore; + template + class ConfigureLoadStoreFromBuffer; +} // namespace compose + +enum class EnqueuePolicy +{ + Defer, + Immediate +}; + +namespace core +{ + /* + * Actual data members of `ConfigureLoadStore<>` and methods that don't + * depend on the ChildClass template parameter. By extracting the members to + * this struct, we can pass them around between different instances of the + * class template. Numbers of method instantiations can be reduced. + */ + class ConfigureLoadStore + { + template + friend class compose::ConfigureLoadStore; + template + friend class compose::ConfigureLoadStoreFromBuffer; + + protected: + ConfigureLoadStore(RecordComponent &); + RecordComponent &m_rc; + + std::optional m_offset; + std::optional m_extent; + + [[nodiscard]] auto dim() const -> uint8_t; + auto storeChunkConfig() -> internal::LoadStoreConfig; + + public: + auto getOffset() -> Offset const &; + auto getExtent() -> Extent const &; + /* + * If the type is non-const, then the return type should be + * ConfigureLoadStoreFromBuffer<>, ... + */ + template + struct shared_ptr_return_type_impl + { + using type = ConfigureLoadStoreFromBuffer>; + }; + /* + * ..., but if it is a const type, Load operations make no sense, so the + * return type should be ConfigureStoreChunkFromBuffer<>. + */ + template + struct shared_ptr_return_type_impl + { + using type = + ConfigureStoreChunkFromBuffer>; + }; + + template + using shared_ptr_return_type = + typename shared_ptr_return_type_impl>::type; + + /* + * As loading into unique pointer types makes no sense, the case is + * simpler for unique pointers. Just remove the array extents here. + */ + template + using unique_ptr_return_type = ConfigureStoreChunkFromBuffer< + UniquePtrWithLambda>>; + + // @todo rvalue references..? + template + auto withSharedPtr(std::shared_ptr) -> shared_ptr_return_type; + template + auto withUniquePtr(UniquePtrWithLambda) -> unique_ptr_return_type; + template + auto withUniquePtr(std::unique_ptr) + -> unique_ptr_return_type; + template + auto withRawPtr(T *data) -> shared_ptr_return_type; + template + auto withContiguousContainer(T_ContiguousContainer &data) + -> std::enable_if_t< + auxiliary::IsContiguousContainer_v, + shared_ptr_return_type< + typename T_ContiguousContainer::value_type>>; + + template + [[nodiscard]] auto enqueueStore() -> DynamicMemoryView; + // definition for this one is in RecordComponent.tpp since it needs the + // definition of class RecordComponent. + template + [[nodiscard]] auto enqueueStore(F &&createBuffer) + -> DynamicMemoryView; + + template + [[nodiscard]] auto enqueueLoad() + -> auxiliary::DeferredComputation>; + + template + [[nodiscard]] auto load(EnqueuePolicy) -> std::shared_ptr; + + [[nodiscard]] auto enqueueLoadVariant() + -> auxiliary::DeferredComputation< + auxiliary::detail::shared_ptr_dataset_types>; + + [[nodiscard]] auto loadVariant(EnqueuePolicy) + -> auxiliary::detail::shared_ptr_dataset_types; + }; + + template + class ConfigureStoreChunkFromBuffer : public ConfigureLoadStore + { + public: + Ptr_Type m_buffer; + std::optional m_mem_select; + + ConfigureStoreChunkFromBuffer(Ptr_Type buffer, ConfigureLoadStore &&); + + auto storeChunkConfig() -> internal::LoadStoreConfigWithBuffer; + + auto enqueueStore() -> auxiliary::DeferredComputation; + + auto store(EnqueuePolicy) -> void; + + /** This intentionally shadows the parent class's enqueueLoad methods in + * order to show a compile error when using enqueueLoad() on an object + * of this class. The parent method can still be accessed through + * as_parent() if needed. + */ + template + auto enqueueLoad() + { + static_assert( + auxiliary::dependent_false_v, + "Cannot load chunk data into a buffer that is const or a " + "unique_ptr."); + } + + template + auto load(EnqueuePolicy) + { + static_assert( + auxiliary::dependent_false_v, + "Cannot load chunk data into a buffer that is const or a " + "unique_ptr."); + } + }; + + template + class ConfigureLoadStoreFromBuffer + : public ConfigureStoreChunkFromBuffer + { + public: + using ConfigureStoreChunkFromBuffer< + Ptr_Type>::ConfigureStoreChunkFromBuffer; + + auto enqueueLoad() -> auxiliary::DeferredComputation; + + auto load(EnqueuePolicy) -> void; + }; +} // namespace core + +namespace compose +{ + /** Basic configuration for a Load/Store operation. + * + * @tparam ChildClass CRT pattern. + * The purpose is that in child classes `return *this` should return + * an instance of the child class, not of ConfigureLoadStore. + * Instantiate with void when using without subclass. + */ + template + class ConfigureLoadStore + { + public: + auto offset(Offset) -> ChildClass &; + auto extent(Extent) -> ChildClass &; + }; + + /** Configuration for a Store operation with a buffer type. + * + * This class does intentionally not support Load operations since there are + * pointer types (const pointers, unique pointers) where Load operations + * make no sense. See the \ref ConfigureLoadStoreFromBuffer class template + * for both Load/Store operations. + * + * @tparam Ptr_Type The type of pointer used internally. + * @tparam ChildClass CRT pattern. + * The purpose is that in child classes `return *this` should return + * an instance of the child class, not of + * ConfigureStoreChunkFromBuffer. Instantiate with void when using without + * subclass. + */ + template + class ConfigureStoreChunkFromBuffer + { + public: + auto memorySelection(MemorySelection) -> ChildClass &; + }; +} // namespace compose + +class ConfigureLoadStore + : public core::ConfigureLoadStore + , public compose::ConfigureLoadStore +{ + friend class RecordComponent; + friend class core::ConfigureLoadStore; + + ConfigureLoadStore(RecordComponent &rc); + ConfigureLoadStore(core::ConfigureLoadStore &&); +}; + +template +class ConfigureStoreChunkFromBuffer + : public core::ConfigureStoreChunkFromBuffer + , public compose::ConfigureLoadStore< + ConfigureStoreChunkFromBuffer> + , public compose::ConfigureStoreChunkFromBuffer< + ConfigureStoreChunkFromBuffer> +{ + friend class core::ConfigureLoadStore; + + using core::ConfigureStoreChunkFromBuffer< + Ptr_Type>::ConfigureStoreChunkFromBuffer; +}; + +template +class ConfigureLoadStoreFromBuffer + : public core::ConfigureLoadStoreFromBuffer + , public compose::ConfigureLoadStore> + , public compose::ConfigureStoreChunkFromBuffer< + ConfigureLoadStoreFromBuffer> +{ + friend class ConfigureLoadStoreCore; + + using core::ConfigureLoadStoreFromBuffer< + Ptr_Type>::ConfigureLoadStoreFromBuffer; +}; +} // namespace openPMD + +#include "openPMD/UndefDatatypeMacros.hpp" +// comment to prevent these includes from being moved by clang-format +#include "openPMD/LoadStoreChunk.tpp" diff --git a/include/openPMD/LoadStoreChunk.tpp b/include/openPMD/LoadStoreChunk.tpp new file mode 100644 index 0000000000..c3c104cebd --- /dev/null +++ b/include/openPMD/LoadStoreChunk.tpp @@ -0,0 +1,64 @@ +#pragma once + +#include "openPMD/LoadStoreChunk.hpp" + +namespace openPMD::core +{ +template +auto ConfigureLoadStore::withSharedPtr(std::shared_ptr data) + -> shared_ptr_return_type +{ + if (!data) + { + throw std::runtime_error( + "Unallocated pointer passed during chunk store."); + } + return shared_ptr_return_type( + std::static_pointer_cast>(std::move(data)), + {std::move(*this)}); +} +template +auto ConfigureLoadStore::withUniquePtr(UniquePtrWithLambda data) + -> unique_ptr_return_type + +{ + if (!data) + { + throw std::runtime_error( + "Unallocated pointer passed during chunk store."); + } + return unique_ptr_return_type( + std::move(data).template static_cast_>(), + {std::move(*this)}); +} +template +auto ConfigureLoadStore::withRawPtr(T *data) -> shared_ptr_return_type +{ + if (!data) + { + throw std::runtime_error( + "Unallocated pointer passed during chunk store."); + } + return shared_ptr_return_type( + auxiliary::shareRaw(data), {std::move(*this)}); +} + +template +auto ConfigureLoadStore::withUniquePtr(std::unique_ptr data) + -> unique_ptr_return_type +{ + return withUniquePtr(UniquePtrWithLambda(std::move(data))); +} +template +auto ConfigureLoadStore::withContiguousContainer(T_ContiguousContainer &data) + -> std::enable_if_t< + auxiliary::IsContiguousContainer_v, + shared_ptr_return_type> +{ + if (!m_extent.has_value() && dim() == 1) + { + m_extent = Extent{data.size()}; + } + return withRawPtr(data.data()); +} +} // namespace openPMD::core diff --git a/include/openPMD/RecordComponent.hpp b/include/openPMD/RecordComponent.hpp index c3c5a3dbc0..83d016250c 100644 --- a/include/openPMD/RecordComponent.hpp +++ b/include/openPMD/RecordComponent.hpp @@ -22,15 +22,13 @@ #include "openPMD/Dataset.hpp" #include "openPMD/Datatype.hpp" +#include "openPMD/LoadStoreChunk.hpp" #include "openPMD/auxiliary/ShareRaw.hpp" #include "openPMD/auxiliary/TypeTraits.hpp" #include "openPMD/auxiliary/UniquePtr.hpp" #include "openPMD/backend/Attributable.hpp" #include "openPMD/backend/BaseRecordComponent.hpp" -// comment to prevent this include from being moved by clang-format -#include "openPMD/DatatypeMacros.hpp" - #include #include #include @@ -118,6 +116,17 @@ namespace internal class BaseRecordData; } // namespace internal +namespace core +{ + class ConfigureLoadStore; + template + class ConfigureLoadStoreFromBuffer; + template + class ConfigureStoreChunkFromBuffer; + struct VisitorEnqueueLoadVariant; + struct VisitorLoadVariant; +} // namespace core + template class BaseRecord; @@ -139,6 +148,13 @@ class RecordComponent : public BaseRecordComponent friend class MeshRecordComponent; template friend T &internal::makeOwning(T &self, Series); + friend class core::ConfigureLoadStore; + template + friend class core::ConfigureLoadStoreFromBuffer; + template + friend class core::ConfigureStoreChunkFromBuffer; + friend struct core::VisitorEnqueueLoadVariant; + friend struct core::VisitorLoadVariant; public: enum class Allocation @@ -223,6 +239,8 @@ class RecordComponent : public BaseRecordComponent */ bool empty() const; + ConfigureLoadStore prepareLoadStore(); + /** Load and allocate a chunk of data * * Set offset to {0u} and extent to {-1u} for full selection. @@ -233,11 +251,8 @@ class RecordComponent : public BaseRecordComponent template std::shared_ptr loadChunk(Offset = {0u}, Extent = {-1u}); -#define OPENPMD_ENUMERATE_TYPES(type) , std::shared_ptr - using shared_ptr_dataset_types = auxiliary::detail::variant_tail_t< - auxiliary::detail::bottom OPENPMD_FOREACH_DATASET_DATATYPE( - OPENPMD_ENUMERATE_TYPES)>; -#undef OPENPMD_ENUMERATE_TYPES + using shared_ptr_dataset_types = + auxiliary::detail::shared_ptr_dataset_types; /** std::variant-based version of allocating loadChunk(Offset, Extent) * @@ -509,6 +524,23 @@ class RecordComponent : public BaseRecordComponent void storeChunk( auxiliary::WriteBuffer buffer, Datatype datatype, Offset o, Extent e); + void storeChunk_impl( + auxiliary::WriteBuffer buffer, + Datatype datatype, + internal::LoadStoreConfigWithBuffer); + + template + DynamicMemoryView storeChunkSpan_impl(internal::LoadStoreConfig); + template + DynamicMemoryView storeChunkSpanCreateBuffer_impl( + internal::LoadStoreConfig, F &&createBuffer); + + template + void + loadChunk_impl(std::shared_ptr, internal::LoadStoreConfigWithBuffer); + template + std::shared_ptr loadChunkAllocate_impl(internal::LoadStoreConfig); + // clang-format off OPENPMD_protected // clang-format on @@ -553,6 +585,4 @@ OPENPMD_protected } // namespace openPMD -#include "openPMD/UndefDatatypeMacros.hpp" -// comment to prevent these includes from being moved by clang-format #include "RecordComponent.tpp" diff --git a/include/openPMD/RecordComponent.tpp b/include/openPMD/RecordComponent.tpp index 923e3e2e09..e90499ae6e 100644 --- a/include/openPMD/RecordComponent.tpp +++ b/include/openPMD/RecordComponent.tpp @@ -133,6 +133,62 @@ RecordComponent::storeChunk(Offset o, Extent e, F &&createBuffer) return DynamicMemoryView{std::move(getBufferView), size, *this}; } +template +inline DynamicMemoryView RecordComponent::storeChunkSpanCreateBuffer_impl( + internal::LoadStoreConfig cfg, F &&createBuffer) +{ + auto [o, e] = std::move(cfg); + verifyChunk(o, e); + + /* + * The openPMD backend might not yet know about this dataset. + * Flush the openPMD hierarchy to the backend without flushing any actual + * data yet. + */ + seriesFlush_impl( + {FlushLevel::SkeletonOnly}); + + size_t size = 1; + for (auto ext : e) + { + size *= ext; + } + /* + * Flushing the skeleton does not create datasets, + * so we might need to do it now. + */ + if (!written()) + { + auto &rc = get(); + if (!rc.m_dataset.has_value()) + { + throw error::WrongAPIUsage( + "[RecordComponent] Must specify dataset type and extent before " + "using storeChunk() (see RecordComponent::resetDataset())."); + } + Parameter dCreate(rc.m_dataset.value()); + dCreate.name = rc.m_name; + IOHandler()->enqueue(IOTask(this, dCreate)); + } + Parameter getBufferView; + getBufferView.offset = o; + getBufferView.extent = e; + getBufferView.dtype = getDatatype(); + IOHandler()->enqueue(IOTask(this, getBufferView)); + IOHandler()->flush(internal::defaultFlushParams); + auto &out = *getBufferView.out; + if (!out.backendManagedBuffer) + { + // note that data might have either + // type shared_ptr or shared_ptr + auto data = std::forward(createBuffer)(size); + out.ptr = static_cast(data.get()); + storeChunk(std::move(data), std::move(o), std::move(e)); + } + setDirtyRecursive(true); + return DynamicMemoryView{std::move(getBufferView), size, *this}; +} + namespace detail { template diff --git a/src/LoadStoreChunk.cpp b/src/LoadStoreChunk.cpp new file mode 100644 index 0000000000..526c01a3d9 --- /dev/null +++ b/src/LoadStoreChunk.cpp @@ -0,0 +1,368 @@ + + +#include "openPMD/LoadStoreChunk.hpp" +#include "openPMD/Datatype.hpp" +#include "openPMD/RecordComponent.hpp" +#include "openPMD/Span.hpp" +#include "openPMD/auxiliary/Memory.hpp" +#include "openPMD/auxiliary/ShareRawInternal.hpp" +#include "openPMD/auxiliary/TypeTraits.hpp" +#include "openPMD/auxiliary/UniquePtr.hpp" + +// comment to keep clang-format from reordering +#include "openPMD/DatatypeMacros.hpp" + +#include +#include +#include +#include + +namespace openPMD +{ +namespace +{ + template + auto asWriteBuffer(std::shared_ptr &&ptr) -> auxiliary::WriteBuffer + { + /* std::static_pointer_cast correctly reference-counts the pointer */ + return auxiliary::WriteBuffer( + std::static_pointer_cast(std::move(ptr))); + } + template + auto asWriteBuffer(UniquePtrWithLambda &&ptr) -> auxiliary::WriteBuffer + { + return auxiliary::WriteBuffer( + std::move(ptr).template static_cast_()); + } + + /* + * There is no backend support currently for const unique pointers. + * We support these mostly for providing a clean API to users that have such + * pointers and want to store from them, but there will be no + * backend-specific optimizations for such buffers as there are for + * non-const unique pointers. + */ + template + auto asWriteBuffer(UniquePtrWithLambda &&ptr) + -> auxiliary::WriteBuffer + { + auto raw_ptr = ptr.release(); + return asWriteBuffer( + std::shared_ptr{ + raw_ptr, + [deleter = std::move(ptr.get_deleter())]( + auto const *delete_me) { deleter(delete_me); }}); + } +} // namespace + +namespace core +{ + ConfigureLoadStore::ConfigureLoadStore(RecordComponent &rc) : m_rc(rc) + {} + + auto ConfigureLoadStore::dim() const -> uint8_t + { + return m_rc.getDimensionality(); + } + + auto ConfigureLoadStore::storeChunkConfig() -> internal::LoadStoreConfig + { + return internal::LoadStoreConfig{getOffset(), getExtent()}; + } + + auto ConfigureLoadStore::getOffset() -> Offset const & + { + if (!m_offset.has_value()) + { + if (m_rc.joinedDimension().has_value()) + { + m_offset = std::make_optional(); + } + else + { + m_offset = std::make_optional(dim(), 0); + } + } + return *m_offset; + } + + auto ConfigureLoadStore::getExtent() -> Extent const & + { + if (!m_extent.has_value()) + { + m_extent = std::make_optional(m_rc.getExtent()); + if (m_offset.has_value()) + { + auto it_o = m_offset->begin(); + auto end_o = m_offset->end(); + auto it_e = m_extent->begin(); + auto end_e = m_extent->end(); + for (; it_o != end_o && it_e != end_e; ++it_e, ++it_o) + { + *it_e -= *it_o; + } + } + } + return *m_extent; + } + + template + auto ConfigureLoadStore::enqueueStore() -> DynamicMemoryView + { + return m_rc.storeChunkSpan_impl(storeChunkConfig()); + } + + template + auto ConfigureLoadStore::enqueueLoad() + -> auxiliary::DeferredComputation> + { + auto res = m_rc.loadChunkAllocate_impl(storeChunkConfig()); + return auxiliary::DeferredComputation>( + [res_lambda = std::move(res), rc = m_rc]() mutable { + rc.seriesFlush(); + return res_lambda; + }); + } + + template + auto ConfigureLoadStore::load(EnqueuePolicy ep) -> std::shared_ptr + { + auto res = m_rc.loadChunkAllocate_impl(storeChunkConfig()); + switch (ep) + { + case EnqueuePolicy::Defer: + break; + case EnqueuePolicy::Immediate: + m_rc.seriesFlush(); + break; + } + return res; + } + + struct VisitorEnqueueLoadVariant + { + template + static auto call(RecordComponent &rc, internal::LoadStoreConfig cfg) + -> auxiliary::DeferredComputation< + auxiliary::detail::shared_ptr_dataset_types> + { + auto res = rc.loadChunkAllocate_impl(std::move(cfg)); + return auxiliary::DeferredComputation< + auxiliary::detail::shared_ptr_dataset_types>( + + [res_lambda = std::move(res), rc_lambda = rc]() mutable + -> auxiliary::detail::shared_ptr_dataset_types { + std::cout << "Flushing Series from Future" << std::endl; + rc_lambda.seriesFlush(); + std::cout << "Flushed Series from Future" << std::endl; + return res_lambda; + }); + } + }; + + auto ConfigureLoadStore::enqueueLoadVariant() + -> auxiliary::DeferredComputation< + auxiliary::detail::shared_ptr_dataset_types> + { + return m_rc.visit(this->storeChunkConfig()); + } + + struct VisitorLoadVariant + { + template + static auto call(RecordComponent &rc, internal::LoadStoreConfig cfg) + -> auxiliary::detail::shared_ptr_dataset_types + { + return rc.loadChunkAllocate_impl(std::move(cfg)); + } + }; + + auto ConfigureLoadStore::loadVariant(EnqueuePolicy ep) + -> auxiliary::detail::shared_ptr_dataset_types + { + auto res = m_rc.visit(this->storeChunkConfig()); + switch (ep) + { + case EnqueuePolicy::Defer: + break; + case EnqueuePolicy::Immediate: + m_rc.seriesFlush(); + break; + } + return res; + } + + template + ConfigureStoreChunkFromBuffer::ConfigureStoreChunkFromBuffer( + Ptr_Type buffer, ConfigureLoadStore &&core) + : ConfigureLoadStore(std::move(core)), m_buffer(std::move(buffer)) + {} + + template + auto ConfigureStoreChunkFromBuffer::storeChunkConfig() + -> internal::LoadStoreConfigWithBuffer + { + return internal::LoadStoreConfigWithBuffer{ + this->getOffset(), this->getExtent(), m_mem_select}; + } + + template + auto ConfigureStoreChunkFromBuffer::enqueueStore() + -> auxiliary::DeferredComputation + { + this->m_rc.storeChunk_impl( + asWriteBuffer(std::move(m_buffer)), + determineDatatype>(), + storeChunkConfig()); + return auxiliary::DeferredComputation( + [rc_lambda = m_rc]() mutable -> void { rc_lambda.seriesFlush(); }); + } + + template + auto ConfigureStoreChunkFromBuffer::store(EnqueuePolicy ep) + -> void + { + this->m_rc.storeChunk_impl( + asWriteBuffer(std::move(m_buffer)), + determineDatatype>(), + storeChunkConfig()); + switch (ep) + { + case EnqueuePolicy::Defer: + break; + case EnqueuePolicy::Immediate: + m_rc.seriesFlush(); + break; + } + } + + template + auto ConfigureLoadStoreFromBuffer::enqueueLoad() + -> auxiliary::DeferredComputation + { + static_assert( + std::is_same_v< + Ptr_Type, + std::shared_ptr< + std::remove_cv_t>>, + "ConfigureLoadStoreFromBuffer must be instantiated with a " + "non-const " + "shared_ptr type."); + this->m_rc.loadChunk_impl( + std::move(this->m_buffer), this->storeChunkConfig()); + return auxiliary::DeferredComputation( + [rc_lambda = this->m_rc]() mutable -> void { + rc_lambda.seriesFlush(); + }); + } + + template + auto ConfigureLoadStoreFromBuffer::load(EnqueuePolicy ep) -> void + { + this->m_rc.loadChunk_impl( + std::move(this->m_buffer), this->storeChunkConfig()); + switch (ep) + { + + case EnqueuePolicy::Defer: + break; + case EnqueuePolicy::Immediate: + this->m_rc.seriesFlush(); + break; + } + } +} // namespace core + +namespace compose +{ + template + auto ConfigureLoadStore::extent(Extent extent) -> ChildClass & + { + static_cast(this)->m_extent = + std::make_optional(std::move(extent)); + return *static_cast(this); + } + + template + auto ConfigureLoadStore::offset(Offset offset) -> ChildClass & + { + static_cast(this)->m_offset = + std::make_optional(std::move(offset)); + return *static_cast(this); + } + + template + auto ConfigureStoreChunkFromBuffer::memorySelection( + MemorySelection sel) -> ChildClass & + { + static_cast(this)->m_mem_select = + std::make_optional(std::move(sel)); + return *static_cast(this); + } +} // namespace compose + +template class compose::ConfigureLoadStore; + +/* clang-format would destroy the NOLINT comments */ +// clang-format off +#define INSTANTIATE_METHOD_TEMPLATES(dtype) \ + template auto core::ConfigureLoadStore::enqueueStore() \ + -> DynamicMemoryView; \ + template auto core::ConfigureLoadStore::enqueueLoad() \ + /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ + -> auxiliary::DeferredComputation>; \ + template auto core::ConfigureLoadStore::load(EnqueuePolicy) \ + ->std::shared_ptr; +// clang-format on + +OPENPMD_FOREACH_NONVECTOR_DATATYPE(INSTANTIATE_METHOD_TEMPLATES) + +#undef INSTANTIATE_METHOD_TEMPLATES + +/* clang-format would destroy the NOLINT comments */ +// clang-format off +#define INSTANTIATE_HALF(pointer_type) \ + template class ConfigureStoreChunkFromBuffer; \ + template class core::ConfigureStoreChunkFromBuffer; \ + template class compose::ConfigureLoadStore< \ + /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ + ConfigureStoreChunkFromBuffer>; \ + template class compose::ConfigureStoreChunkFromBuffer< \ + /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ + ConfigureStoreChunkFromBuffer>; +// clang-format on + +/* clang-format would destroy the NOLINT comments */ +// clang-format off +#define INSTANTIATE_FULL(pointer_type) \ + INSTANTIATE_HALF(pointer_type) \ + template class ConfigureLoadStoreFromBuffer; \ + template class core::ConfigureLoadStoreFromBuffer; \ + template class compose::ConfigureLoadStore< \ + /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ + ConfigureLoadStoreFromBuffer>; \ + template class compose::ConfigureStoreChunkFromBuffer< \ + /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ + ConfigureLoadStoreFromBuffer>; +// clang-format on + +#define INSTANTIATE_STORE_CHUNK_FROM_BUFFER(dtype) \ + INSTANTIATE_FULL(std::shared_ptr) \ + INSTANTIATE_HALF(std::shared_ptr) \ + INSTANTIATE_HALF(UniquePtrWithLambda) +// /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ + +OPENPMD_FOREACH_NONVECTOR_DATATYPE(INSTANTIATE_STORE_CHUNK_FROM_BUFFER) + +#undef INSTANTIATE_STORE_CHUNK_FROM_BUFFER +#undef INSTANTIATE_METHOD_TEMPLATES +#undef INSTANTIATE_FULL +#undef INSTANTIATE_HALF + +ConfigureLoadStore::ConfigureLoadStore(RecordComponent &rc) + : core::ConfigureLoadStore{rc} +{} +ConfigureLoadStore::ConfigureLoadStore(core::ConfigureLoadStore &&core) + : core::ConfigureLoadStore{std::move(core)} +{} +} // namespace openPMD diff --git a/src/RecordComponent.cpp b/src/RecordComponent.cpp index a57f1d8f97..b8dffd9caf 100644 --- a/src/RecordComponent.cpp +++ b/src/RecordComponent.cpp @@ -21,6 +21,7 @@ #include "openPMD/RecordComponent.hpp" #include "openPMD/Dataset.hpp" #include "openPMD/DatatypeHelpers.hpp" +#include "openPMD/DatatypeMacros.hpp" #include "openPMD/Error.hpp" #include "openPMD/IO/Format.hpp" #include "openPMD/Series.hpp" @@ -67,6 +68,72 @@ auto resource(T &t) -> attribute_types & return t.template resource(); } +ConfigureLoadStore RecordComponent::prepareLoadStore() +{ + return ConfigureLoadStore{*this}; +} + +namespace +{ +#if (defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 11000) || \ + (defined(__apple_build_version__) && __clang_major__ < 14) + template + auto createSpanBufferFallback(size_t size) -> std::shared_ptr + { + return std::shared_ptr{new T[size], [](auto *ptr) { delete[] ptr; }}; + } +#else + template + auto createSpanBufferFallback(size_t size) -> std::shared_ptr + { + return std::shared_ptr{new T[size]}; + } +#endif +} // namespace + +template +DynamicMemoryView +RecordComponent::storeChunkSpan_impl(internal::LoadStoreConfig cfg) +{ + return storeChunkSpanCreateBuffer_impl( + std::move(cfg), &createSpanBufferFallback); +} + +template +std::shared_ptr +RecordComponent::loadChunkAllocate_impl(internal::LoadStoreConfig cfg) +{ + using T = std::remove_extent_t; + // static_assert(!std::is_same_v, "EVIL"); + auto [o, e] = std::move(cfg); + + size_t numPoints = 1; + for (auto val : e) + { + numPoints *= val; + } + +#if (defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 11000) || \ + (defined(__apple_build_version__) && __clang_major__ < 14) + auto newData = std::shared_ptr( + new T[numPoints], [](T *p) { delete[] p; }); + prepareLoadStore() + .offset(std::move(o)) + .extent(std::move(e)) + .withSharedPtr(newData) + .load(EnqueuePolicy::Defer); + return newData; +#else + auto newData = std::shared_ptr(new T[numPoints]); + prepareLoadStore() + .offset(std::move(o)) + .extent(std::move(e)) + .withSharedPtr(newData) + .load(EnqueuePolicy::Defer); + return std::static_pointer_cast(std::move(newData)); +#endif +} + RecordComponent::RecordComponent() : BaseRecordComponent(NoInit()) { setData(std::make_shared()); @@ -489,6 +556,25 @@ void RecordComponent::storeChunk( rc.push_chunk(IOTask(this, std::move(dWrite))); } +void RecordComponent::storeChunk_impl( + auxiliary::WriteBuffer buffer, + Datatype dtype, + internal::LoadStoreConfigWithBuffer cfg) +{ + auto [o, e, memorySelection] = std::move(cfg); + verifyChunk(dtype, o, e); + + Parameter dWrite; + dWrite.offset = std::move(o); + dWrite.extent = std::move(e); + dWrite.memorySelection = memorySelection; + dWrite.dtype = dtype; + /* std::static_pointer_cast correctly reference-counts the pointer */ + dWrite.data = std::move(buffer); + auto &rc = get(); + rc.push_chunk(IOTask(this, std::move(dWrite))); +} + void RecordComponent::verifyChunk( Datatype dtype, Offset const &o, Extent const &e) const { @@ -663,6 +749,100 @@ namespace detail }; } // namespace detail +template +void RecordComponent::loadChunk_impl( + std::shared_ptr data, + internal::LoadStoreConfigWithBuffer cfg) +{ + if (cfg.memorySelection.has_value()) + { + throw error::WrongAPIUsage( + "Unsupported: Memory selections in chunk loading."); + } + using T = std::remove_cv_t>; + Datatype dtype = determineDatatype(data); + /* + * For constant components, we implement type conversion, so there is + * a separate check further below. + * This is especially useful for the short-attribute representation in the + * JSON/TOML backends as they might implicitly turn a LONG into an INT in a + * constant component. The frontend needs to catch such edge cases. + * Ref. `if (constant())` branch. + */ + if (dtype != getDatatype() && !constant()) + if (!isSameInteger(getDatatype()) && + !isSameFloatingPoint(getDatatype()) && + !isSameComplexFloatingPoint(getDatatype()) && + !isSameChar(getDatatype())) + { + std::string const data_type_str = datatypeToString(getDatatype()); + std::string const requ_type_str = + datatypeToString(determineDatatype()); + std::string err_msg = + "Type conversion during chunk loading not yet implemented! "; + err_msg += "Data: " + data_type_str + "; Load as: " + requ_type_str; + throw std::runtime_error(err_msg); + } + + auto dim = getDimensionality(); + auto [offset, extent, memorySelection] = std::move(cfg); + + if (extent.size() != dim || offset.size() != dim) + { + std::ostringstream oss; + oss << "Dimensionality of chunk (" + << "offset=" << offset.size() << "D, " + << "extent=" << extent.size() << "D) " + << "and record component (" << int(dim) << "D) " + << "do not match."; + throw std::runtime_error(oss.str()); + } + Extent dse = getExtent(); + for (uint8_t i = 0; i < dim; ++i) + if (dse[i] < offset[i] + extent[i]) + throw std::runtime_error( + "Chunk does not reside inside dataset (Dimension on index " + + std::to_string(i) + ". DS: " + std::to_string(dse[i]) + + " - Chunk: " + std::to_string(offset[i] + extent[i]) + ")"); + + auto &rc = get(); + if (constant()) + { + uint64_t numPoints = 1u; + for (auto const &dimensionSize : extent) + numPoints *= dimensionSize; + + std::optional val = + switchNonVectorType>( + /* from = */ getDatatype(), rc.m_constantValue); + + if (val.has_value()) + { + auto raw_ptr = static_cast(data.get()); + std::fill(raw_ptr, raw_ptr + numPoints, *val); + } + else + { + std::string const data_type_str = datatypeToString(getDatatype()); + std::string const requ_type_str = + datatypeToString(determineDatatype()); + std::string err_msg = + "Type conversion during chunk loading not possible! "; + err_msg += "Data: " + data_type_str + "; Load as: " + requ_type_str; + throw error::WrongAPIUsage(err_msg); + } + } + else + { + Parameter dRead; + dRead.offset = offset; + dRead.extent = extent; + dRead.dtype = getDatatype(); + dRead.data = std::static_pointer_cast(data); + rc.push_chunk(IOTask(this, dRead)); + } +} + template void RecordComponent::loadChunk(std::shared_ptr data, Offset o, Extent e) { @@ -864,7 +1044,9 @@ void RecordComponent::verifyChunk(Offset const &o, Extent const &e) const template void RecordComponent::verifyChunk( \ Offset const &o, Extent const &e) const; \ template DynamicMemoryView RecordComponent::storeChunk( \ - Offset offset, Extent extent); + Offset offset, Extent extent); \ + template DynamicMemoryView RecordComponent::storeChunkSpan_impl( \ + internal::LoadStoreConfig cfg); #define OPENPMD_INSTANTIATE_CONST_AND_NONCONST(type) \ template void RecordComponent::storeChunk( \ @@ -878,7 +1060,11 @@ void RecordComponent::verifyChunk(Offset const &o, Extent const &e) const template std::shared_ptr RecordComponent::loadChunk( \ Offset o, Extent e); \ template void RecordComponent::storeChunk( \ - UniquePtrWithLambda data, Offset o, Extent e); + UniquePtrWithLambda data, Offset o, Extent e); \ + template void RecordComponent::loadChunk_impl( \ + std::shared_ptr data, internal::LoadStoreConfigWithBuffer cfg); \ + template std::shared_ptr RecordComponent::loadChunkAllocate_impl( \ + internal::LoadStoreConfig cfg); #define OPENPMD_INSTANTIATE_FULLMATRIX(type) \ template RecordComponent &RecordComponent::makeConstant(type); \ diff --git a/src/auxiliary/Future.cpp b/src/auxiliary/Future.cpp index d55ab3f2d7..025db3f355 100644 --- a/src/auxiliary/Future.cpp +++ b/src/auxiliary/Future.cpp @@ -45,7 +45,7 @@ template class DeferredComputation; #define INSTANTIATE_FUTURE(dtype) \ /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ template class DeferredComputation>; -OPENPMD_FOREACH_DATASET_DATATYPE(INSTANTIATE_FUTURE) +OPENPMD_FOREACH_NONVECTOR_DATATYPE(INSTANTIATE_FUTURE) #undef INSTANTIATE_FUTURE // clang-format on } // namespace openPMD::auxiliary diff --git a/src/auxiliary/UniquePtr.cpp b/src/auxiliary/UniquePtr.cpp index bc9af25ae1..1cf2089f35 100644 --- a/src/auxiliary/UniquePtr.cpp +++ b/src/auxiliary/UniquePtr.cpp @@ -98,7 +98,7 @@ UniquePtrWithLambda::UniquePtrWithLambda( #define OPENPMD_INSTANTIATE_WITH_AND_WITHOUT_EXTENT(type) \ OPENPMD_INSTANTIATE(type) OPENPMD_INSTANTIATE(OPENPMD_ARRAY(type)) -OPENPMD_FOREACH_DATASET_DATATYPE(OPENPMD_INSTANTIATE_WITH_AND_WITHOUT_EXTENT) +OPENPMD_FOREACH_NONVECTOR_DATATYPE(OPENPMD_INSTANTIATE_WITH_AND_WITHOUT_EXTENT) // Instantiate this directly, do not instantiate the // `std::unique_ptr`-based constructor. template class UniquePtrWithLambda; diff --git a/src/binding/python/RecordComponent.cpp b/src/binding/python/RecordComponent.cpp index 6774b5e182..21ba30f8e4 100644 --- a/src/binding/python/RecordComponent.cpp +++ b/src/binding/python/RecordComponent.cpp @@ -655,7 +655,7 @@ inline PythonDynamicMemoryView store_chunk_span( std::begin(shape), [&maskIt](std::uint64_t) { return !*(maskIt++); }); - return switchNonVectorType( + return switchDatasetType( r.getDatatype(), r, offset, extent); } @@ -725,7 +725,7 @@ void load_chunk( } } - switchNonVectorType( + switchDatasetType( r.getDatatype(), r, buffer, buffer_info, offset, extent); } diff --git a/test/SerialIOTest.cpp b/test/SerialIOTest.cpp index 8a9b74c263..8612867207 100644 --- a/test/SerialIOTest.cpp +++ b/test/SerialIOTest.cpp @@ -2992,7 +2992,7 @@ TEST_CASE("git_hdf5_legacy_picongpu", "[serial][hdf5]") auto radiationMask = o.iterations[200] .particles["e"]["radiationMask"][RecordComponent::SCALAR]; - switchNonVectorType( + switchDatasetType( radiationMask.getDatatype(), radiationMask); auto particlePatches = o.iterations[200].particles["e"].particlePatches; From 7a4e61a9c5561608312bc35193a9139876a272c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20P=C3=B6schel?= Date: Thu, 7 Aug 2025 13:53:46 +0200 Subject: [PATCH 06/11] Fix for determineDatatype --- include/openPMD/Datatype.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/openPMD/Datatype.hpp b/include/openPMD/Datatype.hpp index 26b31c8458..dec366500d 100644 --- a/include/openPMD/Datatype.hpp +++ b/include/openPMD/Datatype.hpp @@ -294,7 +294,8 @@ template inline constexpr Datatype determineDatatype(T &&val) { (void)val; // don't need this, it only has a name for Doxygen - using T_stripped = std::remove_cv_t>; + using T_stripped = + std::remove_extent_t>>; if constexpr (auxiliary::IsPointer_v) { return determineDatatype>(); From 49d6522597540fdd1dadcbf88dede8a5977ff4b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20P=C3=B6schel?= Date: Thu, 7 Aug 2025 14:07:21 +0200 Subject: [PATCH 07/11] Adapt RecordComponent implementation based on new API --- include/openPMD/LoadStoreChunk.hpp | 7 +- include/openPMD/RecordComponent.hpp | 34 ---- include/openPMD/RecordComponent.tpp | 105 ++++------- src/LoadStoreChunk.cpp | 10 +- src/RecordComponent.cpp | 238 +++++-------------------- src/auxiliary/Future.cpp | 4 +- src/binding/python/RecordComponent.cpp | 8 - 7 files changed, 93 insertions(+), 313 deletions(-) diff --git a/include/openPMD/LoadStoreChunk.hpp b/include/openPMD/LoadStoreChunk.hpp index 8d6dee401f..ef9c2e5707 100644 --- a/include/openPMD/LoadStoreChunk.hpp +++ b/include/openPMD/LoadStoreChunk.hpp @@ -97,7 +97,8 @@ namespace core template struct shared_ptr_return_type_impl { - using type = ConfigureLoadStoreFromBuffer>; + using type = ConfigureLoadStoreFromBuffer< + std::shared_ptr>>; }; /* * ..., but if it is a const type, Load operations make no sense, so the @@ -106,8 +107,8 @@ namespace core template struct shared_ptr_return_type_impl { - using type = - ConfigureStoreChunkFromBuffer>; + using type = ConfigureStoreChunkFromBuffer< + std::shared_ptr const>>; }; template diff --git a/include/openPMD/RecordComponent.hpp b/include/openPMD/RecordComponent.hpp index 83d016250c..2c00dc4715 100644 --- a/include/openPMD/RecordComponent.hpp +++ b/include/openPMD/RecordComponent.hpp @@ -290,25 +290,6 @@ class RecordComponent : public BaseRecordComponent template void loadChunk(std::shared_ptr data, Offset offset, Extent extent); - /** Load a chunk of data into pre-allocated memory, array version. - * - * @param data Preallocated, contiguous buffer, large enough to load the - * the requested data into it. - * The shared pointer must own and manage the buffer. - * Optimizations might be implemented based on this - * assumption (e.g. skipping the operation if the backend - * is the unique owner). - * The array-based overload helps avoid having to manually - * specify the delete[] destructor (C++17 feature). - * @param offset Offset within the dataset. Set to {0u} for full selection. - * @param extent Extent within the dataset, counted from the offset. - * Set to {-1u} for full selection. - * If offset is non-zero and extent is {-1u} the leftover - * extent in the record component will be selected. - */ - template - void loadChunk(std::shared_ptr data, Offset offset, Extent extent); - /** Load a chunk of data into pre-allocated memory, raw pointer version. * * @param data Preallocated, contiguous buffer, large enough to load the @@ -348,18 +329,6 @@ class RecordComponent : public BaseRecordComponent template void storeChunk(std::shared_ptr data, Offset offset, Extent extent); - /** Store a chunk of data from a chunk of memory, array version. - * - * @param data Preallocated, contiguous buffer, large enough to read the - * the specified data from it. - * The array-based overload helps avoid having to manually - * specify the delete[] destructor (C++17 feature). - * @param offset Offset within the dataset. - * @param extent Extent within the dataset, counted from the offset. - */ - template - void storeChunk(std::shared_ptr data, Offset offset, Extent extent); - /** Store a chunk of data from a chunk of memory, unique pointer version. * * @param data Preallocated, contiguous buffer, large enough to read the @@ -521,9 +490,6 @@ class RecordComponent : public BaseRecordComponent */ RecordComponent &makeEmpty(Dataset d); - void storeChunk( - auxiliary::WriteBuffer buffer, Datatype datatype, Offset o, Extent e); - void storeChunk_impl( auxiliary::WriteBuffer buffer, Datatype datatype, diff --git a/include/openPMD/RecordComponent.tpp b/include/openPMD/RecordComponent.tpp index e90499ae6e..44e4bd319d 100644 --- a/include/openPMD/RecordComponent.tpp +++ b/include/openPMD/RecordComponent.tpp @@ -23,6 +23,7 @@ #include "openPMD/Datatype.hpp" #include "openPMD/Error.hpp" +#include "openPMD/LoadStoreChunk.hpp" #include "openPMD/RecordComponent.hpp" #include "openPMD/Span.hpp" #include "openPMD/auxiliary/Memory.hpp" @@ -31,6 +32,7 @@ #include "openPMD/auxiliary/UniquePtr.hpp" #include +#include #include namespace openPMD @@ -40,8 +42,11 @@ template inline void RecordComponent::storeChunk(std::unique_ptr data, Offset o, Extent e) { - storeChunk( - UniquePtrWithLambda(std::move(data)), std::move(o), std::move(e)); + prepareLoadStore() + .offset(std::move(o)) + .extent(std::move(e)) + .withUniquePtr(std::move(data)) + .store(EnqueuePolicy::Defer); } template @@ -49,88 +54,31 @@ inline typename std::enable_if_t< auxiliary::IsContiguousContainer_v> RecordComponent::storeChunk(T_ContiguousContainer &data, Offset o, Extent e) { - uint8_t dim = getDimensionality(); + auto storeChunkConfig = prepareLoadStore(); - // default arguments - // offset = {0u}: expand to right dim {0u, 0u, ...} - Offset offset = o; - if (o.size() == 1u && o.at(0) == 0u) + auto joined_dim = joinedDimension(); + if (!joined_dim.has_value() && (o.size() != 1 || o.at(0) != 0u)) { - if (joinedDimension().has_value()) - { - offset.clear(); - } - else if (dim > 1u) - { - offset = Offset(dim, 0u); - } + storeChunkConfig.offset(std::move(o)); + } + if (e.size() != 1 || e.at(0) != -1u) + { + storeChunkConfig.extent(std::move(e)); } - // extent = {-1u}: take full size - Extent extent(dim, 1u); - // avoid outsmarting the user: - // - stdlib data container implement 1D -> 1D chunk to write - if (e.size() == 1u && e.at(0) == -1u && dim == 1u) - extent.at(0) = data.size(); - else - extent = e; - - storeChunk(auxiliary::shareRaw(data.data()), offset, extent); + std::move(storeChunkConfig) + .withContiguousContainer(data) + .store(EnqueuePolicy::Defer); } template inline DynamicMemoryView RecordComponent::storeChunk(Offset o, Extent e, F &&createBuffer) { - verifyChunk(o, e); - - /* - * The openPMD backend might not yet know about this dataset. - * Flush the openPMD hierarchy to the backend without flushing any actual - * data yet. - */ - seriesFlush_impl( - {FlushLevel::SkeletonOnly}); - - size_t size = 1; - for (auto ext : e) - { - size *= ext; - } - /* - * Flushing the skeleton does not create datasets, - * so we might need to do it now. - */ - if (!written()) - { - auto &rc = get(); - if (!rc.m_dataset.has_value()) - { - throw error::WrongAPIUsage( - "[RecordComponent] Must specify dataset type and extent before " - "using storeChunk() (see RecordComponent::resetDataset())."); - } - Parameter dCreate(rc.m_dataset.value()); - dCreate.name = rc.m_name; - IOHandler()->enqueue(IOTask(this, dCreate)); - } - Parameter getBufferView; - getBufferView.offset = o; - getBufferView.extent = e; - getBufferView.dtype = getDatatype(); - IOHandler()->enqueue(IOTask(this, getBufferView)); - IOHandler()->flush(internal::defaultFlushParams); - auto &out = *getBufferView.out; - if (!out.backendManagedBuffer) - { - // note that data might have either - // type shared_ptr or shared_ptr - auto data = std::forward(createBuffer)(size); - out.ptr = static_cast(data.get()); - storeChunk(std::move(data), std::move(o), std::move(e)); - } - setDirtyRecursive(true); - return DynamicMemoryView{std::move(getBufferView), size, *this}; + return prepareLoadStore() + .offset(std::move(o)) + .extent(std::move(e)) + .enqueueStore(std::forward(createBuffer)); } template @@ -220,4 +168,13 @@ inline auto RecordComponent::visit(Args &&...args) return switchDatasetType>( getDatatype(), *this, std::forward(args)...); } + +// definitions for LoadStoreChunk.hpp +template +auto core::ConfigureLoadStore::enqueueStore(F &&createBuffer) + -> DynamicMemoryView +{ + return m_rc.storeChunkSpanCreateBuffer_impl( + storeChunkConfig(), std::forward(createBuffer)); +} } // namespace openPMD diff --git a/src/LoadStoreChunk.cpp b/src/LoadStoreChunk.cpp index 526c01a3d9..6c65ac59d9 100644 --- a/src/LoadStoreChunk.cpp +++ b/src/LoadStoreChunk.cpp @@ -306,18 +306,22 @@ template class compose::ConfigureLoadStore; /* clang-format would destroy the NOLINT comments */ // clang-format off #define INSTANTIATE_METHOD_TEMPLATES(dtype) \ - template auto core::ConfigureLoadStore::enqueueStore() \ - -> DynamicMemoryView; \ template auto core::ConfigureLoadStore::enqueueLoad() \ /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ -> auxiliary::DeferredComputation>; \ template auto core::ConfigureLoadStore::load(EnqueuePolicy) \ ->std::shared_ptr; // clang-format on +#define INSTANTIATE_METHOD_TEMPLATES_WITH_AND_WITHOUT_EXTENT(type) \ + INSTANTIATE_METHOD_TEMPLATES(type) \ + INSTANTIATE_METHOD_TEMPLATES(type[]) template auto \ + core::ConfigureLoadStore::enqueueStore() -> DynamicMemoryView; -OPENPMD_FOREACH_NONVECTOR_DATATYPE(INSTANTIATE_METHOD_TEMPLATES) +OPENPMD_FOREACH_NONVECTOR_DATATYPE( + INSTANTIATE_METHOD_TEMPLATES_WITH_AND_WITHOUT_EXTENT) #undef INSTANTIATE_METHOD_TEMPLATES +#undef INSTANTIATE_METHOD_TEMPLATES_WITH_AND_WITHOUT_EXTENT /* clang-format would destroy the NOLINT comments */ // clang-format off diff --git a/src/RecordComponent.cpp b/src/RecordComponent.cpp index b8dffd9caf..995f120703 100644 --- a/src/RecordComponent.cpp +++ b/src/RecordComponent.cpp @@ -21,9 +21,9 @@ #include "openPMD/RecordComponent.hpp" #include "openPMD/Dataset.hpp" #include "openPMD/DatatypeHelpers.hpp" -#include "openPMD/DatatypeMacros.hpp" #include "openPMD/Error.hpp" #include "openPMD/IO/Format.hpp" +#include "openPMD/LoadStoreChunk.hpp" #include "openPMD/Series.hpp" #include "openPMD/auxiliary/Memory.hpp" #include "openPMD/backend/Attributable.hpp" @@ -33,6 +33,9 @@ // comment so clang-format does not move this #include "openPMD/DatatypeMacros.hpp" +// comment +#include "openPMD/DatatypeMacros.hpp" + #include #include #include @@ -541,21 +544,6 @@ void RecordComponent::readBase(bool require_unit_si) } } -void RecordComponent::storeChunk( - auxiliary::WriteBuffer buffer, Datatype dtype, Offset o, Extent e) -{ - verifyChunk(dtype, o, e); - - Parameter dWrite; - dWrite.offset = std::move(o); - dWrite.extent = std::move(e); - dWrite.dtype = dtype; - /* std::static_pointer_cast correctly reference-counts the pointer */ - dWrite.data = std::move(buffer); - auto &rc = get(); - rc.push_chunk(IOTask(this, std::move(dWrite))); -} - void RecordComponent::storeChunk_impl( auxiliary::WriteBuffer buffer, Datatype dtype, @@ -691,40 +679,22 @@ template std::shared_ptr RecordComponent::loadChunk(Offset o, Extent e) { uint8_t dim = getDimensionality(); + auto operation = prepareLoadStore(); // default arguments // offset = {0u}: expand to right dim {0u, 0u, ...} - Offset offset = o; - if (o.size() == 1u && o.at(0) == 0u && dim > 1u) - offset = Offset(dim, 0u); + if (o.size() != 1u || o.at(0) != 0u || dim <= 1u) + { + operation.offset(std::move(o)); + } // extent = {-1u}: take full size - Extent extent(dim, 1u); - if (e.size() == 1u && e.at(0) == -1u) + if (e.size() != 1u || e.at(0) != -1u) { - extent = getExtent(); - for (uint8_t i = 0u; i < dim; ++i) - extent[i] -= offset[i]; + operation.extent(std::move(e)); } - else - extent = e; - - uint64_t numPoints = 1u; - for (auto const &dimensionSize : extent) - numPoints *= dimensionSize; -#if (defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 11000) || \ - (defined(__apple_build_version__) && __clang_major__ < 14) - auto newData = - std::shared_ptr(new T[numPoints], [](T *p) { delete[] p; }); - loadChunk(newData, offset, extent); - return newData; -#else - auto newData = std::shared_ptr[]>( - new std::remove_extent_t[numPoints]); - loadChunk(newData, offset, extent); - return std::static_pointer_cast(std::move(newData)); -#endif + return operation.load(EnqueuePolicy::Defer); } namespace detail @@ -846,182 +816,74 @@ void RecordComponent::loadChunk_impl( template void RecordComponent::loadChunk(std::shared_ptr data, Offset o, Extent e) { - Datatype dtype = determineDatatype(data); - /* - * For constant components, we implement type conversion, so there is - * a separate check further below. - * This is especially useful for the short-attribute representation in the - * JSON/TOML backends as they might implicitly turn a LONG into an INT in a - * constant component. The frontend needs to catch such edge cases. - * Ref. `if (constant())` branch. - */ - if (dtype != getDatatype() && !constant()) - if (!isSameInteger(getDatatype()) && - !isSameFloatingPoint(getDatatype()) && - !isSameComplexFloatingPoint(getDatatype()) && - !isSameChar(getDatatype())) - { - std::string const data_type_str = datatypeToString(getDatatype()); - std::string const requ_type_str = - datatypeToString(determineDatatype()); - std::string err_msg = - "Type conversion during chunk loading not yet implemented! "; - err_msg += "Data: " + data_type_str + "; Load as: " + requ_type_str; - throw std::runtime_error(err_msg); - } - + // static_assert(!std::is_same_v, "EVIL"); uint8_t dim = getDimensionality(); + auto operation = prepareLoadStore(); // default arguments // offset = {0u}: expand to right dim {0u, 0u, ...} - Offset offset = o; - if (o.size() == 1u && o.at(0) == 0u && dim > 1u) - offset = Offset(dim, 0u); - - // extent = {-1u}: take full size - Extent extent(dim, 1u); - if (e.size() == 1u && e.at(0) == -1u) + if (o.size() != 1u || o.at(0) != 0u || dim <= 1u) { - extent = getExtent(); - for (uint8_t i = 0u; i < dim; ++i) - extent[i] -= offset[i]; + operation.offset(std::move(o)); } - else - extent = e; - if (extent.size() != dim || offset.size() != dim) - { - std::ostringstream oss; - oss << "Dimensionality of chunk (" - << "offset=" << offset.size() << "D, " - << "extent=" << extent.size() << "D) " - << "and record component (" << int(dim) << "D) " - << "do not match."; - throw std::runtime_error(oss.str()); - } - Extent dse = getExtent(); - for (uint8_t i = 0; i < dim; ++i) - if (dse[i] < offset[i] + extent[i]) - throw std::runtime_error( - "Chunk does not reside inside dataset (Dimension on index " + - std::to_string(i) + ". DS: " + std::to_string(dse[i]) + - " - Chunk: " + std::to_string(offset[i] + extent[i]) + ")"); - if (!data) - throw std::runtime_error( - "Unallocated pointer passed during chunk loading."); - - auto &rc = get(); - if (constant()) - { - uint64_t numPoints = 1u; - for (auto const &dimensionSize : extent) - numPoints *= dimensionSize; - - std::optional val = - switchNonVectorType>( - /* dt = */ getDatatype(), rc.m_constantValue); - - if (val.has_value()) - { - T *raw_ptr = data.get(); - std::fill(raw_ptr, raw_ptr + numPoints, *val); - } - else - { - std::string const data_type_str = datatypeToString(getDatatype()); - std::string const requ_type_str = - datatypeToString(determineDatatype()); - std::string err_msg = - "Type conversion during chunk loading not possible! "; - err_msg += "Data: " + data_type_str + "; Load as: " + requ_type_str; - throw error::WrongAPIUsage(err_msg); - } - } - else + // extent = {-1u}: take full size + if (e.size() != 1u || e.at(0) != -1u) { - Parameter dRead; - dRead.offset = offset; - dRead.extent = extent; - dRead.dtype = getDatatype(); - dRead.data = std::static_pointer_cast(data); - rc.push_chunk(IOTask(this, dRead)); + operation.extent(std::move(e)); } -} -template -void RecordComponent::loadChunk( - std::shared_ptr ptr, Offset offset, Extent extent) -{ - loadChunk( - std::static_pointer_cast(std::move(ptr)), - std::move(offset), - std::move(extent)); + operation.withSharedPtr(std::move(data)).load(EnqueuePolicy::Defer); } template void RecordComponent::loadChunkRaw(T *ptr, Offset offset, Extent extent) { - loadChunk(auxiliary::shareRaw(ptr), std::move(offset), std::move(extent)); + prepareLoadStore() + .offset(std::move(offset)) + .extent(std::move(extent)) + .withRawPtr(ptr) + .load(EnqueuePolicy::Defer); } template void RecordComponent::storeChunk(std::shared_ptr data, Offset o, Extent e) { - if (!data) - throw std::runtime_error( - "Unallocated pointer passed during chunk store."); - Datatype dtype = determineDatatype(data); - - /* std::static_pointer_cast correctly reference-counts the pointer */ - storeChunk( - auxiliary::WriteBuffer(std::static_pointer_cast(data)), - dtype, - std::move(o), - std::move(e)); + prepareLoadStore() + .offset(std::move(o)) + .extent(std::move(e)) + .withSharedPtr(std::move(data)) + .store(EnqueuePolicy::Defer); } template void RecordComponent::storeChunk( UniquePtrWithLambda data, Offset o, Extent e) { - if (!data) - throw std::runtime_error( - "Unallocated pointer passed during chunk store."); - Datatype dtype = determineDatatype<>(data); - - storeChunk( - auxiliary::WriteBuffer{std::move(data).template static_cast_()}, - dtype, - std::move(o), - std::move(e)); -} - -template -void RecordComponent::storeChunk(std::shared_ptr data, Offset o, Extent e) -{ - storeChunk( - std::static_pointer_cast(std::move(data)), - std::move(o), - std::move(e)); + prepareLoadStore() + .offset(std::move(o)) + .extent(std::move(e)) + .withUniquePtr(std::move(data)) + .store(EnqueuePolicy::Defer); } template void RecordComponent::storeChunkRaw(T *ptr, Offset offset, Extent extent) { - storeChunk(auxiliary::shareRaw(ptr), std::move(offset), std::move(extent)); + prepareLoadStore() + .offset(std::move(offset)) + .extent(std::move(extent)) + .withRawPtr(ptr) + .store(EnqueuePolicy::Defer); } template DynamicMemoryView RecordComponent::storeChunk(Offset offset, Extent extent) { - return storeChunk(std::move(offset), std::move(extent), [](size_t size) { -#if (defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 11000) || \ - (defined(__apple_build_version__) && __clang_major__ < 14) - return std::shared_ptr{new T[size], [](auto *ptr) { delete[] ptr; }}; -#else - return std::shared_ptr< T[] >{ new T[ size ] }; -#endif - }); + return prepareLoadStore() + .offset(std::move(offset)) + .extent(std::move(extent)) + .enqueueStore(); } template @@ -1035,10 +897,6 @@ void RecordComponent::verifyChunk(Offset const &o, Extent const &e) const #define OPENPMD_ARRAY(type) type[] #define OPENPMD_INSTANTIATE_BASIC(type) \ - template void RecordComponent::loadChunk( \ - std::shared_ptr data, Offset o, Extent e); \ - template void RecordComponent::loadChunk( \ - std::shared_ptr data, Offset o, Extent e); \ template void RecordComponent::loadChunkRaw( \ OPENPMD_PTR(type) ptr, Offset offset, Extent extent); \ template void RecordComponent::verifyChunk( \ @@ -1049,14 +907,12 @@ void RecordComponent::verifyChunk(Offset const &o, Extent const &e) const internal::LoadStoreConfig cfg); #define OPENPMD_INSTANTIATE_CONST_AND_NONCONST(type) \ - template void RecordComponent::storeChunk( \ - std::shared_ptr data, Offset o, Extent e); \ - template void RecordComponent::storeChunk( \ - std::shared_ptr data, Offset o, Extent e); \ template void RecordComponent::storeChunkRaw( \ OPENPMD_PTR(type) ptr, Offset offset, Extent extent); #define OPENPMD_INSTANTIATE_WITH_AND_WITHOUT_EXTENT(type) \ + template void RecordComponent::loadChunk( \ + std::shared_ptr data, Offset o, Extent e); \ template std::shared_ptr RecordComponent::loadChunk( \ Offset o, Extent e); \ template void RecordComponent::storeChunk( \ @@ -1067,6 +923,8 @@ void RecordComponent::verifyChunk(Offset const &o, Extent const &e) const internal::LoadStoreConfig cfg); #define OPENPMD_INSTANTIATE_FULLMATRIX(type) \ + template void RecordComponent::storeChunk( \ + std::shared_ptr data, Offset o, Extent e); \ template RecordComponent &RecordComponent::makeConstant(type); \ template RecordComponent &RecordComponent::makeEmpty( \ uint8_t dimensions); diff --git a/src/auxiliary/Future.cpp b/src/auxiliary/Future.cpp index 025db3f355..25561e34c5 100644 --- a/src/auxiliary/Future.cpp +++ b/src/auxiliary/Future.cpp @@ -45,8 +45,10 @@ template class DeferredComputation; #define INSTANTIATE_FUTURE(dtype) \ /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ template class DeferredComputation>; -OPENPMD_FOREACH_NONVECTOR_DATATYPE(INSTANTIATE_FUTURE) +#define INSTANTIATE_FUTURE_WITH_AND_WITHOUT_EXTENT(type) INSTANTIATE_FUTURE(type) INSTANTIATE_FUTURE(type[]) +OPENPMD_FOREACH_NONVECTOR_DATATYPE(INSTANTIATE_FUTURE_WITH_AND_WITHOUT_EXTENT) #undef INSTANTIATE_FUTURE +#undef INSTANTIATE_FUTURE_WITH_AND_WITHOUT_EXTENT // clang-format on } // namespace openPMD::auxiliary diff --git a/src/binding/python/RecordComponent.cpp b/src/binding/python/RecordComponent.cpp index 21ba30f8e4..fb087f6e45 100644 --- a/src/binding/python/RecordComponent.cpp +++ b/src/binding/python/RecordComponent.cpp @@ -627,14 +627,6 @@ struct StoreChunkSpan static constexpr char const *errorMsg = "RecordComponent.store_chunk()"; }; - -template <> -PythonDynamicMemoryView StoreChunkSpan::call( - RecordComponent &, Offset const &, Extent const &) -{ - throw std::runtime_error( - "[RecordComponent.store_chunk()] Only PODs allowed."); -} } // namespace inline PythonDynamicMemoryView store_chunk_span( From f03cadae03ed4dc70d6b8014e71bb0edf5a0c85a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20P=C3=B6schel?= Date: Thu, 7 Aug 2025 14:08:38 +0200 Subject: [PATCH 08/11] Testing --- test/ParallelIOTest.cpp | 69 +++++++++++++++++++++++++++++++++++++++-- test/SerialIOTest.cpp | 22 ++++++++----- 2 files changed, 82 insertions(+), 9 deletions(-) diff --git a/test/ParallelIOTest.cpp b/test/ParallelIOTest.cpp index 0bf5b972c5..f517588dd2 100644 --- a/test/ParallelIOTest.cpp +++ b/test/ParallelIOTest.cpp @@ -432,13 +432,42 @@ void available_chunks_test(std::string const &file_ending) } )END"; - std::vector data{2, 4, 6, 8}; + std::vector xdata{2, 4, 6, 8}; + std::vector ydata{0, 0, 0, 0, 0, // + 0, 1, 2, 3, 0, // + 0, 4, 5, 6, 0, // + 0, 7, 8, 9, 0, // + 0, 0, 0, 0, 0}; + std::vector ydata_firstandlastrow{-1, -1, -1}; { Series write(name, Access::CREATE, MPI_COMM_WORLD, parameters.str()); Iteration it0 = write.iterations[0]; auto E_x = it0.meshes["E"]["x"]; E_x.resetDataset({Datatype::INT, {mpi_size, 4}}); - E_x.storeChunk(data, {mpi_rank, 0}, {1, 4}); + E_x.storeChunk(xdata, {mpi_rank, 0}, {1, 4}); + auto E_y = it0.meshes["E"]["y"]; + E_y.resetDataset({Datatype::INT, {5, 3ul * mpi_size}}); + E_y.prepareLoadStore() + .withContiguousContainer(ydata_firstandlastrow) + .offset({0, 3ul * mpi_rank}) + .extent({1, 3}) + .enqueueStore(); + E_y.prepareLoadStore() + .offset({1, 3ul * mpi_rank}) + .extent({3, 3}) + .withContiguousContainer(ydata) + .memorySelection({{1, 1}, {5, 5}}) + .enqueueStore(); + // if condition checks if this PR is available in ADIOS2: + // https://github.com/ornladios/ADIOS2/pull/4169 + if constexpr (CanTheMemorySelectionBeReset) + { + E_y.prepareLoadStore() + .withContiguousContainer(ydata_firstandlastrow) + .offset({4, 3ul * mpi_rank}) + .extent({1, 3}) + .enqueueStore(); + } it0.close(); } @@ -470,6 +499,42 @@ void available_chunks_test(std::string const &file_ending) { REQUIRE(ranks[i] == i); } + + auto E_y = it0.meshes["E"]["y"]; + auto width = E_y.getExtent()[1]; + auto first_row = + E_y.prepareLoadStore().extent({1, width}).enqueueLoad().get(); + auto middle_rows = E_y.prepareLoadStore() + .offset({1, 0}) + .extent({3, width}) + .enqueueLoad() + .get(); + auto last_row = + E_y.prepareLoadStore().offset({4, 0}).enqueueLoad().get(); + read.flush(); + + for (auto row : [&]() -> std::vector *> { + if constexpr (CanTheMemorySelectionBeReset) + { + return {&first_row, &last_row}; + } + else + { + return {&first_row}; + } + }()) + { + for (size_t i = 0; i < width; ++i) + { + REQUIRE(row->get()[i] == -1); + } + } + for (size_t i = 0; i < width * 3; ++i) + { + size_t row = i / width; + int required_value = row * 3 + (i % 3) + 1; + REQUIRE(middle_rows.get()[i] == required_value); + } } } diff --git a/test/SerialIOTest.cpp b/test/SerialIOTest.cpp index 8612867207..5e5830f38d 100644 --- a/test/SerialIOTest.cpp +++ b/test/SerialIOTest.cpp @@ -913,7 +913,11 @@ inline void constant_scalar(std::string const &file_ending) new unsigned int[6], [](unsigned int const *p) { delete[] p; }); unsigned int e{0}; std::generate(E.get(), E.get() + 6, [&e] { return e++; }); - E_y.storeChunk(std::move(E), {0, 0, 0}, {1, 2, 3}); + // check that const-type unique pointers work in the builder pattern + E_y.prepareLoadStore() + .extent({1, 2, 3}) + .withUniquePtr(std::move(E).static_cast_()) + .enqueueStore(); // store a number of predefined attributes in E Mesh &E_mesh = s.snapshots()[1].meshes["E"]; @@ -1724,13 +1728,17 @@ inline void write_test( auto opaqueTypeDataset = rc.visit(); auto variantTypeDataset = rc.loadChunkVariant(); + auto variantTypeDataset2 = rc.prepareLoadStore().enqueueLoadVariant().get(); rc.seriesFlush(); - std::visit( - [](auto &&shared_ptr) { - std::cout << "First value in loaded chunk: '" << shared_ptr.get()[0] - << '\'' << std::endl; - }, - variantTypeDataset); + for (auto ptr : {&variantTypeDataset, &variantTypeDataset2}) + { + std::visit( + [](auto &&shared_ptr) { + std::cout << "First value in loaded chunk: '" + << shared_ptr.get()[0] << '\'' << std::endl; + }, + *ptr); + } #ifndef _WIN32 if (test_rank_table) From 3ff5169668ff318feabcfa119f8c5ef85a6123f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20P=C3=B6schel?= Date: Thu, 7 Aug 2025 16:08:22 +0200 Subject: [PATCH 09/11] Enable support for const unique pointers? --- src/LoadStoreChunk.cpp | 8 +++++--- src/auxiliary/UniquePtr.cpp | 5 ++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/LoadStoreChunk.cpp b/src/LoadStoreChunk.cpp index 6c65ac59d9..d2bce73371 100644 --- a/src/LoadStoreChunk.cpp +++ b/src/LoadStoreChunk.cpp @@ -314,8 +314,9 @@ template class compose::ConfigureLoadStore; // clang-format on #define INSTANTIATE_METHOD_TEMPLATES_WITH_AND_WITHOUT_EXTENT(type) \ INSTANTIATE_METHOD_TEMPLATES(type) \ - INSTANTIATE_METHOD_TEMPLATES(type[]) template auto \ - core::ConfigureLoadStore::enqueueStore() -> DynamicMemoryView; + INSTANTIATE_METHOD_TEMPLATES(type[]) \ + template auto core::ConfigureLoadStore::enqueueStore() \ + -> DynamicMemoryView; OPENPMD_FOREACH_NONVECTOR_DATATYPE( INSTANTIATE_METHOD_TEMPLATES_WITH_AND_WITHOUT_EXTENT) @@ -353,7 +354,8 @@ OPENPMD_FOREACH_NONVECTOR_DATATYPE( #define INSTANTIATE_STORE_CHUNK_FROM_BUFFER(dtype) \ INSTANTIATE_FULL(std::shared_ptr) \ INSTANTIATE_HALF(std::shared_ptr) \ - INSTANTIATE_HALF(UniquePtrWithLambda) + INSTANTIATE_HALF(UniquePtrWithLambda) \ + INSTANTIATE_HALF(UniquePtrWithLambda) // /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ OPENPMD_FOREACH_NONVECTOR_DATATYPE(INSTANTIATE_STORE_CHUNK_FROM_BUFFER) diff --git a/src/auxiliary/UniquePtr.cpp b/src/auxiliary/UniquePtr.cpp index 1cf2089f35..dc14a3ff1c 100644 --- a/src/auxiliary/UniquePtr.cpp +++ b/src/auxiliary/UniquePtr.cpp @@ -96,7 +96,10 @@ UniquePtrWithLambda::UniquePtrWithLambda( std::unique_ptr); #define OPENPMD_INSTANTIATE_WITH_AND_WITHOUT_EXTENT(type) \ - OPENPMD_INSTANTIATE(type) OPENPMD_INSTANTIATE(OPENPMD_ARRAY(type)) + OPENPMD_INSTANTIATE(type) \ + OPENPMD_INSTANTIATE(OPENPMD_ARRAY(type)) \ + OPENPMD_INSTANTIATE(type const) \ + OPENPMD_INSTANTIATE(OPENPMD_ARRAY(type const)) OPENPMD_FOREACH_NONVECTOR_DATATYPE(OPENPMD_INSTANTIATE_WITH_AND_WITHOUT_EXTENT) // Instantiate this directly, do not instantiate the From 7ac3661d829bbb8f377fb9542f9377862064ddd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20P=C3=B6schel?= Date: Thu, 7 Aug 2025 16:15:21 +0200 Subject: [PATCH 10/11] Use a better trick to cheat clang-tidy --- src/LoadStoreChunk.cpp | 50 +++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/src/LoadStoreChunk.cpp b/src/LoadStoreChunk.cpp index d2bce73371..710417b2b7 100644 --- a/src/LoadStoreChunk.cpp +++ b/src/LoadStoreChunk.cpp @@ -303,18 +303,19 @@ namespace compose template class compose::ConfigureLoadStore; -/* clang-format would destroy the NOLINT comments */ -// clang-format off +// need this for clang-tidy +#define OPENPMD_ARRAY(type) type[] +#define OPENPMD_APPLY_TEMPLATE(template_, type) template_ + #define INSTANTIATE_METHOD_TEMPLATES(dtype) \ template auto core::ConfigureLoadStore::enqueueLoad() \ - /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ - -> auxiliary::DeferredComputation>; \ + -> auxiliary::DeferredComputation; \ template auto core::ConfigureLoadStore::load(EnqueuePolicy) \ ->std::shared_ptr; -// clang-format on #define INSTANTIATE_METHOD_TEMPLATES_WITH_AND_WITHOUT_EXTENT(type) \ INSTANTIATE_METHOD_TEMPLATES(type) \ - INSTANTIATE_METHOD_TEMPLATES(type[]) \ + INSTANTIATE_METHOD_TEMPLATES(OPENPMD_ARRAY(type)) \ template auto core::ConfigureLoadStore::enqueueStore() \ -> DynamicMemoryView; @@ -324,39 +325,32 @@ OPENPMD_FOREACH_NONVECTOR_DATATYPE( #undef INSTANTIATE_METHOD_TEMPLATES #undef INSTANTIATE_METHOD_TEMPLATES_WITH_AND_WITHOUT_EXTENT -/* clang-format would destroy the NOLINT comments */ -// clang-format off #define INSTANTIATE_HALF(pointer_type) \ - template class ConfigureStoreChunkFromBuffer; \ - template class core::ConfigureStoreChunkFromBuffer; \ - template class compose::ConfigureLoadStore< \ - /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ - ConfigureStoreChunkFromBuffer>; \ + template class OPENPMD_APPLY_TEMPLATE( \ + ConfigureStoreChunkFromBuffer, pointer_type); \ + template class core::OPENPMD_APPLY_TEMPLATE( \ + ConfigureStoreChunkFromBuffer, pointer_type); \ + template class compose::ConfigureLoadStore; \ template class compose::ConfigureStoreChunkFromBuffer< \ - /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ - ConfigureStoreChunkFromBuffer>; -// clang-format on + OPENPMD_APPLY_TEMPLATE(ConfigureStoreChunkFromBuffer, pointer_type)>; -/* clang-format would destroy the NOLINT comments */ -// clang-format off #define INSTANTIATE_FULL(pointer_type) \ INSTANTIATE_HALF(pointer_type) \ - template class ConfigureLoadStoreFromBuffer; \ - template class core::ConfigureLoadStoreFromBuffer; \ - template class compose::ConfigureLoadStore< \ - /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ - ConfigureLoadStoreFromBuffer>; \ + template class OPENPMD_APPLY_TEMPLATE( \ + ConfigureLoadStoreFromBuffer, pointer_type); \ + template class core::OPENPMD_APPLY_TEMPLATE( \ + ConfigureLoadStoreFromBuffer, pointer_type); \ + template class compose::ConfigureLoadStore; \ template class compose::ConfigureStoreChunkFromBuffer< \ - /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ - ConfigureLoadStoreFromBuffer>; -// clang-format on + OPENPMD_APPLY_TEMPLATE(ConfigureLoadStoreFromBuffer, pointer_type)>; #define INSTANTIATE_STORE_CHUNK_FROM_BUFFER(dtype) \ INSTANTIATE_FULL(std::shared_ptr) \ INSTANTIATE_HALF(std::shared_ptr) \ INSTANTIATE_HALF(UniquePtrWithLambda) \ INSTANTIATE_HALF(UniquePtrWithLambda) -// /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ OPENPMD_FOREACH_NONVECTOR_DATATYPE(INSTANTIATE_STORE_CHUNK_FROM_BUFFER) @@ -364,6 +358,8 @@ OPENPMD_FOREACH_NONVECTOR_DATATYPE(INSTANTIATE_STORE_CHUNK_FROM_BUFFER) #undef INSTANTIATE_METHOD_TEMPLATES #undef INSTANTIATE_FULL #undef INSTANTIATE_HALF +#undef OPENPMD_ARRAY +#undef OPENPMD_APPLY_TEMPLATE ConfigureLoadStore::ConfigureLoadStore(RecordComponent &rc) : core::ConfigureLoadStore{rc} From 3e8f3741a90665d54b6ff78a29b890cb68bd275a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20P=C3=B6schel?= Date: Fri, 8 Aug 2025 11:59:33 +0200 Subject: [PATCH 11/11] clang-tidy fixes --- src/RecordComponent.cpp | 2 +- src/auxiliary/Future.cpp | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/RecordComponent.cpp b/src/RecordComponent.cpp index 995f120703..250c7831c3 100644 --- a/src/RecordComponent.cpp +++ b/src/RecordComponent.cpp @@ -784,7 +784,7 @@ void RecordComponent::loadChunk_impl( std::optional val = switchNonVectorType>( - /* from = */ getDatatype(), rc.m_constantValue); + /* dt = */ getDatatype(), rc.m_constantValue); if (val.has_value()) { diff --git a/src/auxiliary/Future.cpp b/src/auxiliary/Future.cpp index 25561e34c5..0abbf3bdf3 100644 --- a/src/auxiliary/Future.cpp +++ b/src/auxiliary/Future.cpp @@ -41,15 +41,21 @@ auto DeferredComputation::valid() const noexcept -> bool template class DeferredComputation; template class DeferredComputation; -// clang-format off + +// need this for clang-tidy +#define OPENPMD_ARRAY(type) type[] +#define OPENPMD_APPLY_TEMPLATE(template_, type) template_ + #define INSTANTIATE_FUTURE(dtype) \ - /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ - template class DeferredComputation>; -#define INSTANTIATE_FUTURE_WITH_AND_WITHOUT_EXTENT(type) INSTANTIATE_FUTURE(type) INSTANTIATE_FUTURE(type[]) + template class DeferredComputation; +#define INSTANTIATE_FUTURE_WITH_AND_WITHOUT_EXTENT(type) \ + INSTANTIATE_FUTURE(type) INSTANTIATE_FUTURE(OPENPMD_ARRAY(type)) OPENPMD_FOREACH_NONVECTOR_DATATYPE(INSTANTIATE_FUTURE_WITH_AND_WITHOUT_EXTENT) #undef INSTANTIATE_FUTURE #undef INSTANTIATE_FUTURE_WITH_AND_WITHOUT_EXTENT -// clang-format on +#undef OPENPMD_ARRAY +#undef OPENPMD_APPLY_TEMPLATE } // namespace openPMD::auxiliary #include "openPMD/UndefDatatypeMacros.hpp"