Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
8f6820a
Add method getHostAllocatorID() and route seq/omp::allocatorID() meth…
rhornung67 Aug 19, 2026
4dc85f6
Change default host alloc to axom malloc and associated changes
rhornung67 Aug 24, 2026
11f118a
add compilation option to change default host allocator
rhornung67 Aug 25, 2026
abc8eb4
Merge branch 'develop' into task/rhornung67/host-memory-api
rhornung67 Aug 25, 2026
7566b92
Moved getDefaultHostAllocatorID() into axom::detail to avoid Axom API…
rhornung67 Aug 25, 2026
cf9a523
Add CI test job on dane to test Umpire host allocation
rhornung67 Aug 25, 2026
5db9bd4
Make style
rhornung67 Aug 25, 2026
f88cc30
Add release note about default host allocator change.
rhornung67 Aug 25, 2026
43ee2c5
Merge branch 'develop' into task/rhornung67/host-memory-api
rhornung67 Aug 25, 2026
20e4ab1
Merge branch 'develop' into task/rhornung67/host-memory-api
rhornung67 Aug 26, 2026
13bfcda
Fix grammar
rhornung67 Aug 26, 2026
8a0a4d2
Explicitly unset macro in malloc case (reviewer suggestion)
rhornung67 Aug 26, 2026
1d39dcd
Merge branch 'develop' into task/rhornung67/host-memory-api
rhornung67 Aug 26, 2026
887ef6b
Merge branch 'develop' into task/rhornung67/host-memory-api
rhornung67 Aug 27, 2026
c68478b
Fix issues with sidre group not choosing default host allocator
rhornung67 Aug 27, 2026
e641dc0
Clarify method comment.
rhornung67 Aug 27, 2026
c8abea7
Merge branch 'develop' into task/rhornung67/host-memory-api
rhornung67 Aug 28, 2026
89dd157
Change order of allocate conditional checks based on new default
rhornung67 Aug 28, 2026
b28af9c
Merge branch 'develop' into task/rhornung67/host-memory-api
rhornung67 Aug 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitlab/build_dane.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ dane-gcc_13_3_1-werror-src:
EXTRA_CMAKE_OPTIONS: "-DENABLE_WARNINGS_AS_ERRORS:BOOL=ON"
extends: .src_build_on_dane

dane-gcc_13_3_1-umpire_host_default-werror-src:
variables:
COMPILER: "gcc@13.3.1"
HOST_CONFIG: "dane-toss_4_x86_64_ib-${COMPILER}.cmake"
EXTRA_CMAKE_OPTIONS: "-DENABLE_WARNINGS_AS_ERRORS:BOOL=ON -DAXOM_DEFAULT_HOST_ALLOCATOR=UMPIRE_HOST"
extends: .src_build_on_dane

dane-sanitizers-gcc_13_3_1-src:
variables:
COMPILER: "gcc@13.3.1"
Expand Down
4 changes: 4 additions & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/
- Quest: Status-returning reader/writer operations in `C2CReader`, `MFEMReader`, `ProEReader`,
`STEPReader`, `STLReader`, `STLWriter`, and their parallel variants are now marked `[[nodiscard]]`.
Callers that previously ignored returned status values must check them to avoid compiler diagnostics.
- Axom's host execution-space default allocator is now a configure-time policy. The default policy is malloc, regardless
of whether Axom is configured with Umpire enabled. Umpire builds may opt into the Umpire `HOST` resource with
`-DAXOM_DEFAULT_HOST_ALLOCATOR=UMPIRE_HOST`. Runtime per-use selection remains available through existing explicit
allocator-ID arguments.

### Fixed
- MIR/Bump: `MergeCoordsetPoints` now only emits its node-merge `SLIC_INFO` when MIR `verbose` is enabled on the Conduit options passed through ELVIRA.
Expand Down
1 change: 1 addition & 0 deletions src/axom/config.hpp.in
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
* Compiler defines for library capabilities
*/
#cmakedefine AXOM_USE_UMPIRE_SHARED_MEMORY
#cmakedefine AXOM_DEFAULT_HOST_ALLOCATOR_USES_UMPIRE_HOST

