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