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