xref: /petsc/src/sys/objects/device/interface/mark_dcontext.cxx (revision 2d776b4963042cdf8a412ba09e923aa51facd799)
1 #include "petscdevice_interface_internal.hpp" /*I <petscdevice.h> I*/
2 
3 #include <petsc/private/cpp/object_pool.hpp>
4 #include <petsc/private/cpp/utility.hpp>
5 
6 #include <unordered_map>
7 #include <algorithm> // std::remove_if(), std::find_if()
8 #include <vector>
9 #include <string>
10 #include <sstream> // std::ostringstream
11 
12 #if defined(__clang__)
13   #pragma clang diagnostic push
14   #pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
15 #endif
16 
17 // ==========================================================================================
18 // PetscEvent
19 // ==========================================================================================
20 
21 struct PetscEventAllocator : public Petsc::AllocatorBase<PetscEvent> {
22   PETSC_NODISCARD static PetscErrorCode create(PetscEvent *event) noexcept
23   {
24     PetscFunctionBegin;
25     PetscCall(PetscNew(event));
26     PetscFunctionReturn(0);
27   }
28 
29   PETSC_NODISCARD static PetscErrorCode destroy(PetscEvent event) noexcept
30   {
31     PetscFunctionBegin;
32     PetscCall(reset(event));
33     PetscCall(PetscFree(event));
34     PetscFunctionReturn(0);
35   }
36 
37   PETSC_NODISCARD static PetscErrorCode reset(PetscEvent event, bool zero = true) noexcept
38   {
39     PetscFunctionBegin;
40     if (zero) {
41       if (auto &destroy = event->destroy) {
42         PetscCall((*destroy)(event));
43         destroy = nullptr;
44       }
45       event->dctx_id    = 0;
46       event->dctx_state = 0;
47       PetscAssert(!event->data, PETSC_COMM_SELF, PETSC_ERR_PLIB, "Event failed to destroy its data member: %p", event->data);
48     }
49     event->dtype = PETSC_DEVICE_DEFAULT();
50     PetscFunctionReturn(0);
51   }
52 };
53 
54 static Petsc::ObjectPool<PetscEvent, PetscEventAllocator> event_pool;
55 
56 static PetscErrorCode PetscDeviceContextCreateEvent_Private(PetscDeviceContext dctx, PetscEvent *event)
57 {
58   PetscFunctionBegin;
59   PetscValidDeviceContext(dctx, 1);
60   PetscValidPointer(event, 2);
61   PetscCall(event_pool.allocate(event));
62   PetscCall(PetscDeviceContextGetDeviceType(dctx, &(*event)->dtype));
63   PetscTryTypeMethod(dctx, createevent, *event);
64   PetscFunctionReturn(0);
65 }
66 
67 static PetscErrorCode PetscEventDestroy_Private(PetscEvent *event)
68 {
69   PetscFunctionBegin;
70   PetscValidPointer(event, 1);
71   if (*event) PetscCall(event_pool.deallocate(Petsc::util::exchange(*event, nullptr)));
72   PetscFunctionReturn(0);
73 }
74 
75 static PetscErrorCode PetscDeviceContextRecordEvent_Private(PetscDeviceContext dctx, PetscEvent event)
76 {
77   PetscObjectId    id;
78   PetscObjectState state;
79 
80   PetscFunctionBegin;
81   PetscValidDeviceContext(dctx, 1);
82   PetscValidPointer(event, 2);
83   id    = PetscObjectCast(dctx)->id;
84   state = PetscObjectCast(dctx)->state;
85   // technically state can never be less than event->dctx_state (only equal) but we include
86   // it in the check just in case
87   if ((id == event->dctx_id) && (state <= event->dctx_state)) PetscFunctionReturn(0);
88   if (dctx->ops->recordevent) {
89     // REVIEW ME:
90     // TODO maybe move this to impls, as they can determine whether they can interoperate with
91     // other device types more readily
92     if (PetscDefined(USE_DEBUG) && (event->dtype != PETSC_DEVICE_HOST)) {
93       PetscDeviceType dtype;
94 
95       PetscCall(PetscDeviceContextGetDeviceType(dctx, &dtype));
96       PetscCheck(event->dtype == dtype, PETSC_COMM_SELF, PETSC_ERR_ARG_INCOMP, "Event type %s does not match device context type %s", PetscDeviceTypes[event->dtype], PetscDeviceTypes[dtype]);
97     }
98     PetscUseTypeMethod(dctx, recordevent, event);
99   }
100   event->dctx_id    = id;
101   event->dctx_state = state;
102   PetscFunctionReturn(0);
103 }
104 
105 static PetscErrorCode PetscDeviceContextWaitForEvent_Private(PetscDeviceContext dctx, PetscEvent event)
106 {
107   PetscFunctionBegin;
108   PetscValidDeviceContext(dctx, 1);
109   PetscValidPointer(event, 2);
110   // empty data implies you cannot wait on this event
111   if (!event->data) PetscFunctionReturn(0);
112   if (PetscDefined(USE_DEBUG)) {
113     const auto      etype = event->dtype;
114     PetscDeviceType dtype;
115 
116     PetscCall(PetscDeviceContextGetDeviceType(dctx, &dtype));
117     PetscCheck(etype == dtype, PETSC_COMM_SELF, PETSC_ERR_ARG_INCOMP, "Event type %s does not match device context type %s", PetscDeviceTypes[etype], PetscDeviceTypes[dtype]);
118   }
119   if (PetscObjectCast(dctx)->id == event->dctx_id) PetscFunctionReturn(0);
120   PetscTryTypeMethod(dctx, waitforevent, event);
121   PetscFunctionReturn(0);
122 }
123 
124 // ==========================================================================================
125 // PetscStackFrame
126 //
127 // A helper class that (when debugging is enabled) contains the stack frame from which
128 // PetscDeviceContextMakrIntentFromID(). It is intended to be derived from, since this enables
129 // empty-base-class optimization to kick in when debugging is disabled.
130 // ==========================================================================================
131 
132 template <bool use_debug>
133 struct PetscStackFrame;
134 
135 template <>
136 struct PetscStackFrame</* use_debug = */ true> {
137   std::string file{};
138   std::string function{};
139   int         line{};
140 
141   PetscStackFrame() = default;
142 
143   PetscStackFrame(const char *file_, const char *func_, int line_) noexcept : file(split_on_petsc_path_(file_)), function(func_), line(line_) { }
144 
145   bool operator==(const PetscStackFrame &other) const noexcept { return line == other.line && file == other.file && function == other.function; }
146 
147 private:
148   static std::string split_on_petsc_path_(std::string &&in) noexcept
149   {
150     auto pos = in.find("petsc/src");
151 
152     if (pos == std::string::npos) pos = in.find("petsc/include");
153     if (pos == std::string::npos) pos = 0;
154     return in.substr(pos);
155   }
156 
157   friend std::ostream &operator<<(std::ostream &os, const PetscStackFrame &frame)
158   {
159     os << '(' << frame.function << "() at " << frame.file << ':' << frame.line << ')';
160     return os;
161   }
162 };
163 
164 template <>
165 struct PetscStackFrame</* use_debug = */ false> {
166   template <typename... T>
167   constexpr PetscStackFrame(T &&...) noexcept
168   {
169   }
170 
171   constexpr bool operator==(const PetscStackFrame &) const noexcept { return true; }
172 
173   friend std::ostream &operator<<(std::ostream &os, const PetscStackFrame &) noexcept
174   {
175     os << "(unknown)";
176     return os;
177   }
178 };
179 
180 // ==========================================================================================
181 // MarkedObjectMap
182 //
183 // A mapping from a PetscObjectId to a PetscEvent and (if debugging is enabled) a
184 // PetscStackFrame containing the location where PetscDeviceContextMarkIntentFromID was called
185 // ==========================================================================================
186 
187 class MarkedObjectMap : public Petsc::RegisterFinalizeable<MarkedObjectMap> {
188 public:
189   // Note we derive from PetscStackFrame so that the empty base class optimization can kick
190   // in. If it were just a member it would still take up storage in optimized builds
191   class snapshot_type : private PetscStackFrame<PetscDefined(USE_DEBUG)> {
192   public:
193     using frame_type = PetscStackFrame<PetscDefined(USE_DEBUG)>;
194 
195     snapshot_type() = default;
196     snapshot_type(PetscDeviceContext, frame_type) noexcept;
197 
198     ~snapshot_type() noexcept;
199 
200     // movable
201     snapshot_type(snapshot_type &&) noexcept;
202     snapshot_type &operator=(snapshot_type &&) noexcept;
203 
204     // not copyable
205     snapshot_type(const snapshot_type &) noexcept            = delete;
206     snapshot_type &operator=(const snapshot_type &) noexcept = delete;
207 
208     PETSC_NODISCARD PetscEvent        event() const noexcept { return event_; }
209     PETSC_NODISCARD const frame_type &frame() const noexcept { return *this; }
210     PETSC_NODISCARD frame_type       &frame() noexcept { return *this; }
211     PETSC_NODISCARD PetscObjectId     dctx_id() const noexcept { return event()->dctx_id; }
212 
213   private:
214     PetscEvent event_{}; // the state of device context when this snapshot was recorded
215 
216     PETSC_NODISCARD static PetscEvent init_event_(PetscDeviceContext) noexcept;
217   };
218 
219   // the "value" each key maps to
220   struct mapped_type {
221     using dependency_type = std::vector<snapshot_type>;
222 
223     PetscMemoryAccessMode mode = PETSC_MEMORY_ACCESS_READ;
224     snapshot_type         last_write{};
225     dependency_type       dependencies{};
226   };
227 
228   using map_type = std::unordered_map<PetscObjectId, mapped_type>;
229 
230   map_type map;
231 
232 private:
233   friend class RegisterFinalizeable<MarkedObjectMap>;
234 
235   PETSC_NODISCARD PetscErrorCode finalize_() noexcept;
236 };
237 
238 // ==========================================================================================
239 // MarkedObejctMap Private API
240 // ==========================================================================================
241 
242 inline PetscErrorCode MarkedObjectMap::finalize_() noexcept
243 {
244   PetscFunctionBegin;
245   PetscCall(PetscInfo(nullptr, "Finalizing marked object map\n"));
246   if (PetscDefined(USE_DEBUG)) {
247     std::ostringstream oss;
248     auto               wrote_to_oss = false;
249     const auto         end          = this->map.cend();
250     PetscMPIInt        rank;
251 
252     PetscCallMPI(MPI_Comm_rank(PETSC_COMM_WORLD, &rank));
253     for (auto it = this->map.cbegin(); it != end; ++it) {
254       // need a temporary since we want to prepend "object xxx has orphaned dependencies" if
255       // any of the dependencies have orphans. but we also need to check that in the loop, so
256       // use a temporary to accumulate and then build the rest from it.
257       std::ostringstream oss_tmp;
258       auto               wrote_to_oss_tmp = false;
259       //const auto        &mapped           = it->second;
260       //const auto         mode             = PetscMemoryAccessModes(mapped.mode);
261 
262       // for (auto &&dep : mapped.dependencies) {
263       //   // if (!dep.ctx->options.allow_orphans) {
264       //   //   wrote_to_oss_tmp = true;
265       //   //   oss_tmp<<"  ["<<rank<<"] dctx "<<dep.ctx<<" (id "<<dep.dctx_id()<<", state "<<dep.dctx_state<<", intent "<<mode<<' '<<dep.frame()<<")\n";
266       //   // }
267       // }
268       // check if we wrote to it
269       if (wrote_to_oss_tmp) {
270         oss << '[' << rank << "] object " << it->first << " has orphaned dependencies:\n" << oss_tmp.str();
271         wrote_to_oss = true;
272       }
273     }
274     if (wrote_to_oss) {
275       //PetscCall((*PetscErrorPrintf)("%s\n",oss.str().c_str()));
276       //SETERRQ(PETSC_COMM_SELF,PETSC_ERR_PLIB,"Orphaned dependencies found, see above");
277     }
278   }
279   // replace with new map, since clear() does not necessarily free memory
280   PetscCallCXX(this->map = map_type{});
281   PetscFunctionReturn(0);
282 }
283 
284 // ==========================================================================================
285 // MarkedObejctMap::snapshot_type Private API
286 // ==========================================================================================
287 
288 inline PetscEvent MarkedObjectMap::snapshot_type::init_event_(PetscDeviceContext dctx) noexcept
289 {
290   PetscEvent event = nullptr;
291 
292   PetscFunctionBegin;
293   PetscCallAbort(PETSC_COMM_SELF, PetscDeviceContextCreateEvent_Private(dctx, &event));
294   PetscCallAbort(PETSC_COMM_SELF, PetscDeviceContextRecordEvent_Private(dctx, event));
295   PetscFunctionReturn(event);
296 }
297 
298 // ==========================================================================================
299 // MarkedObejctMap::snapshot_type Public API
300 // ==========================================================================================
301 
302 MarkedObjectMap::snapshot_type::snapshot_type(PetscDeviceContext dctx, frame_type frame) noexcept : frame_type(std::move(frame)), event_(init_event_(dctx)) { }
303 
304 MarkedObjectMap::snapshot_type::~snapshot_type() noexcept
305 {
306   PetscFunctionBegin;
307   PetscCallAbort(PETSC_COMM_SELF, PetscEventDestroy_Private(&event_));
308   PetscFunctionReturnVoid();
309 }
310 
311 // movable
312 MarkedObjectMap::snapshot_type::snapshot_type(snapshot_type &&other) noexcept : frame_type(std::move(other)), event_(Petsc::util::exchange(other.event_, nullptr)) { }
313 
314 MarkedObjectMap::snapshot_type &MarkedObjectMap::snapshot_type::operator=(snapshot_type &&other) noexcept
315 {
316   PetscFunctionBegin;
317   if (this != &other) {
318     frame_type::operator=(std::move(other));
319     PetscCallAbort(PETSC_COMM_SELF, PetscEventDestroy_Private(&event_));
320     event_ = Petsc::util::exchange(other.event_, nullptr);
321   }
322   PetscFunctionReturn(*this);
323 }
324 
325 // A mapping between PetscObjectId (i.e. some PetscObject) to the list of PetscEvent's encoding
326 // the last time the PetscObject was accessed
327 static MarkedObjectMap marked_object_map;
328 
329 // ==========================================================================================
330 // Utility Functions
331 // ==========================================================================================
332 
333 template <typename T>
334 static PetscErrorCode PetscDeviceContextMapIterVisitor(PetscDeviceContext dctx, T &&callback) noexcept
335 {
336   const auto dctx_id    = PetscObjectCast(dctx)->id;
337   auto      &dctx_deps  = CxxDataCast(dctx)->deps;
338   auto      &object_map = marked_object_map.map;
339 
340   PetscFunctionBegin;
341   for (auto &&dep : dctx_deps) {
342     const auto mapit = object_map.find(dep);
343 
344     // Need this check since the final PetscDeviceContext may run through this *after* the map
345     // has been finalized (and cleared), and hence might fail to find its dependencies. This is
346     // perfectly valid since the user no longer cares about dangling dependencies after PETSc
347     // is finalized
348     if (PetscLikely(mapit != object_map.end())) {
349       auto      &deps = mapit->second.dependencies;
350       const auto end  = deps.end();
351       const auto it   = std::remove_if(deps.begin(), end, [&](const MarkedObjectMap::snapshot_type &obj) { return obj.dctx_id() == dctx_id; });
352 
353       PetscCall(callback(mapit, deps.cbegin(), static_cast<decltype(deps.cend())>(it)));
354       // remove ourselves
355       PetscCallCXX(deps.erase(it, end));
356       // continue to next object, but erase this one if it has no more dependencies
357       if (deps.empty()) PetscCallCXX(object_map.erase(mapit));
358     }
359   }
360   PetscCallCXX(dctx_deps.clear());
361   PetscFunctionReturn(0);
362 }
363 
364 PetscErrorCode PetscDeviceContextSyncClearMap_Internal(PetscDeviceContext dctx)
365 {
366   using map_iterator = MarkedObjectMap::map_type::const_iterator;
367   using dep_iterator = MarkedObjectMap::mapped_type::dependency_type::const_iterator;
368 
369   PetscFunctionBegin;
370   PetscCall(PetscDeviceContextMapIterVisitor(dctx, [&](map_iterator mapit, dep_iterator it, dep_iterator end) {
371     PetscFunctionBegin;
372     if (PetscDefined(USE_DEBUG_AND_INFO)) {
373       std::ostringstream oss;
374       const auto         mode = PetscMemoryAccessModeToString(mapit->second.mode);
375 
376       oss << "synced dctx " << PetscObjectCast(dctx)->id << ", remaining leaves for obj " << mapit->first << ": {";
377       while (it != end) {
378         oss << "[dctx " << it->dctx_id() << ", " << mode << ' ' << it->frame() << ']';
379         if (++it != end) oss << ", ";
380       }
381       oss << '}';
382       PetscCall(PetscInfo(nullptr, "%s\n", oss.str().c_str()));
383     }
384     PetscFunctionReturn(0);
385   }));
386   {
387     // the recursive sync clear map call is unbounded in case of a dependenct loop so we make a
388     // copy
389     // clang-format off
390     const std::vector<CxxData::upstream_type::value_type> upstream_copy(
391       std::make_move_iterator(CxxDataCast(dctx)->upstream.begin()),
392       std::make_move_iterator(CxxDataCast(dctx)->upstream.end())
393     );
394     // clang-format on
395 
396     // aftermath, clear our set of parents (to avoid infinite recursion) and mark ourselves as no
397     // longer contained (while the empty graph technically *is* always contained, it is not what
398     // we mean by it)
399     PetscCall(CxxDataCast(dctx)->clear());
400     //dctx->contained = PETSC_FALSE;
401     for (auto &&upstrm : upstream_copy) {
402       // check that this parent still points to what we originally thought it was
403       PetscCheck(upstrm.second.id == PetscObjectCast(upstrm.first)->id, PETSC_COMM_SELF, PETSC_ERR_PLIB, "Upstream dctx %" PetscInt64_FMT " no longer exists, now has id %" PetscInt64_FMT, upstrm.second.id, PetscObjectCast(upstrm.first)->id);
404       PetscCall(PetscDeviceContextSyncClearMap_Internal(upstrm.first));
405     }
406   }
407   PetscFunctionReturn(0);
408 }
409 
410 PetscErrorCode PetscDeviceContextCheckNotOrphaned_Internal(PetscDeviceContext dctx)
411 {
412   std::ostringstream oss;
413   //const auto         allow = dctx->options.allow_orphans, contained = dctx->contained;
414   const auto allow = true, contained = true;
415   auto       wrote_to_oss = false;
416   using map_iterator      = MarkedObjectMap::map_type::const_iterator;
417   using dep_iterator      = MarkedObjectMap::mapped_type::dependency_type::const_iterator;
418 
419   PetscFunctionBegin;
420   PetscCall(PetscDeviceContextMapIterVisitor(dctx, [&](map_iterator mapit, dep_iterator it, dep_iterator end) {
421     PetscFunctionBegin;
422     if (allow || contained) PetscFunctionReturn(0);
423     wrote_to_oss = true;
424     oss << "- PetscObject (id " << mapit->first << "), intent " << PetscMemoryAccessModeToString(mapit->second.mode) << ' ' << it->frame();
425     if (std::distance(it, end) == 0) oss << " (orphaned)"; // we were the only dependency
426     oss << '\n';
427     PetscFunctionReturn(0);
428   }));
429   PetscCheck(!wrote_to_oss, PETSC_COMM_SELF, PETSC_ERR_ORDER, "Destroying PetscDeviceContext ('%s', id %" PetscInt64_FMT ") would leave the following dangling (possibly orphaned) dependants:\n%s\nMust synchronize before destroying it, or allow it to be destroyed with orphans",
430              PetscObjectCast(dctx)->name ? PetscObjectCast(dctx)->name : "unnamed", PetscObjectCast(dctx)->id, oss.str().c_str());
431   PetscCall(CxxDataCast(dctx)->clear());
432   PetscFunctionReturn(0);
433 }
434 
435 template <bool use_debug>
436 static PetscErrorCode PetscDeviceContextMarkIntentFromID_Private(PetscDeviceContext dctx, PetscObjectId id, PetscMemoryAccessMode mode, PetscStackFrame<use_debug> frame, const char *name)
437 {
438 #define DEBUG_INFO(mess, ...) PetscDebugInfo(dctx, "dctx %" PetscInt64_FMT " (%s) - obj %" PetscInt64_FMT " (%s): " mess, dctx_id, PetscObjectCast(dctx)->name ? PetscObjectCast(dctx)->name : "unnamed", id, name, ##__VA_ARGS__)
439   const auto dctx_id             = PetscObjectCast(dctx)->id;
440   auto      &marked              = marked_object_map.map[id];
441   auto      &old_mode            = marked.mode;
442   auto      &object_dependencies = marked.dependencies;
443 
444   PetscFunctionBegin;
445   if ((mode == PETSC_MEMORY_ACCESS_READ) && (old_mode == mode)) {
446     const auto end = object_dependencies.end();
447     const auto it  = std::find_if(object_dependencies.begin(), end, [&](const MarkedObjectMap::snapshot_type &obj) { return obj.dctx_id() == dctx_id; });
448 
449     PetscCall(DEBUG_INFO("new mode (%s) COMPATIBLE with %s mode (%s), no need to serialize\n", PetscMemoryAccessModeToString(mode), PetscMemoryAccessModeToString(old_mode), object_dependencies.empty() ? "default" : "old"));
450     if (it != end) {
451       // we have been here before, all we must do is update our entry then we can bail
452       PetscCall(DEBUG_INFO("found old self as dependency, updating\n"));
453       PetscAssert(CxxDataCast(dctx)->deps.find(id) != CxxDataCast(dctx)->deps.end(), PETSC_COMM_SELF, PETSC_ERR_PLIB, "PetscDeviceContext %" PetscInt64_FMT " listed as dependency for object %" PetscInt64_FMT " (%s), but does not have the object in private dependency list!", dctx_id, id, name);
454 
455       it->frame() = std::move(frame);
456       PetscCall(PetscDeviceContextRecordEvent_Private(dctx, it->event()));
457       PetscFunctionReturn(0);
458     }
459 
460     // we have not been here before, need to serialize with the last write event (if it exists)
461     // and add ourselves to the dependency list
462     if (const auto event = marked.last_write.event()) PetscCall(PetscDeviceContextWaitForEvent_Private(dctx, event));
463   } else {
464     // we are incompatible with the previous mode
465     PetscCall(DEBUG_INFO("new mode (%s) NOT COMPATIBLE with %s mode (%s), serializing then clearing (%zu) %s\n", PetscMemoryAccessModeToString(mode), object_dependencies.empty() ? "default" : "old", PetscMemoryAccessModeToString(old_mode),
466                          object_dependencies.size(), object_dependencies.size() == 1 ? "dependency" : "dependencies"));
467     for (const auto &dep : object_dependencies) {
468       if (dep.dctx_id() == dctx_id) {
469         PetscCall(DEBUG_INFO("found old self as dependency, skipping\n"));
470         continue;
471       }
472       PetscCall(PetscDeviceContextWaitForEvent_Private(dctx, dep.event()));
473     }
474 
475     // if the previous mode wrote, bump it to the previous write spot
476     if (PetscMemoryAccessWrite(old_mode)) {
477       PetscAssert(object_dependencies.size() == 1, PETSC_COMM_SELF, PETSC_ERR_PLIB, "Can only have a single writer as dependency!");
478       PetscCall(DEBUG_INFO("moving last write dependency (intent %s)\n", PetscMemoryAccessModeToString(old_mode)));
479       // note the move around object_dependencies.back() not around event(), this is to enable
480       // the rvalue event() overload
481       marked.last_write = std::move(object_dependencies.back());
482     }
483 
484     // clear out the old dependencies and update the mode, we are about to append ourselves
485     object_dependencies.clear();
486     old_mode = mode;
487   }
488   // become the new leaf by appending ourselves
489   PetscCall(DEBUG_INFO("%s with intent %s\n", object_dependencies.empty() ? "dependency list is empty, creating new leaf" : "appending to existing leaves", PetscMemoryAccessModeToString(mode)));
490   PetscCallCXX(object_dependencies.emplace_back(dctx, std::move(frame)));
491   PetscCallCXX(CxxDataCast(dctx)->deps.emplace(id));
492   PetscFunctionReturn(0);
493 #undef DEBUG_INFO
494 }
495 
496 /*@C
497   PetscDeviceContextMarkIntentFromID - Indicate a `PetscDeviceContext`s access intent to the
498   auto-dependency system
499 
500   Not Collective
501 
502   Input Parameters:
503 + dctx - The `PetscDeviceContext`
504 . id   - The `PetscObjectId` to mark
505 . mode - The desired access intent
506 - name - The object name (for debug purposes, ignored in optimized builds)
507 
508   Notes:
509   This routine formally informs the dependency system that `dctx` will access the object
510   represented by `id` with `mode` and adds `dctx` to `id`'s list of dependencies (termed
511   "leaves").
512 
513   If the existing set of leaves have an incompatible `PetscMemoryAccessMode` to `mode`, `dctx`
514   will be serialized against them.
515 
516   Level: intermediate
517 
518 .seealso: `PetscDeviceContextWaitForContext()`, `PetscDeviceContextSynchronize()`,
519 `PetscObjectGetId()`, `PetscMemoryAccessMode`
520 @*/
521 PetscErrorCode PetscDeviceContextMarkIntentFromID(PetscDeviceContext dctx, PetscObjectId id, PetscMemoryAccessMode mode, const char name[])
522 {
523 #if PetscDefined(USE_DEBUG)
524   const auto index    = petscstack.currentsize > 2 ? petscstack.currentsize - 2 : 0;
525   const auto file     = petscstack.file[index];
526   const auto function = petscstack.function[index];
527   const auto line     = petscstack.line[index];
528 #else
529   constexpr const char *file     = nullptr;
530   constexpr const char *function = nullptr;
531   constexpr auto        line     = 0;
532 #endif
533 
534   PetscFunctionBegin;
535   PetscCall(PetscDeviceContextGetOptionalNullContext_Internal(&dctx));
536   if (name) PetscValidCharPointer(name, 4);
537   PetscCall(marked_object_map.register_finalize());
538   PetscCall(PetscLogEventBegin(DCONTEXT_Mark, dctx, nullptr, nullptr, nullptr));
539   PetscCall(PetscDeviceContextMarkIntentFromID_Private(dctx, id, mode, MarkedObjectMap::snapshot_type::frame_type{file, function, line}, name ? name : "unknown object"));
540   PetscCall(PetscLogEventEnd(DCONTEXT_Mark, dctx, nullptr, nullptr, nullptr));
541   PetscFunctionReturn(0);
542 }
543 
544 #if defined(__clang__)
545   #pragma clang diagnostic pop
546 #endif
547