@@ -289,9 +289,39 @@ struct op_caller {
289289
290290} // namespace op
291291
292+ // ------------------------------------------------------------------------------
293+ // Public API Semantics for Copy and Reallocate Operations
294+ // ------------------------------------------------------------------------------
295+ //
296+ // The following functions use different semantics for size parameters based on
297+ // pointer type, matching standard C++ conventions:
298+ //
299+ // COPY OPERATIONS:
300+ // - umpire::copy(T* src, T* dst, std::size_t len) for non-void T:
301+ // len is a COUNT OF ELEMENTS (will be multiplied by sizeof(T) internally)
302+ //
303+ // - umpire::copy(void* src, void* dst, std::size_t len):
304+ // len is BYTES (used directly, no sizeof multiplication)
305+ //
306+ // REALLOCATE OPERATIONS:
307+ // - umpire::reallocate(T** ptr, std::size_t new_size) for non-void T:
308+ // new_size is a COUNT OF ELEMENTS (will be multiplied by sizeof(T) internally)
309+ //
310+ // - umpire::reallocate(void** ptr, std::size_t new_size):
311+ // new_size is BYTES (used directly, no sizeof multiplication)
312+ //
313+ // RATIONALE:
314+ // This matches how detail::get_size<T>(count) works:
315+ // - For void: returns count as-is (bytes)
316+ // - For typed pointers: returns count * sizeof(T) (elements to bytes)
317+ //
318+ // This keeps the API consistent with C++ idioms where typed operations work
319+ // with element counts and void* operations work with byte counts.
320+ // ------------------------------------------------------------------------------
321+
292322// Global operation implementations that use the op_caller
293323template <typename T>
294- auto copy (T* src, T* dst, std::size_t len)
324+ void copy (T* src, T* dst, std::size_t len)
295325{
296326 op::op_caller<op::copy>::exec (src, dst, len);
297327}
@@ -318,6 +348,10 @@ camp::resources::EventProxy<camp::resources::Resource> memset(T* src, int v, std
318348template <typename T>
319349inline T* reallocate (T** src, std::size_t size)
320350{
351+ // Handle nullptr specially - op_caller can't look up null in allocation map
352+ if (*src == nullptr ) {
353+ return op::reallocate<resource::host_platform>::exec (src, size);
354+ }
321355 return op::op_caller<op::reallocate>::exec (src, size);
322356}
323357
@@ -326,6 +360,10 @@ template <typename T>
326360inline camp::resources::EventProxy<camp::resources::Resource> reallocate (T** src, std::size_t size,
327361 camp::resources::Resource& ctx)
328362{
363+ // Handle nullptr specially - op_caller can't look up null in allocation map
364+ if (*src == nullptr ) {
365+ return op::reallocate<resource::host_platform>::exec (src, size, ctx);
366+ }
329367 return op::op_caller<op::reallocate>::exec (src, size, ctx);
330368}
331369
@@ -444,11 +482,12 @@ T* op::reallocate<Src>::exec(T** ptr, std::size_t new_size)
444482 // Allocate new memory
445483 T* new_ptr = static_cast <T*>(allocator.allocate (new_bytes));
446484
447- // Calculate copy size (minimum of old and new size)
448- std::size_t copy_size = (old_bytes > new_bytes) ? new_bytes : old_bytes;
485+ // Calculate copy size in bytes (minimum of old and new size)
486+ std::size_t copy_bytes = (old_bytes > new_bytes) ? new_bytes : old_bytes;
449487
450- // Copy data from old to new location using auto-dispatch copy
451- umpire::copy (current_ptr, new_ptr, copy_size);
488+ // Copy data using void* to pass bytes directly (avoids sizeof(T) multiplication in copy)
489+ // Note: We cast to void* so that detail::get_size<void>(len) returns len as-is (bytes)
490+ umpire::copy (static_cast <void *>(current_ptr), static_cast <void *>(new_ptr), copy_bytes);
452491
453492 // Deallocate old memory
454493 allocator.deallocate (current_ptr);
@@ -507,11 +546,12 @@ camp::resources::EventProxy<camp::resources::Resource> op::reallocate<Src>::exec
507546 // Allocate new memory
508547 T* new_ptr = static_cast <T*>(allocator.allocate (new_bytes));
509548
510- // Calculate copy size (minimum of old and new size)
511- std::size_t copy_size = (old_bytes > new_bytes) ? new_bytes : old_bytes;
549+ // Calculate copy size in bytes (minimum of old and new size)
550+ std::size_t copy_bytes = (old_bytes > new_bytes) ? new_bytes : old_bytes;
512551
513- // Copy data from old to new location asynchronously using auto-dispatch copy
514- auto event = umpire::copy (current_ptr, new_ptr, copy_size, ctx);
552+ // Copy data using void* to pass bytes directly (avoids sizeof(T) multiplication in copy)
553+ // Note: We cast to void* so that detail::get_size<void>(len) returns len as-is (bytes)
554+ auto event = umpire::copy (static_cast <void *>(current_ptr), static_cast <void *>(new_ptr), copy_bytes, ctx);
515555
516556 // IMPORTANT: In a fully async implementation, we would need to chain the deallocation
517557 // to happen after the copy completes. However, since we don't have that mechanism yet,
@@ -571,11 +611,11 @@ void* op::reallocate<Src>::exec(void** ptr_ptr, std::size_t new_size)
571611 // Allocate new memory
572612 void * new_ptr = allocator.allocate (new_size);
573613
574- // Calculate copy size (minimum of old and new size)
575- std::size_t copy_size = (old_size > new_size) ? new_size : old_size;
614+ // Calculate copy size in bytes (minimum of old and new size)
615+ std::size_t copy_bytes = (old_size > new_size) ? new_size : old_size;
576616
577- // Copy data from old to new location using auto-dispatch copy
578- umpire::copy (current_ptr, new_ptr, copy_size );
617+ // Copy data from old to new location (void* naturally uses bytes)
618+ umpire::copy (static_cast < void *>( current_ptr), static_cast < void *>( new_ptr), copy_bytes );
579619
580620 // Deallocate old memory
581621 allocator.deallocate (current_ptr);
@@ -629,11 +669,11 @@ camp::resources::EventProxy<camp::resources::Resource> op::reallocate<Src>::exec
629669 // Allocate new memory
630670 void * new_ptr = allocator.allocate (new_size);
631671
632- // Calculate copy size (minimum of old and new size)
633- std::size_t copy_size = (old_size > new_size) ? new_size : old_size;
672+ // Calculate copy size in bytes (minimum of old and new size)
673+ std::size_t copy_bytes = (old_size > new_size) ? new_size : old_size;
634674
635- // Copy data from old to new location asynchronously using auto-dispatch copy
636- auto event = umpire::copy (current_ptr, new_ptr, copy_size , ctx);
675+ // Copy data from old to new location asynchronously (void* naturally uses bytes)
676+ auto event = umpire::copy (static_cast < void *>( current_ptr), static_cast < void *>( new_ptr), copy_bytes , ctx);
637677
638678 // Deallocate old memory
639679 allocator.deallocate (current_ptr);
0 commit comments