vecmem 1.30.0
Loading...
Searching...
No Matches
copy.ipp
1/*
2 * VecMem project, part of the ACTS project (R&D line)
3 *
4 * (c) 2021-2026 CERN for the benefit of the ACTS project
5 *
6 * Mozilla Public License Version 2.0
7 */
8#pragma once
9
10// VecMem include(s).
11#include "vecmem/containers/details/resize_jagged_vector.hpp"
12#include "vecmem/containers/jagged_vector.hpp"
13#include "vecmem/edm/details/schema_traits.hpp"
14#include "vecmem/memory/get_default_resource.hpp"
15#include "vecmem/utils/debug.hpp"
16#include "vecmem/utils/type_traits.hpp"
17
18// System include(s).
19#include <algorithm>
20#include <cassert>
21#include <numeric>
22#include <sstream>
23#include <stdexcept>
24#include <type_traits>
25
26namespace vecmem {
27
28template <typename TYPE>
30
31 // Check if anything needs to be done.
32 if ((data.size_ptr() == nullptr) || (data.capacity() == 0)) {
33 return;
34 }
35
36 // Initialize the "size variable" correctly on the buffer.
38 data.size_ptr(), 0);
39 VECMEM_DEBUG_MSG(2,
40 "Prepared a device vector buffer of capacity %u "
41 "for use on a device (ptr: %p)",
42 data.capacity(), static_cast<void*>(data.size_ptr()));
43}
44
45template <typename TYPE>
47
48 setup(ignore_event, data);
49
50 // Return a new event.
51 return create_event();
52}
53
54template <typename TYPE>
56 int value) const {
57
58 // Check if anything needs to be done.
59 if (data.capacity() == 0) {
60 return;
61 }
62
63 // Call memset with the correct arguments.
64 do_memset(data.capacity() * sizeof(TYPE), data.ptr(), value);
65 VECMEM_DEBUG_MSG(2, "Set %u vector elements to %i at ptr: %p",
66 data.capacity(), value, static_cast<void*>(data.ptr()));
67}
68
69template <typename TYPE>
71
72 memset(ignore_event, data, value);
73
74 // Return a new event.
75 return create_event();
76}
77
78template <typename TYPE>
80 const vecmem::data::vector_view<TYPE>& data, memory_resource& resource,
81 type::copy_type cptype) const {
82
83 // Set up the result buffer. No need to call setup(...) on it, as the buffer
84 // is not resizable.
86 resource);
87
88 // Copy the payload of the vector. Explicitly waiting for the copy to finish
89 // before returning the buffer.
90 operator()(data, result, cptype)->wait();
91
92 // Return the buffer.
93 return result;
94}
95
96template <typename TYPE>
98 ignore_event_t, const data::vector_view<std::add_const_t<TYPE>>& from_view,
100
101 copy_view_impl(from_view, to_view, cptype);
102}
103
104template <typename TYPE>
106 const data::vector_view<std::add_const_t<TYPE>>& from_view,
107 data::vector_view<TYPE> to_view, type::copy_type cptype) const {
108
110 return create_event();
111}
112
113template <typename TYPE, typename ALLOC>
115 ignore_event_t, const data::vector_view<std::add_const_t<TYPE>>& from_view,
116 std::vector<TYPE, ALLOC>& to_vec, type::copy_type cptype) const {
117
118 // Figure out the size of the buffer.
119 const typename data::vector_view<std::add_const_t<TYPE>>::size_type size =
121
122 // If the size is zero, don't do an actual copy.
123 if (size == 0u) {
124 return;
125 }
126
127 // Make the target vector the correct size.
128 to_vec.resize(size);
129 // Perform the memory copy.
130 do_copy(size * sizeof(TYPE), from_view.ptr(), to_vec.data(), cptype);
131}
132
133template <typename TYPE, typename ALLOC>
135 const data::vector_view<std::add_const_t<TYPE>>& from_view,
136 std::vector<TYPE, ALLOC>& to_vec, type::copy_type cptype) const {
137
139 // Return a new event.
140 return create_event();
141}
142
143template <typename TYPE>
145 const data::vector_view<TYPE>& data) const {
146
147 // Handle the simple case, when the view/buffer is not resizable.
148 if (data.size_ptr() == nullptr) {
149 return data.capacity();
150 }
151
152 // If it *is* resizable, don't assume that the size is host-accessible.
153 // Explicitly copy it for access.
156 data.size_ptr(), &result, type::unknown);
157
158 // Wait for the copy operation to finish. With some backends
159 // (khm... SYCL... khm...) copies can be asynchronous even into
160 // non-pinned host memory.
161 create_event()->wait();
162
163 // Return what we got.
164 return result;
165}
166
167template <typename TYPE>
169 const data::vector_view<TYPE>& data, memory_resource& pinnedHostMr) const {
170
171 // Helper result value type.
172 using value_type = typename data::vector_view<TYPE>::size_type;
173
174 // Handle the case when the view/buffer is not resizable.
175 if (data.size_ptr() == nullptr) {
176 // Create the result value, in "default" memory.
177 auto value = make_unique_alloc<value_type>(*(get_default_resource()));
178 *value = data.capacity();
179 return {std::move(value), vecmem::copy::create_event()};
180 }
181
182 // Create the result value, in pinned host memory.
183 auto value = make_unique_alloc<value_type>(pinnedHostMr);
184
185 // Set up the copy into a pinned host memory buffer.
186 do_copy(sizeof(value_type), data.size_ptr(), value.get(), type::unknown);
187
188 // Return the appropriate "future" value.
189 return {std::move(value), create_event()};
190}
191
192template <typename TYPE>
194
195 // Check if anything needs to be done.
196 if (data.size() == 0) {
197 return;
198 }
199
200 // "Set up" the inner vector descriptors, using the host-accessible data.
201 // But only if the jagged vector buffer is resizable.
202 if (data.host_ptr()[0].size_ptr() != nullptr) {
203 do_memset(
204 sizeof(typename data::vector_buffer<TYPE>::size_type) * data.size(),
205 data.host_ptr()[0].size_ptr(), 0);
206 }
207
208 // Check if anything else needs to be done.
209 if (data.ptr() == data.host_ptr()) {
210 return;
211 }
212
213 // Copy the description of the inner vectors of the buffer.
214 do_copy(
215 data.size() *
216 sizeof(
218 data.host_ptr(), data.ptr(), type::host_to_device);
219 VECMEM_DEBUG_MSG(2,
220 "Prepared a jagged device vector buffer of size %u "
221 "for use on a device",
222 data.size());
223}
224
225template <typename TYPE>
227
228 setup(ignore_event, data);
229 // Return a new event.
230 return create_event();
231}
232
233template <typename TYPE>
235 int value) const {
236
237 // Do different things for jagged vectors that are contiguous in memory.
238 if (is_contiguous(data.host_ptr(), data.capacity())) {
239 // For contiguous jagged vectors we can just do a single memset
240 // operation. First, let's calculate the "total size" of the jagged
241 // vector.
242 const std::size_t total_size = std::accumulate(
243 data.host_ptr(), data.host_ptr() + data.size(),
244 static_cast<std::size_t>(0u),
245 [](std::size_t sum, const data::vector_view<TYPE>& iv) {
246 return sum + iv.capacity();
247 });
248 // Find the first "inner vector" that has a non-zero capacity.
249 for (std::size_t i = 0; i < data.size(); ++i) {
251 if ((iv.capacity() != 0u) && (iv.ptr() != nullptr)) {
252 // Call memset with its help.
253 do_memset(total_size * sizeof(TYPE), iv.ptr(), value);
254 return;
255 }
256 }
257 // If we are still here, apparently we didn't need to do anything.
258 return;
259 } else {
260 // For non-contiguous jagged vectors call memset one-by-one on the
261 // inner vectors. Note that we don't use vecmem::copy::memset here,
262 // as that would require us to wait for each memset individually.
263 for (std::size_t i = 0; i < data.size(); ++i) {
265 do_memset(iv.capacity() * sizeof(TYPE), iv.ptr(), value);
266 }
267 }
268}
269
270template <typename TYPE>
272 int value) const {
273
274 memset(ignore_event, data, value);
275 // Return a new event.
276 return create_event();
277}
278
279template <typename TYPE>
281 const data::jagged_vector_view<TYPE>& data, memory_resource& resource,
282 memory_resource* host_access_resource, type::copy_type cptype) const {
283
284 // Create the result buffer object.
286 (((data.capacity() > 0u) && (data.host_ptr()[0].size_ptr() != nullptr))
291 assert(result.size() == data.size());
292
293 // Copy the description of the "inner vectors" if necessary.
294 if (host_access_resource != nullptr) {
295 setup(result)->wait();
296 }
297
298 // Copy the payload of the inner vectors. Explicitly waiting for the copy to
299 // finish before returning the buffer.
300 operator()(data, result, cptype)->wait();
301
302 // Return the newly created object.
303 return result;
304}
305
306template <typename TYPE>
309 const data::jagged_vector_view<std::add_const_t<TYPE>>& from_view,
311
312 copy_view_impl(from_view, to_view, cptype);
313}
314
315template <typename TYPE>
317 const data::jagged_vector_view<std::add_const_t<TYPE>>& from_view,
318 data::jagged_vector_view<TYPE> to_view, type::copy_type cptype) const {
319
321 return create_event();
322}
323
324template <typename TYPE, typename ALLOC1, typename ALLOC2>
327 const data::jagged_vector_view<std::add_const_t<TYPE>>& from_view,
328 std::vector<std::vector<TYPE, ALLOC2>, ALLOC1>& to_vec,
329 type::copy_type cptype) const {
330
331 // Resize the output object to the correct size.
333 const auto sizes = get_sizes(from_view);
334 assert(sizes.size() == to_vec.size());
335 for (typename data::jagged_vector_view<std::add_const_t<TYPE>>::size_type
336 i = 0;
337 i < from_view.size(); ++i) {
338 to_vec[i].resize(sizes[i]);
339 }
340
341 // Perform the memory copy.
343}
344
345template <typename TYPE, typename ALLOC1, typename ALLOC2>
347 const data::jagged_vector_view<std::add_const_t<TYPE>>& from_view,
348 std::vector<std::vector<TYPE, ALLOC2>, ALLOC1>& to_vec,
349 type::copy_type cptype) const {
350
352 return create_event();
353}
354
355template <typename TYPE>
356std::vector<typename data::vector_view<TYPE>::size_type> copy::get_sizes(
357 const data::jagged_vector_view<TYPE>& data) const {
358
359 // Create the result vector.
360 std::vector<typename data::vector_view<TYPE>::size_type> result(data.size(),
361 0);
362
363 // Try to get the "resizable sizes" first.
364 for (std::size_t i = 0; i < data.size(); ++i) {
365 // Find the first "inner vector" that has a non-zero capacity, and is
366 // resizable.
367 if ((data.host_ptr()[i].capacity() != 0) &&
368 (data.host_ptr()[i].size_ptr() != nullptr)) {
369 // Copy the sizes of the inner vectors into the result vector.
371 (data.size() - i),
372 data.host_ptr()[i].size_ptr(), result.data() + i,
374 // Wait for the copy operation to finish. With some backends
375 // (khm... SYCL... khm...) copies can be asynchronous even into
376 // non-pinned host memory.
377 create_event()->wait();
378 // At this point the result vector should have been set up
379 // correctly.
380 return result;
381 }
382 }
383
384 // If we're still here, then the buffer is not resizable. So let's just
385 // collect the capacity of each of the inner vectors.
386 for (std::size_t i = 0; i < data.size(); ++i) {
387 result[i] = data.host_ptr()[i].capacity();
388 }
389 return result;
390}
391
392template <typename TYPE>
395 const std::vector<typename data::vector_view<TYPE>::size_type>& sizes,
397
398 // Finish early if possible.
399 if ((sizes.size() == 0) && (data.size() == 0)) {
400 return;
401 }
402 // Make sure that the sizes match up.
403 if (sizes.size() != data.size()) {
404 std::ostringstream msg;
405 msg << "sizes.size() (" << sizes.size() << ") != data.size() ("
406 << data.size() << ")";
407 throw std::length_error(msg.str());
408 }
409 // Make sure that the target jagged vector is either resizable, or it has
410 // the correct sizes/capacities already.
411 bool perform_copy = true;
413 i < data.size(); ++i) {
414 // Perform a capacity check in all cases. Whether or not the target is
415 // resizable.
416 if (data.host_ptr()[i].capacity() < sizes[i]) {
417 std::ostringstream msg;
418 msg << "data.host_ptr()[" << i << "].capacity() ("
419 << data.host_ptr()[i].capacity() << ") < sizes[" << i << "] ("
420 << sizes[i] << ")";
421 throw std::length_error(msg.str());
422 }
423 // Make sure that the inner vectors are consistently either all
424 // resizable, or none of them are.
425 if (data.host_ptr()[i].size_ptr() == nullptr) {
426 perform_copy = false;
427 } else if (perform_copy == false) {
428 throw std::invalid_argument(
429 "Inconsistent target jagged vector view received for resizing");
430 }
431 }
432 // If no copy is necessary, we're done.
433 if (perform_copy == false) {
434 return;
435 }
436 // Perform the copy with some internal knowledge of how resizable jagged
437 // vector buffers work.
438 do_copy(sizeof(typename data::vector_view<TYPE>::size_type) * sizes.size(),
439 sizes.data(), data.host_ptr()->size_ptr(), type::unknown);
440}
441
442template <typename TYPE>
444 const std::vector<typename data::vector_view<TYPE>::size_type>& sizes,
446
448 // Return a new event.
449 return create_event();
450}
451
452template <typename TYPE>
453async_sizes<typename data::vector_view<TYPE>::size_type> copy::get_sizes(
454 const data::jagged_vector_view<TYPE>& data,
455 memory_resource& pinnedHostMr) const {
456
457 // Try to get the "resizable sizes" first.
458 for (std::size_t i = 0; i < data.size(); ++i) {
459 // Find the first "inner vector" that has a non-zero capacity, and is
460 // resizable.
461 if ((data.host_ptr()[i].capacity() != 0) &&
462 (data.host_ptr()[i].size_ptr() != nullptr)) {
463
464 // Create the result value in pinned host memory.
465 vector<typename data::vector_view<TYPE>::size_type> result(
466 data.size(), 0, &pinnedHostMr);
467
468 // Copy the sizes of the inner vectors into the result vector.
470 (data.size() - i),
471 data.host_ptr()[i].size_ptr(), result.data() + i,
473 // Return the appropriate "future" value.
474 return {std::move(result), create_event()};
475 }
476 }
477
478 // If we're still here, then the buffer is not resizable. So let's just
479 // collect the capacity of each of the inner vectors.
480 vector<typename data::vector_view<TYPE>::size_type> result(data.size(), 0);
481 for (std::size_t i = 0; i < data.size(); ++i) {
482 result[i] = data.host_ptr()[i].capacity();
483 }
484 return {std::move(result), vecmem::copy::create_event()};
485}
486
487template <typename SCHEMA>
489
490 // Copy the data layout to the device, if needed.
491 if (data.layout().ptr() != data.host_layout().ptr()) {
492 assert(data.layout().capacity() > 0u);
493 [[maybe_unused]] bool did_copy =
494 copy_view_impl(data.host_layout(), data.layout(), type::unknown);
496 }
497
498 // Initialize the "size variable(s)" correctly on the buffer.
499 if (data.size().ptr() != nullptr) {
500 assert(data.size().capacity() > 0u);
501 do_memset(data.size().capacity() * sizeof(char), data.size().ptr(), 0);
502 }
503 VECMEM_DEBUG_MSG(3,
504 "Prepared an SoA container of capacity %u "
505 "for use on a device (layout: {%u, %p}, size: {%u, %p})",
506 data.capacity(), data.layout().size(),
507 static_cast<void*>(data.layout().ptr()),
508 data.size().size(), static_cast<void*>(data.size().ptr()));
509}
510
511template <typename SCHEMA>
513
514 setup(ignore_event, data);
515 // Return a new event.
516 return create_event();
517}
518
519template <typename... VARTYPES>
521 int value) const {
522
523 // For buffers, we can do this in one go.
524 if (data.payload().ptr() != nullptr) {
525 memset(ignore_event, data.payload(), value);
526 } else {
527 // Do the operation using the helper function, recursively.
528 memset_impl<0>(data, value);
529 }
530}
531
532template <typename... VARTYPES>
534 int value) const {
535
536 memset(ignore_event, data, value);
537 // Return a new event.
538 return create_event();
539}
540
541template <typename... VARTYPES>
542edm::buffer<edm::details::remove_cv_t<edm::schema<VARTYPES...>>> copy::to(
543 const edm::view<edm::schema<VARTYPES...>>& data, memory_resource& resource,
544 [[maybe_unused]] memory_resource* host_access_resource,
545 type::copy_type cptype) const {
546
547 // Create the result buffer object.
549 ((data.size().ptr() != nullptr) ? data::buffer_type::resizable
551 edm::buffer<edm::details::remove_cv_t<edm::schema<VARTYPES...>>> result;
553 edm::schema<VARTYPES...>>::value) {
554 // Set up a buffer that has (a) jagged vector(s).
556 btype};
557 // The idea here is that since we are going to copy data into the newly
558 // created buffer, the only thing that setup(...) needs to do is to set
559 // up "the layout" in non-host-accessible memory. (Zeroing the size
560 // variable(s) is not needed.) When doing a device-to-host copy, copying
561 // "the layout" is not actually needed. Worse yet, the copy object is
562 // not capable of memsetting the size variable(s). Since the device
563 // specific copy object cannot memset host memory.
564 //
565 // Long story short, the entire setup is skipped in the absence of a
566 // host-accessible memory resource.
567 if (host_access_resource != nullptr) {
568 setup(result)->wait();
569 }
570 } else {
571 // Set up a buffer absent of jagged vectors.
572 result = {data.capacity(), resource, btype};
573 // In the absence of jagged vectors there is no "layout" to copy. And
574 // since the size of the buffer will be set during the copy correctly,
575 // there is nothing else that setup(...) would need to do. So no need
576 // to call it here.
577 }
578
579 // Perform the copy.
580 operator()(data, result, cptype)->wait();
581
582 // Return the buffer.
583 return result;
584}
585
586template <typename... VARTYPES>
589 const edm::view<edm::details::add_const_t<edm::schema<VARTYPES...>>>&
590 from_view,
592
593 // First, handle the simple case, when both views have a contiguous memory
594 // layout.
595 if ((from_view.payload().ptr() != nullptr) &&
596 (to_view.payload().ptr() != nullptr) &&
597 (from_view.payload().capacity() == to_view.payload().capacity())) {
598
599 // If the "common size" is zero, we're done.
600 if (from_view.payload().capacity() == 0) {
601 return;
602 }
603
604 // Let the user know what's happening.
605 VECMEM_DEBUG_MSG(2, "Performing simple SoA copy of %u bytes",
606 from_view.payload().size());
607
608 // Copy the payload with a single copy operation.
609 copy_view_impl(from_view.payload(), to_view.payload(), cptype);
610
611 // If the target view is resizable, set its size.
612 if (to_view.size().ptr() != nullptr) {
613 // If the source is also resizable, the situation should be simple.
614 if (from_view.size().ptr() != nullptr) {
615 // Check that the sizes are the same.
616 if (from_view.size().capacity() != to_view.size().capacity()) {
617 std::ostringstream msg;
618 msg << "from_view.size().capacity() ("
619 << from_view.size().capacity()
620 << ") != to_view.size().capacity() ("
621 << to_view.size().capacity() << ")";
622 throw std::length_error(msg.str());
623 }
624 // Perform a dumb copy.
625 copy_view_impl(from_view.size(), to_view.size(), cptype);
626 } else {
627 // If not, then copy the size(s) recursively.
629 }
630 }
631
632 return;
633 }
634
635 // If not, then do an un-optimized copy, variable-by-variable.
637}
638
639template <typename... VARTYPES>
641 const edm::view<edm::details::add_const_t<edm::schema<VARTYPES...>>>&
642 from_view,
643 edm::view<edm::schema<VARTYPES...>> to_view, type::copy_type cptype) const {
644
646 // Return a new event.
647 return create_event();
648}
649
650template <typename... VARTYPES, template <typename> class INTERFACE>
653 const edm::view<edm::details::add_const_t<edm::schema<VARTYPES...>>>&
654 from_view,
656 type::copy_type cptype) const {
657
658 // Resize the output object to the correct size(s).
660
661 // Perform the memory copy.
663}
664
665template <typename... VARTYPES, template <typename> class INTERFACE>
667 const edm::view<edm::details::add_const_t<edm::schema<VARTYPES...>>>&
668 from_view,
670 type::copy_type cptype) const {
671
673 return create_event();
674}
675
676template <typename... VARTYPES>
678 const edm::view<edm::schema<VARTYPES...>>& data) const {
679
680 // Helper result value type.
681 using value_type = typename edm::view<edm::schema<VARTYPES...>>::size_type;
682
683 // Start by taking the capacity of the container.
684 value_type size = data.capacity();
685
686 // If there are jagged vectors in the container and/or the container is not
687 // resizable, we're done. It is done in two separate if statements to avoid
688 // MSVC trying to be too smart, and giving a warning...
689 if constexpr (std::disjunction_v<
691 return size;
692 } else {
693 // All the rest is put into an else block, to avoid MSVC trying to be
694 // too smart, and giving a warning about unreachable code...
695 if (data.size().ptr() == nullptr) {
696 return size;
697 }
698
699 // A small security check.
700 assert(data.size().size() == sizeof(value_type));
701
702 // Get the exact size of the container.
703 do_copy(sizeof(value_type), data.size().ptr(), &size, type::unknown);
704 // We have to wait for this to finish, since the "size" variable is
705 // not going to be available outside of this function. And
706 // asynchronous SYCL memory copies can happen from variables on the
707 // stack as well...
708 create_event()->wait();
709
710 // Return what we got.
711 return size;
712 }
713}
714
715template <typename... VARTYPES>
716async_size<typename edm::view<edm::schema<VARTYPES...>>::size_type>
718 memory_resource& pinnedHostMr) const {
719
720 // Helper result value type.
721 using value_type = typename edm::view<edm::schema<VARTYPES...>>::size_type;
722
723 // If there are jagged vectors in the container and/or the container is not
724 // resizable, we're done. It is done in two separate if statements to avoid
725 // MSVC trying to be too smart, and giving a warning...
726 if constexpr (std::disjunction_v<
728 // Create the result value, in "default" memory.
729 auto value = make_unique_alloc<value_type>(*(get_default_resource()));
730 *value = data.capacity();
731 return {std::move(value), vecmem::copy::create_event()};
732 } else {
733 // All the rest is put into an else block, to avoid MSVC trying to be
734 // too smart, and giving a warning about unreachable code...
735 if (data.size().ptr() == nullptr) {
736 // Create the result value, in "default" memory.
737 auto value =
738 make_unique_alloc<value_type>(*(get_default_resource()));
739 *value = data.capacity();
740 return {std::move(value), vecmem::copy::create_event()};
741 }
742
743 // A small security check.
744 assert(data.size().size() == sizeof(value_type));
745
746 // Create the result value, in pinned host memory.
747 auto value = make_unique_alloc<value_type>(pinnedHostMr);
748
749 // Get the exact size of the container.
750 do_copy(sizeof(value_type), data.size().ptr(), value.get(),
752
753 // Return the appropriate "future" value.
754 return {std::move(value), create_event()};
755 }
756}
757
758template <typename... VARTYPES>
759std::vector<data::vector_view<int>::size_type> copy::get_sizes(
760 const edm::view<edm::schema<VARTYPES...>>& data) const {
761
762 // Make sure that there's a jagged vector in here.
763 static_assert(
765 "Function can only be used on containers with jagged vectors");
766
767 // Get the sizes using the helper function.
768 return get_sizes_impl<0>(data);
769}
770
771template <typename... VARTYPES>
774 memory_resource& pinnedHostMr) const {
775
776 // Get the sizes using the helper function.
777 return get_sizes_impl<0>(data, pinnedHostMr);
778}
779
780template <typename TYPE>
781bool copy::copy_view_impl(
782 const data::vector_view<std::add_const_t<TYPE>>& from_view,
783 data::vector_view<TYPE> to_view, type::copy_type cptype) const {
784
785 // Get the size of the source view.
786 const typename data::vector_view<std::add_const_t<TYPE>>::size_type size =
787 get_size(from_view);
788 // If it's zero, don't do an actual copy.
789 if (size == 0u) {
790 return false;
791 }
792
793 // Make sure that the copy can happen.
794 if (to_view.capacity() < size) {
795 std::ostringstream msg;
796 msg << "Target capacity (" << to_view.capacity() << ") < source size ("
797 << size << ")";
798 throw std::length_error(msg.str());
799 }
800
801 // Make sure that if the target view is resizable, that it would be set up
802 // for the correct size.
803 if (to_view.size_ptr() != nullptr) {
804 // Select what type of copy this should be. Keeping in mind that we copy
805 // from a variable on the host stack. So the question is just whether
806 // the target is the host, or a device.
807 type::copy_type size_cptype = type::unknown;
808 switch (cptype) {
811 size_cptype = type::host_to_device;
812 break;
815 size_cptype = type::host_to_host;
816 break;
817 default:
818 break;
819 }
820 // Perform the copy.
821 do_copy(sizeof(typename data::vector_view<TYPE>::size_type), &size,
822 to_view.size_ptr(), size_cptype);
823 // We have to wait for this to finish, since the "size" variable is not
824 // going to be available outside of this function. And asynchronous SYCL
825 // memory copies can happen from variables on the stack as well...
826 create_event()->wait();
827 }
828
829 // Copy the payload.
830 do_copy(size * sizeof(TYPE), from_view.ptr(), to_view.ptr(), cptype);
831 return true;
832}
833
834template <typename TYPE>
835bool copy::copy_view_impl(
836 const data::jagged_vector_view<std::add_const_t<TYPE>>& from_view,
837 data::jagged_vector_view<TYPE> to_view, type::copy_type cptype) const {
838
839 // Sanity checks.
840 if (from_view.size() > to_view.size()) {
841 std::ostringstream msg;
842 msg << "from_view.size() (" << from_view.size()
843 << ") > to_view.size() (" << to_view.size() << ")";
844 throw std::length_error(msg.str());
845 }
846
847 // Get the "outer size" of the container.
848 const typename data::jagged_vector_view<std::add_const_t<TYPE>>::size_type
849 size = from_view.size();
850 if (size == 0u) {
851 return false;
852 }
853
854 // Calculate the contiguous-ness of the memory allocations.
855 const bool from_is_contiguous = is_contiguous(from_view.host_ptr(), size);
856 const bool to_is_contiguous = is_contiguous(to_view.host_ptr(), size);
857 VECMEM_DEBUG_MSG(3, "from_is_contiguous = %d, to_is_contiguous = %d",
858 from_is_contiguous, to_is_contiguous);
859
860 // Get the sizes of the source jagged vector.
861 const auto sizes = get_sizes(from_view);
862
863 // Before even attempting the copy, make sure that the target view either
864 // has the correct sizes, or can be resized correctly. Captire the event
865 // marking the end of the operation. We need to wait for the copy to finish
866 // before returning from this function.
867 auto set_sizes_event = set_sizes(sizes, to_view);
868
869 // Check whether the source and target capacities match up. We can only
870 // perform the "optimised copy" if they do.
871 std::vector<typename data::vector_view<std::add_const_t<TYPE>>::size_type>
872 capacities(size);
873 bool capacities_match = true;
874 for (std::size_t i = 0; i < size; ++i) {
875 if (from_view.host_ptr()[i].capacity() !=
876 to_view.host_ptr()[i].capacity()) {
877 capacities_match = false;
878 break;
879 }
880 capacities[i] = from_view.host_ptr()[i].capacity();
881 }
882
883 // Perform the copy as best as we can.
884 if (from_is_contiguous && to_is_contiguous && capacities_match) {
885 // Perform the copy in one go.
886 copy_views_contiguous_impl(capacities, from_view.host_ptr(),
887 to_view.host_ptr(), cptype);
888 } else {
889 // Do the copy as best as we can. Note that since they are not
890 // contiguous anyway, we use the sizes of the vectors here, not their
891 // capcities.
892 copy_views_impl(sizes, from_view.host_ptr(), to_view.host_ptr(),
893 cptype);
894 }
895
896 // Before returning, make sure that the "size set" operation has finished.
897 // Because the "sizes" variable is just about to go out of scope.
898 set_sizes_event->wait();
899 return true;
900}
901
902template <typename TYPE>
903void copy::copy_views_impl(
904 const std::vector<typename data::vector_view<TYPE>::size_type>& sizes,
905 const data::vector_view<std::add_const_t<TYPE>>* from_view,
906 data::vector_view<TYPE>* to_view, type::copy_type cptype) const {
907
908 // Some security checks.
909 assert(from_view != nullptr);
910 assert(to_view != nullptr);
911
912 // Helper variable(s) used in the copy.
913 const std::size_t size = sizes.size();
914 [[maybe_unused]] std::size_t copy_ops = 0;
915
916 // Perform the copy in multiple steps.
917 for (std::size_t i = 0; i < size; ++i) {
918
919 // Skip empty "inner vectors".
920 if (sizes[i] == 0) {
921 continue;
922 }
923
924 // Some sanity checks.
925 assert(from_view[i].ptr() != nullptr);
926 assert(to_view[i].ptr() != nullptr);
927 assert(sizes[i] <= from_view[i].capacity());
928 assert(sizes[i] <= to_view[i].capacity());
929
930 // Perform the copy.
931 do_copy(sizes[i] * sizeof(TYPE), from_view[i].ptr(), to_view[i].ptr(),
932 cptype);
933 ++copy_ops;
934 }
935
936 // Let the user know what happened.
937 VECMEM_DEBUG_MSG(2,
938 "Copied the payload of a jagged vector of type "
939 "\"%s\" with %lu copy operation(s)",
940 typeid(TYPE).name(), copy_ops);
941}
942
943template <typename TYPE>
944void copy::copy_views_contiguous_impl(
945 const std::vector<typename data::vector_view<TYPE>::size_type>& sizes,
946 const data::vector_view<std::add_const_t<TYPE>>* from_view,
947 data::vector_view<TYPE>* to_view, type::copy_type cptype) const {
948
949 // Some security checks.
950 assert(from_view != nullptr);
951 assert(to_view != nullptr);
952 assert(is_contiguous(from_view, sizes.size()));
953 assert(is_contiguous(to_view, sizes.size()));
954
955 // Helper variable(s) used in the copy.
956 const std::size_t size = sizes.size();
957 const std::size_t total_size =
958 std::accumulate(sizes.begin(), sizes.end(),
959 static_cast<std::size_t>(0)) *
960 sizeof(TYPE);
961
962 // Find the first non-empty element.
963 for (std::size_t i = 0; i < size; ++i) {
964
965 // Jump over empty elements.
966 if (sizes[i] == 0) {
967 continue;
968 }
969
970 // Some sanity checks.
971 assert(from_view[i].ptr() != nullptr);
972 assert(to_view[i].ptr() != nullptr);
973
974 // Perform the copy.
975 do_copy(total_size, from_view[i].ptr(), to_view[i].ptr(), cptype);
976 break;
977 }
978
979 // Let the user know what happened.
980 VECMEM_DEBUG_MSG(2,
981 "Copied the payload of a jagged vector of type "
982 "\"%s\" with 1 copy operation(s)",
983 typeid(TYPE).name());
984}
985
986template <typename TYPE>
987bool copy::is_contiguous(const data::vector_view<TYPE>* data,
988 std::size_t size) {
989
990 // We should never call this function for an empty jagged vector.
991 assert(size > 0);
992
993 // Check whether all memory blocks are contiguous.
994 auto ptr = data[0].ptr();
995 for (std::size_t i = 1; i < size; ++i) {
996 if ((ptr + data[i - 1].capacity()) != data[i].ptr()) {
997 return false;
998 }
999 ptr = data[i].ptr();
1000 }
1001 return true;
1002}
1003
1004template <std::size_t INDEX, typename... VARTYPES>
1005void copy::memset_impl(edm::view<edm::schema<VARTYPES...>> data,
1006 int value) const {
1007
1008 // Scalars do not have their own dedicated @c memset functions.
1009 if constexpr (edm::type::details::is_scalar<typename std::tuple_element<
1010 INDEX, std::tuple<VARTYPES...>>::type>::value) {
1011 do_memset(sizeof(typename std::tuple_element<
1012 INDEX, std::tuple<VARTYPES...>>::type::type),
1013 data.template get<INDEX>(), value);
1014 } else {
1015 // But vectors and jagged vectors do.
1016 memset(data.template get<INDEX>(), value);
1017 }
1018 // Recurse into the next variable.
1019 if constexpr (sizeof...(VARTYPES) > (INDEX + 1)) {
1020 memset_impl<INDEX + 1>(data, value);
1021 }
1022}
1023
1024template <std::size_t INDEX, typename... VARTYPES,
1025 template <typename> class INTERFACE>
1026void copy::resize_impl(
1027 const edm::view<edm::details::add_const_t<edm::schema<VARTYPES...>>>&
1028 from_view,
1029 edm::host<edm::schema<VARTYPES...>, INTERFACE>& to_vec,
1030 [[maybe_unused]] type::copy_type cptype) const {
1031
1032 // The target is a host container, so the copy type can't be anything
1033 // targeting a device.
1034 assert((cptype == type::device_to_host) || (cptype == type::host_to_host) ||
1035 (cptype == type::unknown));
1036
1037 // First, handle containers with no jagged vectors in them.
1038 if constexpr (std::disjunction_v<
1039 edm::type::details::is_jagged_vector<VARTYPES>...> ==
1040 false) {
1041 // The single size of the source container.
1042 typename edm::view<
1043 edm::details::add_const_t<edm::schema<VARTYPES...>>>::size_type
1044 size = from_view.capacity();
1045 // If the container is resizable, take its size.
1046 if (from_view.size().ptr() != nullptr) {
1047 // A small security check.
1048 assert(from_view.size().size() ==
1049 sizeof(typename edm::view<edm::details::add_const_t<
1050 edm::schema<VARTYPES...>>>::size_type));
1051 // Get the exact size of the container.
1052 do_copy(sizeof(typename edm::view<edm::details::add_const_t<
1053 edm::schema<VARTYPES...>>>::size_type),
1054 from_view.size().ptr(), &size, cptype);
1055 // We have to wait for this to finish, since the "size" variable is
1056 // not going to be available outside of this function. And
1057 // asynchronous SYCL memory copies can happen from variables on the
1058 // stack as well...
1059 create_event()->wait();
1060 }
1061 // Resize the target container.
1062 VECMEM_DEBUG_MSG(4, "Resizing a (non-jagged) container to size %u",
1063 size);
1064 to_vec.resize(size);
1065 } else {
1066 // Resize vector and jagged vector variables one by one. Note that
1067 // evaluation order matters here. Since all jagged vectors are vectors,
1068 // but not all vectors are jagged vectors. ;-)
1069 if constexpr (edm::type::details::is_jagged_vector<
1070 typename std::tuple_element<
1071 INDEX, std::tuple<VARTYPES...>>::type>::value) {
1072 // Get the sizes of this jagged vector.
1073 auto sizes = get_sizes(from_view.template get<INDEX>());
1074 // Set the "outer size" of the jagged vector.
1075 VECMEM_DEBUG_MSG(
1076 4, "Resizing jagged vector variable at index %lu to size %lu",
1077 INDEX, sizes.size());
1078 details::resize_jagged_vector(to_vec.template get<INDEX>(),
1079 sizes.size());
1080 // Set the "inner sizes" of the jagged vector.
1081 for (std::size_t i = 0; i < sizes.size(); ++i) {
1082 to_vec.template get<INDEX>()[i].resize(sizes[i]);
1083 }
1084 } else if constexpr (edm::type::details::is_vector<
1085 typename std::tuple_element<
1086 INDEX,
1087 std::tuple<VARTYPES...>>::type>::value) {
1088 // Get the size of this vector.
1089 auto size = get_size(from_view.template get<INDEX>());
1090 // Resize the target vector.
1091 VECMEM_DEBUG_MSG(4,
1092 "Resizing vector variable at index %lu to size %u",
1093 INDEX, size);
1094 to_vec.template get<INDEX>().resize(size);
1095 }
1096 // Call this function recursively.
1097 if constexpr (sizeof...(VARTYPES) > (INDEX + 1)) {
1098 resize_impl<INDEX + 1>(from_view, to_vec, cptype);
1099 }
1100 }
1101}
1102
1103template <std::size_t INDEX, typename... VARTYPES>
1104void copy::copy_sizes_impl(
1105 [[maybe_unused]] const edm::view<
1106 edm::details::add_const_t<edm::schema<VARTYPES...>>>& from_view,
1107 [[maybe_unused]] edm::view<edm::schema<VARTYPES...>> to_view,
1108 [[maybe_unused]] type::copy_type cptype) const {
1109
1110 // This should only be called for a resizable target container, with
1111 // a non-resizable source container.
1112 assert(to_view.size().ptr() != nullptr);
1113 assert(from_view.size().ptr() == nullptr);
1114
1115 // First, handle containers with no jagged vectors in them.
1116 if constexpr (std::disjunction_v<
1117 edm::type::details::is_jagged_vector<VARTYPES>...> ==
1118 false) {
1119 // Size of the source container.
1120 typename edm::view<
1121 edm::details::add_const_t<edm::schema<VARTYPES...>>>::size_type
1122 size = from_view.capacity();
1123 // Choose the copy type.
1124 type::copy_type size_cptype = type::unknown;
1125 switch (cptype) {
1128 size_cptype = type::host_to_device;
1129 break;
1130 case type::host_to_host:
1132 size_cptype = type::host_to_host;
1133 break;
1134 default:
1135 break;
1136 }
1137 // Set the size of the target container.
1138 do_copy(sizeof(typename edm::view<edm::details::add_const_t<
1139 edm::schema<VARTYPES...>>>::size_type),
1140 &size, to_view.size().ptr(), size_cptype);
1141 // We have to wait for this to finish, since the "size" variable is not
1142 // going to be available outside of this function. And asynchronous SYCL
1143 // memory copies can happen from variables on the stack as well...
1144 create_event()->wait();
1145 } else {
1146 // For the jagged vector case we recursively copy the sizes of every
1147 // jagged vector variable. The rest of the variables are not resizable
1148 // in such a container, so they are ignored here.
1149 if constexpr (edm::type::details::is_jagged_vector<
1150 typename std::tuple_element<
1151 INDEX, std::tuple<VARTYPES...>>::type>::value) {
1152 // Copy the sizes for this variable.
1153 const auto sizes = get_sizes(from_view.template get<INDEX>());
1154 set_sizes(sizes, to_view.template get<INDEX>())->wait();
1155 }
1156 // Call this function recursively.
1157 if constexpr (sizeof...(VARTYPES) > (INDEX + 1)) {
1158 copy_sizes_impl<INDEX + 1>(from_view, to_view, cptype);
1159 }
1160 }
1161}
1162
1163template <std::size_t INDEX, typename... VARTYPES>
1164void copy::copy_payload_impl(
1165 const edm::view<edm::details::add_const_t<edm::schema<VARTYPES...>>>&
1166 from_view,
1167 edm::view<edm::schema<VARTYPES...>> to_view, type::copy_type cptype) const {
1168
1169 // Scalars do not have their own dedicated @c copy functions.
1170 if constexpr (edm::type::details::is_scalar<typename std::tuple_element<
1171 INDEX, std::tuple<VARTYPES...>>::type>::value) {
1172 do_copy(sizeof(typename std::tuple_element<
1173 INDEX, std::tuple<VARTYPES...>>::type::type),
1174 from_view.template get<INDEX>(), to_view.template get<INDEX>(),
1175 cptype);
1176 } else {
1177 // But vectors and jagged vectors do.
1178 copy_view_impl(from_view.template get<INDEX>(),
1179 to_view.template get<INDEX>(), cptype);
1180 }
1181 // Recurse into the next variable.
1182 if constexpr (sizeof...(VARTYPES) > (INDEX + 1)) {
1183 copy_payload_impl<INDEX + 1>(from_view, to_view, cptype);
1184 }
1185}
1186
1187template <std::size_t INDEX, typename... VARTYPES>
1188std::vector<data::vector_view<int>::size_type> copy::get_sizes_impl(
1189 const edm::view<edm::schema<VARTYPES...>>& view) const {
1190
1191 // Make sure that there's a jagged vector in here.
1192 static_assert(
1193 edm::details::has_jagged_vector<edm::schema<VARTYPES...>>::value,
1194 "Function can only be used on containers with jagged vectors");
1195
1196 // Check if the variable at INDEX is a jagged vector.
1197 if constexpr (edm::type::details::is_jagged_vector<
1198 typename std::tuple_element<
1199 INDEX, std::tuple<VARTYPES...>>::type>::value) {
1200 // If it is, get its (inner) sizes.
1201 return get_sizes(view.template get<INDEX>());
1202 } else if constexpr (sizeof...(VARTYPES) > (INDEX + 1)) {
1203 // If it's not, and this was not the last variable, recurse into the
1204 // next variable.
1205 return get_sizes_impl<INDEX + 1>(view);
1206 }
1207
1208 // We should never get here as long as the code was written correctly.
1209#if defined(__GNUC__)
1210 __builtin_unreachable();
1211#elif defined(_MSC_VER)
1212 __assume(0);
1213#endif
1214}
1215
1216template <std::size_t INDEX, typename... VARTYPES>
1217async_sizes<data::vector_view<int>::size_type> copy::get_sizes_impl(
1218 const edm::view<edm::schema<VARTYPES...>>& view,
1219 memory_resource& pinnedHostMr) const {
1220
1221 // Make sure that there's a jagged vector in here.
1222 static_assert(
1223 edm::details::has_jagged_vector<edm::schema<VARTYPES...>>::value,
1224 "Function can only be used on containers with jagged vectors");
1225
1226 // Check if the variable at INDEX is a jagged vector.
1227 if constexpr (edm::type::details::is_jagged_vector<
1228 typename std::tuple_element<
1229 INDEX, std::tuple<VARTYPES...>>::type>::value) {
1230 // If it is, get its (inner) sizes.
1231 return get_sizes(view.template get<INDEX>(), pinnedHostMr);
1232 } else if constexpr (sizeof...(VARTYPES) > (INDEX + 1)) {
1233 // If it's not, and this was not the last variable, recurse into the
1234 // next variable.
1235 return get_sizes_impl<INDEX + 1>(view, pinnedHostMr);
1236 }
1237
1238 // We should never get here as long as the code was written correctly.
1239#if defined(__GNUC__)
1240 __builtin_unreachable();
1241#elif defined(_MSC_VER)
1242 __assume(0);
1243#endif
1244}
1245
1246} // namespace vecmem
An allocator class that wraps a memory resource.
Definition allocator.hpp:37
Return type for asynchronous size retrievals.
Definition async_size.hpp:25
VECMEM_NODISCARD event_type set_sizes(const std::vector< typename data::vector_view< TYPE >::size_type > &sizes, data::jagged_vector_view< TYPE > data) const
Helper function for setting the sizes of a resizable jagged vector.
VECMEM_NODISCARD event_type operator()(const data::vector_view< std::add_const_t< TYPE > > &from, data::vector_view< TYPE > to, type::copy_type cptype=type::unknown) const
Copy a 1-dimensional vector's data between two existing memory blocks.
VECMEM_NODISCARD event_type setup(data::vector_view< TYPE > data) const
Set up the internal state of a vector buffer correctly on a device.
virtual void do_copy(std::size_t size, const void *from, void *to, type::copy_type cptype) const
Perform a "low level" memory copy.
Definition copy.cpp:43
std::unique_ptr< abstract_event > event_type
Event type used by the copy class.
Definition copy.hpp:82
data::vector_view< TYPE >::size_type get_size(const data::vector_view< TYPE > &data) const
Helper function for getting the size of a resizable 1D buffer.
Definition copy.ipp:144
virtual void do_memset(std::size_t size, void *ptr, int value) const
Perform a "low level" memory filling operation.
Definition copy.cpp:56
virtual VECMEM_NODISCARD event_type create_event() const
Create an event for synchronization.
Definition copy.cpp:66
std::vector< typename data::vector_view< TYPE >::size_type > get_sizes(const data::jagged_vector_view< TYPE > &data) const
Helper function for getting the sizes of a resizable jagged vector.
Definition copy.ipp:356
VECMEM_NODISCARD event_type memset(data::vector_view< TYPE > data, int value) const
Set all bytes of the vector to some value.
data::vector_buffer< std::remove_cv_t< TYPE > > to(const data::vector_view< TYPE > &data, memory_resource &resource, type::copy_type cptype=type::unknown) const
Copy a 1-dimensional vector to the specified memory resource.
Definition copy.ipp:79
Object owning all the data of a jagged vector.
Definition jagged_vector_buffer.hpp:30
typename base_type::value_type value_type
Use the base class's value_type.
Definition jagged_vector_buffer.hpp:38
A view for jagged vectors.
Definition jagged_vector_view.hpp:45
VECMEM_HOST_AND_DEVICE pointer host_ptr() const
Access the host accessible array describing the inner vectors.
Definition jagged_vector_view.ipp:105
VECMEM_HOST_AND_DEVICE size_type capacity() const
Get the maximum capacity of the "outer" vector.
Definition jagged_vector_view.ipp:92
VECMEM_HOST_AND_DEVICE pointer ptr() const
Get a pointer to the vector elements.
Definition jagged_vector_view.ipp:99
VECMEM_HOST_AND_DEVICE size_type size() const
Get the "outer" size of the jagged vector.
Definition jagged_vector_view.ipp:86
Object owning the data held by it.
Definition vector_buffer.hpp:29
typename base_type::size_type size_type
Size type definition coming from the base class.
Definition vector_buffer.hpp:35
Class holding data about a 1 dimensional vector/array.
Definition vector_view.hpp:38
VECMEM_HOST_AND_DEVICE pointer ptr() const
Get a pointer to the vector elements.
Definition vector_view.ipp:91
unsigned int size_type
We cannot use boolean types.
Definition vector_view.hpp:47
VECMEM_HOST_AND_DEVICE size_pointer size_ptr() const
Get a pointer to the size of the vector.
Definition vector_view.ipp:84
VECMEM_HOST_AND_DEVICE size_type capacity() const
Get the maximum capacity of the vector.
Definition vector_view.ipp:78
Technical base type for buffer<schema<VARTYPES...>>
Definition buffer.hpp:28
Technical base type for host<schema<VARTYPES...>,INTERFACE>
Definition host.hpp:28
Technical base type for view<schema<VARTYPES...>>
Definition view.hpp:29
VECMEM_HOST std::vector< typename vector_view< T >::size_type > get_capacities(const jagged_vector_view< T > &data)
Get the capacities of the inner vectors of a jagged vector.
Definition jagged_vector_view.ipp:111
buffer_type
"Overall type" for a buffer object
Definition buffer_type.hpp:13
@ fixed_size
The buffer has a fixed number of elements.
@ resizable
The buffer is resizable/expandable.
void resize_jagged_vector(std::vector< std::vector< T, ALLOC1 >, ALLOC2 > &vec, std::size_t size)
Resize a generic jagged vector.
Definition resize_jagged_vector.hpp:23
VECMEM_HOST std::vector< vecmem::data::vector_view< int >::size_type > get_capacities(const view< schema< VARTYPES... > > &soa)
Helper function to get the capacities of the jagged vectors in a view.
Definition view.ipp:215
Main namespace for the vecmem classes/functions.
Definition atomic_ref.hpp:16
constexpr ignore_event_t ignore_event
Constant object of type ignore_event_t to use as an argument for functions that do not need to return...
Definition copy.hpp:42
VECMEM_HOST data::vector_view< T > get_data(array< T, N > &a)
Helper function creating a vecmem::data::vector_view object.
Definition array.ipp:217
copy_type
Types of memory copies to handle.
Definition copy.hpp:65
@ device_to_host
Copy operation between a device and the host.
Definition copy.hpp:69
@ device_to_device
Copy operation between two devices.
Definition copy.hpp:73
@ unknown
Unknown copy type, determined at runtime.
Definition copy.hpp:75
@ host_to_host
Copy operation on the host.
Definition copy.hpp:71
@ host_to_device
Copy operation between the host and a device.
Definition copy.hpp:67
Technical base type for has_jagged_vector<schema<VARTYPES...>>
Definition schema_traits.hpp:238
Meta type describing the "schema" of an SoA container.
Definition schema.hpp:46
Definition schema_traits.hpp:108
Tag type to indicate overloads of functions that do not need to return an event.
Definition copy.hpp:37