Skip to content

Commit 2b3db65

Browse files
Fixup issues
1 parent f8a50f9 commit 2b3db65

15 files changed

Lines changed: 651 additions & 166 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ if (UMPIRE_ENABLE_TESTS)
203203
add_subdirectory(tests)
204204
endif ()
205205

206-
if (UMPIRE_ENABLE_BENCHMARKS)
206+
if (UMPIRE_ENABLE_BENCHMARKS OR UMPIRE_ENABLE_DEVELOPER_BENCHMARKS)
207207
add_subdirectory(benchmarks)
208208
if ((NOT CMAKE_BUILD_TYPE) OR (NOT ${CMAKE_BUILD_TYPE} STREQUAL "Release"))
209209
message("-- Warning: CMAKE_BUILD_TYPE not set to Release, benchmark information will not be reliable for this build!")

benchmarks/CMakeLists.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ if (UMPIRE_ENABLE_CUDA)
2323
SOURCES resource_aware_pool_stress_test.cpp
2424
DEPENDS_ON umpire cuda)
2525

26+
blt_add_target_compile_flags(
27+
TO resource_aware_pool_stress_test
28+
FLAGS ${UMPIRE_DISABLE_DEPRECATED_WARNINGS_FLAG})
29+
2630
if (UMPIRE_ENABLE_DEVICE_ALLOCATOR)
2731
blt_add_executable(
2832
NAME device_allocator_stress_test
@@ -43,6 +47,10 @@ elseif (UMPIRE_ENABLE_HIP)
4347
SOURCES resource_aware_pool_stress_test.cpp
4448
DEPENDS_ON umpire blt::hip blt::hip_runtime)
4549