/*
* Compiler defines for third-party executables
Expand Down
6 changes: 3 additions & 3 deletions src/axom/core/ArrayBase.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -911,7 +911,7 @@ struct DeviceStagingBuffer
#if defined(AXOM_USE_CUDA) && defined(AXOM_USE_UMPIRE)
if(m_deviceStage)
{
int allocator_id = axom::detail::getAllocatorID<axom::MemorySpace::Host>();
int allocator_id = axom::detail::getDefaultHostAllocatorID();
m_staging_buf = axom::allocate<T>(nelems, allocator_id);
if(read_from_data)
{
Expand Down Expand Up @@ -1018,7 +1018,7 @@ struct ArrayOps
if constexpr(std::is_default_constructible_v<T>)
{
#if defined(AXOM_USE_GPU) && defined(AXOM_USE_UMPIRE)
if(space != MemorySpace::Host)
if(space != MemorySpace::Host && space != MemorySpace::Malloc)
{
if constexpr(std::is_trivially_default_constructible_v<T>)
{
Expand Down Expand Up @@ -1062,7 +1062,7 @@ struct ArrayOps
void fill(T* array, IndexType begin, IndexType nelems, const T& value)
{
#if defined(AXOM_USE_GPU) && defined(AXOM_USE_UMPIRE)
if(space != MemorySpace::Host)
if(space != MemorySpace::Host && space != MemorySpace::Malloc)
{
if constexpr(std::is_trivially_copyable_v<T>)
{
Expand Down
28 changes: 17 additions & 11 deletions src/axom/core/examples/core_array_perf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,12 +161,10 @@ int allocatorIdFromPolicy(axom::runtime_policy::Policy policy)
{
AXOM_UNUSED_VAR(policy);
#if defined(AXOM_USE_UMPIRE)
int allocatorID = policy == axom::runtime_policy::Policy::seq
? axom::detail::getAllocatorID<axom::MemorySpace::Host>()
:
int allocatorID =
policy == axom::runtime_policy::Policy::seq ? axom::detail::getDefaultHostAllocatorID() :
#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP)
policy == axom::runtime_policy::Policy::omp
? axom::detail::getAllocatorID<axom::MemorySpace::Host>()
policy == axom::runtime_policy::Policy::omp ? axom::detail::getDefaultHostAllocatorID()
:
#endif
#if defined(AXOM_RUNTIME_POLICY_USE_CUDA)
Expand Down Expand Up @@ -204,12 +202,20 @@ class MDMappingPerfTester
{
m_allocatorId = allocatorIdFromPolicy(params.runtimePolicy);
#ifdef AXOM_USE_UMPIRE
umpire::ResourceManager& rm = umpire::ResourceManager::getInstance();
umpire::Allocator allocator = rm.getAllocator(m_allocatorId);
std::cout << axom::fmt::format("Allocator id: {}, Umpire memory space {}",
m_allocatorId,
allocator.getName())
<< std::endl;
if(m_allocatorId == axom::MALLOC_ALLOCATOR_ID)
{
std::cout << axom::fmt::format("Allocator id: {}, malloc memory space", m_allocatorId)
<< std::endl;
}
else
{
umpire::ResourceManager& rm = umpire::ResourceManager::getInstance();
umpire::Allocator allocator = rm.getAllocator(m_allocatorId);
std::cout << axom::fmt::format("Allocator id: {}, Umpire memory space {}",
m_allocatorId,
allocator.getName())
<< std::endl;
}
#else
std::cout << axom::fmt::format("Allocator id: {}, default memory space", m_allocatorId)
<< std::endl;
Expand Down
2 changes: 1 addition & 1 deletion src/axom/core/execution/execution_space.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
* with OpenMp, i.e., RAJA_ENABLE_OPENMP must be defined in the generated
* RAJA/config.hpp.
*
* The default memory allocator when using this execution space is HOST.
* The default memory allocator when using this execution space is malloc.
*
* When using this execution space, the data must reside on CPU/host memory.
*
Expand Down
18 changes: 3 additions & 15 deletions src/axom/core/execution/internal/omp_exec.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,6 @@
#error OMP_EXEC requires an OpenMP enabled RAJA
#endif

// Umpire includes
#ifdef AXOM_USE_UMPIRE
#include "umpire/Umpire.hpp"
#endif

namespace axom
{
/*!
Expand All @@ -41,25 +36,18 @@ struct execution_space<OMP_EXEC>
using atomic_policy = RAJA::omp_atomic;
using sync_policy = RAJA::omp_synchronize;

#ifdef AXOM_USE_UMPIRE
#ifdef AXOM_DEFAULT_HOST_ALLOCATOR_USES_UMPIRE_HOST
static constexpr MemorySpace memory_space = MemorySpace::Host;
#else
static constexpr MemorySpace memory_space = MemorySpace::Dynamic;
static constexpr MemorySpace memory_space = MemorySpace::Malloc;
#endif

AXOM_HOST_DEVICE static constexpr bool async() noexcept { return false; }
AXOM_HOST_DEVICE static constexpr bool valid() noexcept { return true; }
AXOM_HOST_DEVICE static constexpr bool onDevice() noexcept { return false; }
AXOM_HOST_DEVICE static constexpr char* name() noexcept { return (char*)"[OMP_EXEC]"; }

static int allocatorID() noexcept
{
#ifdef AXOM_USE_UMPIRE
return axom::getUmpireResourceAllocatorID(umpire::resource::Host);
#else
return axom::getDefaultAllocatorID();
#endif
}
static int allocatorID() noexcept { return axom::detail::getDefaultHostAllocatorID(); }
AXOM_HOST_DEVICE static constexpr runtime_policy::Policy runtimePolicy() noexcept
{
return runtime_policy::Policy::omp;
Expand Down
18 changes: 3 additions & 15 deletions src/axom/core/execution/internal/seq_exec.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,6 @@
#include "RAJA/RAJA.hpp"
#endif

// Umpire includes
#ifdef AXOM_USE_UMPIRE
#include "umpire/Umpire.hpp"
#endif

namespace axom
{
/*!
Expand Down Expand Up @@ -51,25 +46,18 @@ struct execution_space<SEQ_EXEC>

using sync_policy = void;

#ifdef AXOM_USE_UMPIRE
#ifdef AXOM_DEFAULT_HOST_ALLOCATOR_USES_UMPIRE_HOST
static constexpr MemorySpace memory_space = MemorySpace::Host;
#else
static constexpr MemorySpace memory_space = MemorySpace::Dynamic;
static constexpr MemorySpace memory_space = MemorySpace::Malloc;
#endif

AXOM_HOST_DEVICE static constexpr bool async() noexcept { return false; }
AXOM_HOST_DEVICE static constexpr bool valid() noexcept { return true; }
AXOM_HOST_DEVICE static constexpr bool onDevice() noexcept { return false; }
AXOM_HOST_DEVICE static constexpr char* name() noexcept { return (char*)"[SEQ_EXEC]"; }

static int allocatorID() noexcept
{
#ifdef AXOM_USE_UMPIRE
return axom::getUmpireResourceAllocatorID(umpire::resource::Host);
#else
return axom::getDefaultAllocatorID();
#endif
}
static int allocatorID() noexcept { return axom::detail::getDefaultHostAllocatorID(); }
AXOM_HOST_DEVICE static constexpr runtime_policy::Policy runtimePolicy() noexcept
{
return runtime_policy::Policy::seq;
Expand Down
65 changes: 47 additions & 18 deletions src/axom/core/memory_management.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,15 +157,24 @@ inline void setDefaultAllocator(umpire::resource::MemoryResourceType resource_ty
#endif

/*!
* \brief Sets the default memory allocator to use.
* \param [in] allocId the Umpire allocator id
* \brief Sets the default memory allocator for the Umpire ResourceManager.
* \param [in] allocId the Axom allocator id
*
* \note When Axom is compiled with Umpire and \a allocId is
* axom::MALLOC_ALLOCATOR_ID, this function sets Umpire's default
* allocator to its Host resource.
* \note This function has no effect when Axom is not compiled with Umpire.
*/
inline void setDefaultAllocator(int allocId)
{
#ifdef AXOM_USE_UMPIRE
umpire::ResourceManager& rm = umpire::ResourceManager::getInstance();
if(allocId == MALLOC_ALLOCATOR_ID)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't setDefaultAllocator also be influenced by AXOM_DEFAULT_HOST_ALLOCATOR_USES_UMPIRE_HOST ?

It seems that if we're in an Umpire build, we can never get back to the malloc allocator as the default.
E.g., consider the following in a build with Umpire:

axom::setDefaultAllocator(Host);       // starts at Malloc, ends at host
axom::setDefaultAllocator(Malloc);   // still ends at Host

@rhornung67 rhornung67 Aug 27, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is only setting the default allocator in the Umpire ResourceManager. As long as all Axom usage honors the Axom default (Malloc or Host) when appropriate this should be OK.

Do we need to handle the case where a user calls setDefaultAllocator() with something that is neither Malloc or Host, but is still valid on the host, such as Pinned when Umpire is enabled? As I understand it, that is the main purpose of this method.

I think only the method documentation needs to be clarified, which I did.

Does that make sense now?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is only setting the default allocator in the Umpire ResourceManager. As long as all Axom usage honors the Axom default (Malloc or Host) when appropriate this should be OK.

If that's the case, perhaps it should be renamed setUmpireDefaultAllocator ?
(and be a no-op in non-umpire configs)

This is the current doxygen brief for the setDefaultAllocator

\brief Sets the default memory allocator to use.

Until now, we've been using it as Axom's default allocator
(which points to Umpire when Axom is configured against Umpire).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I modified the doxygen brief comment to indicate that it sets the default allocator in the Umpire ResourceManager, which it did before my changes IIRC. It is a no-op in non-umpire configs.

{
rm.setDefaultAllocator(rm.getAllocator(umpire::resource::Host));
return;
}

umpire::Allocator allocator = rm.getAllocator(allocId);
rm.setDefaultAllocator(allocator);
#else
Expand All @@ -188,6 +197,27 @@ inline int getDefaultAllocatorID()
#endif
}

