xref: /petsc/src/sys/objects/device/interface/memory.cxx (revision 0e6b6b5985dd9b1172860d21fb88bd3966bf7c54)
1*0e6b6b59SJacob Faibussowitsch #include <petsc/private/deviceimpl.h> /*I <petscdevice.h> I*/
2*0e6b6b59SJacob Faibussowitsch 
3*0e6b6b59SJacob Faibussowitsch #include <petsc/private/cpp/register_finalize.hpp>
4*0e6b6b59SJacob Faibussowitsch #include <petsc/private/cpp/array.hpp>
5*0e6b6b59SJacob Faibussowitsch 
6*0e6b6b59SJacob Faibussowitsch #include <unordered_map>
7*0e6b6b59SJacob Faibussowitsch #include <algorithm> // std::find_if
8*0e6b6b59SJacob Faibussowitsch #include <cstring>   // std::memset
9*0e6b6b59SJacob Faibussowitsch 
10*0e6b6b59SJacob Faibussowitsch const char *const PetscDeviceCopyModes[] = {"host_to_host", "device_to_host", "host_to_device", "device_to_device", "auto", "PetscDeviceCopyMode", "PETSC_DEVICE_COPY_", nullptr};
11*0e6b6b59SJacob Faibussowitsch static_assert(Petsc::util::integral_value(PETSC_DEVICE_COPY_HTOH) == 0, "");
12*0e6b6b59SJacob Faibussowitsch static_assert(Petsc::util::integral_value(PETSC_DEVICE_COPY_DTOH) == 1, "");
13*0e6b6b59SJacob Faibussowitsch static_assert(Petsc::util::integral_value(PETSC_DEVICE_COPY_HTOD) == 2, "");
14*0e6b6b59SJacob Faibussowitsch static_assert(Petsc::util::integral_value(PETSC_DEVICE_COPY_DTOD) == 3, "");
15*0e6b6b59SJacob Faibussowitsch static_assert(Petsc::util::integral_value(PETSC_DEVICE_COPY_AUTO) == 4, "");
16*0e6b6b59SJacob Faibussowitsch 
17*0e6b6b59SJacob Faibussowitsch // ==========================================================================================
18*0e6b6b59SJacob Faibussowitsch // MemoryMap
19*0e6b6b59SJacob Faibussowitsch //
20*0e6b6b59SJacob Faibussowitsch // Since the pointers allocated via PetscDeviceAllocate() may be device pointers we cannot just
21*0e6b6b59SJacob Faibussowitsch // store meta-data within the pointer itself (as we can't dereference them). So instead we need
22*0e6b6b59SJacob Faibussowitsch // to keep an extra map to keep track of them
23*0e6b6b59SJacob Faibussowitsch //
24*0e6b6b59SJacob Faibussowitsch // Each entry maps pointer -> [PetscMemType, PetscObjectId, size]
25*0e6b6b59SJacob Faibussowitsch // ==========================================================================================
26*0e6b6b59SJacob Faibussowitsch 
27*0e6b6b59SJacob Faibussowitsch // GCC implementation for std::hash<T*>. LLVM's libc++ is almost 2x slower because they do all
28*0e6b6b59SJacob Faibussowitsch // kinds of complicated murmur hashing, so we make sure to enforce GCC's version.
29*0e6b6b59SJacob Faibussowitsch struct PointerHash {
30*0e6b6b59SJacob Faibussowitsch   std::size_t operator()(const void *ptr) const noexcept { return reinterpret_cast<std::size_t>(ptr); }
31*0e6b6b59SJacob Faibussowitsch };
32*0e6b6b59SJacob Faibussowitsch 
33*0e6b6b59SJacob Faibussowitsch class MemoryMap : public Petsc::RegisterFinalizeable<MemoryMap> {
34*0e6b6b59SJacob Faibussowitsch public:
35*0e6b6b59SJacob Faibussowitsch   struct PointerAttributes {
36*0e6b6b59SJacob Faibussowitsch     PetscMemType  mtype{}; // memtype of allocation
37*0e6b6b59SJacob Faibussowitsch     PetscObjectId id{};    // id of allocation
38*0e6b6b59SJacob Faibussowitsch     std::size_t   size{};  // size of allocation (bytes)
39*0e6b6b59SJacob Faibussowitsch 
40*0e6b6b59SJacob Faibussowitsch     // even though this is a POD and can be aggregate initialized, the STL uses () constructors
41*0e6b6b59SJacob Faibussowitsch     // in unordered_map and so we need to provide a trivial contructor...
42*0e6b6b59SJacob Faibussowitsch     constexpr PointerAttributes(PetscMemType, PetscObjectId, std::size_t) noexcept;
43*0e6b6b59SJacob Faibussowitsch     constexpr PointerAttributes() noexcept                                              = default;
44*0e6b6b59SJacob Faibussowitsch     constexpr PointerAttributes(const PointerAttributes &) noexcept                     = default;
45*0e6b6b59SJacob Faibussowitsch     PETSC_CONSTEXPR_14 PointerAttributes &operator=(const PointerAttributes &) noexcept = default;
46*0e6b6b59SJacob Faibussowitsch     constexpr PointerAttributes(PointerAttributes &&) noexcept                          = default;
47*0e6b6b59SJacob Faibussowitsch     PETSC_CONSTEXPR_14 PointerAttributes &operator=(PointerAttributes &&) noexcept      = default;
48*0e6b6b59SJacob Faibussowitsch 
49*0e6b6b59SJacob Faibussowitsch     bool operator==(const PointerAttributes &) const noexcept;
50*0e6b6b59SJacob Faibussowitsch 
51*0e6b6b59SJacob Faibussowitsch     PETSC_NODISCARD bool contains(const void *, const void *) const noexcept;
52*0e6b6b59SJacob Faibussowitsch   };
53*0e6b6b59SJacob Faibussowitsch 
54*0e6b6b59SJacob Faibussowitsch   using map_type = std::unordered_map<void *, PointerAttributes, PointerHash>;
55*0e6b6b59SJacob Faibussowitsch 
56*0e6b6b59SJacob Faibussowitsch   map_type map;
57*0e6b6b59SJacob Faibussowitsch 
58*0e6b6b59SJacob Faibussowitsch   // return the iterator of the allocation containing ptr, or map.cend() if not found
59*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD map_type::const_iterator search_for(const void *) const noexcept;
60*0e6b6b59SJacob Faibussowitsch 
61*0e6b6b59SJacob Faibussowitsch private:
62*0e6b6b59SJacob Faibussowitsch   friend class Petsc::RegisterFinalizeable<MemoryMap>;
63*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD PetscErrorCode register_finalize_() noexcept;
64*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD PetscErrorCode finalize_() noexcept;
65*0e6b6b59SJacob Faibussowitsch };
66*0e6b6b59SJacob Faibussowitsch 
67*0e6b6b59SJacob Faibussowitsch // ==========================================================================================
68*0e6b6b59SJacob Faibussowitsch // PointerAttributes
69*0e6b6b59SJacob Faibussowitsch // ==========================================================================================
70*0e6b6b59SJacob Faibussowitsch 
71*0e6b6b59SJacob Faibussowitsch constexpr MemoryMap::PointerAttributes::PointerAttributes(PetscMemType mtype_, PetscObjectId id_, std::size_t size_) noexcept : mtype(mtype_), id(id_), size(size_) { }
72*0e6b6b59SJacob Faibussowitsch 
73*0e6b6b59SJacob Faibussowitsch bool MemoryMap::PointerAttributes::operator==(const PointerAttributes &other) const noexcept {
74*0e6b6b59SJacob Faibussowitsch   return mtype == other.mtype && id == other.id && size == other.size;
75*0e6b6b59SJacob Faibussowitsch }
76*0e6b6b59SJacob Faibussowitsch 
77*0e6b6b59SJacob Faibussowitsch bool MemoryMap::PointerAttributes::contains(const void *ptr_begin, const void *ptr) const noexcept {
78*0e6b6b59SJacob Faibussowitsch   return (ptr >= ptr_begin) && (ptr < (static_cast<const char *>(ptr_begin) + size));
79*0e6b6b59SJacob Faibussowitsch }
80*0e6b6b59SJacob Faibussowitsch 
81*0e6b6b59SJacob Faibussowitsch // ==========================================================================================
82*0e6b6b59SJacob Faibussowitsch // Memory map functions
83*0e6b6b59SJacob Faibussowitsch // ==========================================================================================
84*0e6b6b59SJacob Faibussowitsch 
85*0e6b6b59SJacob Faibussowitsch PetscErrorCode MemoryMap::register_finalize_() noexcept {
86*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
87*0e6b6b59SJacob Faibussowitsch   // Preallocate, this does give a modest performance bump since unordered_map is so __dog__
88*0e6b6b59SJacob Faibussowitsch   // slow if it needs to rehash. Experiments show that users tend not to have more than 5 or
89*0e6b6b59SJacob Faibussowitsch   // so concurrently live pointers lying around. 10 at most.
90*0e6b6b59SJacob Faibussowitsch   PetscCallCXX(map.reserve(16));
91*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
92*0e6b6b59SJacob Faibussowitsch }
93*0e6b6b59SJacob Faibussowitsch 
94*0e6b6b59SJacob Faibussowitsch PetscErrorCode MemoryMap::finalize_() noexcept {
95*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
96*0e6b6b59SJacob Faibussowitsch   PetscCall(PetscInfo(nullptr, "Finalizing memory map\n"));
97*0e6b6b59SJacob Faibussowitsch   PetscCallCXX(map = map_type{});
98*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
99*0e6b6b59SJacob Faibussowitsch }
100*0e6b6b59SJacob Faibussowitsch 
101*0e6b6b59SJacob Faibussowitsch /*
102*0e6b6b59SJacob Faibussowitsch   MemoryMap::search_for - retrieve an iterator to the key-value pair for a pointer in the map
103*0e6b6b59SJacob Faibussowitsch 
104*0e6b6b59SJacob Faibussowitsch   Input Parameter:
105*0e6b6b59SJacob Faibussowitsch . ptr - pointer to search for
106*0e6b6b59SJacob Faibussowitsch 
107*0e6b6b59SJacob Faibussowitsch   Notes:
108*0e6b6b59SJacob Faibussowitsch   Accounts for sub-regions, i.e. if ptr is contained within another pointers region, it returns
109*0e6b6b59SJacob Faibussowitsch   the iterator to the super-pointers key-value pair.
110*0e6b6b59SJacob Faibussowitsch 
111*0e6b6b59SJacob Faibussowitsch   If ptr is not found, returns map.end()
112*0e6b6b59SJacob Faibussowitsch */
113*0e6b6b59SJacob Faibussowitsch MemoryMap::map_type::const_iterator MemoryMap::search_for(const void *ptr) const noexcept {
114*0e6b6b59SJacob Faibussowitsch   const auto end = map.end();
115*0e6b6b59SJacob Faibussowitsch   const auto it  = map.find(const_cast<map_type::key_type>(ptr));
116*0e6b6b59SJacob Faibussowitsch 
117*0e6b6b59SJacob Faibussowitsch   // ptr was found, and points to an entire block
118*0e6b6b59SJacob Faibussowitsch   if (it != end) return it;
119*0e6b6b59SJacob Faibussowitsch   // wasn't found, but maybe its part of a block. have to search every block for it
120*0e6b6b59SJacob Faibussowitsch   // clang-format off
121*0e6b6b59SJacob Faibussowitsch   return std::find_if(map.begin(), end, [ptr](const map_type::const_iterator::value_type &map_it) {
122*0e6b6b59SJacob Faibussowitsch     return map_it.second.contains(map_it.first, ptr);
123*0e6b6b59SJacob Faibussowitsch   });
124*0e6b6b59SJacob Faibussowitsch   // clang-format on
125*0e6b6b59SJacob Faibussowitsch }
126*0e6b6b59SJacob Faibussowitsch 
127*0e6b6b59SJacob Faibussowitsch static MemoryMap memory_map;
128*0e6b6b59SJacob Faibussowitsch 
129*0e6b6b59SJacob Faibussowitsch // ==========================================================================================
130*0e6b6b59SJacob Faibussowitsch // Utility functions
131*0e6b6b59SJacob Faibussowitsch // ==========================================================================================
132*0e6b6b59SJacob Faibussowitsch 
133*0e6b6b59SJacob Faibussowitsch static PetscErrorCode PetscDeviceCheckCapable_Private(PetscDeviceContext dctx, bool cond, const char descr[]) {
134*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
135*0e6b6b59SJacob Faibussowitsch   PetscCheck(cond, PETSC_COMM_SELF, PETSC_ERR_SUP, "Device context (id: %" PetscInt64_FMT ", name: %s, type: %s) can only handle %s host memory", PetscObjectCast(dctx)->id, PetscObjectCast(dctx)->name, dctx->device ? PetscDeviceTypes[dctx->device->type] : "unknown", descr);
136*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
137*0e6b6b59SJacob Faibussowitsch }
138*0e6b6b59SJacob Faibussowitsch 
139*0e6b6b59SJacob Faibussowitsch // A helper utility, since register is called from PetscDeviceRegisterMemory() and
140*0e6b6b59SJacob Faibussowitsch // PetscDevicAllocate(). The latter also needs the generated id, so instead of making it search
141*0e6b6b59SJacob Faibussowitsch // the map again we just return it here
142*0e6b6b59SJacob Faibussowitsch static PetscErrorCode PetscDeviceRegisterMemory_Private(const void *PETSC_RESTRICT ptr, PetscMemType mtype, std::size_t size, PetscObjectId *PETSC_RESTRICT id = nullptr) {
143*0e6b6b59SJacob Faibussowitsch   auto      &map = memory_map.map;
144*0e6b6b59SJacob Faibussowitsch   const auto it  = memory_map.search_for(ptr);
145*0e6b6b59SJacob Faibussowitsch 
146*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
147*0e6b6b59SJacob Faibussowitsch   if (it == map.cend()) {
148*0e6b6b59SJacob Faibussowitsch     // pointer was never registered with the map, insert it and bail
149*0e6b6b59SJacob Faibussowitsch     const auto newid = PetscObjectNewId_Internal();
150*0e6b6b59SJacob Faibussowitsch 
151*0e6b6b59SJacob Faibussowitsch     if (PetscDefined(USE_DEBUG)) {
152*0e6b6b59SJacob Faibussowitsch       const auto tmp = MemoryMap::PointerAttributes(mtype, newid, size);
153*0e6b6b59SJacob Faibussowitsch 
154*0e6b6b59SJacob Faibussowitsch       for (const auto &entry : map) {
155*0e6b6b59SJacob Faibussowitsch         // REVIEW ME: maybe this should just be handled...
156*0e6b6b59SJacob Faibussowitsch         PetscCheck(!tmp.contains(ptr, entry.first), PETSC_COMM_SELF, PETSC_ERR_ORDER, "Trying to register pointer %p (memtype %s, size %zu) but it appears you have already registered a sub-region of it (pointer %p, memtype %s, size %zu). Must register the larger region first", ptr, PetscMemTypeToString(mtype), size,
157*0e6b6b59SJacob Faibussowitsch                    entry.first, PetscMemTypeToString(entry.second.mtype), entry.second.size);
158*0e6b6b59SJacob Faibussowitsch       }
159*0e6b6b59SJacob Faibussowitsch     }
160*0e6b6b59SJacob Faibussowitsch     // clang-format off
161*0e6b6b59SJacob Faibussowitsch     if (id) *id = newid;
162*0e6b6b59SJacob Faibussowitsch     PetscCallCXX(map.emplace(
163*0e6b6b59SJacob Faibussowitsch       std::piecewise_construct,
164*0e6b6b59SJacob Faibussowitsch       std::forward_as_tuple(const_cast<MemoryMap::map_type::key_type>(ptr)),
165*0e6b6b59SJacob Faibussowitsch       std::forward_as_tuple(mtype, newid, size)
166*0e6b6b59SJacob Faibussowitsch     ));
167*0e6b6b59SJacob Faibussowitsch     // clang-format on
168*0e6b6b59SJacob Faibussowitsch     PetscFunctionReturn(0);
169*0e6b6b59SJacob Faibussowitsch   }
170*0e6b6b59SJacob Faibussowitsch   if (PetscDefined(USE_DEBUG)) {
171*0e6b6b59SJacob Faibussowitsch     const auto &old = it->second;
172*0e6b6b59SJacob Faibussowitsch 
173*0e6b6b59SJacob Faibussowitsch     PetscCheck(MemoryMap::PointerAttributes(mtype, old.id, size) == old, PETSC_COMM_SELF, PETSC_ERR_LIB, "Pointer %p appears to have been previously allocated with memtype %s, size %zu and assigned id %" PetscInt64_FMT ", which does not match new values: (mtype %s, size %zu, id %" PetscInt64_FMT ")", it->first,
174*0e6b6b59SJacob Faibussowitsch                PetscMemTypeToString(old.mtype), old.size, old.id, PetscMemTypeToString(mtype), size, old.id);
175*0e6b6b59SJacob Faibussowitsch   }
176*0e6b6b59SJacob Faibussowitsch   if (id) *id = it->second.id;
177*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
178*0e6b6b59SJacob Faibussowitsch }
179*0e6b6b59SJacob Faibussowitsch 
180*0e6b6b59SJacob Faibussowitsch /*@C
181*0e6b6b59SJacob Faibussowitsch   PetscDeviceRegisterMemory - Register a pointer for use with device-aware memory system
182*0e6b6b59SJacob Faibussowitsch 
183*0e6b6b59SJacob Faibussowitsch   Not Collective
184*0e6b6b59SJacob Faibussowitsch 
185*0e6b6b59SJacob Faibussowitsch   Input Parameters:
186*0e6b6b59SJacob Faibussowitsch + ptr   - The pointer to register
187*0e6b6b59SJacob Faibussowitsch . mtype - The `PetscMemType` of the pointer
188*0e6b6b59SJacob Faibussowitsch - size  - The size (in bytes) of the memory region
189*0e6b6b59SJacob Faibussowitsch 
190*0e6b6b59SJacob Faibussowitsch   Notes:
191*0e6b6b59SJacob Faibussowitsch   `ptr` need not point to the beginning of the memory range, however the user should register
192*0e6b6b59SJacob Faibussowitsch   the
193*0e6b6b59SJacob Faibussowitsch 
194*0e6b6b59SJacob Faibussowitsch   It's OK to re-register the same `ptr` repeatedly (subsequent registrations do nothing)
195*0e6b6b59SJacob Faibussowitsch   however the given `mtype` and `size` must match the original registration.
196*0e6b6b59SJacob Faibussowitsch 
197*0e6b6b59SJacob Faibussowitsch   `size` may be 0 (in which case this routine does nothing).
198*0e6b6b59SJacob Faibussowitsch 
199*0e6b6b59SJacob Faibussowitsch   Level: intermediate
200*0e6b6b59SJacob Faibussowitsch 
201*0e6b6b59SJacob Faibussowitsch .seealso: `PetscDeviceMalloc()`, `PetscDeviceArrayCopy()`, `PetscDeviceFree()`,
202*0e6b6b59SJacob Faibussowitsch `PetscDeviceArrayZero()`
203*0e6b6b59SJacob Faibussowitsch @*/
204*0e6b6b59SJacob Faibussowitsch PetscErrorCode PetscDeviceRegisterMemory(const void *PETSC_RESTRICT ptr, PetscMemType mtype, std::size_t size) {
205*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
206*0e6b6b59SJacob Faibussowitsch   if (PetscMemTypeHost(mtype)) PetscValidPointer(ptr, 1);
207*0e6b6b59SJacob Faibussowitsch   if (PetscUnlikely(!size)) PetscFunctionReturn(0); // there is no point registering empty range
208*0e6b6b59SJacob Faibussowitsch   PetscCall(PetscDeviceRegisterMemory_Private(ptr, mtype, size));
209*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
210*0e6b6b59SJacob Faibussowitsch }
211*0e6b6b59SJacob Faibussowitsch 
212*0e6b6b59SJacob Faibussowitsch /*@C
213*0e6b6b59SJacob Faibussowitsch   PetscDeviceAllocate - Allocate device-aware memory
214*0e6b6b59SJacob Faibussowitsch 
215*0e6b6b59SJacob Faibussowitsch   Not Collective, Asynchronous, Auto-dependency aware
216*0e6b6b59SJacob Faibussowitsch 
217*0e6b6b59SJacob Faibussowitsch   Input Parameters:
218*0e6b6b59SJacob Faibussowitsch + dctx  - The `PetscDeviceContext` used to allocate the memory
219*0e6b6b59SJacob Faibussowitsch . clear - Whether or not the memory should be zeroed
220*0e6b6b59SJacob Faibussowitsch . mtype - The type of memory to allocate
221*0e6b6b59SJacob Faibussowitsch - n     - The amount (in bytes) to allocate
222*0e6b6b59SJacob Faibussowitsch 
223*0e6b6b59SJacob Faibussowitsch   Output Parameter:
224*0e6b6b59SJacob Faibussowitsch . ptr - The pointer to store the result in
225*0e6b6b59SJacob Faibussowitsch 
226*0e6b6b59SJacob Faibussowitsch   Notes:
227*0e6b6b59SJacob Faibussowitsch   The user should prefer `PetscDeviceMalloc()` over this routine as it automatically computes
228*0e6b6b59SJacob Faibussowitsch   the size of the allocation based on the size of the datatype.
229*0e6b6b59SJacob Faibussowitsch 
230*0e6b6b59SJacob Faibussowitsch   Memory allocated with this function must be freed with `PetscDeviceFree()` or
231*0e6b6b59SJacob Faibussowitsch   `PetscDeviceDeallocate()`.
232*0e6b6b59SJacob Faibussowitsch 
233*0e6b6b59SJacob Faibussowitsch   Note result stored `ptr` is immediately valid and the user may freely inspect or manipulate
234*0e6b6b59SJacob Faibussowitsch   its value on function return, i.e.\:
235*0e6b6b59SJacob Faibussowitsch 
236*0e6b6b59SJacob Faibussowitsch .vb
237*0e6b6b59SJacob Faibussowitsch   PetscInt *ptr;
238*0e6b6b59SJacob Faibussowitsch 
239*0e6b6b59SJacob Faibussowitsch   PetscDeviceAllocate(dctx, PETSC_FALSE, PETSC_MEMTYPE_DEVICE, 20, (void**)&ptr);
240*0e6b6b59SJacob Faibussowitsch 
241*0e6b6b59SJacob Faibussowitsch   PetscInt *sub_ptr = ptr + 10; // OK, no need to synchronize
242*0e6b6b59SJacob Faibussowitsch 
243*0e6b6b59SJacob Faibussowitsch   ptr[0] = 10; // ERROR, directly accessing contents of ptr is undefined until synchronization
244*0e6b6b59SJacob Faibussowitsch .ve
245*0e6b6b59SJacob Faibussowitsch 
246*0e6b6b59SJacob Faibussowitsch   If `n` is zero, then `ptr` is set to `PETSC_NULLPTR`.
247*0e6b6b59SJacob Faibussowitsch 
248*0e6b6b59SJacob Faibussowitsch   DAG representation:
249*0e6b6b59SJacob Faibussowitsch .vb
250*0e6b6b59SJacob Faibussowitsch   time ->
251*0e6b6b59SJacob Faibussowitsch 
252*0e6b6b59SJacob Faibussowitsch   -> dctx - |= CALL =| -\- dctx -->
253*0e6b6b59SJacob Faibussowitsch                          \- ptr ->
254*0e6b6b59SJacob Faibussowitsch .ve
255*0e6b6b59SJacob Faibussowitsch 
256*0e6b6b59SJacob Faibussowitsch   Level: intermediate
257*0e6b6b59SJacob Faibussowitsch 
258*0e6b6b59SJacob Faibussowitsch .N ASYNC_API
259*0e6b6b59SJacob Faibussowitsch 
260*0e6b6b59SJacob Faibussowitsch .seealso: `PetscDeviceMalloc()`, `PetscGetMemType()`, `PetscDeviceFree()`,
261*0e6b6b59SJacob Faibussowitsch `PetscDeviceDeallocate()`, `PetscDeviceArrayCopy()`, `PetscDeviceArrayZero()`
262*0e6b6b59SJacob Faibussowitsch @*/
263*0e6b6b59SJacob Faibussowitsch PetscErrorCode PetscDeviceAllocate(PetscDeviceContext dctx, PetscBool clear, PetscMemType mtype, size_t n, void **PETSC_RESTRICT ptr) {
264*0e6b6b59SJacob Faibussowitsch   PetscObjectId id = 0;
265*0e6b6b59SJacob Faibussowitsch 
266*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
267*0e6b6b59SJacob Faibussowitsch   PetscValidPointer(ptr, 5);
268*0e6b6b59SJacob Faibussowitsch   *ptr = nullptr;
269*0e6b6b59SJacob Faibussowitsch   if (PetscUnlikely(!n)) PetscFunctionReturn(0);
270*0e6b6b59SJacob Faibussowitsch   PetscCall(memory_map.register_finalize());
271*0e6b6b59SJacob Faibussowitsch   PetscCall(PetscDeviceContextGetOptionalNullContext_Internal(&dctx));
272*0e6b6b59SJacob Faibussowitsch 
273*0e6b6b59SJacob Faibussowitsch   // get our pointer here
274*0e6b6b59SJacob Faibussowitsch   if (dctx->ops->memalloc) {
275*0e6b6b59SJacob Faibussowitsch     PetscUseTypeMethod(dctx, memalloc, clear, mtype, n, ptr);
276*0e6b6b59SJacob Faibussowitsch   } else {
277*0e6b6b59SJacob Faibussowitsch     PetscCall(PetscDeviceCheckCapable_Private(dctx, PetscMemTypeHost(mtype), "allocating"));
278*0e6b6b59SJacob Faibussowitsch     PetscCall(PetscMallocA(1, clear, __LINE__, PETSC_FUNCTION_NAME, __FILE__, n, ptr));
279*0e6b6b59SJacob Faibussowitsch   }
280*0e6b6b59SJacob Faibussowitsch   PetscCall(PetscDeviceRegisterMemory_Private(*ptr, mtype, n, &id));
281*0e6b6b59SJacob Faibussowitsch   // Note this is a "write" so that the next dctx to try and read from the pointer has to wait
282*0e6b6b59SJacob Faibussowitsch   // for the allocation to be ready
283*0e6b6b59SJacob Faibussowitsch   PetscCall(PetscDeviceContextMarkIntentFromID(dctx, id, PETSC_MEMORY_ACCESS_WRITE, "memory allocation"));
284*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
285*0e6b6b59SJacob Faibussowitsch }
286*0e6b6b59SJacob Faibussowitsch 
287*0e6b6b59SJacob Faibussowitsch /*@C
288*0e6b6b59SJacob Faibussowitsch   PetscDeviceDeallocate - Free device-aware memory
289*0e6b6b59SJacob Faibussowitsch 
290*0e6b6b59SJacob Faibussowitsch   Not Collective, Asynchronous, Auto-dependency aware
291*0e6b6b59SJacob Faibussowitsch 
292*0e6b6b59SJacob Faibussowitsch   Input Parameters:
293*0e6b6b59SJacob Faibussowitsch + dctx  - The `PetscDeviceContext` used to free the memory
294*0e6b6b59SJacob Faibussowitsch - ptr   - The pointer to free
295*0e6b6b59SJacob Faibussowitsch 
296*0e6b6b59SJacob Faibussowitsch   Notes:
297*0e6b6b59SJacob Faibussowitsch   `ptr` must have been allocated using any of `PetscDeviceMalloc()`, `PetscDeviceCalloc()` or
298*0e6b6b59SJacob Faibussowitsch   `PetscDeviceAllocate()`, or registered with the system via `PetscDeviceRegisterMemory()`.
299*0e6b6b59SJacob Faibussowitsch 
300*0e6b6b59SJacob Faibussowitsch   The user should prefer `PetscDeviceFree()` over this routine as it automatically sets `ptr`
301*0e6b6b59SJacob Faibussowitsch   to `PETSC_NULLPTR` on successful deallocation.
302*0e6b6b59SJacob Faibussowitsch 
303*0e6b6b59SJacob Faibussowitsch   `ptr` may be `NULL`.
304*0e6b6b59SJacob Faibussowitsch 
305*0e6b6b59SJacob Faibussowitsch   DAG representation:
306*0e6b6b59SJacob Faibussowitsch .vb
307*0e6b6b59SJacob Faibussowitsch   time ->
308*0e6b6b59SJacob Faibussowitsch 
309*0e6b6b59SJacob Faibussowitsch   -> dctx -/- |= CALL =| - dctx ->
310*0e6b6b59SJacob Faibussowitsch   -> ptr -/
311*0e6b6b59SJacob Faibussowitsch .ve
312*0e6b6b59SJacob Faibussowitsch 
313*0e6b6b59SJacob Faibussowitsch   Level: intermediate
314*0e6b6b59SJacob Faibussowitsch 
315*0e6b6b59SJacob Faibussowitsch .N ASYNC_API
316*0e6b6b59SJacob Faibussowitsch 
317*0e6b6b59SJacob Faibussowitsch .seealso: `PetscDeviceFree()`, `PetscDeviceAllocate()`
318*0e6b6b59SJacob Faibussowitsch @*/
319*0e6b6b59SJacob Faibussowitsch PetscErrorCode PetscDeviceDeallocate(PetscDeviceContext dctx, void *PETSC_RESTRICT ptr) {
320*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
321*0e6b6b59SJacob Faibussowitsch   if (ptr) {
322*0e6b6b59SJacob Faibussowitsch     auto      &map      = memory_map.map;
323*0e6b6b59SJacob Faibussowitsch     const auto found_it = map.find(const_cast<MemoryMap::map_type::key_type>(ptr));
324*0e6b6b59SJacob Faibussowitsch 
325*0e6b6b59SJacob Faibussowitsch     if (PetscUnlikelyDebug(found_it == map.end())) {
326*0e6b6b59SJacob Faibussowitsch       // OK this is a bad pointer, now determine why
327*0e6b6b59SJacob Faibussowitsch       const auto it = memory_map.search_for(ptr);
328*0e6b6b59SJacob Faibussowitsch 
329*0e6b6b59SJacob Faibussowitsch       // if it is map.cend() then no allocation owns it, meaning it was not allocated by us!
330*0e6b6b59SJacob Faibussowitsch       PetscCheck(it != map.cend(), PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Pointer %p was not allocated via PetscDeviceAllocate()", ptr);
331*0e6b6b59SJacob Faibussowitsch       // if we are here then we did allocate it but the user has tried to do something along
332*0e6b6b59SJacob Faibussowitsch       // the lines of:
333*0e6b6b59SJacob Faibussowitsch       //
334*0e6b6b59SJacob Faibussowitsch       // allocate(&ptr, size);
335*0e6b6b59SJacob Faibussowitsch       // deallocate(ptr+5);
336*0e6b6b59SJacob Faibussowitsch       //
337*0e6b6b59SJacob Faibussowitsch       SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Attempting to deallocate pointer %p which is a suballocation of %p (memtype %s, id %" PetscInt64_FMT ", size %zu bytes)", ptr, it->first, PetscMemTypeToString(it->second.mtype), it->second.id,
338*0e6b6b59SJacob Faibussowitsch               it->second.size);
339*0e6b6b59SJacob Faibussowitsch     }
340*0e6b6b59SJacob Faibussowitsch     PetscCall(PetscDeviceContextGetOptionalNullContext_Internal(&dctx));
341*0e6b6b59SJacob Faibussowitsch 
342*0e6b6b59SJacob Faibussowitsch     // mark intent BEFORE we free, note we mark as write so that we are made to wait on any
343*0e6b6b59SJacob Faibussowitsch     // outstanding reads (don't want to kill the pointer before they are done)
344*0e6b6b59SJacob Faibussowitsch     PetscCall(PetscDeviceContextMarkIntentFromID(dctx, found_it->second.id, PETSC_MEMORY_ACCESS_WRITE, "memory deallocation"));
345*0e6b6b59SJacob Faibussowitsch 
346*0e6b6b59SJacob Faibussowitsch     // do free
347*0e6b6b59SJacob Faibussowitsch     if (dctx->ops->memfree) {
348*0e6b6b59SJacob Faibussowitsch       PetscUseTypeMethod(dctx, memfree, found_it->second.mtype, (void **)&ptr);
349*0e6b6b59SJacob Faibussowitsch     } else {
350*0e6b6b59SJacob Faibussowitsch       PetscCall(PetscDeviceCheckCapable_Private(dctx, PetscMemTypeHost(found_it->second.mtype), "freeing"));
351*0e6b6b59SJacob Faibussowitsch     }
352*0e6b6b59SJacob Faibussowitsch     // if ptr still exists, then the device context could not handle it
353*0e6b6b59SJacob Faibussowitsch     if (ptr) PetscCall(PetscFree(ptr));
354*0e6b6b59SJacob Faibussowitsch     PetscCallCXX(map.erase(found_it));
355*0e6b6b59SJacob Faibussowitsch   }
356*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
357*0e6b6b59SJacob Faibussowitsch }
358*0e6b6b59SJacob Faibussowitsch 
359*0e6b6b59SJacob Faibussowitsch /*@C
360*0e6b6b59SJacob Faibussowitsch   PetscDeviceMemcpy - Copy memory in a device-aware manner
361*0e6b6b59SJacob Faibussowitsch 
362*0e6b6b59SJacob Faibussowitsch   Not Collective, Asynchronous, Auto-dependency aware
363*0e6b6b59SJacob Faibussowitsch 
364*0e6b6b59SJacob Faibussowitsch   Input Parameters:
365*0e6b6b59SJacob Faibussowitsch + dctx - The `PetscDeviceContext` used to copy the memory
366*0e6b6b59SJacob Faibussowitsch . dest - The pointer to copy to
367*0e6b6b59SJacob Faibussowitsch . src  - The pointer to copy from
368*0e6b6b59SJacob Faibussowitsch - n    - The amount (in bytes) to copy
369*0e6b6b59SJacob Faibussowitsch 
370*0e6b6b59SJacob Faibussowitsch   Notes:
371*0e6b6b59SJacob Faibussowitsch   Both `dest` and `src` must have been allocated by `PetscDeviceAllocate()` or
372*0e6b6b59SJacob Faibussowitsch   `PetscDeviceMalloc()` or registered with `PetscDeviceRegisterMemory()`.
373*0e6b6b59SJacob Faibussowitsch 
374*0e6b6b59SJacob Faibussowitsch   `src` and `dest` cannot overlap.
375*0e6b6b59SJacob Faibussowitsch 
376*0e6b6b59SJacob Faibussowitsch   If both `src` and `dest` are on the host this routine is fully synchronous.
377*0e6b6b59SJacob Faibussowitsch 
378*0e6b6b59SJacob Faibussowitsch   The user should prefer `PetscDeviceArrayCopy()` over this routine as it automatically
379*0e6b6b59SJacob Faibussowitsch   computes the number of bytes to copy from the size of the pointer types.
380*0e6b6b59SJacob Faibussowitsch 
381*0e6b6b59SJacob Faibussowitsch   DAG representation:
382*0e6b6b59SJacob Faibussowitsch .vb
383*0e6b6b59SJacob Faibussowitsch   time ->
384*0e6b6b59SJacob Faibussowitsch 
385*0e6b6b59SJacob Faibussowitsch   -> dctx - |= CALL =| - dctx ->
386*0e6b6b59SJacob Faibussowitsch   -> dest --------------------->
387*0e6b6b59SJacob Faibussowitsch   -> src ---------------------->
388*0e6b6b59SJacob Faibussowitsch .ve
389*0e6b6b59SJacob Faibussowitsch 
390*0e6b6b59SJacob Faibussowitsch   Level: intermediate
391*0e6b6b59SJacob Faibussowitsch 
392*0e6b6b59SJacob Faibussowitsch .N ASYNC_API
393*0e6b6b59SJacob Faibussowitsch 
394*0e6b6b59SJacob Faibussowitsch .seealso: `PetscDeviceArrayCopy()`, `PetscDeviceMalloc()`, `PetscDeviceFree()`
395*0e6b6b59SJacob Faibussowitsch @*/
396*0e6b6b59SJacob Faibussowitsch PetscErrorCode PetscDeviceMemcpy(PetscDeviceContext dctx, void *PETSC_RESTRICT dest, const void *PETSC_RESTRICT src, std::size_t n) {
397*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
398*0e6b6b59SJacob Faibussowitsch   if (!n) PetscFunctionReturn(0);
399*0e6b6b59SJacob Faibussowitsch   PetscCheck(dest, PETSC_COMM_SELF, PETSC_ERR_POINTER, "Trying to copy to a NULL pointer");
400*0e6b6b59SJacob Faibussowitsch   PetscCheck(src, PETSC_COMM_SELF, PETSC_ERR_POINTER, "Trying to copy from a NULL pointer");
401*0e6b6b59SJacob Faibussowitsch   if (dest == src) PetscFunctionReturn(0);
402*0e6b6b59SJacob Faibussowitsch   PetscCall(PetscDeviceContextGetOptionalNullContext_Internal(&dctx));
403*0e6b6b59SJacob Faibussowitsch 
404*0e6b6b59SJacob Faibussowitsch   {
405*0e6b6b59SJacob Faibussowitsch     const auto dest_it = memory_map.search_for(dest);
406*0e6b6b59SJacob Faibussowitsch     const auto src_it  = memory_map.search_for(src);
407*0e6b6b59SJacob Faibussowitsch 
408*0e6b6b59SJacob Faibussowitsch     PetscAssert(dest_it != memory_map.map.cend(), PETSC_COMM_SELF, PETSC_ERR_POINTER, "Destination pointer %p was not registered with the memory tracker, call PetscDeviceRegisterMemory() on it", dest);
409*0e6b6b59SJacob Faibussowitsch     PetscAssert(src_it != memory_map.map.cend(), PETSC_COMM_SELF, PETSC_ERR_POINTER, "Source pointer %p was not registered with the memory tracker, call PetscDeviceRegisterMemory() on it", src);
410*0e6b6b59SJacob Faibussowitsch 
411*0e6b6b59SJacob Faibussowitsch     PetscCall(PetscDeviceContextMarkIntentFromID(dctx, src_it->second.id, PETSC_MEMORY_ACCESS_READ, "memory copy (src)"));
412*0e6b6b59SJacob Faibussowitsch     PetscCall(PetscDeviceContextMarkIntentFromID(dctx, dest_it->second.id, PETSC_MEMORY_ACCESS_WRITE, "memory copy (dest)"));
413*0e6b6b59SJacob Faibussowitsch 
414*0e6b6b59SJacob Faibussowitsch     const auto mode = PetscMemTypeToDeviceCopyMode(dest_it->second.mtype, src_it->second.mtype);
415*0e6b6b59SJacob Faibussowitsch     // perform the copy
416*0e6b6b59SJacob Faibussowitsch     if (dctx->ops->memcopy) {
417*0e6b6b59SJacob Faibussowitsch       PetscUseTypeMethod(dctx, memcopy, dest, src, n, mode);
418*0e6b6b59SJacob Faibussowitsch     } else {
419*0e6b6b59SJacob Faibussowitsch       // REVIEW ME: we might potentially need to sync here if the memory is device-allocated
420*0e6b6b59SJacob Faibussowitsch       // (pinned) but being copied by a host dctx
421*0e6b6b59SJacob Faibussowitsch       PetscCall(PetscDeviceCheckCapable_Private(dctx, mode == PETSC_DEVICE_COPY_HTOH, "copying"));
422*0e6b6b59SJacob Faibussowitsch       PetscCall(PetscMemcpy(dest, src, n));
423*0e6b6b59SJacob Faibussowitsch     }
424*0e6b6b59SJacob Faibussowitsch 
425*0e6b6b59SJacob Faibussowitsch     if (mode == PETSC_DEVICE_COPY_HTOD) {
426*0e6b6b59SJacob Faibussowitsch       PetscCall(PetscLogCpuToGpu(n));
427*0e6b6b59SJacob Faibussowitsch     } else if (mode == PETSC_DEVICE_COPY_DTOH) {
428*0e6b6b59SJacob Faibussowitsch       PetscCall(PetscLogGpuToCpu(n));
429*0e6b6b59SJacob Faibussowitsch     }
430*0e6b6b59SJacob Faibussowitsch   }
431*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
432*0e6b6b59SJacob Faibussowitsch }
433*0e6b6b59SJacob Faibussowitsch 
434*0e6b6b59SJacob Faibussowitsch /*@C
435*0e6b6b59SJacob Faibussowitsch   PetscDeviceMemset - Memset device-aware memory
436*0e6b6b59SJacob Faibussowitsch 
437*0e6b6b59SJacob Faibussowitsch   Not Collective, Asynchronous, Auto-dependency aware
438*0e6b6b59SJacob Faibussowitsch 
439*0e6b6b59SJacob Faibussowitsch   Input Parameters:
440*0e6b6b59SJacob Faibussowitsch + dctx  - The `PetscDeviceContext` used to memset the memory
441*0e6b6b59SJacob Faibussowitsch . ptr   - The pointer to the memory
442*0e6b6b59SJacob Faibussowitsch . v     - The value to set
443*0e6b6b59SJacob Faibussowitsch - n     - The amount (in bytes) to set
444*0e6b6b59SJacob Faibussowitsch 
445*0e6b6b59SJacob Faibussowitsch   Notes:
446*0e6b6b59SJacob Faibussowitsch   `ptr` must have been allocated by `PetscDeviceAllocate()` or `PetscDeviceMalloc()` or
447*0e6b6b59SJacob Faibussowitsch   registered with `PetscDeviceRegisterMemory()`.
448*0e6b6b59SJacob Faibussowitsch 
449*0e6b6b59SJacob Faibussowitsch   The user should prefer `PetscDeviceArrayZero()` over this routine as it automatically
450*0e6b6b59SJacob Faibussowitsch   computes the number of bytes to copy from the size of the pointer types, though they should
451*0e6b6b59SJacob Faibussowitsch   note that it only zeros memory.
452*0e6b6b59SJacob Faibussowitsch 
453*0e6b6b59SJacob Faibussowitsch   This routine is analogous to `memset()`. That is, this routine copies the value
454*0e6b6b59SJacob Faibussowitsch   `static_cast<unsigned char>(v)` into each of the first count characters of the object pointed
455*0e6b6b59SJacob Faibussowitsch   to by `dest`.
456*0e6b6b59SJacob Faibussowitsch 
457*0e6b6b59SJacob Faibussowitsch   If `dest` is on device, this routine is asynchronous.
458*0e6b6b59SJacob Faibussowitsch 
459*0e6b6b59SJacob Faibussowitsch   DAG representation:
460*0e6b6b59SJacob Faibussowitsch .vb
461*0e6b6b59SJacob Faibussowitsch   time ->
462*0e6b6b59SJacob Faibussowitsch 
463*0e6b6b59SJacob Faibussowitsch   -> dctx - |= CALL =| - dctx ->
464*0e6b6b59SJacob Faibussowitsch   -> dest --------------------->
465*0e6b6b59SJacob Faibussowitsch .ve
466*0e6b6b59SJacob Faibussowitsch 
467*0e6b6b59SJacob Faibussowitsch   Level: intermediate
468*0e6b6b59SJacob Faibussowitsch 
469*0e6b6b59SJacob Faibussowitsch .N ASYNC_API
470*0e6b6b59SJacob Faibussowitsch 
471*0e6b6b59SJacob Faibussowitsch .seealso: `PetscDeviceArrayZero()`, `PetscDeviceMalloc()`, `PetscDeviceFree()`
472*0e6b6b59SJacob Faibussowitsch @*/
473*0e6b6b59SJacob Faibussowitsch PetscErrorCode PetscDeviceMemset(PetscDeviceContext dctx, void *ptr, PetscInt v, std::size_t n) {
474*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
475*0e6b6b59SJacob Faibussowitsch   if (PetscUnlikely(!n)) PetscFunctionReturn(0);
476*0e6b6b59SJacob Faibussowitsch   PetscCheck(ptr, PETSC_COMM_SELF, PETSC_ERR_POINTER, "Trying to memset a NULL pointer");
477*0e6b6b59SJacob Faibussowitsch   PetscCall(PetscDeviceContextGetOptionalNullContext_Internal(&dctx));
478*0e6b6b59SJacob Faibussowitsch   {
479*0e6b6b59SJacob Faibussowitsch     const auto ptr_it = memory_map.search_for(ptr);
480*0e6b6b59SJacob Faibussowitsch 
481*0e6b6b59SJacob Faibussowitsch     // mark
482*0e6b6b59SJacob Faibussowitsch     PetscAssert(ptr_it != memory_map.map.cend(), PETSC_COMM_SELF, PETSC_ERR_POINTER, "Pointer %p was not registered with the memory tracker, call PetscDeviceRegisterMemory() on it", ptr);
483*0e6b6b59SJacob Faibussowitsch     PetscCall(PetscDeviceContextMarkIntentFromID(dctx, ptr_it->second.id, PETSC_MEMORY_ACCESS_WRITE, "memory set"));
484*0e6b6b59SJacob Faibussowitsch 
485*0e6b6b59SJacob Faibussowitsch     // set
486*0e6b6b59SJacob Faibussowitsch     if (dctx->ops->memset) {
487*0e6b6b59SJacob Faibussowitsch       PetscUseTypeMethod(dctx, memset, ptr_it->second.mtype, ptr, v, n);
488*0e6b6b59SJacob Faibussowitsch     } else {
489*0e6b6b59SJacob Faibussowitsch       // REVIEW ME: we might potentially need to sync here if the memory is device-allocated
490*0e6b6b59SJacob Faibussowitsch       // (pinned) but being memset by a host dctx
491*0e6b6b59SJacob Faibussowitsch       PetscCall(PetscDeviceCheckCapable_Private(dctx, PetscMemTypeHost(ptr_it->second.mtype), "memsetting"));
492*0e6b6b59SJacob Faibussowitsch       std::memset(ptr, static_cast<int>(v), n);
493*0e6b6b59SJacob Faibussowitsch     }
494*0e6b6b59SJacob Faibussowitsch   }
495*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
496*0e6b6b59SJacob Faibussowitsch }
497