diff --git a/src/axom/core/execution/internal/omp_exec.hpp b/src/axom/core/execution/internal/omp_exec.hpp index f6881199db..1b2daa7331 100644 --- a/src/axom/core/execution/internal/omp_exec.hpp +++ b/src/axom/core/execution/internal/omp_exec.hpp @@ -66,7 +66,7 @@ struct execution_space } static bool usesMemorySpace(axom::MemorySpace m) noexcept { - return m == MemorySpace::Dynamic + return m == MemorySpace::Dynamic || m == MemorySpace::Malloc #ifdef AXOM_USE_UMPIRE || m == MemorySpace::Host || m == MemorySpace::Unified #endif diff --git a/src/axom/quest/IntersectionShaper.hpp b/src/axom/quest/IntersectionShaper.hpp index 81a4a5e566..3f1517f97f 100644 --- a/src/axom/quest/IntersectionShaper.hpp +++ b/src/axom/quest/IntersectionShaper.hpp @@ -2799,7 +2799,8 @@ class IntersectionShaper : public Shaper // m_bpGrp->createNativeLayout(m_bpNodeInt); const conduit::Node& topoNode = m_bpNodeInt.fetch_existing("topologies").fetch_existing(m_bpTopo); - const std::string coordsetName = topoNode.fetch_existing("coordset").as_string(); + const conduit::Node& topoCoordsetNode = topoNode.fetch_existing("coordset"); + const std::string coordsetName = topoCoordsetNode.as_string(); // Assume unstructured and hexahedral SLIC_ERROR_IF(topoNode["type"].as_string() != "unstructured", diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index 9915958434..03adca91fe 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -79,12 +79,6 @@ Shaper::Shaper(RuntimePolicy execPolicy, // This may take too long if there are repeated construction. m_bpGrp->createNativeLayout(m_bpNodeInt); -#if defined(AXOM_DEBUG) - std::string whyBad; - bool goodMesh = verifyInputMesh(whyBad); - SLIC_ASSERT_MSG(goodMesh, whyBad); -#endif - m_cellCount = conduit::blueprint::mesh::topology::length( m_bpNodeInt.fetch_existing("topologies").fetch_existing(m_bpTopo)); @@ -113,8 +107,7 @@ Shaper::Shaper(RuntimePolicy execPolicy, { AXOM_ANNOTATE_SCOPE("Shaper::Shaper_Node"); m_bpGrp = m_dataStore.getRoot()->createGroup("internalGrp"); - m_bpGrp->setDefaultAllocator(m_allocatorId); - + m_bpGrp->setDefaultArrayAllocator(m_allocatorId); m_bpGrp->importConduitTreeExternal(bpNode); // We want unstructured topo but can accomodate structured. @@ -146,12 +139,6 @@ Shaper::Shaper(RuntimePolicy execPolicy, m_bpGrp->createNativeLayout(m_bpNodeInt); -#if defined(AXOM_DEBUG) - std::string whyBad; - bool goodMesh = verifyInputMesh(whyBad); - SLIC_ASSERT_MSG(goodMesh, whyBad); -#endif - m_cellCount = conduit::blueprint::mesh::topology::length( bpNode.fetch_existing("topologies").fetch_existing(m_bpTopo)); diff --git a/src/axom/quest/examples/quest_shape_in_memory.cpp b/src/axom/quest/examples/quest_shape_in_memory.cpp index 0468a6d321..50a768d3a7 100644 --- a/src/axom/quest/examples/quest_shape_in_memory.cpp +++ b/src/axom/quest/examples/quest_shape_in_memory.cpp @@ -442,10 +442,17 @@ void printMfemMeshInfo(mfem::Mesh* mesh, const std::string& prefixMessage = "") } #endif +/************************************************************ + * Shared variables. + ************************************************************/ + const std::string topoName = "mesh"; const std::string coordsetName = "coords"; int cellCount = -1; +const auto hostAllocId = axom::execution_space::allocatorID(); +int arrayAllocId = axom::INVALID_ALLOCATOR_ID; + // Computational mesh in different forms, initialized in main #if defined(AXOM_USE_MFEM) std::shared_ptr shapingDC; @@ -498,15 +505,13 @@ axom::sidre::Group* createBoxMesh(axom::sidre::Group* meshGrp) break; } -#if defined(AXOM_DEBUG) - conduit::Node meshNode, info; - meshGrp->createNativeLayout(meshNode); - SLIC_ASSERT(conduit::blueprint::mesh::verify(meshNode, info)); -#endif - // State group is optional to blueprint, and we don't use it, but mint checks for it. meshGrp->createGroup("state"); + auto hostAllocForScalarAndStringViews = [](const axom::sidre::View& v) { + return (v.isScalar() || v.isString()) ? hostAllocId : axom::INVALID_ALLOCATOR_ID; + }; + return meshGrp; } @@ -1111,9 +1116,9 @@ axom::sidre::View* getElementVolumes( constexpr int NUM_COMPS_PER_VERT = 2; /* - Get vertex coordinates. We use UnstructuredMesh for this, - so get it on host first then transfer to device if needed. - */ + Get vertex coordinates. We use UnstructuredMesh for this, + so get it on host first then transfer to device if needed. + */ auto* connData = meshGrp->getGroup("topologies") ->getGroup(topoName) ->getGroup("elements") @@ -1539,7 +1544,7 @@ int main(int argc, char** argv) axom::utilities::raii::AnnotationsWrapper annotations_raii_wrapper(params.annotationMode); - const int allocatorId = axom::policyToDefaultAllocatorID(params.policy); + const int arrayAllocId = axom::policyToDefaultAllocatorID(params.policy); AXOM_ANNOTATE_SCOPE("quest shaping example"); AXOM_ANNOTATE_BEGIN("init"); @@ -1669,10 +1674,11 @@ int main(int argc, char** argv) "-DAXOM_ENABLE_MFEM_SIDRE_DATACOLLECTION."); #endif + conduit::Node* topoCoordsetNode = nullptr; if(params.useBlueprintSidre() || params.useBlueprintConduit()) { compMeshGrp = ds.getRoot()->createGroup("compMesh"); - compMeshGrp->setDefaultAllocator(allocatorId); + compMeshGrp->setDefaultArrayAllocator(arrayAllocId); createBoxMesh(compMeshGrp); @@ -1721,18 +1727,18 @@ int main(int argc, char** argv) if(params.useBlueprintSidre()) { shaper = - std::make_shared(params.policy, allocatorId, shapeSet, compMeshGrp); + std::make_shared(params.policy, arrayAllocId, shapeSet, compMeshGrp); } if(params.useBlueprintConduit()) { shaper = - std::make_shared(params.policy, allocatorId, shapeSet, *compMeshNode); + std::make_shared(params.policy, arrayAllocId, shapeSet, *compMeshNode); } #if defined(AXOM_USE_MFEM) if(params.useMfem()) { shaper = std::make_shared(params.policy, - allocatorId, + arrayAllocId, shapeSet, shapingDC.get()); } diff --git a/src/axom/quest/util/mesh_helpers.cpp b/src/axom/quest/util/mesh_helpers.cpp index b2ac50a717..8e4209341f 100644 --- a/src/axom/quest/util/mesh_helpers.cpp +++ b/src/axom/quest/util/mesh_helpers.cpp @@ -530,14 +530,6 @@ void convert_blueprint_structured_explicit_to_unstructured_impl_2d(axom::sidre:: meshGrp->createGroup("fields"); } - #if defined(AXOM_DEBUG) && defined(AXOM_USE_CONDUIT) - AXOM_ANNOTATE_BEGIN("validate_post"); - conduit::Node info; - bool isValid = verifyBlueprintMesh(meshGrp, info); - SLIC_ASSERT_MSG(isValid, "Internal error: Generated mesh is invalid."); - AXOM_ANNOTATE_END("validate_post"); - #endif - return; } diff --git a/src/axom/quest/util/mesh_helpers.hpp b/src/axom/quest/util/mesh_helpers.hpp index 043cc6f895..6e367402d3 100644 --- a/src/axom/quest/util/mesh_helpers.hpp +++ b/src/axom/quest/util/mesh_helpers.hpp @@ -66,19 +66,23 @@ mfem::Mesh* make_cartesian_mfem_mesh_3D(const primal::BoundingBox& bb #if defined(AXOM_USE_SIDRE) /*! - /brief Creates a 3D Cartesian blueprint mesh lying within a given bounding box - - \param meshGrp Put the mesh in this Group - \param bbox The bounding box for the mesh - \param res The resolution of the mesh - \param topologyName Name of the blueprint topoloyy to use. - \param coordsetName Name of the blueprint coordset to use. - \param runtimePolicy Runtime policy, see axom::runtime_policy. - Memory in \c meshGrp must be compatible with the - specified policy. - - \return The meshGrp pointer -*/ + * /brief Creates a 3D Cartesian blueprint mesh lying within a given bounding box + * + * \param meshGrp Put the mesh in this Group + * \param bbox The bounding box for the mesh + * \param res The resolution of the mesh + * \param topologyName Name of the blueprint topoloyy to use. + * \param coordsetName Name of the blueprint coordset to use. + * \param runtimePolicy Runtime policy, see axom::runtime_policy. + * Memory in \c meshGrp must be compatible with the + * specified policy. + * + * All data that grows with mesh size will be allocated by the + * default allocator id. All other data are allocated with + * axom::execution_space::allocatorID(). + * + * \return The meshGrp pointer + */ axom::sidre::Group* make_structured_blueprint_box_mesh_3d( axom::sidre::Group* meshGrp, const primal::BoundingBox& bbox, @@ -87,6 +91,13 @@ axom::sidre::Group* make_structured_blueprint_box_mesh_3d( const std::string& coordsetName = "coords", axom::runtime_policy::Policy runtimePolicy = axom::runtime_policy::Policy::seq); +/*! + * \brief Make an unstructured blueprint box mesh. + * + * All data that grows with mesh size will be allocated by the + * default allocator id. All other data are allocated with + * axom::execution_space::allocatorID(). + */ axom::sidre::Group* make_unstructured_blueprint_box_mesh_3d( axom::sidre::Group* meshGrp, const primal::BoundingBox& bbox, @@ -96,19 +107,23 @@ axom::sidre::Group* make_unstructured_blueprint_box_mesh_3d( axom::runtime_policy::Policy runtimePolicy = axom::runtime_policy::Policy::seq); /*! - /brief Creates a 2D Cartesian blueprint mesh lying within a given bounding box - - \param meshGrp Put the mesh in this Group - \param bbox The bounding box for the mesh - \param res The resolution of the mesh - \param topologyName Name of the blueprint topoloyy to use. - \param coordsetName Name of the blueprint coordset to use. - \param runtimePolicy Runtime policy, see axom::runtime_policy. - Memory in \c meshGrp must be compatible with the - specified policy. - - \return The meshGrp pointer -*/ + * /brief Creates a 2D Cartesian blueprint mesh lying within a given bounding box + * + * \param meshGrp Put the mesh in this Group + * \param bbox The bounding box for the mesh + * \param res The resolution of the mesh + * \param topologyName Name of the blueprint topoloyy to use. + * \param coordsetName Name of the blueprint coordset to use. + * \param runtimePolicy Runtime policy, see axom::runtime_policy. + * Memory in \c meshGrp must be compatible with the + * specified policy. + * + * All data that grows with mesh size will be allocated by the + * default allocator id. All other data are allocated with + * axom::execution_space::allocatorID(). + * + * \return The meshGrp pointer + */ axom::sidre::Group* make_structured_blueprint_box_mesh_2d( axom::sidre::Group* meshGrp, const primal::BoundingBox& bbox, @@ -117,6 +132,11 @@ axom::sidre::Group* make_structured_blueprint_box_mesh_2d( const std::string& coordsetName = "coords", axom::runtime_policy::Policy runtimePolicy = axom::runtime_policy::Policy::seq); +/*! + * All data that grows with mesh size will be allocated by the + * default allocator id. All other data are allocated with + * axom::execution_space::allocatorID(). + */ axom::sidre::Group* make_unstructured_blueprint_box_mesh_2d( axom::sidre::Group* meshGrp, const primal::BoundingBox& bbox, @@ -126,18 +146,18 @@ axom::sidre::Group* make_unstructured_blueprint_box_mesh_2d( axom::runtime_policy::Policy runtimePolicy = axom::runtime_policy::Policy::seq); /*! - \brief Convert a structured explicit blueprint mesh to unstructured. - \param meshGrp Put the mesh in this Group - \param topologyName Name of the blueprint topoloyy to use. - \param runtimePolicy Runtime policy, see axom::runtime_policy. - Memory in \c meshGrp must be compatible with the - specified policy. - - All input mesh data are expected to have the allocator id of - meshGrp->getDefaultAllocatorID(). On output, they will also have - the same allocator id, even if the intermediate steps have to - transfers memory to and from another space. -*/ + * \brief Convert a structured explicit blueprint mesh to unstructured. + * \param meshGrp Put the mesh in this Group + * \param topologyName Name of the blueprint topoloyy to use. + * \param runtimePolicy Runtime policy, see axom::runtime_policy. + * Memory in \c meshGrp must be compatible with the + * specified policy. + * + * All input mesh data are expected to have the allocator id of + * meshGrp->getDefaultAllocatorID(). On output, they will also have + * the same allocator id, even if the intermediate steps have to + * transfers memory to and from another space. + */ void convert_blueprint_structured_explicit_to_unstructured_3d(axom::sidre::Group* meshGrp, const std::string& topoName, axom::runtime_policy::Policy runtimePolicy); @@ -156,24 +176,24 @@ void convert_blueprint_structured_explicit_to_unstructured_impl_2d(axom::sidre:: #if defined(AXOM_USE_CONDUIT) /*! - \brief Check if blueprint mesh is valid. -*/ + * \brief Check if blueprint mesh is valid. + */ bool verifyBlueprintMesh(const axom::sidre::Group* meshGrp, conduit::Node info); #endif #endif /*! - @brief Fill in 3D structured mesh cartesian coordinates. - \param runtimePolicy Runtime execution space selector - \param xView Vertex x-values array - \param yView Vertex y-values array - \param zView Vertex z-values array - \param domainBox Physical domain - - Array data must be in a memory space accessible by the - selected \c runtimePolicy. -*/ + * @brief Fill in 3D structured mesh cartesian coordinates. + * \param runtimePolicy Runtime execution space selector + * \param xView Vertex x-values array + * \param yView Vertex y-values array + * \param zView Vertex z-values array + * \param domainBox Physical domain + * + * Array data must be in a memory space accessible by the + * selected \c runtimePolicy. + */ void fill_cartesian_coords_3d(axom::runtime_policy::Policy runtimePolicy, const primal::BoundingBox& domainBox, axom::ArrayView& xView, @@ -181,29 +201,29 @@ void fill_cartesian_coords_3d(axom::runtime_policy::Policy runtimePolicy, axom::ArrayView& zView); /*! - @brief Fill in 2D structured mesh cartesian coordinates. - \param runtimePolicy Runtime execution space selector - \param xView Vertex x-values array - \param yView Vertex y-values array - \param domainBox Physical domain - - Array data must be in a memory space accessible by the - selected \c runtimePolicy. -*/ + * @brief Fill in 2D structured mesh cartesian coordinates. + * \param runtimePolicy Runtime execution space selector + * \param xView Vertex x-values array + * \param yView Vertex y-values array + * \param domainBox Physical domain + * + * Array data must be in a memory space accessible by the + * selected \c runtimePolicy. + */ void fill_cartesian_coords_2d(axom::runtime_policy::Policy runtimePolicy, const primal::BoundingBox& domainBox, axom::ArrayView& xView, axom::ArrayView& yView); /*! - @brief Fill in 3D structured mesh cartesian coordinates. - \tparam ExecSpace Execution space, e.g. \c axom::SEQ_EXEC. - @see axom::execution_space - \param xView Vertex x-values array - \param yView Vertex y-values array - \param zView Vertex z-values array - \param domainBox Physical domain -*/ + * @brief Fill in 3D structured mesh cartesian coordinates. + * \tparam ExecSpace Execution space, e.g. \c axom::SEQ_EXEC. + * @see axom::execution_space + * \param xView Vertex x-values array + * \param yView Vertex y-values array + * \param zView Vertex z-values array + * \param domainBox Physical domain + */ template void fill_cartesian_coords_3d_impl(const primal::BoundingBox& domainBox, axom::ArrayView& xView, @@ -211,13 +231,13 @@ void fill_cartesian_coords_3d_impl(const primal::BoundingBox& domainB axom::ArrayView& zView); /*! - @brief Fill in 2D structured mesh cartesian coordinates. - \tparam ExecSpace Execution space, e.g. \c axom::SEQ_EXEC. - @see axom::execution_space - \param xView Vertex x-values array - \param yView Vertex y-values array - \param domainBox Physical domain -*/ + * @brief Fill in 2D structured mesh cartesian coordinates. + * \tparam ExecSpace Execution space, e.g. \c axom::SEQ_EXEC. + * @see axom::execution_space + * \param xView Vertex x-values array + * \param yView Vertex y-values array + * \param domainBox Physical domain + */ template void fill_cartesian_coords_2d_impl(const primal::BoundingBox& domainBox, axom::ArrayView& xView, diff --git a/src/axom/sidre/CMakeLists.txt b/src/axom/sidre/CMakeLists.txt index fda1af5e70..e137045c39 100644 --- a/src/axom/sidre/CMakeLists.txt +++ b/src/axom/sidre/CMakeLists.txt @@ -21,6 +21,7 @@ axom_component_requires(NAME Sidre set(sidre_headers core/Array.hpp core/Buffer.hpp + core/ConduitMemory.hpp core/Group.hpp core/DataStore.hpp core/View.hpp @@ -31,6 +32,7 @@ set(sidre_headers set(sidre_sources core/Buffer.cpp + core/ConduitMemory.cpp core/Group.cpp core/DataStore.cpp core/View.cpp @@ -141,6 +143,9 @@ if(NANOBIND_FOUND) # (CMake treats modules separately from executables, # executable flags not automatically applied) if(AXOM_ENABLE_HIP) + # Make CMake compile the pysidre file with the HIP compiler. + set_source_files_properties(nanobind_sidre.cpp PROPERTIES LANGUAGE HIP) + string (REPLACE " " ";" MODULE_LINK_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") target_link_options(pysidre PRIVATE ${MODULE_LINK_FLAGS}) endif() diff --git a/src/axom/sidre/core/ConduitMemory.cpp b/src/axom/sidre/core/ConduitMemory.cpp new file mode 100644 index 0000000000..c2a7cb37c2 --- /dev/null +++ b/src/axom/sidre/core/ConduitMemory.cpp @@ -0,0 +1,287 @@ +// Copyright (c) 2017-2025, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level LICENSE file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "axom/sidre/core/ConduitMemory.hpp" + +namespace axom +{ +namespace sidre +{ + +std::map> ConduitMemory::s_axomToInstance; +std::map> ConduitMemory::s_conduitToInstance; +const conduit::index_t ConduitMemory::s_defaultConduitId = conduit::Node().allocator(); + +void ConduitMemory::privateRegisterAllocator() +{ + using conduit::utils::register_allocator; + auto deallocator = [](void* ptr) -> void { + char* cPtr = (char*)(ptr); + axom::deallocate(cPtr); + }; + m_deallocCallback = deallocator; +#if defined(AXOM_USE_CONDUIT_STD_FUNCTION) + m_allocCallback = [=](size_t itemCount, size_t itemByteSize) -> void* { + void* ptr = axom::allocate(itemCount * itemByteSize, m_axomId); + return ptr; + }; + m_conduitId = register_allocator(m_allocCallback, m_deallocCallback); +#else + /* + * Note: conduit-0.9.4 allows the callbacks as std::function types. + * Once we are there, we can use a single allocator, eliminating + * the need for these if-else blocks. + */ + if(m_axomId == MALLOC_ALLOCATOR_ID) + { + m_allocCallback = [](size_t itemCount, size_t itemByteSize) { + void* ptr = axom::allocate(itemCount * itemByteSize, MALLOC_ALLOCATOR_ID); + return ptr; + }; + m_conduitId = register_allocator(m_allocCallback, m_deallocCallback); + } + else if(m_axomId == axom::INVALID_ALLOCATOR_ID) + { + m_allocCallback = nullptr; + m_conduitId = -1; + } + else if(m_axomId == 0) + { + m_allocCallback = [](size_t itemCount, size_t itemByteSize) { + void* ptr = axom::allocate(itemCount * itemByteSize, 0); + return ptr; + }; + m_conduitId = register_allocator(m_allocCallback, m_deallocCallback); + } + else if(m_axomId == 1) + { + m_allocCallback = [](size_t itemCount, size_t itemByteSize) { + void* ptr = axom::allocate(itemCount * itemByteSize, 1); + return ptr; + }; + m_conduitId = register_allocator(m_allocCallback, m_deallocCallback); + } + else if(m_axomId == 2) + { + m_allocCallback = [](size_t itemCount, size_t itemByteSize) { + void* ptr = axom::allocate(itemCount * itemByteSize, 2); + return ptr; + }; + m_conduitId = register_allocator(m_allocCallback, m_deallocCallback); + } + else if(m_axomId == 3) + { + m_allocCallback = [](size_t itemCount, size_t itemByteSize) { + void* ptr = axom::allocate(itemCount * itemByteSize, 3); + return ptr; + }; + m_conduitId = register_allocator(m_allocCallback, m_deallocCallback); + } + else if(m_axomId == 4) + { + m_allocCallback = [](size_t itemCount, size_t itemByteSize) { + void* ptr = axom::allocate(itemCount * itemByteSize, 4); + return ptr; + }; + m_conduitId = register_allocator(m_allocCallback, m_deallocCallback); + } + else if(m_axomId == 5) + { + m_allocCallback = [](size_t itemCount, size_t itemByteSize) { + void* ptr = axom::allocate(itemCount * itemByteSize, 5); + return ptr; + }; + m_conduitId = register_allocator(m_allocCallback, m_deallocCallback); + } + else if(m_axomId == 6) + { + m_allocCallback = [](size_t itemCount, size_t itemByteSize) { + void* ptr = axom::allocate(itemCount * itemByteSize, 6); + return ptr; + }; + m_conduitId = register_allocator(m_allocCallback, m_deallocCallback); + } + else if(m_axomId == 7) + { + m_allocCallback = [](size_t itemCount, size_t itemByteSize) { + void* ptr = axom::allocate(itemCount * itemByteSize, 7); + return ptr; + }; + m_conduitId = register_allocator(m_allocCallback, m_deallocCallback); + } + else if(m_axomId == 8) + { + m_allocCallback = [](size_t itemCount, size_t itemByteSize) { + void* ptr = axom::allocate(itemCount * itemByteSize, 8); + return ptr; + }; + m_conduitId = register_allocator(m_allocCallback, m_deallocCallback); + } + else if(m_axomId == 9) + { + m_allocCallback = [](size_t itemCount, size_t itemByteSize) { + void* ptr = axom::allocate(itemCount * itemByteSize, 9); + return ptr; + }; + m_conduitId = register_allocator(m_allocCallback, m_deallocCallback); + } + else if(m_axomId == 10) + { + m_allocCallback = [](size_t itemCount, size_t itemByteSize) { + void* ptr = axom::allocate(itemCount * itemByteSize, 10); + return ptr; + }; + m_conduitId = register_allocator(m_allocCallback, m_deallocCallback); + } + else if(m_axomId == 11) + { + m_allocCallback = [](size_t itemCount, size_t itemByteSize) { + void* ptr = axom::allocate(itemCount * itemByteSize, 11); + return ptr; + }; + m_conduitId = register_allocator(m_allocCallback, m_deallocCallback); + } + else if(m_axomId == 12) + { + m_allocCallback = [](size_t itemCount, size_t itemByteSize) { + void* ptr = axom::allocate(itemCount * itemByteSize, 12); + return ptr; + }; + m_conduitId = register_allocator(m_allocCallback, m_deallocCallback); + } + else if(m_axomId == 13) + { + m_allocCallback = [](size_t itemCount, size_t itemByteSize) { + void* ptr = axom::allocate(itemCount * itemByteSize, 13); + return ptr; + }; + m_conduitId = register_allocator(m_allocCallback, m_deallocCallback); + } + else if(m_axomId == 14) + { + m_allocCallback = [](size_t itemCount, size_t itemByteSize) { + void* ptr = axom::allocate(itemCount * itemByteSize, 14); + return ptr; + }; + m_conduitId = register_allocator(m_allocCallback, m_deallocCallback); + } + else if(m_axomId == 15) + { + m_allocCallback = [](size_t itemCount, size_t itemByteSize) { + void* ptr = axom::allocate(itemCount * itemByteSize, 15); + return ptr; + }; + m_conduitId = register_allocator(m_allocCallback, m_deallocCallback); + } + else if(m_axomId == 15) + { + m_allocCallback = [](size_t itemCount, size_t itemByteSize) { + void* ptr = axom::allocate(itemCount * itemByteSize, 15); + return ptr; + }; + m_conduitId = register_allocator(m_allocCallback, m_deallocCallback); + } + else if(m_axomId == 17) + { + m_allocCallback = [](size_t itemCount, size_t itemByteSize) { + void* ptr = axom::allocate(itemCount * itemByteSize, 17); + return ptr; + }; + m_conduitId = register_allocator(m_allocCallback, m_deallocCallback); + } + else if(m_axomId == 18) + { + m_allocCallback = [](size_t itemCount, size_t itemByteSize) { + void* ptr = axom::allocate(itemCount * itemByteSize, 18); + return ptr; + }; + m_conduitId = register_allocator(m_allocCallback, m_deallocCallback); + } + else if(m_axomId == 19) + { + m_allocCallback = [](size_t itemCount, size_t itemByteSize) { + void* ptr = axom::allocate(itemCount * itemByteSize, 19); + return ptr; + }; + m_conduitId = register_allocator(m_allocCallback, m_deallocCallback); + } + else if(m_axomId == 20) + { + m_allocCallback = [](size_t itemCount, size_t itemByteSize) { + void* ptr = axom::allocate(itemCount * itemByteSize, 20); + return ptr; + }; + m_conduitId = register_allocator(m_allocCallback, m_deallocCallback); + } + else + { + std::cerr << "*** Work-around for conduit::utils::register_allocator " + "needs case for " + "m_axomId = " + << std::to_string(m_axomId) << ". Please add it to ConduitMemory.hpp."; + axom::utilities::processAbort(); + } +#endif +} + +const ConduitMemory& ConduitMemory::instanceForAxomId(int axomAllocId) +{ + if(s_axomToInstance.empty()) + { + // Required one-time actions + static auto axomMemcopy = [](void* dst, const void* src, size_t byteCount) { + axom::copy(dst, src, byteCount); + }; + static auto axomMemset = [](void* ptr, int value, size_t count) { + if(axom::getAllocatorIDFromPointer(ptr) == axom::MALLOC_ALLOCATOR_ID) + { + std::memset(ptr, value, count); + } + else + { +#if defined(AXOM_USE_UMPIRE) + umpire::ResourceManager& rm = umpire::ResourceManager::getInstance(); + rm.memset(ptr, value, count); +#else + std::cerr << "*** Error: Unrecognized axom allocator id" << std::endl; + axom::utilities::processAbort(); +#endif + } + }; + conduit::utils::set_memcpy_handler(axomMemcopy); + conduit::utils::set_memset_handler(axomMemset); + } + + auto it = s_axomToInstance.find(axomAllocId); + if(it == s_axomToInstance.end()) + { + std::shared_ptr newInstance(new ConduitMemory(axomAllocId)); + s_axomToInstance[axomAllocId] = newInstance; + it = s_axomToInstance.insert({axomAllocId, newInstance}).first; + + auto conduitAllocId = newInstance->m_conduitId; + assert(s_conduitToInstance.find(conduitAllocId) == s_conduitToInstance.end()); + s_conduitToInstance[conduitAllocId] = newInstance; + } + assert(it->first == axomAllocId); + + return *it->second; +} + +const ConduitMemory& ConduitMemory::instanceForConduitId(conduit::index_t conduitAllocId) +{ + auto it = s_conduitToInstance.find(conduitAllocId); + if(it == s_conduitToInstance.end()) + { + // conduitAllocId doesn't have a corresponding axom allocator. + return instanceForAxomId(axom::INVALID_ALLOCATOR_ID); + } + assert(it->first == conduitAllocId); + + return *it->second; +} + +} // end namespace sidre +} // end namespace axom diff --git a/src/axom/sidre/core/ConduitMemory.hpp b/src/axom/sidre/core/ConduitMemory.hpp new file mode 100644 index 0000000000..7b7bbacf79 --- /dev/null +++ b/src/axom/sidre/core/ConduitMemory.hpp @@ -0,0 +1,166 @@ +// Copyright (c) 2017-2025, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level LICENSE file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +/*! + ****************************************************************************** + * + * \file ConduitMemory.hpp + * + * \brief Call-backs for using Axom memory management in Conduit. + * + ****************************************************************************** + */ + +#ifndef SIDRE_CONDUITMEMORY_HPP_ +#define SIDRE_CONDUITMEMORY_HPP_ + +// Standard C++ headers +#include +#include +#include + +#include "axom/config.hpp" +#include "axom/core/memory_management.hpp" +#include "axom/core/utilities/Utilities.hpp" +#include "conduit_node.hpp" +#include "conduit_utils.hpp" + +namespace axom +{ +namespace sidre +{ + +/*! + * @brief Object to do Conduit memory operations through Axom. + * + * This class has no public constructor. Use instanceForAxomId(int + * axomAllocId) to access the instance for a specific Axom allocator + * id. The construction registers the appropriate callbacks with + * Conduit, including the required memset and memcopy callbacks. + * + * Allocator ids have a 1-to-1 relationship with allocators. + * + * Axom's allocator is an extension of the Umpire allocator + * when Umpire is used. Conduit's allocator is opaque, but when used + * by this class, it is associated with an Axom allocator (which is + * an Umpire allocator). + * + * Examples for setting Conduit allocator ids when you have Axom + * allocator ids: + * + * @code{.cpp} + * void foo(conduit::Node& n, int axomAllocId) { + * n.set_allocator(axomAllocIdToConduit(axomAllocId)); + * } + * + * void bar(conduit::Node& n, int axomAllocId) { + * const auto& instance = getInstance(axomAllocId); + * assert(instance.axomId() == axomAllocId); + * n.set_allocator(instance.conduitId()); + * } + * @endcode + */ +struct ConduitMemory +{ + //!@brief Return the Axom allocator id. + int axomId() const { return m_axomId; } + + //!@brief Return the Conduit allocator id coresponding to axomId(). + conduit::index_t conduitId() const { return m_conduitId; } + + /*! + * @brief Convert an Axom allocator id to Conduit, registering + * a new Conduit allocator if needed. + */ + static conduit::index_t axomAllocIdToConduit(int axomAllocId) + { + return instanceForAxomId(axomAllocId).conduitId(); + } + + /*! + * @brief Convert a Conduit allocator id to Axom. + * + * The allocator must have been registered by a prior + * instanceForAxomId() call. + */ + static int conduitAllocIdToAxom(conduit::index_t conduitAllocId) + { + return instanceForConduitId(conduitAllocId).axomId(); + } + + /*! + * @brief Return the instance for the given Axom allocator id. + * + * This method IS NOT thread safe for new values of @c axomAllocId. + */ + static const ConduitMemory& instanceForAxomId(int axomAllocId); + + /*! + * @brief Return the instance for the given Conduit allocator id. + * + * If @c conduitAllocId doesn't correspond to an Axom allocator, + * an object corresponding to axom::INVALID_ALLOCATOR_ID will be returned. + * + * This method IS thread safe. + */ + static const ConduitMemory& instanceForConduitId(conduit::index_t conduitAllocId); + + //!@brief Return the default conduit allocator id. + static conduit::index_t defaultConduitId() { return s_defaultConduitId; } + + ~ConduitMemory() { } + +private: + //!@brief Mapping from Axom allocator to an instance. + static std::map> s_axomToInstance; + + //!@brief Mapping from Conduit allocator to an instance. + static std::map> s_conduitToInstance; + + //!@brief Conduit's default allocator id. + static const conduit::index_t s_defaultConduitId; + + //!@brief Axom's allocator id. + int m_axomId; + //!@brief Conduit's allocator id equivalent to m_axomId. + conduit::index_t m_conduitId; + +#if(CONDUIT_VERSION_MAJOR >= 0 && CONDUIT_VERSION_MINOR >= 9 && CONDUIT_VERSION_PATCH > 4) || \ + (CONDUIT_VERSION_MAJOR >= 0 && CONDUIT_VERSION_MINOR >= 10) || CONDUIT_VERSION_MAJOR >= 1 + #define AXOM_CONDUIT_USES_STD_FUNCTION 1 +#endif + +#if defined(AXOM_CONDUIT_USES_STD_FUNCTION) + using AllocatorCallback = std::function; + using DeallocCallback = std::function; +#else + typedef void* (*AllocatorCallback)(size_t, size_t); + typedef void (*DeallocCallback)(void*); +#endif + + AllocatorCallback m_allocCallback; + DeallocCallback m_deallocCallback; + + static void staticDeallocator(void* ptr) + { + char* cPtr = (char*)(ptr); + axom::deallocate(cPtr); + } + + ConduitMemory() = delete; + + /*! + * @brief Constructor creates allocator/deallocator function and registers + * them with Conduit. + */ + explicit ConduitMemory(int axomAllocId) : m_axomId(axomAllocId) { privateRegisterAllocator(); } + + void privateRegisterAllocator(); +}; + +} /* end namespace sidre */ +} /* end namespace axom */ + +#endif // AXOM_USE_CONDUIT diff --git a/src/axom/sidre/core/Group.cpp b/src/axom/sidre/core/Group.cpp index f4e7ed5301..551b8b94c6 100644 --- a/src/axom/sidre/core/Group.cpp +++ b/src/axom/sidre/core/Group.cpp @@ -653,11 +653,10 @@ View* Group::createView(const std::string& path, const DataType& dtype, void* ex */ View* Group::createViewAndAllocate(const std::string& path, TypeID type, IndexType num_elems, int allocID) { - allocID = getValidAllocatorID(allocID); - View* view = createView(path, type, num_elems); if(view != nullptr) { + allocID = getValidArrayAllocatorId(allocID); view->allocate(allocID); } return view; @@ -677,11 +676,10 @@ View* Group::createViewWithShapeAndAllocate(const std::string& path, const IndexType* shape, int allocID) { - allocID = getValidAllocatorID(allocID); - View* view = createViewWithShape(path, type, ndims, shape); if(view != nullptr) { + allocID = getValidArrayAllocatorId(allocID); view->allocate(allocID); } return view; @@ -697,11 +695,10 @@ View* Group::createViewWithShapeAndAllocate(const std::string& path, */ View* Group::createViewAndAllocate(const std::string& path, const DataType& dtype, int allocID) { - allocID = getValidAllocatorID(allocID); - View* view = createView(path, dtype); if(view != nullptr) { + allocID = getValidArrayAllocatorId(allocID); view->allocate(allocID); } return view; @@ -714,12 +711,12 @@ View* Group::createViewAndAllocate(const std::string& path, const DataType& dtyp * ************************************************************************* */ -View* Group::createViewString(const std::string& path, const std::string& value) +View* Group::createViewString(const std::string& path, const std::string& value, int allocID) { View* view = createView(path); if(view != nullptr) { - view->setString(value); + view->setString(value, allocID); } return view; @@ -916,10 +913,8 @@ View* Group::copyView(View* view) * ************************************************************************* */ -View* Group::deepCopyView(const View* view, int allocID) +View* Group::deepCopyView(const View* view, int arrayAllocId, int tupleAllocId) { - allocID = getValidAllocatorID(allocID); - if(view == nullptr || hasChildView(view->getName())) { SLIC_CHECK_MSG(view != nullptr, @@ -936,7 +931,9 @@ View* Group::deepCopyView(const View* view, int allocID) } View* copy = createView(view->getName()); - view->deepCopyView(copy, allocID); + arrayAllocId = getValidArrayAllocatorId(arrayAllocId); + tupleAllocId = getValidTupleAllocatorId(tupleAllocId); + view->deepCopyView(copy, arrayAllocId, tupleAllocId); return copy; } @@ -1041,7 +1038,7 @@ const Group* Group::getGroup(const std::string& path) const * ************************************************************************* */ -Group* Group::createGroup(const std::string& path, bool is_list) +Group* Group::createGroup(const std::string& path, bool is_list, bool accept_existing) { std::string intpath(path); bool create_groups_in_path = true; @@ -1063,7 +1060,8 @@ Group* Group::createGroup(const std::string& path, bool is_list) } return nullptr; } - else if(intpath.empty() || group->hasChildGroup(intpath) || group->hasChildView(intpath)) + else if(intpath.empty() || (group->hasChildGroup(intpath) && !accept_existing) || + group->hasChildView(intpath)) { SLIC_CHECK_MSG(!intpath.empty(), SIDRE_GROUP_LOG_PREPEND << "Cannot create a Group with an empty path."); @@ -1077,16 +1075,22 @@ Group* Group::createGroup(const std::string& path, bool is_list) return nullptr; } - Group* new_group = new(std::nothrow) Group(intpath, group->getDataStore(), is_list); - if(new_group == nullptr) + if(!group->hasGroup(intpath)) { - return nullptr; + Group* new_group = new(std::nothrow) Group(intpath, group->getDataStore(), is_list); + if(new_group == nullptr) + { + return nullptr; + } + + SLIC_ASSERT(group->getDefaultArrayAllocatorID() == m_default_array_alloc_id); + SLIC_ASSERT(group->getDefaultTupleAllocatorID() == m_default_tuple_alloc_id); + new_group->setDefaultArrayAllocator(m_default_array_alloc_id); + new_group->setDefaultTupleAllocator(m_default_tuple_alloc_id); + return group->attachGroup(new_group); } -#ifdef AXOM_USE_UMPIRE - new_group->setDefaultAllocator(group->getDefaultAllocator()); -#endif - return group->attachGroup(new_group); + return group->getGroup(intpath); } Group* Group::createUnnamedGroup(bool is_list) @@ -1111,7 +1115,9 @@ Group* Group::createUnnamedGroup(bool is_list) } #ifdef AXOM_USE_UMPIRE - new_group->setDefaultAllocator(getDefaultAllocator()); + // Why only do this with Umpire? BTNG. + new_group->setDefaultArrayAllocator(getDefaultArrayAllocator()); + new_group->setDefaultTupleAllocator(getDefaultTupleAllocator()); #endif return attachGroup(new_group); } @@ -1337,10 +1343,9 @@ Group* Group::copyGroup(Group* group) * ************************************************************************* */ -Group* Group::deepCopyGroup(const Group* srcGroup, int allocID) +Group* Group::deepCopyGroup(const Group* srcGroup, int arrayAllocId, int tupleAllocId) { - allocID = getValidAllocatorID(allocID); - + SLIC_ASSERT(srcGroup != this); if(srcGroup == nullptr || hasChildGroup(srcGroup->getName())) { SLIC_CHECK_MSG(srcGroup != nullptr, @@ -1361,18 +1366,54 @@ Group* Group::deepCopyGroup(const Group* srcGroup, int allocID) // copy child Groups to new Group for(auto& grp : srcGroup->groups()) { - dstGroup->deepCopyGroup(&grp, allocID); + dstGroup->deepCopyGroup(&grp, arrayAllocId, tupleAllocId); } // copy Views to new Group for(auto& view : srcGroup->views()) { - dstGroup->deepCopyView(&view, allocID); + dstGroup->deepCopyView(&view, arrayAllocId, tupleAllocId); } return dstGroup; } +/* + ************************************************************************* + * + * Deep copy another Group into this Group. + * + * All contents in this group and their data are destroyed before the copy. + * The deep copy of a Group will copy the group hierarchy and deep copy + * all Views within the hierarchy. + * + ************************************************************************* + */ +Group* Group::deepCopyGroupToSelf(const Group* srcGroup) +{ + SLIC_ASSERT(srcGroup != this); + SLIC_ERROR_IF(m_is_list && !srcGroup->m_is_list, + "Group::deepCopyToSelf cannot copy from a list Group '" + srcGroup->getPath() + + "' to a non-list Group '" + getPath() + "'"); + + destroyGroupsAndData(); + destroyViewsAndData(); + + // copy child Groups to new Group + for(auto& grp : srcGroup->groups()) + { + deepCopyGroup(&grp); + } + + // copy Views to new Group + for(auto& view : srcGroup->views()) + { + deepCopyView(&view); + } + + return this; +} + /* ************************************************************************* * @@ -1426,6 +1467,64 @@ bool Group::createNativeLayout(Node& n, const Attribute* attr) const return hasSavedViews; } +/* + ************************************************************************* + * + * Deep-copy Group's native layout to given Conduit node. + * + ************************************************************************* + */ +bool Group::deepCopyToConduit(Node& dst, int tupleAllocId, int arrayAllocId, const Attribute* attr) const +{ + dst.set(DataType::object()); + bool hasSavedViews = false; + + // Dump the group's views + for(auto& view : views()) + { + // Check that the view's name is not also a child group name + SLIC_CHECK_MSG(m_is_list || !hasChildGroup(view.getName()), + SIDRE_GROUP_LOG_PREPEND << "'" << view.getName() + << "' is the name of both a group and a view."); + + if(attr == nullptr || view.hasAttributeValue(attr)) + { + conduit::Node& child_node = m_is_list ? dst.append() : dst[view.getName()]; + const int allocId = view.isScalar() || view.isString() ? tupleAllocId : arrayAllocId; + if(allocId != axom::INVALID_ALLOCATOR_ID) + { + const conduit::index_t conduitAllocId = ConduitMemory::axomAllocIdToConduit(allocId); + child_node.set_allocator(conduitAllocId); + } + view.deepCopyToConduit(child_node); + hasSavedViews = true; + } + } + + // Recursively dump the child groups + for(auto& group : groups()) + { + conduit::Node& child_node = m_is_list ? dst.append() : dst[group.getName()]; + if(group.deepCopyToConduit(child_node, tupleAllocId, arrayAllocId, attr)) + { + hasSavedViews = true; + } + else + { + if(m_is_list) + { + dst.remove(group.getName()); + } + else + { + dst.remove(dst.number_of_children() - 1); + } + } + } + + return hasSavedViews; +} + /* ************************************************************************* * @@ -1573,6 +1672,23 @@ void Group::printTree(const int nlevels, std::ostream& os) const } } +void Group::hostPrint(const std::string& indent, std::ostream& os) const +{ + for(const auto& view : this->views()) + { + os << indent << view.getName() << ':'; + view.hostPrint(os); + os << std::endl; + } + + const std::string nextIndent = indent + std::string(" "); + for(const auto& group : this->groups()) + { + os << indent << group.getName() << ':' << std::endl; + group.hostPrint(nextIndent, os); + } +} + /* ************************************************************************* * @@ -2176,7 +2292,11 @@ Group::Group(const std::string& name, DataStore* datastore, bool is_list) , m_view_coll(nullptr) , m_group_coll(nullptr) #ifdef AXOM_USE_UMPIRE - , m_default_allocator_id(axom::getDefaultAllocatorID()) + , m_default_array_alloc_id(axom::getDefaultAllocatorID()) + , m_default_tuple_alloc_id(axom::getDefaultAllocatorID()) +#else + , m_default_array_alloc_id(axom::MALLOC_ALLOCATOR_ID) + , m_default_tuple_alloc_id(axom::MALLOC_ALLOCATOR_ID) #endif { if(is_list) @@ -2712,7 +2832,13 @@ bool Group::importConduitTreeExternal(conduit::Node& node, bool preserve_content if(cld_name != "sidre_group_name") { //create string view - createViewString(cld_name, cld_node.as_string()); + // createViewString(cld_name, cld_node.data_ptr()); + int allocId = ConduitMemory::conduitAllocIdToAxom(cld_node.allocator()); + if(allocId == axom::INVALID_ALLOCATOR_ID) + { + allocId = axom::MALLOC_ALLOCATOR_ID; + } + createViewString(cld_name, cld_node.as_string(), allocId); } } else if(cld_dtype.is_number()) @@ -2720,8 +2846,13 @@ bool Group::importConduitTreeExternal(conduit::Node& node, bool preserve_content if(cld_dtype.number_of_elements() == 1) { // create scalar view + int allocId = ConduitMemory::conduitAllocIdToAxom(cld_node.allocator()); + if(allocId == axom::INVALID_ALLOCATOR_ID) + { + allocId = axom::MALLOC_ALLOCATOR_ID; + } View* view = createView(cld_name); - view->setScalar(cld_node); + view->setScalar(cld_node, allocId); } else { @@ -3205,24 +3336,5 @@ bool Group::rename(const std::string& new_name) return do_rename; } -/* - ************************************************************************* - * - * PRIVATE method to return a valid umpire::Allocator ID. - * - ************************************************************************* - */ -int Group::getValidAllocatorID(int allocID) -{ -#ifdef AXOM_USE_UMPIRE - if(allocID == INVALID_ALLOCATOR_ID) - { - allocID = m_default_allocator_id; - } -#endif - - return allocID; -} - } /* end namespace sidre */ } /* end namespace axom */ diff --git a/src/axom/sidre/core/Group.hpp b/src/axom/sidre/core/Group.hpp index 3ad0f11777..42147ec4bc 100644 --- a/src/axom/sidre/core/Group.hpp +++ b/src/axom/sidre/core/Group.hpp @@ -18,6 +18,7 @@ // axom headers #include "axom/config.hpp" +#include "axom/core/Array.hpp" #include "axom/core/ItemCollection.hpp" #include "axom/core/Macros.hpp" #include "axom/core/MapCollection.hpp" @@ -42,6 +43,7 @@ // Sidre headers #include "SidreTypes.hpp" #include "View.hpp" +#include "ConduitMemory.hpp" namespace axom { @@ -119,12 +121,21 @@ class DataStore; * while unnamed Views should be created by passing an empty string as the * path argument to any of the createView methods. * - * * \attention when Views or Groups are created, destroyed, copied, or moved, * indices of other Views and Groups in associated Group objects may * become invalid. This is analogous to iterator invalidation for STL * containers when the container contents change. * + * A Group has two allocators for managing memory in its hierarchy. + * An array allocator is intended for computational data, typically + * large arrays but it could be any size. Another allocator is intended + * for metadata such as strings, scalars and tuples (typically small + * arrays, although it could be any size). These allocators are the + * defaults for the hierarchy's Views and Groups, unless specifically + * overridden. @see setDefaultArrayAllocator() @setDefaultTupleAllocator() + * Moreover only arrays (buffers and external data) are subject to + * shallow-copying. Strings, scalars and tuples are always deep-copied + * even for hierarchy shallow-copies. */ class Group { @@ -272,57 +283,90 @@ class Group */ bool isRoot() const { return m_parent == this; } -#ifdef AXOM_USE_UMPIRE - /*! - * \brief Return the ID of the default umpire::Allocator associated with this - * Group. - */ - int getDefaultAllocatorID() const { return m_default_allocator_id; } + //! \brief Return the ID of the default array allocator id associated with this Group. + int getDefaultArrayAllocatorID() const { return m_default_array_alloc_id; } - /*! - * \brief Return the default umpire::Allocator associated with this Group. - */ - umpire::Allocator getDefaultAllocator() const +#if defined(AXOM_USE_UMPIRE) + //! \brief Return the default array umpire::Allocator associated with this Group. + umpire::Allocator getDefaultArrayAllocator() const { umpire::ResourceManager& rm = umpire::ResourceManager::getInstance(); - return rm.getAllocator(m_default_allocator_id); + return rm.getAllocator(m_default_array_alloc_id); } - /*! - * \brief Set the default umpire::Allocator associated with this Group. - */ - Group* setDefaultAllocator(umpire::Allocator alloc) + //! \brief Set the default array umpire::Allocator associated with this Group. + Group* setDefaultArrayAllocator(umpire::Allocator alloc) { - m_default_allocator_id = alloc.getId(); + setDefaultAllocator(alloc.getId()); return this; } +#endif /*! - * \brief Set the default umpire::Allocator associated with this Group. - */ - Group* setDefaultAllocator(int allocId) + * \brief Set the default array allocator id associated with this Group. + * + * This allocator is the default for array data, even if the array is + * length 1. (Note, tuples are not arrays in this specific sense. @see + * setDefaultTupleAllocator(int).) + */ + Group* setDefaultArrayAllocator(int allocId) { - m_default_allocator_id = allocId; + SLIC_ASSERT(allocId != axom::INVALID_ALLOCATOR_ID); + m_default_array_alloc_id = allocId; return this; } -#else - /*! - * \brief Return the ID of the default umpire::Allocator associated with this - * Group. - */ - int getDefaultAllocatorID() const { return axom::getDefaultAllocatorID(); } + + //! \brief Return the ID of the default scalar/tuple allocator id associated with this Group. + int getDefaultTupleAllocatorID() const { return m_default_tuple_alloc_id; } + +#if defined(AXOM_USE_UMPIRE) + //! \brief Return the default scalar/tuple umpire::Allocator associated with this Group. + umpire::Allocator getDefaultTupleAllocator() const + { + umpire::ResourceManager& rm = umpire::ResourceManager::getInstance(); + return rm.getAllocator(m_default_tuple_alloc_id); + } + + //! \brief Set the default scalar/tuple umpire::Allocator associated with this Group. + Group* setDefaultTupleAllocator(umpire::Allocator alloc) + { + setDefaultTupleAllocator(alloc.getId()); + return this; + } +#endif /*! - * \brief Set the default umpire::Allocator associated with this Group. - */ - Group* setDefaultAllocator(int allocId) + * \brief Set the default scalar/tuple allocator id associated with this Group. + * + * This allocator is the default for tuple data, including strings + * and scalars. (Arrays are not tuples in this sense. @see + * setDefaultArrayAllocator(int).) + */ + Group* setDefaultTupleAllocator(int allocId) { - AXOM_UNUSED_VAR(allocId); - SLIC_ASSERT(allocId == axom::getDefaultAllocatorID()); + SLIC_ASSERT(allocId != axom::INVALID_ALLOCATOR_ID); + m_default_tuple_alloc_id = allocId; + ConduitMemory::instanceForAxomId(m_default_tuple_alloc_id).conduitId(); return this; } + + //! \brief For backward compatibility, same as getDefaultArrayAllocatorID(). + int getDefaultAllocatorID() const { return getDefaultArrayAllocatorID(); } + +#if defined(AXOM_USE_UMPIRE) + //! \brief For backward compatibility, same as getDefaultArrayAllocator(). + umpire::Allocator getDefaultAllocator() const { return getDefaultArrayAllocator(); } + + //! \brief For backward compatibility, same as setDefaultArrayAllocator(). + Group* setDefaultAllocator(umpire::Allocator alloc) + { + return setDefaultArrayAllocator(alloc.getId()); + } #endif + //! \brief For backward compatibility, same as setDefaultArrayAllocator(). + Group* setDefaultAllocator(int allocId) { return setDefaultArrayAllocator(allocId); } + /*! * \brief Insert information about data associated with Group subtree with * this Group at root of tree (default 'recursive' is true), or for @@ -420,7 +464,6 @@ class Group //! @name View access methods. /*! - * \brief Return pointer to non-const View with given name or path. * * This method requires that all groups in the path exist if a path is given. @@ -817,12 +860,12 @@ class Group * \sa View::setScalar() */ template - View* createViewScalar(const std::string& path, ScalarType value) + View* createViewScalar(const std::string& path, ScalarType value, int allocID = INVALID_ALLOCATOR_ID) { View* view = createView(path); if(view != nullptr) { - view->setScalar(value); + view->setScalar(value, allocID); } return view; @@ -832,7 +875,7 @@ class Group * \brief Create View object with given name or path in this Group * set its data to given string. * - * This is equivalent to: createView(name)->setString(value); + * This is equivalent to: createView(name)->setString(value, allocID); * * If given data type object is empty, data will not be allocated. * @@ -840,7 +883,9 @@ class Group * * \sa View::setString() */ - View* createViewString(const std::string& path, const std::string& value); + View* createViewString(const std::string& path, + const std::string& value, + int allocID = INVALID_ALLOCATOR_ID); ///@} @@ -939,7 +984,9 @@ class Group * \return pointer to the new copied View object or nullptr if a View * is not copied into this Group. */ - View* deepCopyView(const View* view, int allocID = INVALID_ALLOCATOR_ID); + View* deepCopyView(const View* view, + int arrayAllocID = INVALID_ALLOCATOR_ID, + int tupleAllocID = INVALID_ALLOCATOR_ID); ///@} @@ -1152,8 +1199,10 @@ class Group /*! * \brief Create a child Group within this Group with given name or path. * - * If name is an empty string or Group already has a child Group with - * given name or path, method is a no-op. + * If path is an empty string, method is a no-op. + * + * If Group already has a child Group with given name or path + * and accept_existing is false, method is a no-op. * * The optional is_list argument is used to determine if the created * child Group will hold items in list format. @@ -1161,7 +1210,7 @@ class Group * \return pointer to created Group object or nullptr if new * Group is not created. */ - Group* createGroup(const std::string& path, bool is_list = false); + Group* createGroup(const std::string& path, bool is_list = false, bool accept_existing = false); /* * \brief Create a child Group within this Group with no name. @@ -1306,7 +1355,33 @@ class Group * \return pointer to the new copied Group object or nullptr if a Group * is not copied into this Group. */ - Group* deepCopyGroup(const Group* srcGroup, int allocID = INVALID_ALLOCATOR_ID); + Group* deepCopyGroup(const Group* srcGroup, + int arrayAllocID = INVALID_ALLOCATOR_ID, + int tupleAllocID = INVALID_ALLOCATOR_ID); + + /*! + * \brief Create a deep copy of Group hierarchy rooted at given Group + * directly to this group. + * \param [in] srcGroup Source for copy + * + * The difference between this method and deepCopyGroup(const Group*) + * is that this method copies into itself instead of into a new child + * Group. + * + * Note that all Views in the Group hierarchy are deep-copied as well. + * + * The deep copy of the Group creates a duplicate of the entire Group + * hierarchy and performs a deep copy of the data described by the Views + * in the hierarchy. + * + * The Views in the new Group hierarchy will each allocate and use + * new Buffers to hold their copied data. Each Buffer will be sized to + * receive only the data values seen by the description of the original + * View and will have zero offset and a strid of one. + * + * \return pointer to this. + */ + Group* deepCopyGroupToSelf(const Group* srcGroup); ///@} @@ -1335,6 +1410,14 @@ class Group */ void printTree(const int nlevels, std::ostream& os = std::cout) const; + /*! + * \brief Print Groups and Views in the hierarchy rooted at this group, + * in a way that won't crash for non-host data. + * + * If data is not host-accessible, print the pointer and a comment. + */ + void hostPrint(const std::string& indent = "", std::ostream& os = std::cout) const; + ///@} /*! @@ -1358,6 +1441,34 @@ class Group */ bool createNativeLayout(Node& n, const Attribute* attr = nullptr) const; + /*! + * \brief Deep-copy Group's native layout to given Conduit node. + * + * \param [out] dst + * \param [in] tupleAllocId overriding allocator for tuples, scalars and strings. + * If equal to INVALID_ALLOCATOR_ID, don't override destination's allocator. + * \param [in] arrayAllocId overriding allocator for arrays. + * If equal to INVALID_ALLOCATOR_ID, don't override destination's allocator. + * \param [in] attr copy Views that have Attribute set. + * + * This is similar to createNativeLayout, except for the leaves being + * deep-copied. + * + * The destination's default data allocator is used for all data, + * unless the overriding defaults are specified. + * - If \c tupleAllocId != \c INVALID_ALLOCATOR_ID, it's used for tuples, + * scalars and strings. + * - If \c arrayAllocId != \c INVALID_ALLOCATOR_ID, it's used for arrays. + * + * \return True if the Group or any of its children were added to the Node, + * false otherwise. + * + */ + bool deepCopyToConduit(Node& dst, + int tupleAllocId = INVALID_ALLOCATOR_ID, + int arrayAllocId = INVALID_ALLOCATOR_ID, + const Attribute* attr = nullptr) const; + /*! * \brief Copy Group's layout to given Conduit node without data * @@ -1715,7 +1826,8 @@ class Group * * This differs from the importConduitTree in that it does not copy any * data held by the Node as an array. Instead it imports the existing - * pointer to the array as an external pointer. + * pointer to the array as an external pointer. This method defines + * arrays as non-string types with more than 1 element. * * This imports the hierarchy from the Node into a Sidre Group with the * same tree structure. @@ -1920,7 +2032,14 @@ class Group * \brief Private method. If allocatorID is a valid allocator ID then return * it. Otherwise return the ID of the default allocator of the owning group. */ - int getValidAllocatorID(int allocatorID); + int getValidArrayAllocatorId(int allocatorId) + { + return allocatorId == INVALID_ALLOCATOR_ID ? m_default_array_alloc_id : allocatorId; + } + int getValidTupleAllocatorId(int allocatorId) + { + return allocatorId == INVALID_ALLOCATOR_ID ? m_default_tuple_alloc_id : allocatorId; + } /// Name of this Group object. std::string m_name; @@ -1946,9 +2065,8 @@ class Group /// Collection of child Groups GroupCollection* m_group_coll; -#ifdef AXOM_USE_UMPIRE - int m_default_allocator_id; -#endif + int m_default_array_alloc_id; + int m_default_tuple_alloc_id; }; } /* end namespace sidre */ diff --git a/src/axom/sidre/core/View.cpp b/src/axom/sidre/core/View.cpp index 055a61a866..02306c7b6a 100644 --- a/src/axom/sidre/core/View.cpp +++ b/src/axom/sidre/core/View.cpp @@ -11,8 +11,11 @@ #include "Group.hpp" #include "DataStore.hpp" #include "Attribute.hpp" +#include "SidreTypes.hpp" +#include "axom/core/execution/execution_space.hpp" #include "axom/core/Macros.hpp" +#include "axom/sidre/core/ConduitMemory.hpp" namespace axom { @@ -61,8 +64,6 @@ std::string View::getPathName() const */ View* View::allocate(int allocID) { - allocID = getValidAllocatorID(allocID); - if(isAllocateValid()) { if(m_state == EMPTY) @@ -76,6 +77,7 @@ View* View::allocate(int allocID) TypeID type = static_cast(m_schema.dtype().id()); IndexType num_elems = m_schema.dtype().number_of_elements(); + allocID = getValidAllocatorId(allocID); m_data_buffer->allocate(type, num_elems, allocID); apply(); } @@ -86,14 +88,12 @@ View* View::allocate(int allocID) /* ************************************************************************* * - * Allocate data for view with type and number of elements. + * Allocate array data for view with type and number of elements. * ************************************************************************* */ View* View::allocate(TypeID type, IndexType num_elems, int allocID) { - allocID = getValidAllocatorID(allocID); - if(type == NO_TYPE_ID || num_elems < 0) { SLIC_CHECK_MSG(type != NO_TYPE_ID, @@ -106,6 +106,7 @@ View* View::allocate(TypeID type, IndexType num_elems, int allocID) } describe(type, num_elems); + allocID = getValidArrayAllocatorId(allocID); allocate(allocID); return this; @@ -114,14 +115,12 @@ View* View::allocate(TypeID type, IndexType num_elems, int allocID) /* ************************************************************************* * - * Allocate data for view with type and shape. + * Allocate array data for view with type and shape. * ************************************************************************* */ View* View::allocate(TypeID type, int ndims, const IndexType* shape, int allocID) { - allocID = getValidAllocatorID(allocID); - SLIC_CHECK_MSG(ndims > 0, SIDRE_VIEW_LOG_PREPEND << "Could not allocate: ndim is non-positive."); SLIC_CHECK_MSG(type != NO_TYPE_ID, @@ -148,6 +147,7 @@ View* View::allocate(TypeID type, int ndims, const IndexType* shape, int allocID if(ndims > 0 && shape != nullptr && num_elems > 0 && type != NO_TYPE_ID) { describe(type, ndims, shape); + allocID = getValidArrayAllocatorId(allocID); allocate(allocID); } @@ -157,14 +157,12 @@ View* View::allocate(TypeID type, int ndims, const IndexType* shape, int allocID /* ************************************************************************* * - * Allocate data for view described by a Conduit data type object. + * Allocate array data for view described by a Conduit data type object. * ************************************************************************* */ View* View::allocate(const DataType& dtype, int allocID) { - allocID = getValidAllocatorID(allocID); - if(dtype.is_empty()) { SLIC_CHECK_MSG(!dtype.is_empty(), @@ -173,6 +171,7 @@ View* View::allocate(const DataType& dtype, int allocID) } describe(dtype); + allocID = getValidArrayAllocatorId(allocID); allocate(allocID); return this; @@ -404,7 +403,7 @@ void View::clear() undescribe(); break; case STRING: - case SCALAR: + case TUPLE: unapply(); undescribe(); break; @@ -605,7 +604,7 @@ void* View::getVoidPtr() const } break; case STRING: - case SCALAR: + case TUPLE: rv = const_cast(m_node.data_ptr()); break; default: @@ -712,7 +711,7 @@ bool View::isAllocated() const break; case EXTERNAL: case STRING: - case SCALAR: + case TUPLE: rv = true; break; default: @@ -884,6 +883,114 @@ void View::print(std::ostream& os) const n.to_json_stream(os); } +/* + Print data in a way that won't crash when data is not host-accessible. + + If data is not host-accessible, print the pointer and a comment. +*/ +void View::hostPrint(std::ostream& os) const +{ + if(isString()) + { + if(isHostAccessible()) + { + os << ' ' << '"' << getString() << '"'; + } + else + { + os << ' ' << getVoidPtr() << " # non-host string data"; + } + } + else if(isScalar()) + { + switch(getTypeID()) + { + case detail::SidreTT::id: + hostPrintScalar(); + break; + case detail::SidreTT::id: + hostPrintScalar(); + break; + case detail::SidreTT::id: + hostPrintScalar(); + break; + case detail::SidreTT::id: + hostPrintScalar(); + break; + case detail::SidreTT::id: + hostPrintScalar(); + break; + case detail::SidreTT::id: + hostPrintScalar(); + break; + case detail::SidreTT::id: + hostPrintScalar(); + break; + case detail::SidreTT::id: + hostPrintScalar(); + break; + case detail::SidreTT::id: + hostPrintScalar(); + break; + case detail::SidreTT::id: + hostPrintScalar(); + break; + default: + os << ' ' << getVoidPtr() << " # non-host unknown scalar data"; + } + } + else if(hasBuffer() || (isExternal() && !isOpaque())) + { + switch(getTypeID()) + { + case detail::SidreTT::id: + hostPrintArray(); + break; + case detail::SidreTT::id: + hostPrintArray(); + break; + case detail::SidreTT::id: + hostPrintArray(); + break; + case detail::SidreTT::id: + hostPrintArray(); + break; + case detail::SidreTT::id: + hostPrintArray(); + break; + case detail::SidreTT::id: + hostPrintArray(); + break; + case detail::SidreTT::id: + hostPrintArray(); + break; + case detail::SidreTT::id: + hostPrintArray(); + break; + case detail::SidreTT::id: + hostPrintArray(); + break; + case detail::SidreTT::id: + hostPrintArray(); + break; + default: + os << ' ' << getVoidPtr() << " # " << (isHostAccessible() ? "" : "non-") + << "host data of unrecognized type id " << getTypeID(); + } + } + else if(isOpaque()) + { + if(isHostAccessible()) + { + os << ' ' << getVoidPtr() << " # opaque host data"; + } + else + { + os << ' ' << getVoidPtr() << " # opaque non-host data"; + } + } +} + /* ************************************************************************* * @@ -922,6 +1029,37 @@ void View::createNativeLayout(Node& n) const n.set_external(m_node.schema(), data_ptr); } +/* + ************************************************************************* + * + * Deep-copy data to given Conduit node. + * + ************************************************************************* + */ +void View::deepCopyToConduit(Node& dst) const +{ + // see ATK-726 - Handle undescribed and unallocated views in Sidre's + // createNativeLayout() + // TODO: Need to handle cases where the view is not described + // TODO: Need to handle cases where the view is not allocated + // TODO: Need to handle cases where the view is not applied + + const conduit::DataType& srcDtype = m_node.dtype(); + dst.set(srcDtype); + if(isAllocated()) + { + // Using set_node to set dst: would reset dst's allocator id to + // the Conduit default (not what we want) if dst is an object or + // list. Fortunately, Sidre never uses a Conduit node in those + // modes. +#ifdef AXOM_DEBUG + const auto oldAllocatorId = dst.allocator(); +#endif + dst.set_node(m_node); + SLIC_ASSERT(dst.allocator() == oldAllocatorId); + } +} + /* ************************************************************************* * @@ -1096,7 +1234,7 @@ void View::copyView(View* copy) const // Nothing more to do break; case STRING: - case SCALAR: + case TUPLE: copy->m_node = m_node; copy->m_state = m_state; copy->m_is_applied = true; @@ -1121,7 +1259,7 @@ void View::copyView(View* copy) const * ************************************************************************* */ -void View::deepCopyView(View* copy, int allocID) const +void View::deepCopyView(View* copy, int arrayAllocId, int tupleAllocId) const { SLIC_ASSERT_MSG(copy->m_state == EMPTY && !copy->isDescribed(), SIDRE_VIEW_LOG_PREPEND << "deepCopyView can only copy into undescribed view " @@ -1131,7 +1269,7 @@ void View::deepCopyView(View* copy, int allocID) const { if(hasBuffer() || m_state == EXTERNAL) { - copy->allocate(getTypeID(), getNumDimensions(), m_shape.data(), allocID); + copy->allocate(getTypeID(), getNumDimensions(), m_shape.data(), arrayAllocId); } else { @@ -1145,15 +1283,16 @@ void View::deepCopyView(View* copy, int allocID) const // Nothing more to do break; case STRING: - case SCALAR: - copy->m_node = m_node; + case TUPLE: + copy->m_node.set_allocator(ConduitMemory::axomAllocIdToConduit(tupleAllocId)); + copy->m_node.set_node(m_node); copy->m_state = m_state; copy->m_is_applied = true; break; case EXTERNAL: if(!copy->isAllocated()) { - copy->allocate(allocID); + copy->allocate(arrayAllocId); } if(isApplied()) { @@ -1178,7 +1317,7 @@ void View::deepCopyView(View* copy, int allocID) const case BUFFER: if(isAllocated() && !copy->isAllocated()) { - copy->allocate(allocID); + copy->allocate(arrayAllocId); } if(isApplied()) { @@ -1228,7 +1367,7 @@ bool View::isAllocateValid() const rv = isDescribed(); break; case STRING: - case SCALAR: + case TUPLE: case EXTERNAL: SLIC_CHECK_MSG(false, SIDRE_VIEW_LOG_PREPEND << "Allocate is not valid for view in '" @@ -1273,7 +1412,7 @@ bool View::isApplyValid() const { case EMPTY: case STRING: - case SCALAR: + case TUPLE: SLIC_CHECK_MSG(false, SIDRE_VIEW_LOG_PREPEND << "Apply is not valid for View with state '" << getStateStringName(m_state) << "'.'"); @@ -1321,8 +1460,8 @@ char const* View::getStateStringName(State state) case EXTERNAL: ret_string = "EXTERNAL"; break; - case SCALAR: - ret_string = "SCALAR"; + case TUPLE: + ret_string = "TUPLE"; break; case STRING: ret_string = "STRING"; @@ -1357,9 +1496,9 @@ View::State View::getStateId(const std::string& name) const { res = EXTERNAL; } - else if(name == "SCALAR") + else if(name == "TUPLE") { - res = SCALAR; + res = TUPLE; } else if(name == "STRING") { @@ -1373,6 +1512,21 @@ View::State View::getStateId(const std::string& name) const return res; } +/* + * Return whether view data is accessible on the host CPU. + */ +bool View::isHostAccessible() const +{ + bool rval = false; + void* dataPtr = getVoidPtr(); + if(dataPtr != nullptr) + { + int allocId = axom::getAllocatorIDFromPointer(dataPtr); + rval = axom::execution_space::usesAllocId(allocId); + } + return rval; +} + /* ************************************************************************* * @@ -1418,7 +1572,7 @@ void View::exportTo(conduit::Node& data_holder, std::set& buffer_indi data_holder["state"] = getStateStringName(EMPTY); } break; - case SCALAR: + case TUPLE: case STRING: data_holder["value"] = getNode(); break; @@ -1472,7 +1626,7 @@ void View::importFrom(conduit::Node& data_holder, const std::mapgetDefaultAllocatorID(); + int allocID = m_owning_group->getDefaultArrayAllocatorID(); buff->allocate((TypeID)array_dtype.id(), num_ele, allocID); // copy the data in a way that matches @@ -1865,23 +2019,34 @@ const char* View::getAttributeString(const Attribute* attr) const return m_attr_values.getString(attr); } -/* - ************************************************************************* - * - * PRIVATE method to return a valid umpire::Allocator ID. - * - ************************************************************************* - */ -int View::getValidAllocatorID(int allocID) +int View::getValidArrayAllocatorId(int allocId) +{ + return getOwningGroup()->getValidArrayAllocatorId(allocId); +} + +int View::getValidTupleAllocatorId(int allocId) { -#ifdef AXOM_USE_UMPIRE - if(allocID == INVALID_ALLOCATOR_ID) + return getOwningGroup()->getValidTupleAllocatorId(allocId); +} + +int View::getValidAllocatorId(int allocId) +{ + if(allocId != axom::INVALID_ALLOCATOR_ID) { - allocID = getOwningGroup()->getDefaultAllocatorID(); + return allocId; } -#endif - - return allocID; + if(m_state == BUFFER || m_state == EXTERNAL) + { + return getOwningGroup()->getValidArrayAllocatorId(allocId); + } + if(m_state == TUPLE || m_state == STRING) + { + return getOwningGroup()->getValidTupleAllocatorId(allocId); + } + SLIC_ASSERT_MSG( + false, + "Axom internal error: Cannot determine semantic valid allocator id"); // Should never get here. + return axom::INVALID_ALLOCATOR_ID; } } /* end namespace sidre */ diff --git a/src/axom/sidre/core/View.hpp b/src/axom/sidre/core/View.hpp index 4ec4fb60f7..f32dc06c4f 100644 --- a/src/axom/sidre/core/View.hpp +++ b/src/axom/sidre/core/View.hpp @@ -19,15 +19,18 @@ // Standard C++ headers #include #include +#include // Other axom headers #include "axom/config.hpp" #include "axom/core/memory_management.hpp" +#include "axom/core/Array.hpp" #include "axom/core/Macros.hpp" #include "axom/core/Types.hpp" #include "axom/slic.hpp" // Sidre headers +#include "axom/sidre/core/ConduitMemory.hpp" #include "axom/sidre/core/SidreTypes.hpp" #include "axom/sidre/core/AttrValues.hpp" @@ -198,10 +201,15 @@ class View */ bool isOpaque() const { return m_state == EXTERNAL && !isApplied(); } + /*! + * \brief Return true if view contains a tuple or scalar value. + */ + bool isTuple() const { return m_state == TUPLE; } + /*! * \brief Return true if view contains a scalar value. */ - bool isScalar() const { return m_state == SCALAR; } + bool isScalar() const { return m_state == TUPLE && getNumElements() == 1; } /*! * \brief Return true if view contains a string value. @@ -397,7 +405,7 @@ class View View* reallocate(IndexType num_elems); /*! - * \brief Reallocate data for view as specified by Conduit data type object. + * \brief Reallocate data for this View as specified by Conduit data type object. * * \note Reallocation from a view is allowed under the conditions * described by the allocate() method. If the conditions are not met @@ -517,7 +525,7 @@ class View /*! * \brief Apply view description to data. * - * If view holds a scalar or a string, the method does nothing. + * If view holds a scalar, tuple or a string, the method does nothing. * * \return pointer to this View object. */ @@ -589,7 +597,51 @@ class View ///@} ///@{ - //! @name Methods to set data in the view (scalar, string, or external data). + //! @name Methods to set data in the view (scalar, string, tuple or external data). + + /*! + * \brief Set the view to hold the given tuple. + * + * \return pointer to this View object. + */ + template + View* setTuple(const axom::ArrayView& values, int allocID = INVALID_ALLOCATOR_ID) + { + // If this view already contains a tuple, issue a warning if the user is + // changing the underlying type ( ie: integer -> float ). +#if defined(AXOM_DEBUG) + if(m_state == TUPLE) + { + DataTypeId arg_id = detail::SidreTT::id; + SLIC_CHECK_MSG(arg_id == m_node.dtype().id(), + SIDRE_VIEW_LOG_PREPEND << "You are setting a scalar value which has changed " + << " the underlying data type. " + << "Old type: " << m_node.dtype().name() << ", " + << "new type: " << DataType::id_to_name(arg_id) << "."); + } +#endif + + // Note: most of these calls that set the view class members are + // unnecessary if the view already holds a tuple. + if(m_state == EMPTY || m_state == TUPLE) + { + auto conduitAllocId = ConduitMemory::axomAllocIdToConduit(getValidTupleAllocatorId(allocID)); + m_node.set_allocator(conduitAllocId); + std::vector tmpValues(values.begin(), values.end()); + m_node.set(tmpValues); + m_schema.set(m_node.schema()); + m_state = TUPLE; + m_is_applied = true; + describeShape(); + } + else + { + SLIC_CHECK_MSG(m_state == EMPTY || m_state == TUPLE, + SIDRE_VIEW_LOG_PREPEND << "Unable to set scalar value on view " + << " with state: " << getStateStringName(m_state)); + } + return this; + } /*! * \brief Set the view to hold the given scalar. @@ -597,12 +649,12 @@ class View * \return pointer to this View object. */ template - View* setScalar(ScalarType value) + View* setScalar(ScalarType value, int allocID = INVALID_ALLOCATOR_ID) { // If this view already contains a scalar, issue a warning if the user is // changing the underlying type ( ie: integer -> float ). #if defined(AXOM_DEBUG) - if(m_state == SCALAR) + if(m_state == TUPLE) { DataTypeId arg_id = detail::SidreTT::id; SLIC_CHECK_MSG(arg_id == m_node.dtype().id(), @@ -614,19 +666,20 @@ class View #endif // Note: most of these calls that set the view class members are - // unnecessary if the view already holds a scalar. May be - // a future optimization opportunity to split the - if(m_state == EMPTY || m_state == SCALAR) + // unnecessary if the view already holds a scalar. + if(m_state == EMPTY || m_state == TUPLE) { + auto conduitAllocId = ConduitMemory::axomAllocIdToConduit(getValidTupleAllocatorId(allocID)); + m_node.set_allocator(conduitAllocId); m_node.set(value); m_schema.set(m_node.schema()); - m_state = SCALAR; + m_state = TUPLE; m_is_applied = true; describeShape(); } else { - SLIC_CHECK_MSG(m_state == EMPTY || m_state == SCALAR, + SLIC_CHECK_MSG(m_state == EMPTY || m_state == TUPLE, SIDRE_VIEW_LOG_PREPEND << "Unable to set scalar value on view " << " with state: " << getStateStringName(m_state)); } @@ -638,12 +691,12 @@ class View * * \return pointer to this View object. */ - View* setScalar(Node& value) + View* setScalar(Node& value, int allocID = INVALID_ALLOCATOR_ID) { // If this view already contains a scalar, issue a warning if the user is // changing the underlying type ( ie: integer -> float ). #if defined(AXOM_DEBUG) - if(m_state == SCALAR) + if(m_state == TUPLE) { SLIC_CHECK_MSG(value.dtype().id() == m_node.dtype().id(), SIDRE_VIEW_LOG_PREPEND @@ -655,19 +708,20 @@ class View #endif // Note: most of these calls that set the view class members are - // unnecessary if the view already holds a scalar. May be - // a future optimization opportunity to split the - if(m_state == EMPTY || m_state == SCALAR) + // unnecessary if the view already holds a scalar. + if(m_state == EMPTY || m_state == TUPLE) { + auto conduitAllocId = ConduitMemory::axomAllocIdToConduit(getValidAllocatorId(allocID)); + m_node.set_allocator(conduitAllocId); m_node.set(value); m_schema.set(m_node.schema()); - m_state = SCALAR; + m_state = TUPLE; m_is_applied = true; describeShape(); } else { - SLIC_CHECK_MSG(m_state == EMPTY || m_state == SCALAR, + SLIC_CHECK_MSG(m_state == EMPTY || m_state == TUPLE, SIDRE_VIEW_LOG_PREPEND << "Unable to set scalar value on view with state: " << getStateStringName(m_state)); } @@ -701,16 +755,17 @@ class View * * \return pointer to this View object. */ - View* setString(const std::string& value) + View* setString(const std::string& value, int allocID = INVALID_ALLOCATOR_ID) { // Note: most of these calls that set the view class members are - // unnecessary if the view already holds a string. May be - // a future optimization opportunity to split the + // unnecessary if the view already holds a string. if(m_state == EMPTY || m_state == STRING) { + m_state = STRING; + auto conduitAllocId = ConduitMemory::axomAllocIdToConduit(getValidAllocatorId(allocID)); + m_node.set_allocator(conduitAllocId); m_node.set_string(value); m_schema.set(m_node.schema()); - m_state = STRING; m_is_applied = true; describeShape(); } @@ -831,8 +886,8 @@ class View */ Node::ConstValue getScalar() const { - SLIC_CHECK_MSG(m_state == SCALAR, - SIDRE_VIEW_LOG_PREPEND << "View::getScalar() called on non-scalar view."); + SLIC_CHECK_MSG(isScalar(), + SIDRE_VIEW_LOG_PREPEND << "View::getScalar() called on non-tuple view."); return getData(); } @@ -918,6 +973,13 @@ class View */ void print(std::ostream& os) const; + /*! + * \brief Print data in a way that won't crash for non-host data. + * + * If data is not host-accessible, print the pointer and a comment. + */ + void hostPrint(std::ostream& os = std::cout) const; + ///@} /*! @@ -935,6 +997,11 @@ class View */ void createNativeLayout(Node& n) const; + /*! + * \brief Deep copy View into the given conduit::Node. + */ + void deepCopyToConduit(Node& dst) const; + /*! * \brief Copy metadata of the View to the given Conduit node * @@ -1389,23 +1456,24 @@ class View /*! * \brief Copy contents of this View contents into an undescribed EMPTY View. * - * For SCALAR and STRING the data is copied; EXTERNAL, + * For TUPLE and STRING the data is copied; EXTERNAL, * data pointer is copied; BUFFER attaches the buffer. */ void copyView(View* copy) const; /*! - * \brief Deep copy contents of this View contents into an undescribed - * EMPTY View. + * \brief Deep copy this View into an undescribed EMPTY View. * - * For SCALAR and STRING the data is copied and the state is preserved. + * For TUPLE and STRING the data is copied and the state is preserved. * For BUFFER and EXTERNAL, the data described by this View is copied into a * new Buffer that is of the size needed to hold the copied data. Any * parts of the source Buffer or external array that are not seen due * to offsets and strides in the description will not be copied. The copied * View will have BUFFER state with zero offset and a stride of one. */ - void deepCopyView(View* copy, int allocID = INVALID_ALLOCATOR_ID) const; + void deepCopyView(View* copy, + int arrayAllocId = INVALID_ALLOCATOR_ID, + int tupleAllocID = INVALID_ALLOCATOR_ID) const; /*! * \brief Add view description and references to it's data to a conduit tree. @@ -1497,9 +1565,9 @@ class View // applied may be true or false EXTERNAL, // View holds pointer to external data (no buffer) : // applied may be true or false - SCALAR, // View holds scalar data (via setScalar()): + TUPLE, // View holds tuple (including scalar) data (via setScalar() or setTuple()): // applied is true - STRING // View holds string data (view setString()): + STRING // View holds string data (via setString()): // applied is true }; @@ -1514,10 +1582,90 @@ class View State getStateId(const std::string& name) const; /*! - * \brief Private method. If allocatorID is a valid allocator ID then return - * it. Otherwise return the ID of the default allocator of the owning group. + * If allocID == INVALID_ALLOCATOR_ID, return the default allocator id, + * which depends on the View's data semantic and owning Group. + * + * Note: The data semantic depends on m_state, so that must be set first. + */ + int getValidAllocatorId(int allocId); + + int getValidTupleAllocatorId(int allocId); + + int getValidArrayAllocatorId(int allocId); + + /*! + * \brief Return whether view data is accessible on the host CPU, + * as determined by Axom's memory management. + * + * If data is external and not allocated through Umpire or malloc, + * the behavior is undefined. + * + * By convention, this returns a false if data is not allocated, + * because we expect null pointers to be correctly checked before use. */ - int getValidAllocatorID(int allocatorID); + bool isHostAccessible() const; + + //!@brief Print as a single line, in a way that won't crash for non-host data. + template + void hostPrintScalar(std::ostream& os = std::cout) const + { + if(isHostAccessible()) + { + os << ' ' << T(m_node.value()); + } + else + { + os << ' ' << getVoidPtr() << " # non-host " << typeid(T).name() << " data"; + } + } + + //!@brief Print on host, as a single line. + template + void hostPrintArray(std::ostream& os = std::cout) const + { + constexpr IndexType maxPrintItems = 10; + if(isHostAccessible()) + { + os << " ["; + auto start = (T*)(getVoidPtr()); + auto end = (T*)(getVoidPtr()) + getNumElements(); + if(getNumElements() <= maxPrintItems) + { + for(auto i = start; i < end; ++i) + { + os << *i; + if(i != end - 1) os << ", "; + } + } + else + { + auto a = start + maxPrintItems / 2; + auto b = end - maxPrintItems / 2; + for(auto i = start; i < a; ++i) + { + os << *i << ", "; + } + os << "..., "; + for(auto i = b; i < end; ++i) + { + os << *i; + if(i != end - 1) os << ", "; + } + } + os << "]"; + } + else + { + axom::Array shape(getNumDimensions()); + getShape(shape.size(), shape.data()); + os << ' ' << getVoidPtr() << " # non-host " << typeid(T).name() << " array of (" << shape[0]; + for(axom::IndexType i = 1; i < shape.size(); ++i) + { + os << " x " << shape[i]; + } + os << ") elements"; + } + } /// Name of this View object. std::string m_name; diff --git a/src/axom/sidre/docs/sphinx/view.rst b/src/axom/sidre/docs/sphinx/view.rst index 85e5169862..faf8f771bf 100644 --- a/src/axom/sidre/docs/sphinx/view.rst +++ b/src/axom/sidre/docs/sphinx/view.rst @@ -55,8 +55,10 @@ stride (based on the pointer address and data type). casting the pointer to a proper type when accessed through the view, knowing the size of the data, etc. -A view may also refer to a scalar quantity or a string. Such views hold their -data differently than the pointer cases described above. +A view may also refer to a scalar quantity, a tuple (which is like a +scalar but may have multiple values like an array) or a string. Such +views hold their data differently than the pointer cases described +above. Before we describe the Sidre ``View`` class interface, we present some view concepts that describe various *states* a view can be in at any given time. @@ -67,8 +69,8 @@ The key view concepts that users should be aware of are: * View data description (data type, number of elements, stride, offset, etc.) * View data association (data lives in an attached Sidre buffer, - accessed via an external pointer, or is a scalar or string owned by the - view) + accessed via an external pointer, or is a scalar, tuple or string + owned by the view) * Whether the view data description has been applied to the data The table below summarizes View data associations (rows) and view states with @@ -105,15 +107,16 @@ these as well; e.g., ``isEmpty()``, ``hasBuffer()``, etc. The associations are: * **EXTERNAL.** A view has a non-null pointer to external data; the view may or may not have a data description and the description, if the view has one, may or may not be applied to the external data. - * **SCALAR.** A view was created to hold a scalar value; such a view always + * **TUPLE.** A view was created to hold a tuple value; such a view always has a valid data description, is allocated, and the description is applied. + A scalar value is equivalent to a 1-tuple. * **STRING.** A view was created to hold a string; such a view always has a valid data description, is allocated, and the description is applied. Note that there are specific consequences that follow from each particular association/state that a view is in. For example, an EMPTY view cannot have an -attached buffer. Neither can an EXTERNAL, SCALAR or STRING view. A view that -is EMPTY, BUFFER, SCALAR, or STRING cannot be EXTERNAL, etc. +attached buffer. Neither can an EXTERNAL, TUPLE or STRING view. A view that +is EMPTY, BUFFER, TUPLE, or STRING cannot be EXTERNAL, etc. The following lists summarize the ``View`` class interface: @@ -145,6 +148,7 @@ Data Association Queries * Does view have a buffer attached? * Is view associated with external data? * Is it a scalar view? + * Is it a tuple view? * Is it a string view? Data State Queries @@ -175,6 +179,7 @@ Data Management Operations and detach buffer from view. * Apply current view description to data or apply a new description. * Set view scalar value. + * Set view tuple value. * Set view string. * Set external data pointer, with or without a data description. @@ -193,8 +198,8 @@ Attribute Methods * Get attribute associated with a view by id or name. * Query whether aAttribute has been set explicitly for view. * Reset attribute with given id or name to its default value. - * Set attribute with given id or name to a given scalar value or string. - * Retrieve scalar value or string of an attribute. + * Set attribute with given id or name to a given scalar value, tuple or string. + * Retrieve scalar value, tuple pointer or string of an attribute. * Iterate over attributes of a view. I/O Operations diff --git a/src/axom/sidre/nanobind_sidre.cpp b/src/axom/sidre/nanobind_sidre.cpp index 7102154a9f..bab0b07d37 100644 --- a/src/axom/sidre/nanobind_sidre.cpp +++ b/src/axom/sidre/nanobind_sidre.cpp @@ -357,14 +357,18 @@ NB_MODULE(pysidre, m_sidre) &View::setScalar, nb::rv_policy::reference, "Set the View to hold a scalar value (int).", - nb::arg("value").noconvert()) + nb::arg("value").noconvert(), + nb::arg("allocID") = INVALID_ALLOCATOR_ID) .def("setScalar", &View::setScalar, nb::rv_policy::reference, "Set the View to hold a scalar value (python float, C++ double).", - nb::arg("value").noconvert()) - - .def("setString", &View::setString, "Set the View to hold a string value.") + nb::arg("value").noconvert(), + nb::arg("allocID") = INVALID_ALLOCATOR_ID) + .def("setString", &View::setString, + "Set the View to hold a string value.", + nb::arg("value").noconvert(), + nb::arg("allocID") = INVALID_ALLOCATOR_ID) .def( "setExternalData", [](View& self, const nb::ndarray<>& external_ptr) { @@ -549,18 +553,23 @@ NB_MODULE(pysidre, m_sidre) "Create View object with given name or path in this Group set its data to given scalar " "value (int).", nb::arg("path"), - nb::arg("value").noconvert()) + nb::arg("value").noconvert(), + nb::arg("allocID") = INVALID_ALLOCATOR_ID) .def("createViewScalar", &Group::createViewScalar, nb::rv_policy::reference, "Create View object with given name or path in this Group set its data to given scalar " "value (C++ double, python float).", nb::arg("path"), - nb::arg("value").noconvert()) + nb::arg("value").noconvert(), + nb::arg("allocID") = INVALID_ALLOCATOR_ID) .def("createViewString", &Group::createViewString, nb::rv_policy::reference, - "Create View object with given name or path in this Group set its data to given string.") + "Create View object with given name or path in this Group set its data to given string.", + nb::arg("path"), + nb::arg("value").noconvert(), + nb::arg("allocID") = INVALID_ALLOCATOR_ID) .def("destroyView", nb::overload_cast(&Group::destroyView), @@ -613,7 +622,8 @@ NB_MODULE(pysidre, m_sidre) nb::rv_policy::reference, "Create a child Group within this Group with given name or path.", nb::arg("path"), - nb::arg("is_list") = false) + nb::arg("is_list") = false, + nb::arg("accept_existing") = false) .def("destroyGroup", nb::overload_cast(&Group::destroyGroup), "Destroy child Group in this Group with given name or path.") diff --git a/src/axom/sidre/tests/sidre_group.cpp b/src/axom/sidre/tests/sidre_group.cpp index 60f66672da..e8d716c739 100644 --- a/src/axom/sidre/tests/sidre_group.cpp +++ b/src/axom/sidre/tests/sidre_group.cpp @@ -6,6 +6,7 @@ #include "axom/config.hpp" #include "axom/core.hpp" #include "axom/sidre.hpp" +#include "axom/sidre/core/SidreTypes.hpp" #include "gtest/gtest.h" @@ -1472,6 +1473,48 @@ TEST(sidre_group, groups_move_copy) delete ds; } +// Return vector of known allocator ids. +std::vector getKnownAllocIds() +{ + std::vector allocIds(1, axom::MALLOC_ALLOCATOR_ID); +#ifdef AXOM_USE_UMPIRE + allocIds.push_back(axom::detail::getAllocatorID()); + #ifdef AXOM_USE_GPU + allocIds.push_back(axom::detail::getAllocatorID()); + allocIds.push_back(axom::detail::getAllocatorID()); + // Does it make sense to check Pinned and Constant memory spaces? + #endif +#endif + return allocIds; +} + +//------------------------------------------------------------------------------ +TEST(sidre_group, scalar_memory_allocator) +{ + std::vector allocIds = getKnownAllocIds(); + + DataStore ds; + Group* grp = ds.getRoot()->createGroup("grp"); + + for(auto allocId : allocIds) + { + grp->setDefaultTupleAllocator(allocId); + + std::int32_t scalarInt = 12345; + grp->createViewScalar("scalarInt", scalarInt); + + std::int32_t* scalarIntPtr = static_cast(grp->getView("scalarInt")->getVoidPtr()); + auto allocIdScalarInt = axom::getAllocatorIDFromPointer(scalarIntPtr); + EXPECT_EQ(allocIdScalarInt, allocId); + + std::int32_t scalarIntTmp; + axom::copy(&scalarIntTmp, scalarIntPtr, sizeof(scalarInt)); + EXPECT_EQ(scalarIntTmp, scalarInt); + + grp->destroyViewsAndData(); + } +} + //------------------------------------------------------------------------------ TEST(sidre_group, group_deep_copy) { @@ -1597,6 +1640,135 @@ TEST(sidre_group, group_deep_copy) delete ds; } +//------------------------------------------------------------------------------ +TEST(sidre_group, deep_copy_interspace) +{ + // Test deep copies with a change in memory space. + + std::vector allocIds = getKnownAllocIds(); + + DataStore ds; + + constexpr int N = 5; + auto dtypeDouble = conduit::DataType::float64(1); + auto dtypeIntArray = conduit::DataType::int32(N); + + // Original values on host + const double doubleValue = 10.13456; + axom::Array intArray(N, N); + for(int i = 0; i < N; ++i) + { + intArray[i] = 1001.5 + i; + } + std::string stringValue = "a string"; + + // Temporary space for correctness checks + double tmpDoubleValue = doubleValue; + axom::Array tmpIntArray(N, N); + axom::Array tmpCharArray; + + Group* srcGrandparent = ds.getRoot()->createGroup("srcGrandparent"); + Group* dstGrandparent = ds.getRoot()->createGroup("dstGrandparent"); + + // For each combination of source and destination alloc ids, + // allocate src and copy to dst. + + for(size_t si = 0; si < allocIds.size(); ++si) + { + int srcArrayAllocId = allocIds[si]; + int srcTupleAllocId = allocIds[allocIds.size() - 1 - si]; + + Group* srcParent = srcGrandparent->createGroup("testParent"); + srcParent->setDefaultArrayAllocator(srcArrayAllocId); + srcParent->setDefaultTupleAllocator(srcTupleAllocId); + Group* src = srcParent->createGroup("testGrp"); + EXPECT_EQ(srcParent->getDefaultAllocatorID(), srcArrayAllocId); + EXPECT_EQ(src->getDefaultAllocatorID(), srcArrayAllocId); + + // + // Create, initialize and sanity-check src objects to test. + // + + View* srcString = src->createViewString("aString", stringValue); + const char* srcStringPtr = static_cast(srcString->getNode().data_ptr()); + EXPECT_EQ(axom::getAllocatorIDFromPointer(srcStringPtr), srcTupleAllocId); + + View* srcScalar = src->createViewScalar("aDouble", doubleValue); + double* srcScalarPtr = static_cast(srcScalar->getNode().data_ptr()); + EXPECT_EQ(axom::getAllocatorIDFromPointer(srcScalarPtr), srcTupleAllocId); + axom::copy(srcScalarPtr, &doubleValue, sizeof(double)); + + View* srcArray = src->createViewAndAllocate("aIntArray", dtypeIntArray); + std::int32_t* srcArrayPtr = static_cast(srcArray->getNode().data_ptr()); + EXPECT_EQ(axom::getAllocatorIDFromPointer(srcArrayPtr), srcArrayAllocId); + axom::copy(srcArrayPtr, intArray.data(), N * sizeof(std::int32_t)); + + if(axom::execution_space::usesAllocId(srcArrayAllocId) && + axom::execution_space::usesAllocId(srcTupleAllocId)) + { + std::cout << "srcGrandparent group:" << std::endl; + srcGrandparent->print(); + } + + // + // Copy the source into destinations of different alloc ids + // + + for(size_t di = 0; di < allocIds.size(); ++di) + { + int dstArrayAllocId = allocIds[di]; + int dstTupleAllocId = allocIds[allocIds.size() - 1 - di]; + + std::cout << "Testing copying allocator id " << srcArrayAllocId << " and " << srcTupleAllocId + << " to " << dstArrayAllocId << " and " << dstTupleAllocId << std::endl; + + dstGrandparent->setDefaultArrayAllocator(dstArrayAllocId); + dstGrandparent->setDefaultTupleAllocator(dstTupleAllocId); + dstGrandparent->deepCopyGroup(srcParent); + + axom::sidre::Group* dstParent = dstGrandparent->getGroup(srcParent->getName()); + Group* dst = dstParent->getGroup(src->getName()); + + // + // Check pointers. Copy data to temporary host buffers and check. + // + + double* dstScalarPtr = static_cast(dst->getView(srcScalar->getName())->getVoidPtr()); + EXPECT_NE(dstScalarPtr, nullptr); + EXPECT_NE(dstScalarPtr, srcScalarPtr); + EXPECT_EQ(axom::getAllocatorIDFromPointer(dstScalarPtr), dstTupleAllocId); + axom::copy(&tmpDoubleValue, dstScalarPtr, sizeof(double)); + EXPECT_EQ(tmpDoubleValue, doubleValue); + + std::int32_t* dstArrayPtr = + static_cast(dst->getView(srcArray->getName())->getVoidPtr()); + EXPECT_NE(dstArrayPtr, nullptr); + EXPECT_NE(dstArrayPtr, srcArrayPtr); + EXPECT_EQ(axom::getAllocatorIDFromPointer(dstArrayPtr), dstArrayAllocId); + axom::copy(tmpIntArray.data(), srcArrayPtr, N * sizeof(std::int32_t)); + for(int i = 0; i < N; ++i) + { + EXPECT_EQ(tmpIntArray[i], intArray[i]); + } + + char* dstStringPtr = static_cast(dst->getView(srcString->getName())->getVoidPtr()); + EXPECT_NE(dstStringPtr, nullptr); + EXPECT_NE(dstStringPtr, srcStringPtr); + EXPECT_EQ(axom::getAllocatorIDFromPointer(dstStringPtr), dstTupleAllocId); + tmpCharArray.resize(srcString->getNumElements()); + axom::copy(tmpCharArray.data(), srcStringPtr, srcString->getNumElements()); + for(int i = 0; i < N; ++i) + { + EXPECT_EQ(tmpCharArray[i], stringValue[i]); + } + + dstGrandparent->destroyGroupAndData(srcParent->getName()); + } + + srcGrandparent->destroyGroupAndData(srcParent->getName()); + } +} + //------------------------------------------------------------------------------ TEST(sidre_group, create_destroy_view_and_buffer2) { @@ -1779,6 +1951,77 @@ TEST(sidre_group, copy_to_conduit_node) delete ds1; } + +//------------------------------------------------------------------------------ +TEST(sidre_group, copy_array_to_conduit_node) +{ + DataStore ds1; + + const axom::IndexType count = 5; + + // group_a uses map format, group_b uses list format. + Group* group_a = ds1.getRoot()->createGroup("group_a", false); + Group* group_b = ds1.getRoot()->createGroup("group_b", true); + + // add child groups and views to group_a + View* view_a = + group_a->createViewAndAllocate("i0", axom::sidre::detail::SidreTT::id, count); + View* view_b = + group_b->createViewAndAllocate("d0", axom::sidre::detail::SidreTT::id, count); + + std::int32_t* v_a = view_a->getArray(); + axom::float64* v_b = view_b->getArray(); + + for(IndexType i = 0; i < count; ++i) + { + v_a[i] = 100 + i; + v_b[i] = 10 + i; + } + EXPECT_NE(v_a, nullptr); + EXPECT_NE(v_b, nullptr); + + conduit::Node shallowDst; + ds1.getRoot()->createNativeLayout(shallowDst); + EXPECT_TRUE(shallowDst.has_path("group_a/i0")); + EXPECT_TRUE(shallowDst.has_path("group_b")); + EXPECT_TRUE(shallowDst.fetch_existing("group_b").number_of_children() == 1); + + { + std::int32_t* n_a = shallowDst.fetch_existing("group_a/i0").value(); + axom::float64* n_b = shallowDst.fetch_existing("group_b").child(0).value(); + EXPECT_NE(n_a, nullptr); + EXPECT_NE(n_b, nullptr); + EXPECT_EQ(n_a, v_a); + EXPECT_EQ(n_b, v_b); + } + + conduit::Node deepDst; + ds1.getRoot()->deepCopyToConduit(deepDst); + EXPECT_TRUE(deepDst.has_path("group_a/i0")); + EXPECT_TRUE(deepDst.has_path("group_b")); + EXPECT_TRUE(deepDst.fetch_existing("group_b").number_of_children() == 1); + + { + std::int32_t* n_a = deepDst.fetch_existing("group_a/i0").value(); + axom::float64* n_b = deepDst.fetch_existing("group_b").child(0).value(); + EXPECT_NE(n_a, nullptr); + EXPECT_NE(n_b, nullptr); + EXPECT_NE(n_a, v_a); + EXPECT_NE(n_b, v_b); + // Verify correct deep-copy values + const int hostAllocId = axom::MALLOC_ALLOCATOR_ID; + axom::Array n_a_host {axom::ArrayView {n_a, {count}}, hostAllocId}; + axom::Array n_b_host {axom::ArrayView {n_b, {count}}, hostAllocId}; + axom::IndexType errCount = 0; + for(IndexType i = 0; i < count; ++i) + { + errCount += n_a_host[i] != v_a[i]; + errCount += n_b_host[i] != v_b[i]; + } + EXPECT_EQ(errCount, 0); + } +} + //------------------------------------------------------------------------------ TEST(sidre_group, save_restore_empty_datastore) { diff --git a/src/axom/sidre/tests/sidre_view.cpp b/src/axom/sidre/tests/sidre_view.cpp index 1713ae4223..9bb3dde0a2 100644 --- a/src/axom/sidre/tests/sidre_view.cpp +++ b/src/axom/sidre/tests/sidre_view.cpp @@ -5,6 +5,7 @@ #include "gtest/gtest.h" #include "axom/core/Types.hpp" +#include "axom/sidre/core/ConduitMemory.hpp" #include "axom/slic.hpp" #include "axom/sidre.hpp" @@ -29,7 +30,7 @@ enum State EMPTY, BUFFER, EXTERNAL, - SCALAR, + TUPLE, STRING, NOTYPE }; @@ -52,7 +53,11 @@ static State getState(View* view) } else if(view->isScalar()) { - return SCALAR; + return TUPLE; + } + else if(view->isTuple()) + { + return TUPLE; } else if(view->isString()) { @@ -288,6 +293,35 @@ static void checkScalarValues(View* view, EXPECT_TRUE(view->getShape(1, dims) == 1 && dims[0] == len); } +TEST(sidre_view, tuple_view) +{ + DataStore ds; + Group* root = ds.getRoot(); + + constexpr size_t N = 5; + + axom::Array iArray(N, N); + for(int i = 0; i < iArray.size(); ++i) + { + iArray[i] = i + 100; + } + + View* iView = root->createView("i")->setTuple(iArray.view()); + EXPECT_EQ(iView->getNumElements(), iArray.size()); + EXPECT_TRUE(iView->isTuple()); + EXPECT_FALSE(iView->isScalar() || N == 1); + EXPECT_FALSE(iView->isEmpty()); + EXPECT_FALSE(iView->isExternal()); + + int* iPtr = static_cast(iView->getVoidPtr()); + EXPECT_NE(iPtr, iArray.data()); + + for(int i = 0; i < iArray.size(); ++i) + { + EXPECT_EQ(iArray[i], iPtr[i]); + } +} + TEST(sidre_view, scalar_view) { DataStore* ds = new DataStore(); @@ -296,12 +330,12 @@ TEST(sidre_view, scalar_view) const char* s; View* i0view = root->createView("i0")->setScalar(1); - checkScalarValues(i0view, SCALAR, true, true, true, INT_ID, 1); + checkScalarValues(i0view, TUPLE, true, true, true, INT_ID, 1); i = i0view->getScalar(); EXPECT_EQ(1, i); View* i1view = root->createViewScalar("i1", 2); - checkScalarValues(i1view, SCALAR, true, true, true, INT_ID, 1); + checkScalarValues(i1view, TUPLE, true, true, true, INT_ID, 1); i = i1view->getScalar(); EXPECT_EQ(2, i); @@ -1737,7 +1771,7 @@ TEST(sidre_view, clear_view) // scalar view. { View* view = root->createViewScalar("v_scalar", 1); - EXPECT_TRUE(checkViewValues(view, SCALAR, true, true, true, 1)); + EXPECT_TRUE(checkViewValues(view, TUPLE, true, true, true, 1)); view->clear(); EXPECT_TRUE(checkViewValues(view, EMPTY, false, false, false, 0)); } @@ -1837,6 +1871,151 @@ TEST(sidre_view, deep_copy_shape) delete ds; } +// Return vector of known allocator ids. +std::vector getKnownAllocIds() +{ + std::vector allocIds(1, axom::MALLOC_ALLOCATOR_ID); +#ifdef AXOM_USE_UMPIRE + allocIds.push_back(axom::detail::getAllocatorID()); + #ifdef AXOM_USE_GPU + allocIds.push_back(axom::detail::getAllocatorID()); + allocIds.push_back(axom::detail::getAllocatorID()); + // Does it make sense to check Pinned and Constant memory spaces? + #endif +#endif + return allocIds; +} + +//------------------------------------------------------------------------------ + +TEST(sidre_view, deep_copy_to_conduit) +{ + std::vector allocIds = getKnownAllocIds(); + + DataStore ds; + + constexpr int N = 5; + auto dtypeDouble = conduit::DataType::float64(1); + auto dtypeIntArray = conduit::DataType::int32(N); + + const double doubleValue = 10.13456; + axom::Array intArray(N, N); + for(int i = 0; i < N; ++i) + { + intArray[i] = 1001.5 + i; + } + std::string stringValue = "a string"; + + double tmpDoubleValue = doubleValue; + axom::Array tmpIntArray(N, N); + axom::Array tmpCharArray; + + // For each combination of srcArrayAllocId and dstAllocId, + // allocate src and copy to dst. + + for(size_t si = 0; si < allocIds.size(); ++si) + { + int srcArrayAllocId = allocIds[si]; + int srcTupleAllocId = allocIds[allocIds.size() - 1 - si]; + + Group* src = ds.getRoot()->createGroup("src"); + src->setDefaultArrayAllocator(srcArrayAllocId); + src->setDefaultTupleAllocator(srcArrayAllocId); + + // + // Create, initialize and sanity-check src objects to test. + // + + View* srcString = src->createViewString("aString", stringValue); + const char* srcStringPtr = static_cast(srcString->getNode().data_ptr()); + if(srcArrayAllocId == axom::MALLOC_ALLOCATOR_ID) + { + // Skip this check if srcArrayAllocId is non-MALLOC_ALLOCATOR_ID, + // because a current sidre issue results in always + // placing strings on Conduit's default alloc id. + EXPECT_EQ(axom::getAllocatorIDFromPointer(srcStringPtr), srcArrayAllocId); + } + + View* srcScalar = src->createViewScalar("aDouble", dtypeDouble); + double* srcScalarPtr = static_cast(srcScalar->getNode().data_ptr()); + EXPECT_EQ(axom::getAllocatorIDFromPointer(srcScalarPtr), srcArrayAllocId); + axom::copy(srcScalarPtr, &doubleValue, sizeof(double)); + + View* srcArray = src->createViewAndAllocate("aIntArray", dtypeIntArray); + std::int32_t* srcArrayPtr = static_cast(srcArray->getNode().data_ptr()); + EXPECT_EQ(axom::getAllocatorIDFromPointer(srcArrayPtr), srcArrayAllocId); + axom::copy(srcArrayPtr, intArray.data(), N * sizeof(std::int32_t)); + + if(axom::execution_space::usesAllocId(srcArrayAllocId)) + { + std::cout << "src group:" << std::endl; + src->print(); + } + + // + // Copy the source into destinations of different alloc ids + // Conduit doesn't support separate allocators for arrays and tuples + // so there's only one destination allocator. + // + + for(size_t di = 0; di < allocIds.size(); ++di) + { + int dstAllocId = allocIds[di]; + + std::cout << "Testing copying allocator ids " << srcArrayAllocId << " and " << srcTupleAllocId + << " to " << dstAllocId << std::endl; + + const auto& idConverter = axom::sidre::ConduitMemory::instanceForAxomId(dstAllocId); + auto dstAllocIdConduit = idConverter.conduitId(); + + conduit::Node dst; + dst.set_allocator(dstAllocIdConduit); + + src->deepCopyToConduit(dst); + + // + // Check pointers. Copy data to temporary host buffers and check data. + // + int checkAllocId; + + double* dstScalarPtr = + static_cast(dst.fetch_existing(srcScalar->getName()).data_ptr()); + EXPECT_NE(dstScalarPtr, nullptr); + EXPECT_NE(dstScalarPtr, srcScalarPtr); + checkAllocId = axom::getAllocatorIDFromPointer(dstScalarPtr); + EXPECT_EQ(checkAllocId, dstAllocId); + axom::copy(&tmpDoubleValue, dstScalarPtr, sizeof(double)); + EXPECT_EQ(tmpDoubleValue, doubleValue); + + std::int32_t* dstArrayPtr = + static_cast(dst.fetch_existing(srcArray->getName()).data_ptr()); + EXPECT_NE(dstArrayPtr, nullptr); + EXPECT_NE(dstArrayPtr, srcArrayPtr); + checkAllocId = axom::getAllocatorIDFromPointer(dstArrayPtr); + EXPECT_EQ(checkAllocId, dstAllocId); + axom::copy(tmpIntArray.data(), srcArrayPtr, N * sizeof(std::int32_t)); + for(int i = 0; i < N; ++i) + { + EXPECT_EQ(tmpIntArray[i], intArray[i]); + } + + char* dstStringPtr = static_cast(dst.fetch_existing(srcString->getName()).data_ptr()); + EXPECT_NE(dstStringPtr, nullptr); + EXPECT_NE(dstStringPtr, srcStringPtr); + checkAllocId = axom::getAllocatorIDFromPointer(dstStringPtr); + EXPECT_EQ(checkAllocId, dstAllocId); + tmpCharArray.resize(srcString->getNumElements()); + axom::copy(tmpCharArray.data(), srcStringPtr, srcString->getNumElements()); + for(int i = 0; i < N; ++i) + { + EXPECT_EQ(tmpCharArray[i], stringValue[i]); + } + } + + ds.getRoot()->destroyGroup("src"); + } +} + //------------------------------------------------------------------------------ TEST(sidre_view, reshape_array)