xref: /petsc/src/sys/objects/device/interface/mark_dcontext.cxx (revision 7c441f3aff93c611491d4ea0564d57010b1fd4e9)
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   PETSC_NODISCARD std::string to_string() const noexcept
148   {
149     std::string ret;
150 
151     ret = '(' + function + "() at " + file + ':' + std::to_string(line) + ')';
152     return ret;
153   }
154 
155 private:
156   static std::string split_on_petsc_path_(std::string &&in) noexcept
157   {
158     auto pos = in.find("petsc/src");
159 
160     if (pos == std::string::npos) pos = in.find("petsc/include");
161     if (pos == std::string::npos) pos = 0;
162     return in.substr(pos);
163   }
164 
165   friend std::ostream &operator<<(std::ostream &os, const PetscStackFrame &frame)
166   {
167     os << frame.to_string();
168     return os;
169   }
170 
171   friend void swap(PetscStackFrame &lhs, PetscStackFrame &rhs) noexcept
172   {
173     using std::swap;
174 
175     swap(lhs.file, rhs.file);
176     swap(lhs.function, rhs.function);
177     swap(lhs.line, rhs.line);
178   }
179 };
180 
181 template <>
182 struct PetscStackFrame</* use_debug = */ false> {
183   template <typename... T>
184   constexpr PetscStackFrame(T &&...) noexcept
185   {
186   }
187 
188   constexpr bool operator==(const PetscStackFrame &) const noexcept { return true; }
189 
190   PETSC_NODISCARD static std::string to_string() noexcept { return "(unknown)"; }
191 
192   friend std::ostream &operator<<(std::ostream &os, const PetscStackFrame &) noexcept
193   {
194     os << "(unknown)";
195     return os;
196   }
197 };
198 
199 // ==========================================================================================
200 // MarkedObjectMap
201 //
202 // A mapping from a PetscObjectId to a PetscEvent and (if debugging is enabled) a
203 // PetscStackFrame containing the location where PetscDeviceContextMarkIntentFromID was called
204 // ==========================================================================================
205 
206 class MarkedObjectMap : public Petsc::RegisterFinalizeable<MarkedObjectMap> {
207 public:
208   // Note we derive from PetscStackFrame so that the empty base class optimization can kick
209   // in. If it were just a member it would still take up storage in optimized builds
210   class snapshot_type : private PetscStackFrame<PetscDefined(USE_DEBUG)> {
211   public:
212     using frame_type = PetscStackFrame<PetscDefined(USE_DEBUG)>;
213 
214     snapshot_type() = default;
215     snapshot_type(PetscDeviceContext, frame_type) noexcept;
216 
217     ~snapshot_type() noexcept;
218 
219     // movable
220     snapshot_type(snapshot_type &&) noexcept;
221     snapshot_type &operator=(snapshot_type &&) noexcept;
222 
223     // not copyable
224     snapshot_type(const snapshot_type &) noexcept            = delete;
225     snapshot_type &operator=(const snapshot_type &) noexcept = delete;
226 
227     PETSC_NODISCARD PetscEvent        event() const noexcept { return event_; }
228     PETSC_NODISCARD const frame_type &frame() const noexcept { return *this; }
229     PETSC_NODISCARD frame_type       &frame() noexcept { return *this; }
230 
231     PETSC_NODISCARD PetscObjectId dctx_id() const noexcept
232     {
233       PetscFunctionBegin;
234       PetscAssertAbort(event(), PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE, "Snapshot %s does not contain an event!", frame().to_string().c_str());
235       PetscFunctionReturn(event()->dctx_id);
236     }
237 
238     PETSC_NODISCARD PetscErrorCode ensure_event(PetscDeviceContext) noexcept;
239 
240     friend void swap(snapshot_type &, snapshot_type &) noexcept;
241 
242   private:
243     PetscEvent event_{}; // the state of device context when this snapshot was recorded
244 
245     PETSC_NODISCARD static PetscEvent init_event_(PetscDeviceContext) noexcept;
246   };
247 
248   // the "value" each key maps to
249   struct mapped_type {
250     using dependency_type = std::vector<snapshot_type>;
251 
252     PetscMemoryAccessMode mode = PETSC_MEMORY_ACCESS_READ;
253     snapshot_type         last_write{};
254     dependency_type       dependencies{};
255   };
256 
257   using map_type = std::unordered_map<PetscObjectId, mapped_type>;
258 
259   map_type map;
260 
261 private:
262   friend class RegisterFinalizeable<MarkedObjectMap>;
263 
264   PETSC_NODISCARD PetscErrorCode finalize_() noexcept;
265 };
266 
267 // ==========================================================================================
268 // MarkedObejctMap Private API
269 // ==========================================================================================
270 
271 inline PetscErrorCode MarkedObjectMap::finalize_() noexcept
272 {
273   PetscFunctionBegin;
274   PetscCall(PetscInfo(nullptr, "Finalizing marked object map\n"));
275   if (PetscDefined(USE_DEBUG)) {
276     std::ostringstream oss;
277     auto               wrote_to_oss = false;
278     const auto         end          = this->map.cend();
279     PetscMPIInt        rank;
280 
281     PetscCallMPI(MPI_Comm_rank(PETSC_COMM_WORLD, &rank));
282     for (auto it = this->map.cbegin(); it != end; ++it) {
283       // need a temporary since we want to prepend "object xxx has orphaned dependencies" if
284       // any of the dependencies have orphans. but we also need to check that in the loop, so
285       // use a temporary to accumulate and then build the rest from it.
286       std::ostringstream oss_tmp;
287       auto               wrote_to_oss_tmp = false;
288       //const auto        &mapped           = it->second;
289       //const auto         mode             = PetscMemoryAccessModes(mapped.mode);
290 
291       // for (auto &&dep : mapped.dependencies) {
292       //   // if (!dep.ctx->options.allow_orphans) {
293       //   //   wrote_to_oss_tmp = true;
294       //   //   oss_tmp<<"  ["<<rank<<"] dctx "<<dep.ctx<<" (id "<<dep.dctx_id()<<", state "<<dep.dctx_state<<", intent "<<mode<<' '<<dep.frame()<<")\n";
295       //   // }
296       // }
297       // check if we wrote to it
298       if (wrote_to_oss_tmp) {
299         oss << '[' << rank << "] object " << it->first << " has orphaned dependencies:\n" << oss_tmp.str();
300         wrote_to_oss = true;
301       }
302     }
303     if (wrote_to_oss) {
304       //PetscCall((*PetscErrorPrintf)("%s\n",oss.str().c_str()));
305       //SETERRQ(PETSC_COMM_SELF,PETSC_ERR_PLIB,"Orphaned dependencies found, see above");
306     }
307   }
308   // replace with new map, since clear() does not necessarily free memory
309   PetscCallCXX(this->map = map_type{});
310   PetscFunctionReturn(0);
311 }
312 
313 // ==========================================================================================
314 // MarkedObejctMap::snapshot_type Private API
315 // ==========================================================================================
316 
317 inline PetscEvent MarkedObjectMap::snapshot_type::init_event_(PetscDeviceContext dctx) noexcept
318 {
319   PetscEvent event = nullptr;
320 
321   PetscFunctionBegin;
322   PetscCallAbort(PETSC_COMM_SELF, PetscDeviceContextCreateEvent_Private(dctx, &event));
323   PetscCallAbort(PETSC_COMM_SELF, PetscDeviceContextRecordEvent_Private(dctx, event));
324   PetscFunctionReturn(event);
325 }
326 
327 // ==========================================================================================
328 // MarkedObejctMap::snapshot_type Public API
329 // ==========================================================================================
330 
331 MarkedObjectMap::snapshot_type::snapshot_type(PetscDeviceContext dctx, frame_type frame) noexcept : frame_type(std::move(frame)), event_(init_event_(dctx)) { }
332 
333 MarkedObjectMap::snapshot_type::~snapshot_type() noexcept
334 {
335   PetscFunctionBegin;
336   PetscCallAbort(PETSC_COMM_SELF, PetscEventDestroy_Private(&event_));
337   PetscFunctionReturnVoid();
338 }
339 
340 // movable
341 MarkedObjectMap::snapshot_type::snapshot_type(snapshot_type &&other) noexcept : frame_type(std::move(other)), event_(Petsc::util::exchange(other.event_, nullptr)) { }
342 
343 MarkedObjectMap::snapshot_type &MarkedObjectMap::snapshot_type::operator=(snapshot_type &&other) noexcept
344 {
345   PetscFunctionBegin;
346   if (this != &other) {
347     frame_type::operator=(std::move(other));
348     PetscCallAbort(PETSC_COMM_SELF, PetscEventDestroy_Private(&event_));
349     event_ = Petsc::util::exchange(other.event_, nullptr);
350   }
351   PetscFunctionReturn(*this);
352 }
353 
354 PetscErrorCode MarkedObjectMap::snapshot_type::ensure_event(PetscDeviceContext dctx) noexcept
355 {
356   PetscFunctionBegin;
357   if (PetscUnlikely(!event_)) PetscCall(PetscDeviceContextCreateEvent_Private(dctx, &event_));
358   PetscFunctionReturn(0);
359 }
360 
361 void swap(MarkedObjectMap::snapshot_type &lhs, MarkedObjectMap::snapshot_type &rhs) noexcept
362 {
363   using std::swap;
364 
365   swap(lhs.frame(), rhs.frame());
366   swap(lhs.event_, rhs.event_);
367 }
368 
369 // A mapping between PetscObjectId (i.e. some PetscObject) to the list of PetscEvent's encoding
370 // the last time the PetscObject was accessed
371 static MarkedObjectMap marked_object_map;
372 
373 // ==========================================================================================
374 // Utility Functions
375 // ==========================================================================================
376 
377 template <typename T>
378 static PetscErrorCode PetscDeviceContextMapIterVisitor(PetscDeviceContext dctx, T &&callback) noexcept
379 {
380   const auto dctx_id    = PetscObjectCast(dctx)->id;
381   auto      &dctx_deps  = CxxDataCast(dctx)->deps;
382   auto      &object_map = marked_object_map.map;
383 
384   PetscFunctionBegin;
385   for (auto &&dep : dctx_deps) {
386     const auto mapit = object_map.find(dep);
387 
388     // Need this check since the final PetscDeviceContext may run through this *after* the map
389     // has been finalized (and cleared), and hence might fail to find its dependencies. This is
390     // perfectly valid since the user no longer cares about dangling dependencies after PETSc
391     // is finalized
392     if (PetscLikely(mapit != object_map.end())) {
393       auto      &deps = mapit->second.dependencies;
394       const auto end  = deps.end();
395       const auto it   = std::remove_if(deps.begin(), end, [&](const MarkedObjectMap::snapshot_type &obj) { return obj.dctx_id() == dctx_id; });
396 
397       PetscCall(callback(mapit, deps.cbegin(), static_cast<decltype(deps.cend())>(it)));
398       // remove ourselves
399       PetscCallCXX(deps.erase(it, end));
400       // continue to next object, but erase this one if it has no more dependencies
401       if (deps.empty()) PetscCallCXX(object_map.erase(mapit));
402     }
403   }
404   PetscCallCXX(dctx_deps.clear());
405   PetscFunctionReturn(0);
406 }
407 
408 PetscErrorCode PetscDeviceContextSyncClearMap_Internal(PetscDeviceContext dctx)
409 {
410   using map_iterator = MarkedObjectMap::map_type::const_iterator;
411   using dep_iterator = MarkedObjectMap::mapped_type::dependency_type::const_iterator;
412 
413   PetscFunctionBegin;
414   PetscCall(PetscDeviceContextMapIterVisitor(dctx, [&](map_iterator mapit, dep_iterator it, dep_iterator end) {
415     PetscFunctionBegin;
416     if (PetscDefined(USE_DEBUG_AND_INFO)) {
417       std::ostringstream oss;
418       const auto         mode = PetscMemoryAccessModeToString(mapit->second.mode);
419 
420       oss << "synced dctx " << PetscObjectCast(dctx)->id << ", remaining leaves for obj " << mapit->first << ": {";
421       while (it != end) {
422         oss << "[dctx " << it->dctx_id() << ", " << mode << ' ' << it->frame() << ']';
423         if (++it != end) oss << ", ";
424       }
425       oss << '}';
426       PetscCall(PetscInfo(nullptr, "%s\n", oss.str().c_str()));
427     }
428     PetscFunctionReturn(0);
429   }));
430   {
431     // the recursive sync clear map call is unbounded in case of a dependenct loop so we make a
432     // copy
433     // clang-format off
434     const std::vector<CxxData::upstream_type::value_type> upstream_copy(
435       std::make_move_iterator(CxxDataCast(dctx)->upstream.begin()),
436       std::make_move_iterator(CxxDataCast(dctx)->upstream.end())
437     );
438     // clang-format on
439 
440     // aftermath, clear our set of parents (to avoid infinite recursion) and mark ourselves as no
441     // longer contained (while the empty graph technically *is* always contained, it is not what
442     // we mean by it)
443     PetscCall(CxxDataCast(dctx)->clear());
444     //dctx->contained = PETSC_FALSE;
445     for (auto &&upstrm : upstream_copy) {
446       // check that this parent still points to what we originally thought it was
447       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);
448       PetscCall(PetscDeviceContextSyncClearMap_Internal(upstrm.first));
449     }
450   }
451   PetscFunctionReturn(0);
452 }
453 
454 PetscErrorCode PetscDeviceContextCheckNotOrphaned_Internal(PetscDeviceContext dctx)
455 {
456   std::ostringstream oss;
457   //const auto         allow = dctx->options.allow_orphans, contained = dctx->contained;
458   const auto allow = true, contained = true;
459   auto       wrote_to_oss = false;
460   using map_iterator      = MarkedObjectMap::map_type::const_iterator;
461   using dep_iterator      = MarkedObjectMap::mapped_type::dependency_type::const_iterator;
462 
463   PetscFunctionBegin;
464   PetscCall(PetscDeviceContextMapIterVisitor(dctx, [&](map_iterator mapit, dep_iterator it, dep_iterator end) {
465     PetscFunctionBegin;
466     if (allow || contained) PetscFunctionReturn(0);
467     wrote_to_oss = true;
468     oss << "- PetscObject (id " << mapit->first << "), intent " << PetscMemoryAccessModeToString(mapit->second.mode) << ' ' << it->frame();
469     if (std::distance(it, end) == 0) oss << " (orphaned)"; // we were the only dependency
470     oss << '\n';
471     PetscFunctionReturn(0);
472   }));
473   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",
474              PetscObjectCast(dctx)->name ? PetscObjectCast(dctx)->name : "unnamed", PetscObjectCast(dctx)->id, oss.str().c_str());
475   PetscCall(CxxDataCast(dctx)->clear());
476   PetscFunctionReturn(0);
477 }
478 
479 #define DEBUG_INFO(mess, ...) PetscDebugInfo(dctx, "dctx %" PetscInt64_FMT " (%s) - obj %" PetscInt64_FMT " (%s): " mess, PetscObjectCast(dctx)->id, PetscObjectCast(dctx)->name ? PetscObjectCast(dctx)->name : "unnamed", id, name, ##__VA_ARGS__)
480 
481 // The current mode is compatible with the previous mode (i.e. read-read) so we need only
482 // update the existing version and possibly appeand ourselves to the dependency list
483 
484 template <bool use_debug>
485 static PetscErrorCode MarkFromID_CompatibleModes(MarkedObjectMap::mapped_type &marked, PetscDeviceContext dctx, PetscObjectId id, PetscMemoryAccessMode mode, PetscStackFrame<use_debug> &frame, const char *PETSC_UNUSED name, bool *update_object_dependencies)
486 {
487   const auto dctx_id             = PetscObjectCast(dctx)->id;
488   auto      &object_dependencies = marked.dependencies;
489   const auto end                 = object_dependencies.end();
490   const auto it                  = std::find_if(object_dependencies.begin(), end, [&](const MarkedObjectMap::snapshot_type &obj) { return obj.dctx_id() == dctx_id; });
491 
492   PetscFunctionBegin;
493   PetscCall(DEBUG_INFO("new mode (%s) COMPATIBLE with %s mode (%s), no need to serialize\n", PetscMemoryAccessModeToString(mode), object_dependencies.empty() ? "default" : "old", PetscMemoryAccessModeToString(marked.mode)));
494   if (it != end) {
495     using std::swap;
496 
497     // we have been here before, all we must do is update our entry then we can bail
498     PetscCall(DEBUG_INFO("found old self as dependency, updating\n"));
499     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);
500     swap(it->frame(), frame);
501     PetscCall(PetscDeviceContextRecordEvent_Private(dctx, it->event()));
502     *update_object_dependencies = false;
503     PetscFunctionReturn(0);
504   }
505 
506   // we have not been here before, need to serialize with the last write event (if it exists)
507   // and add ourselves to the dependency list
508   if (const auto event = marked.last_write.event()) PetscCall(PetscDeviceContextWaitForEvent_Private(dctx, event));
509   PetscFunctionReturn(0);
510 }
511 
512 template <bool use_debug>
513 static PetscErrorCode MarkFromID_IncompatibleModes_UpdateLastWrite(MarkedObjectMap::mapped_type &marked, PetscDeviceContext dctx, PetscObjectId id, PetscMemoryAccessMode mode, PetscStackFrame<use_debug> &frame, const char *PETSC_UNUSED name, bool *update_object_dependencies)
514 {
515   const auto      dctx_id    = PetscObjectCast(dctx)->id;
516   auto           &last_write = marked.last_write;
517   auto           &last_dep   = marked.dependencies.back();
518   PetscDeviceType dtype;
519 
520   PetscFunctionBegin;
521   PetscAssert(marked.dependencies.size() == 1, PETSC_COMM_SELF, PETSC_ERR_PLIB, "Can only have a single writer as dependency, have %zu!", marked.dependencies.size());
522   PetscCall(PetscDeviceContextGetDeviceType(dctx, &dtype));
523   if (last_dep.event()->dtype != dtype) {
524     PetscCall(DEBUG_INFO("moving last write dependency (intent %s)\n", PetscMemoryAccessModeToString(marked.mode)));
525     last_write = std::move(last_dep);
526     PetscFunctionReturn(0);
527   }
528 
529   // we match the device type of the dependency, we can reuse its event!
530   auto      &dctx_upstream_deps     = CxxDataCast(dctx)->deps;
531   const auto last_write_was_also_us = last_write.event() && (last_write.dctx_id() == dctx_id);
532   using std::swap;
533 
534   PetscCall(DEBUG_INFO("we matched the previous write dependency's (intent %s) device type (%s), swapping last dependency with last write\n", PetscMemoryAccessModeToString(marked.mode), PetscDeviceTypes[dtype]));
535   if (last_dep.event()->dctx_id != dctx_id) dctx_upstream_deps.emplace(id);
536   PetscAssert(dctx_upstream_deps.find(id) != dctx_upstream_deps.end(), PETSC_COMM_SELF, PETSC_ERR_PLIB, "Did not find id %" PetscInt64_FMT "in object dependencies, but we have apparently recorded the last dependency %s!", id,
537               last_write.frame().to_string().c_str());
538   swap(last_write, last_dep);
539   if (last_write_was_also_us) {
540     PetscCall(DEBUG_INFO("we were also the last write event (intent %s), updating\n", PetscMemoryAccessModeToString(mode)));
541     // we are both the last to write *and* the last to leave a write event. This is the
542     // fast path, we only need to update the frame and update the recorded event
543     swap(last_dep.frame(), frame);
544     // last used to be last_write which is not guaranteed to have an event, so must
545     // create it now
546     PetscCall(last_dep.ensure_event(dctx));
547     PetscCall(PetscDeviceContextRecordEvent_Private(dctx, last_dep.event()));
548     *update_object_dependencies = false;
549   }
550   PetscFunctionReturn(0);
551 }
552 
553 // The current mode is NOT compatible with the previous mode. We must serialize with all events
554 // in the dependency list, possibly clear it, and update the previous write event
555 
556 template <bool use_debug>
557 static PetscErrorCode MarkFromID_IncompatibleModes(MarkedObjectMap::mapped_type &marked, PetscDeviceContext dctx, PetscObjectId id, PetscMemoryAccessMode mode, PetscStackFrame<use_debug> &frame, const char *name, bool *update_object_dependencies)
558 {
559   auto &old_mode            = marked.mode;
560   auto &object_dependencies = marked.dependencies;
561 
562   PetscFunctionBegin;
563   // we are NOT compatible  with the previous mode
564   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),
565                        object_dependencies.size(), object_dependencies.size() == 1 ? "dependency" : "dependencies"));
566 
567   for (const auto &dep : object_dependencies) PetscCall(PetscDeviceContextWaitForEvent_Private(dctx, dep.event()));
568   // if the previous mode wrote, update the last write node with it
569   if (PetscMemoryAccessWrite(old_mode)) PetscCall(MarkFromID_IncompatibleModes_UpdateLastWrite(marked, dctx, id, mode, frame, name, update_object_dependencies));
570 
571   old_mode = mode;
572   // clear out the old dependencies if are about to append ourselves
573   if (*update_object_dependencies) object_dependencies.clear();
574   PetscFunctionReturn(0);
575 }
576 
577 template <bool use_debug>
578 static PetscErrorCode PetscDeviceContextMarkIntentFromID_Private(PetscDeviceContext dctx, PetscObjectId id, PetscMemoryAccessMode mode, PetscStackFrame<use_debug> frame, const char *name)
579 {
580   auto &marked                     = marked_object_map.map[id];
581   auto &object_dependencies        = marked.dependencies;
582   auto  update_object_dependencies = true;
583 
584   PetscFunctionBegin;
585   if ((marked.mode == PETSC_MEMORY_ACCESS_READ) && (mode == PETSC_MEMORY_ACCESS_READ)) {
586     PetscCall(MarkFromID_CompatibleModes(marked, dctx, id, mode, frame, name, &update_object_dependencies));
587   } else {
588     PetscCall(MarkFromID_IncompatibleModes(marked, dctx, id, mode, frame, name, &update_object_dependencies));
589   }
590   if (update_object_dependencies) {
591     // become the new leaf by appending ourselves
592     PetscCall(DEBUG_INFO("%s with intent %s\n", object_dependencies.empty() ? "dependency list is empty, creating new leaf" : "appending to existing leaves", PetscMemoryAccessModeToString(mode)));
593     PetscCallCXX(object_dependencies.emplace_back(dctx, std::move(frame)));
594     PetscCallCXX(CxxDataCast(dctx)->deps.emplace(id));
595   }
596   PetscFunctionReturn(0);
597 }
598 
599 #undef DEBUG_INFO
600 
601 /*@C
602   PetscDeviceContextMarkIntentFromID - Indicate a `PetscDeviceContext`s access intent to the
603   auto-dependency system
604 
605   Not Collective
606 
607   Input Parameters:
608 + dctx - The `PetscDeviceContext`
609 . id   - The `PetscObjectId` to mark
610 . mode - The desired access intent
611 - name - The object name (for debug purposes, ignored in optimized builds)
612 
613   Notes:
614   This routine formally informs the dependency system that `dctx` will access the object
615   represented by `id` with `mode` and adds `dctx` to `id`'s list of dependencies (termed
616   "leaves").
617 
618   If the existing set of leaves have an incompatible `PetscMemoryAccessMode` to `mode`, `dctx`
619   will be serialized against them.
620 
621   Level: intermediate
622 
623 .seealso: `PetscDeviceContextWaitForContext()`, `PetscDeviceContextSynchronize()`,
624 `PetscObjectGetId()`, `PetscMemoryAccessMode`
625 @*/
626 PetscErrorCode PetscDeviceContextMarkIntentFromID(PetscDeviceContext dctx, PetscObjectId id, PetscMemoryAccessMode mode, const char name[])
627 {
628 #if PetscDefined(USE_DEBUG)
629   const auto index    = petscstack.currentsize > 2 ? petscstack.currentsize - 2 : 0;
630   const auto file     = petscstack.file[index];
631   const auto function = petscstack.function[index];
632   const auto line     = petscstack.line[index];
633 #else
634   constexpr const char *file     = nullptr;
635   constexpr const char *function = nullptr;
636   constexpr auto        line     = 0;
637 #endif
638 
639   PetscFunctionBegin;
640   PetscCall(PetscDeviceContextGetOptionalNullContext_Internal(&dctx));
641   if (name) PetscValidCharPointer(name, 4);
642   PetscCall(marked_object_map.register_finalize());
643   PetscCall(PetscLogEventBegin(DCONTEXT_Mark, dctx, nullptr, nullptr, nullptr));
644   PetscCall(PetscDeviceContextMarkIntentFromID_Private(dctx, id, mode, MarkedObjectMap::snapshot_type::frame_type{file, function, line}, name ? name : "unknown object"));
645   PetscCall(PetscLogEventEnd(DCONTEXT_Mark, dctx, nullptr, nullptr, nullptr));
646   PetscFunctionReturn(0);
647 }
648 
649 #if defined(__clang__)
650   #pragma clang diagnostic pop
651 #endif
652