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