namespace detail
{
/*!
* \brief Returns the ID of the default host allocator.
*
* \note This is distinct from the current default allocator returned by
* axom::getDefaultAllocatorID(), which tracks Umpire's default allocator
* when Axom is configured with Umpire.
*
* \return ID of the default host allocator.
*/
inline int getDefaultHostAllocatorID()
{
#if defined(AXOM_DEFAULT_HOST_ALLOCATOR_USES_UMPIRE_HOST)
return getUmpireResourceAllocatorID(umpire::resource::Host);
#else
return MALLOC_ALLOCATOR_ID;
#endif
}
} // namespace detail

/*!
* \brief Get the allocator id from which data has been allocated.
* \return Allocator id. If Umpire doesn't have an allocator for the
Expand Down Expand Up @@ -241,7 +271,7 @@ int getSharedMemoryAllocatorID(std::size_t minSegmentSize = 0);
* \brief Allocates a chunk of memory of type T.
*
* \param [in] n the number of elements to allocate.
* \param [in] allocID the Umpire allocator to use (optional)
* \param [in] allocID the Axom/Umpire allocator to use (optional)
*
* \tparam T the type of pointer returned.
*
Expand All @@ -260,7 +290,7 @@ inline T* allocate(std::size_t n, int allocID = getDefaultAllocatorID()) noexcep
*
* \param [in] n the number of elements to allocate.
* \param [in] name allocation name (must be non-empty for shared memory allocators)
* \param [in] allocID the Umpire allocator to use (optional)
* \param [in] allocID the Axom/Umpire allocator to use (optional)
*
* \return pointer to the new allocation or a nullptr if allocation failed.
*/
Expand Down Expand Up @@ -360,6 +390,11 @@ inline T* allocate(std::size_t n, int allocID) noexcept
{
const std::size_t numbytes = n * sizeof(T);

if(allocID == MALLOC_ALLOCATOR_ID)
{
return static_cast<T*>(std::malloc(numbytes));
}

#ifdef AXOM_USE_UMPIRE
if(umpire::ResourceManager& rm = umpire::ResourceManager::getInstance(); rm.isAllocator(allocID))
{
Expand All @@ -368,11 +403,6 @@ inline T* allocate(std::size_t n, int allocID) noexcept
}
#endif

if(allocID == MALLOC_ALLOCATOR_ID)
{
return static_cast<T*>(std::malloc(numbytes));
}

std::cerr << "Unrecognized allocator id " << allocID << std::endl;
axom::utilities::processAbort();

Expand All @@ -384,6 +414,12 @@ inline T* allocate(std::size_t n, const std::string& name, int allocID) noexcept
{
const std::size_t numbytes = n * sizeof(T);

if(allocID == MALLOC_ALLOCATOR_ID)
{
AXOM_UNUSED_VAR(name);
return static_cast<T*>(std::malloc(numbytes));
}

#ifdef AXOM_USE_UMPIRE
if(umpire::ResourceManager& rm = umpire::ResourceManager::getInstance(); rm.isAllocator(allocID))
{
Expand All @@ -393,12 +429,6 @@ inline T* allocate(std::size_t n, const std::string& name, int allocID) noexcept
}
#endif

if(allocID == MALLOC_ALLOCATOR_ID)
{
AXOM_UNUSED_VAR(name);
return static_cast<T*>(std::malloc(numbytes));
}

std::cerr << "Unrecognized allocator id " << allocID << std::endl;
axom::utilities::processAbort();

Expand Down Expand Up @@ -547,13 +577,12 @@ inline void fill(void* dst, std::size_t n, const T& value) noexcept
doHostFill = false;

// Device memory: fill on host, then copy to device
const auto num_bytes = n * sizeof(T);
T* src = allocate<T>(num_bytes, rm.getDefaultAllocator().getId());
T* src = allocate<T>(n, axom::detail::getDefaultHostAllocatorID());

@BradWhitlock BradWhitlock Aug 28, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch.

It's not here but in allocate() if Umpire is enabled then we check whether the passed allocator Id is a valid Umpire allocator before the malloc check. Since we're defaulting the host allocator id to MALLOC, even for Umpire-enabled builds (unless they select UMPIRE_HOST), should we swap the order of the umpire/malloc allocation checks so we can skip checking whether the allocator Id is valid for Umpire? Or, will allocate() more likely be called with allocators that are associated with Umpire?

for(std::size_t i = 0; i < n; ++i)
{
src[i] = value;
}
rm.copy(dst, src, num_bytes);
axom::copy(dst, src, n * sizeof(T));
deallocate<T>(src);
}
}
Expand Down
28 changes: 26 additions & 2 deletions src/axom/core/tests/core_execution_space.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,30 @@ TEST(core_execution_space, check_invalid)
check_invalid<InvalidSpace>();
}

