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