50+
blt_add_target_compile_flags(
51+
TO resource_aware_pool_stress_test
52+
FLAGS ${UMPIRE_DISABLE_DEPRECATED_WARNINGS_FLAG})
53+
4654
if (UMPIRE_ENABLE_DEVICE_ALLOCATOR)
4755
blt_add_executable(
4856
NAME device_allocator_stress_test
@@ -65,6 +73,10 @@ blt_add_executable(
6573
SOURCES copy_stress_test.cpp
6674
DEPENDS_ON ${stress_test_depends})
6775

76+
blt_add_target_compile_flags(
77+
TO copy_stress_test
78+
FLAGS ${UMPIRE_DISABLE_DEPRECATED_WARNINGS_FLAG})
79+
6880
blt_add_executable(
6981
NAME fixed_pool_stress_test
7082
SOURCES fixed_pool_stress_test.cpp
@@ -135,6 +147,10 @@ blt_add_executable(
135147
SOURCES copy_benchmarks.cpp
136148
DEPENDS_ON ${benchmark_depends})
137149

150+
blt_add_target_compile_flags(
151+
TO copy_benchmarks
152+
FLAGS ${UMPIRE_DISABLE_DEPRECATED_WARNINGS_FLAG})
153+
138154
blt_add_benchmark(
139155
NAME copy_benchmarks
140156
COMMAND copy_benchmarks)

docs/doxygen/Doxyfile.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ WARN_LOGFILE = dox.warnings
448448
# directories like "/usr/src/myproject". Separate the files or directories
449449
# with spaces.
450450

451-
INPUT = @PROJECT_SOURCE_DIR@/src
451+
INPUT = @PROJECT_SOURCE_DIR@/src @PROJECT_SOURCE_DIR@/include
452452

453453
# Allow Markdown files to provide the Doxygen main page
454454

docs/sphinx/getting_started.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,13 @@ allocate memory in different places. Let's ask for a device allocator:
105105
106106
This code gets the default device allocator, and uses it to allocate an array
107107
of 100 floats. Remember, since this is a device pointer, there is no guarantee
108-
you will be able to access it on the host. Luckily, Umpire's ResourceManager
109-
can copy one pointer to another transparently. Let's copy the data from our
110-
first pointer to the DEVICE-allocated pointer.
108+
you will be able to access it on the host. Luckily, Umpire provides a
109+
``copy`` function that can copy one pointer to another transparently. Let's
110+
copy the data from our first pointer to the DEVICE-allocated pointer.
111111

112112
.. code-block:: cpp
113113
114-
rm.copy(my_data, my_data_device);
114+
umpire::copy(my_data, my_data_device, 100);
115115
116116
To free any memory allocated, you can use the deallocate function of the
117117
Allocator, or the ResourceManager. Asking the ResourceManager to deallocate

examples/tutorial/tut_memset.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ int main(int, char**)
3838
<< std::endl;
3939

4040
// _sphinx_tag_tut_memset_start
41-
umpire::memset(data, 0, SIZE * sizeof(double));
41+
umpire::memset(data, 0, SIZE);
4242
// _sphinx_tag_tut_memset_end
4343

4444
std::cout << "Set data from " << destination << " (" << data << ") to 0." << std::endl;

include/umpire/op/cuda.hpp

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ inline cudaStream_t get_stream(camp::resources::Resource& resource)
8989
/**
9090
* @brief Apply memory advice to a CUDA managed memory allocation
9191
*
92+
* Advise is a performance hint, not a correctness requirement, so this is
93+
* intentionally a no-op (with a logged warning) on devices that do not
94+
* support managed memory.
95+
*
9296
* @tparam T Type of memory
9397
* @param ptr Pointer to memory
9498
* @param count Number of elements
@@ -99,11 +103,18 @@ template <typename T>
99103
inline void advise(T* ptr, std::size_t count, int device, cudaMemoryAdvise advice)
100104
{
101105
// Skip if device doesn't support managed memory
102-
if (!supports_managed_memory(device))
106+
if (!supports_managed_memory(device)) {
107+
UMPIRE_LOG(Warning, "cudaMemAdvise skipped: device " << device << " does not support managed memory");
103108
return;
109+
}
104110

105111
std::size_t size = detail::get_size<T>(count);
112+
#if CUDART_VERSION >= 13000
113+
cudaMemLocation loc = {(device == cudaCpuDeviceId) ? cudaMemLocationTypeHost : cudaMemLocationTypeDevice, device};
114+
cudaError_t error = ::cudaMemAdvise(ptr, size, advice, loc);
115+
#else
106116
cudaError_t error = ::cudaMemAdvise(ptr, size, advice, device);
117+
#endif
107118

108119
if (error != cudaSuccess) {
109120
UMPIRE_ERROR(runtime_error,
@@ -228,10 +239,17 @@ inline void prefetch(T* ptr, int device, std::size_t count)
228239
fmt::format("cudaGetDevice failed: {} ({})", cudaGetErrorString(get_dev_err), get_dev_err));
229240
}
230241
int gpu = (device != cudaCpuDeviceId) ? device : current_device;
242+
#if CUDART_VERSION >= 13000
243+
cudaMemLocation loc = {(device == cudaCpuDeviceId) ? cudaMemLocationTypeHost : cudaMemLocationTypeDevice, device};
244+
#endif
231245

232246
if (supports_managed_memory(gpu)) {
233247
std::size_t size = detail::get_size<T>(count);
248+
#if CUDART_VERSION >= 13000
249+
cudaError_t error = ::cudaMemPrefetchAsync(ptr, size, loc, 0, nullptr);
250+
#else
234251
cudaError_t error = ::cudaMemPrefetchAsync(ptr, size, device, nullptr);
252+
#endif
235253

236254
if (error != cudaSuccess) {
237255
UMPIRE_ERROR(runtime_error, fmt::format("cudaMemPrefetchAsync(ptr={}, size={}, device={}) failed with error: {}",
@@ -264,10 +282,17 @@ inline camp::resources::EventProxy<camp::resources::Resource> prefetch_async(T*
264282
fmt::format("cudaGetDevice failed: {} ({})", cudaGetErrorString(get_dev_err), get_dev_err));
265283
}
266284
int gpu = (device != cudaCpuDeviceId) ? device : current_device;
285+
#if CUDART_VERSION >= 13000
286+
cudaMemLocation loc = {(device == cudaCpuDeviceId) ? cudaMemLocationTypeHost : cudaMemLocationTypeDevice, device};
287+
#endif
267288

268289
if (supports_managed_memory(gpu)) {
269290
std::size_t size = detail::get_size<T>(count);
291+
#if CUDART_VERSION >= 13000
292+
cudaError_t error = ::cudaMemPrefetchAsync(ptr, size, loc, 0, stream);
293+
#else
270294
cudaError_t error = ::cudaMemPrefetchAsync(ptr, size, device, stream);
295+
#endif
271296

272297
if (error != cudaSuccess) {
273298
UMPIRE_ERROR(runtime_error,

include/umpire/op/detail/utils.hpp

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,35 @@
11
#pragma once
22

33
#include <cstddef>
4+
#include <limits>
45
#include <type_traits>
56
#include "camp/resource.hpp"
7+
#include "umpire/util/error.hpp"
68

79
namespace umpire {
810
namespace op {
911
namespace detail {
1012

1113
/**
1214
* @brief Calculate size in bytes based on element count and type
13-
*
15+
*
1416
* @tparam T The pointer type (void* or typed pointer)
1517
* @param count Number of elements or bytes (if T is void)
1618
* @return std::size_t Size in bytes
1719
*/
1820
template <typename T>
19-
inline std::size_t get_size(std::size_t count) noexcept
21+
inline std::size_t get_size(std::size_t count)
2022
{
21-
if constexpr (std::is_same_v<T, void>)
23+
if constexpr (std::is_same_v<T, void>) {
2224
return count;
23-
else
25+
} else {
26+
if (count > (std::numeric_limits<std::size_t>::max() / sizeof(T))) {
27+
UMPIRE_ERROR(runtime_error,
28+
fmt::format("Requested size overflow: count ({}) * sizeof(T) ({}) exceeds size_t max", count,
29+
sizeof(T)));
30+
}
2431
return count * sizeof(T);
32+
}
2533
}
2634

2735
/**

include/umpire/op/dispatch.hpp

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "umpire/ResourceManager.hpp"
77
#include "umpire/config.hpp"
88
#include "umpire/op/detail/traits.hpp"
9+
#include "umpire/op/detail/utils.hpp"
910
#include "umpire/resource/platform.hpp"
1011

1112
namespace umpire {
@@ -386,9 +387,27 @@ struct op_caller {
386387
//
387388
// This keeps the API consistent with C++ idioms where typed operations work
388389
// with element counts and void* operations work with byte counts.
390+
//
391+
// *** ARGUMENT ORDER WARNING ***
392+
// umpire::copy(src, dst, ...) takes (SOURCE, DESTINATION) order.
393+
// This is the REVERSE of the deprecated ResourceManager::copy(dst_ptr, src_ptr, ...),
394+
// which takes (DESTINATION, SOURCE) order.
395+
//
396+
// A mechanical migration from ResourceManager::copy() to umpire::copy() that does
397+
// NOT swap the two pointer arguments will compile cleanly (both overloads accept
398+
// the same pointer type) and will silently copy data in the WRONG DIRECTION --
399+
// there is no compiler error or warning to catch this mistake. Callers migrating
400+
// from ResourceManager::copy(dst, src, ...) MUST swap to umpire::copy(src, dst, ...).
401+
//
402+
// Also note the size-parameter semantics above still apply: the typed
403+
// umpire::copy<T>(src, dst, len) overloads take len as an ELEMENT COUNT, while the
404+
// void* umpire::copy(src, dst, len) overload takes len as a BYTE COUNT.
389405
//------------------------------------------------------------------------------
390406

391407
// Global operation implementations that use the op_caller
408+
//
409+
// NOTE: Argument order is (src, dst) -- the REVERSE of the deprecated
410+
// ResourceManager::copy(dst, src, ...). See the "ARGUMENT ORDER WARNING" above.
392411
template <typename T>
393412
void copy(T* src, T* dst, std::size_t len)
394413
{
@@ -524,12 +543,7 @@ PtrType* op::reallocate<Platform>::reallocate_impl_sync(PtrType** ptr_ptr, std::
524543
auto& rm = ResourceManager::getInstance();
525544
Allocator allocator = rm.getDefaultAllocator();
526545

527-
std::size_t alloc_bytes;
528-
if constexpr (std::is_same_v<PtrType, void>) {
529-
alloc_bytes = new_size; // Void: new_size is already in bytes
530-
} else {
531-
alloc_bytes = new_size * sizeof(PtrType); // Typed: new_size is element count
532-
}
546+
std::size_t alloc_bytes = detail::get_size<PtrType>(new_size);
533547

534548
PtrType* new_ptr = static_cast<PtrType*>(allocator.allocate(alloc_bytes));
535549
*ptr_ptr = new_ptr;
@@ -550,13 +564,7 @@ PtrType* op::reallocate<Platform>::reallocate_impl_sync(PtrType** ptr_ptr, std::
550564

551565
// 4. Calculate sizes
552566
std::size_t old_size = rm.getSize(current_ptr);
553-
std::size_t new_bytes;
554-
555-
if constexpr (std::is_same_v<PtrType, void>) {
556-
new_bytes = new_size; // Void: new_size is already in bytes
557-
} else {
558-
new_bytes = new_size * sizeof(PtrType); // Typed: new_size is element count
559-
}
567+
std::size_t new_bytes = detail::get_size<PtrType>(new_size);
560568

561569
// 5. Zero-byte special case
562570
if (new_bytes == 0) {
@@ -603,12 +611,7 @@ camp::resources::EventProxy<camp::resources::Resource> op::reallocate<Platform>:
603611
auto& rm = ResourceManager::getInstance();
604612
Allocator allocator = rm.getDefaultAllocator();
605613

606-
std::size_t alloc_bytes;
607-
if constexpr (std::is_same_v<PtrType, void>) {
608-
alloc_bytes = new_size; // Void: new_size is already in bytes
609-
} else {
610-
alloc_bytes = new_size * sizeof(PtrType); // Typed: new_size is element count
611-
}
614+
std::size_t alloc_bytes = detail::get_size<PtrType>(new_size);
612615

613616
PtrType* new_ptr = static_cast<PtrType*>(allocator.allocate(alloc_bytes));
614617
*ptr_ptr = new_ptr;
@@ -629,13 +632,7 @@ camp::resources::EventProxy<camp::resources::Resource> op::reallocate<Platform>:
629632

630633
// 4. Calculate sizes
631634
std::size_t old_size = rm.getSize(current_ptr);
632-
std::size_t new_bytes;
633-
634-
if constexpr (std::is_same_v<PtrType, void>) {
635-
new_bytes = new_size; // Void: new_size is already in bytes
636-
} else {
637-
new_bytes = new_size * sizeof(PtrType); // Typed: new_size is element count
638-
}
635+
std::size_t new_bytes = detail::get_size<PtrType>(new_size);
639636

640637
// 5. Zero-byte special case
641638
if (new_bytes == 0) {
@@ -648,6 +645,13 @@ camp::resources::EventProxy<camp::resources::Resource> op::reallocate<Platform>:
648645
// 6. Allocate new memory
649646
PtrType* new_ptr = static_cast<PtrType*>(allocator.allocate(new_bytes));
650647

648+
// Guard new pointer for exception safety: if the copy below throws (or the
649+
// wait throws), release the new allocation so it is not leaked and *ptr_ptr
650+
// is not left dangling.
651+
auto new_cleanup = detail::make_scope_exit([&]() {
652+
allocator.deallocate(new_ptr);
653+
});
654+
651655
// 7. Copy data asynchronously
652656
std::size_t copy_bytes = (old_size > new_bytes) ? new_bytes : old_size;
653657

@@ -658,10 +662,16 @@ camp::resources::EventProxy<camp::resources::Resource> op::reallocate<Platform>:
658662
allocator.deallocate(current_ptr);
659663
});
660664

661-
// Wait for async copy to complete before deallocating to avoid race condition
665+
// NOTE: This wait is an intentional blocking point. It guarantees the copy
666+
// has completed before the old buffer is freed (and before we dismiss the
667+
// new-pointer guard below). A true non-blocking async reallocate that defers
668+
// the old-buffer free until the event completes is left as a follow-up.
662669
static_cast<camp::resources::Event>(event).wait();
663670
// cleanup happens automatically via RAII
664671

672+
// Copy (and wait) succeeded: the new allocation is valid, dismiss its guard.
673+
new_cleanup.dismiss();
674+
665675
*ptr_ptr = new_ptr;
666676
return event;
667677
}

include/umpire/op/hip.hpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ inline hipStream_t get_stream(camp::resources::Resource& resource)
8686
/**
8787
* @brief Apply memory advice to a HIP managed memory allocation
8888
*
89+
* @note advise() is a performance hint, not a correctness requirement. On
90+
* devices that do not support managed memory, this is intentionally a
91+
* logged no-op rather than an error.
92+
*
8993
* @tparam T Type of memory
9094
* @param ptr Pointer to memory
9195
* @param count Number of elements
@@ -96,8 +100,10 @@ template <typename T>
96100
inline void advise(T* ptr, std::size_t count, int device, hipMemoryAdvise advice)
97101
{
98102
// Skip if device doesn't support managed memory
99-
if (!supports_managed_memory(device))
103+
if (!supports_managed_memory(device)) {
104+
UMPIRE_LOG(Warning, "hipMemAdvise skipped: device " << device << " does not support managed memory");
100105
return;
106+
}
101107

102108
std::size_t size = detail::get_size<T>(count);
103109
hipError_t error = ::hipMemAdvise(ptr, size, advice, device);

0 commit comments

Comments
 (0)