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
1112namespace 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.
392411template <typename T>
393412void 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}
0 commit comments