//------------------------------------------------------------------------------
TEST(core_execution_space, check_default_host_allocator)
{
int expected_host_allocator = axom::MALLOC_ALLOCATOR_ID;
auto expected_host_memory_space = axom::MemorySpace::Malloc;
#if defined(AXOM_DEFAULT_HOST_ALLOCATOR_USES_UMPIRE_HOST)
expected_host_allocator = axom::getUmpireResourceAllocatorID(umpire::resource::Host);
expected_host_memory_space = axom::MemorySpace::Host;
#endif

EXPECT_EQ(expected_host_memory_space, axom::execution_space<axom::SEQ_EXEC>::memory_space);
EXPECT_EQ(expected_host_allocator, axom::execution_space<axom::SEQ_EXEC>::allocatorID());

#if defined(AXOM_USE_UMPIRE)
EXPECT_EQ(axom::getUmpireResourceAllocatorID(umpire::resource::Host),
axom::detail::getAllocatorID<axom::MemorySpace::Host>());
#endif

#if defined(AXOM_USE_OPENMP) && defined(AXOM_USE_RAJA)
EXPECT_EQ(expected_host_allocator, axom::execution_space<axom::OMP_EXEC>::allocatorID());
EXPECT_EQ(expected_host_memory_space, axom::execution_space<axom::OMP_EXEC>::memory_space);
#endif
}

//==============================================================================
// The following tests require RAJA and UMPIRE
//==============================================================================
Expand All @@ -130,7 +154,7 @@ TEST(core_execution_space, check_seq_exec)
constexpr bool IS_ASYNC = false;
constexpr bool ON_DEVICE = false;

int allocator_id = axom::getUmpireResourceAllocatorID(umpire::resource::Host);
int allocator_id = axom::execution_space<axom::SEQ_EXEC>::allocatorID();
check_execution_mappings<axom::SEQ_EXEC,
#if RAJA_VERSION_MAJOR > 2022
RAJA::seq_exec,
Expand All @@ -153,7 +177,7 @@ TEST(core_execution_space, check_omp_exec)
constexpr bool IS_ASYNC = false;
constexpr bool ON_DEVICE = false;

int allocator_id = axom::getUmpireResourceAllocatorID(umpire::resource::Host);
int allocator_id = axom::execution_space<axom::OMP_EXEC>::allocatorID();
check_execution_mappings<axom::OMP_EXEC,
RAJA::omp_parallel_for_exec,
RAJA::omp_reduce,
Expand Down
Loading