Actual source code: ex11.cxx

  1: static const char help[] = "Tests PetscDeviceContextMarkIntentFromID().\n\n";

  3: #include "petscdevicetestcommon.h"
  4: #include <petscviewer.h>

  6: #include <petsc/private/cpp/type_traits.hpp>
  7: #include <petsc/private/cpp/array.hpp>

  9: #include <cstdarg>       // std::va_list
 10: #include <vector>        // std:vector
 11: #include <unordered_map> // std::take_a_wild_guess
 12: #include <algorithm>     // std::find
 13: #include <iterator>      // std::distance, std::next

 15: #include <petscmacros.h>

 17: #if PETSC_CPP_VERSION > 14
 18: struct Marker {
 19:   PetscMemoryAccessMode mode{};

 21:   PetscErrorCode operator()(PetscDeviceContext dctx, PetscContainer cont) const noexcept
 22:   {
 23:     const auto    obj  = reinterpret_cast<PetscObject>(cont);
 24:     PetscObjectId id   = 0;
 25:     const char   *name = nullptr;

 27:     PetscFunctionBegin;
 28:     PetscCall(PetscObjectGetId(obj, &id));
 29:     PetscCall(PetscObjectGetName(obj, &name));
 30:     PetscCall(PetscDeviceContextMarkIntentFromID(dctx, id, this->mode, name));
 31:     PetscFunctionReturn(PETSC_SUCCESS);
 32:   }
 33: };

 35: static constexpr auto mem_read       = Marker{PETSC_MEMORY_ACCESS_READ};
 36: static constexpr auto mem_write      = Marker{PETSC_MEMORY_ACCESS_WRITE};
 37: static constexpr auto mem_read_write = Marker{PETSC_MEMORY_ACCESS_READ_WRITE};
 38: static constexpr auto mark_funcs     = Petsc::util::make_array(mem_read, mem_write, mem_read_write);

 40: static PetscErrorCode TestDeepStack(PetscDeviceContext dctx, PetscContainer cont, int remaining)
 41: {
 42:   PetscFunctionBeginUser;
 43:   if (remaining) PetscCall(TestDeepStack(dctx, cont, remaining - 1));
 44:   else {
 45:     PetscCall(mem_read(dctx, cont));
 46:     PetscCall(PetscDeviceContextSynchronize(dctx));
 47:   }
 48:   PetscFunctionReturn(PETSC_SUCCESS);
 49: }

 51: static PetscErrorCode MarkedObjectMapView(PetscViewer vwr, std::size_t nkeys, const PetscObjectId *keys, const PetscMemoryAccessMode *modes, const std::size_t *ndeps, const PetscEvent **dependencies)
 52: {
 53:   PetscFunctionBegin;
 54:   if (!vwr) PetscCall(PetscViewerASCIIGetStdout(PETSC_COMM_WORLD, &vwr));
 55:   PetscCall(PetscViewerFlush(vwr));
 56:   PetscCall(PetscViewerASCIIPushSynchronized(vwr));
 57:   PetscCall(PetscViewerASCIISynchronizedPrintf(vwr, "Marked Object Map:\n"));
 58:   PetscCall(PetscViewerASCIIPushTab(vwr));
 59:   PetscCall(PetscViewerASCIISynchronizedPrintf(vwr, "size: %zu\n", nkeys));
 60:   PetscCall(PetscViewerASCIISynchronizedPrintf(vwr, "entries:\n"));
 61:   PetscCall(PetscViewerASCIIPushTab(vwr));
 62:   for (std::size_t i = 0; i < nkeys; ++i) {
 63:     PetscCall(PetscViewerASCIISynchronizedPrintf(vwr, "id %" PetscInt64_FMT " -> {\n", keys[i]));
 64:     PetscCall(PetscViewerASCIIPushTab(vwr));
 65:     PetscCall(PetscViewerASCIISynchronizedPrintf(vwr, "mode: %s\n", PetscMemoryAccessModeToString(modes[i])));
 66:     PetscCall(PetscViewerASCIISynchronizedPrintf(vwr, "dependencies:\n"));
 67:     PetscCall(PetscViewerASCIIPushTab(vwr));
 68:     for (std::size_t j = 0; j < ndeps[i]; ++j) {
 69:       const auto event = dependencies[i][j];

 71:       PetscCall(PetscViewerASCIISynchronizedPrintf(vwr, "event %zu {dtype: %s, dctx_id: %" PetscInt64_FMT ", dctx_state: %" PetscInt64_FMT ", data: %p, destroy: %p}\n", j, PetscDeviceTypes[event->dtype], event->dctx_id, event->dctx_state, event->data,
 72:                                                    reinterpret_cast<void *>(event->destroy)));
 73:     }
 74:     PetscCall(PetscViewerASCIIPopTab(vwr));
 75:     PetscCall(PetscViewerASCIIPopTab(vwr));
 76:     PetscCall(PetscViewerASCIISynchronizedPrintf(vwr, "}\n"));
 77:   }
 78:   PetscCall(PetscViewerASCIIPopTab(vwr));
 79:   PetscCall(PetscViewerASCIIPopTab(vwr));
 80:   PetscCall(PetscViewerFlush(vwr));
 81:   PetscCall(PetscViewerASCIIPopSynchronized(vwr));
 82:   PetscFunctionReturn(PETSC_SUCCESS);
 83: }

 85: PETSC_ATTRIBUTE_FORMAT(10, 11) static PetscErrorCode CheckMarkedObjectMap_Private(PetscBool cond, const char cond_str[], MPI_Comm comm, PetscDeviceContext dctx, std::size_t nkeys, const PetscObjectId *keys, const PetscMemoryAccessMode *modes, const std::size_t *ndeps, const PetscEvent **dependencies, const char *format, ...)
 86: {
 87:   PetscFunctionBegin;
 88:   if (PetscUnlikely(!cond)) {
 89:     std::array<char, 2048> buf;
 90:     std::va_list           argp;
 91:     std::size_t            len;
 92:     PetscViewer            vwr;

 94:     PetscCallCXX(buf.fill(0));
 95:     va_start(argp, format);
 96:     PetscCall(PetscVSNPrintf(buf.data(), buf.size(), format, &len, argp));
 97:     va_end(argp);
 98:     PetscCall(PetscViewerASCIIGetStdout(comm, &vwr));
 99:     if (dctx) PetscCall(PetscDeviceContextView(dctx, vwr));
100:     PetscCall(MarkedObjectMapView(vwr, nkeys, keys, modes, ndeps, dependencies));
101:     SETERRQ(comm, PETSC_ERR_PLIB, "Condition '%s' failed, marked object map in corrupt state: %s", cond_str, buf.data());
102:   }
103:   PetscFunctionReturn(PETSC_SUCCESS);
104: }

106:   #define CheckMarkedObjectMap(__cond__, ...) CheckMarkedObjectMap_Private((PetscBool)(!!(__cond__)), PetscStringize(__cond__), PETSC_COMM_SELF, dctx, nkeys, keys, modes, ndeps, const_cast<const PetscEvent **>(dependencies), __VA_ARGS__);

108: static PetscErrorCode TestAllCombinations(PetscDeviceContext dctx, const std::vector<PetscContainer> &cont)
109: {
110:   std::vector<PetscObjectId> cont_ids;
111:   PetscObjectId              dctx_id;
112:   PetscDeviceType            dtype;

114:   PetscFunctionBegin;
115:   PetscCallCXX(cont_ids.reserve(cont.size()));
116:   for (auto &&c : cont) {
117:     PetscObjectId id;

119:     PetscCall(PetscObjectGetId((PetscObject)c, &id));
120:     PetscCallCXX(cont_ids.emplace_back(id));
121:   }
122:   PetscCall(PetscObjectGetId(PetscObjectCast(dctx), &dctx_id));
123:   PetscCall(PetscDeviceContextGetDeviceType(dctx, &dtype));
124:   for (auto &&func_i : mark_funcs) {
125:     for (auto &&func_j : mark_funcs) {
126:       for (auto it = cont.cbegin(), next = std::next(it); it != cont.cend(); ++it, ++next) {
127:         std::vector<int>       found_keys;
128:         std::size_t            nkeys;
129:         PetscObjectId         *keys;
130:         PetscMemoryAccessMode *modes;
131:         std::size_t           *ndeps;
132:         PetscEvent           **dependencies;

134:         if (next >= cont.cend()) next = cont.cbegin();
135:         PetscCall(func_i(dctx, *it));
136:         PetscCall(func_j(dctx, *next));
137:         PetscCall(PetscGetMarkedObjectMap_Internal(&nkeys, &keys, &modes, &ndeps, &dependencies));
138:         PetscCallCXX(found_keys.resize(nkeys));
139:         {
140:           // The underlying marked object map is *unordered*, and hence the order in which we
141:           // get the keys is not necessarily the same as the order of operations. This is
142:           // confounded by the fact that k and knext are not necessarily "linear", i.e. k could
143:           // be 2 while knext is 0. So we need to map these back to linear space so we can loop
144:           // over them.
145:           const auto keys_end           = keys + nkeys;
146:           const auto num_expected_keys  = std::min(cont.size(), static_cast<std::size_t>(2));
147:           const auto check_applied_mode = [&](PetscContainer container, PetscMemoryAccessMode mode) {
148:             std::ptrdiff_t key_idx = 0;
149:             PetscObjectId  actual_key;

151:             PetscFunctionBegin;
152:             PetscCall(PetscObjectGetId((PetscObject)container, &actual_key));
153:             // search the list of keys from the map for the selected key
154:             key_idx = std::distance(keys, std::find(keys, keys_end, actual_key));
155:             PetscCheck(key_idx >= 0, PETSC_COMM_SELF, PETSC_ERR_PLIB, "Key index %" PetscCount_FMT " < 0, this indicates keys_begin > keys_end?", key_idx);
156:             found_keys[key_idx]++;
157:             PetscCall(CheckMarkedObjectMap(key_idx < std::distance(keys, keys_end), "marked object map could not find expected key %" PetscInt64_FMT, actual_key));
158:             // OK found it, now check the rest of the entries are as we expect them to be
159:             PetscCall(CheckMarkedObjectMap(modes[key_idx] == mode, "unexpected mode %s, expected %s", PetscMemoryAccessModeToString(modes[key_idx]), PetscMemoryAccessModeToString(mode)));
160:             PetscCall(CheckMarkedObjectMap(ndeps[key_idx] == 1, "unexpected number of dependencies %zu, expected 1", ndeps[key_idx]));
161:             PetscCall(CheckMarkedObjectMap(dependencies[key_idx][0]->dtype == dtype, "unexpected device type on event: %s, expected %s", PetscDeviceTypes[dependencies[key_idx][0]->dtype], PetscDeviceTypes[dtype]));
162:             PetscFunctionReturn(PETSC_SUCCESS);
163:           };

165:           // if it == next, then even though we might num_expected_keys keys we never "look
166:           // for" the missing key
167:           PetscCheck(cont.size() == 1 || it != next, PETSC_COMM_SELF, PETSC_ERR_PLIB, "Test assumes different inputs, otherwise key check may fail (cont.size(): %zu, it != next: %s)", cont.size(), it != next ? "true" : "false");
168:           PetscCall(CheckMarkedObjectMap(nkeys == num_expected_keys, "marked object map has %zu keys expected %zu", nkeys, num_expected_keys));
169:           // check that each function properly applied its mode, it == next if cont.size() = 1,
170:           // i.e. testing identity
171:           if (it != next) PetscCall(check_applied_mode(*it, func_i.mode));
172:           PetscCall(check_applied_mode(*next, func_j.mode));
173:         }
174:         // Check that the map contained only keys we were looking for. Any extra keys will have
175:         // zero find count
176:         for (auto it = found_keys.cbegin(); it != found_keys.cend(); ++it) PetscCall(CheckMarkedObjectMap(*it > 0, "Marked Object Map has extra object entry: id %" PetscInt64_FMT, keys[std::distance(found_keys.cbegin(), it)]));

178:         PetscCall(PetscRestoreMarkedObjectMap_Internal(nkeys, &keys, &modes, &ndeps, &dependencies));

180:         PetscCall(PetscDeviceContextSynchronize(dctx));
181:         PetscCall(PetscGetMarkedObjectMap_Internal(&nkeys, &keys, &modes, &ndeps, &dependencies));
182:         PetscCall(CheckMarkedObjectMap(nkeys == 0, "synchronizing device context did not empty dependency map, have %zu keys", nkeys));
183:         PetscCall(PetscRestoreMarkedObjectMap_Internal(nkeys, &keys, &modes, &ndeps, &dependencies));
184:       }
185:     }
186:   }
187:   PetscCall(PetscDeviceContextSynchronize(dctx));
188:   PetscFunctionReturn(PETSC_SUCCESS);
189: }

191: template <typename... T>
192: PETSC_NODISCARD static std::pair<PetscObjectId, std::pair<PetscMemoryAccessMode, std::vector<PetscDeviceContext>>> make_map_entry(PetscObjectId id, PetscMemoryAccessMode mode, T &&...dctxs)
193: {
194:   return {
195:     id, {mode, {std::forward<T>(dctxs)...}}
196:   };
197: }

199: static PetscErrorCode CheckMapEqual(std::unordered_map<PetscObjectId, std::pair<PetscMemoryAccessMode, std::vector<PetscDeviceContext>>> expected_map)
200: {
201:   std::size_t            nkeys;
202:   PetscObjectId         *keys;
203:   PetscMemoryAccessMode *modes;
204:   std::size_t           *ndeps;
205:   PetscEvent           **dependencies;
206:   PetscDeviceContext     dctx = nullptr;

208:   PetscFunctionBegin;
209:   PetscCall(PetscGetMarkedObjectMap_Internal(&nkeys, &keys, &modes, &ndeps, &dependencies));
210:   {
211:     const auto key_end = keys + nkeys;
212:     auto       mode_it = modes;
213:     auto       ndep_it = ndeps;
214:     auto       dep_it  = dependencies;

216:     for (auto key_it = keys; key_it != key_end; ++key_it, ++mode_it, ++ndep_it, ++dep_it) {
217:       const auto found_it = expected_map.find(*key_it);

219:       PetscCall(CheckMarkedObjectMap(found_it != expected_map.cend(), "marked object map did not contain key %" PetscInt64_FMT, *key_it));
220:       {
221:         // must do these here since found_it may be expected_map.cend()
222:         const auto &expected_mode  = found_it->second.first;
223:         const auto &expected_dctxs = found_it->second.second;
224:         auto        sub_dep_it     = *dep_it;

226:         PetscCall(CheckMarkedObjectMap(expected_mode == *mode_it, "unexpected mode %s, expected %s", PetscMemoryAccessModeToString(expected_mode), PetscMemoryAccessModeToString(*mode_it)));
227:         PetscCall(CheckMarkedObjectMap(expected_dctxs.size() == *ndep_it, "unexpected number of dependencies %zu, expected %zu", *ndep_it, expected_dctxs.size()));
228:         // purposefully hide "dctx" with the loop variable, so we get more detailed output in
229:         // the error message
230:         for (auto &&dctx : expected_dctxs) {
231:           const auto      event = *sub_dep_it;
232:           PetscDeviceType dtype;
233:           PetscObjectId   id;

235:           PetscCall(PetscDeviceContextGetDeviceType(dctx, &dtype));
236:           PetscCall(PetscObjectGetId(PetscObjectCast(dctx), &id));
237:           PetscCall(CheckMarkedObjectMap(event->dtype == dtype, "unexpected device type on event: %s, expected %s", PetscDeviceTypes[event->dtype], PetscDeviceTypes[dtype]));
238:           PetscCall(CheckMarkedObjectMap(event->dctx_id == id, "unexpected dctx id on event: %" PetscInt64_FMT ", expected %" PetscInt64_FMT, event->dctx_id, id));
239:           ++sub_dep_it;
240:         }
241:       }
242:       // remove the found iterator from the map, this ensure we either run out of map (which is
243:       // caught by the first check in the loop), or we run out of keys to check, which is
244:       // caught in the end of the loop
245:       PetscCallCXX(expected_map.erase(found_it));
246:     }
247:   }
248:   PetscCall(CheckMarkedObjectMap(expected_map.empty(), "Not all keys in marked object map accounted for!"));
249:   PetscCall(PetscRestoreMarkedObjectMap_Internal(nkeys, &keys, &modes, &ndeps, &dependencies));
250:   PetscFunctionReturn(PETSC_SUCCESS);
251: }

253: int main(int argc, char *argv[])
254: {
255:   PetscContainer     x, y, z;
256:   PetscObjectId      x_id, y_id, z_id;
257:   PetscDeviceContext dctx_a, dctx_b, dctx_c;
258:   auto               container_view   = PETSC_FALSE;
259:   const auto         create_container = [&](PetscContainer *c, const char name[], PetscObjectId *id) {
260:     PetscFunctionBegin;
261:     PetscCall(PetscContainerCreate(PETSC_COMM_WORLD, c));
262:     PetscCall(PetscObjectSetName((PetscObject)*c, name));
263:     PetscCall(PetscObjectGetId((PetscObject)*c, id));
264:     if (container_view) PetscCall(PetscPrintf(PETSC_COMM_WORLD, "Container '%s' -> id %" PetscInt64_FMT "\n", name, *id));
265:     PetscFunctionReturn(PETSC_SUCCESS);
266:   };
267:   const auto sync_all = [&] {
268:     PetscFunctionBegin;
269:     for (auto &&ctx : {dctx_a, dctx_b, dctx_c}) PetscCall(PetscDeviceContextSynchronize(ctx));
270:     PetscFunctionReturn(PETSC_SUCCESS);
271:   };

273:   PetscFunctionBeginUser;
274:   PetscCall(PetscInitialize(&argc, &argv, nullptr, help));

276:   PetscOptionsBegin(PETSC_COMM_WORLD, nullptr, "Test Options", "Sys");
277:   PetscCall(PetscOptionsBool("-container_view", "View container names and ID's", nullptr, container_view, &container_view, nullptr));
278:   PetscOptionsEnd();

280:   PetscCall(create_container(&x, "x", &x_id));
281:   PetscCall(create_container(&y, "y", &y_id));
282:   PetscCall(create_container(&z, "z", &z_id));

284:   PetscCall(PetscDeviceContextCreate(&dctx_a));
285:   PetscCall(PetscObjectSetName(PetscObjectCast(dctx_a), "dctx_a"));
286:   PetscCall(PetscDeviceContextSetStreamType(dctx_a, PETSC_STREAM_DEFAULT));
287:   PetscCall(PetscDeviceContextSetFromOptions(PETSC_COMM_WORLD, dctx_a));
288:   PetscCall(PetscDeviceContextDuplicate(dctx_a, &dctx_b));
289:   PetscCall(PetscObjectSetName(PetscObjectCast(dctx_b), "dctx_b"));
290:   PetscCall(PetscDeviceContextDuplicate(dctx_a, &dctx_c));
291:   PetscCall(PetscObjectSetName(PetscObjectCast(dctx_c), "dctx_c"));
292:   PetscCall(PetscDeviceContextViewFromOptions(dctx_a, nullptr, "-dctx_a_view"));
293:   PetscCall(PetscDeviceContextViewFromOptions(dctx_b, nullptr, "-dctx_b_view"));
294:   PetscCall(PetscDeviceContextViewFromOptions(dctx_c, nullptr, "-dctx_c_view"));

296:   // ensure they are all idle
297:   PetscCall(sync_all());
298:   PetscCall(CheckMapEqual({}));

300:   PetscCall(TestDeepStack(dctx_a, x, PETSCSTACKSIZE + 2));
301:   PetscCall(CheckMapEqual({}));

303:   // do the bulk combination tests, these test only the very basic combinations for simple
304:   // correctness
305:   PetscCall(TestAllCombinations(dctx_a, {x}));
306:   PetscCall(TestAllCombinations(dctx_a, {x, y, z}));

308:   // Now do some specific tests, these should test more complicated scenarios. First and
309:   // foremost, ensure they are all idle, and that it does not change the map
310:   PetscCall(sync_all());
311:   // Map should be empty
312:   PetscCall(CheckMapEqual({}));

314:   // Syncing again shouldn't magically fill the map back up
315:   PetscCall(sync_all());
316:   PetscCall(CheckMapEqual({}));

318:   const auto test_multiple_readers = [&](std::array<PetscDeviceContext, 2> readers, std::size_t sync_idx) {
319:     // the reader which synchronizes
320:     const auto sync_reader = readers[sync_idx];
321:     // the reader that will remain in the map after sync_reader synchronizes
322:     const auto remain_idx    = sync_idx + 1 >= readers.size() ? 0 : sync_idx + 1;
323:     const auto remain_reader = readers[remain_idx];

325:     PetscFunctionBegin;
326:     for (auto &&ctx : readers) PetscCall(mem_read(ctx, x));
327:     for (auto &&ctx : readers) PetscCall(mem_read(ctx, y));
328:     PetscCall(CheckMapEqual({
329:       make_map_entry(x_id, PETSC_MEMORY_ACCESS_READ, readers[0], readers[1]),
330:       make_map_entry(y_id, PETSC_MEMORY_ACCESS_READ, readers[0], readers[1]),
331:     }));
332:     // synchronizing sync_reader should remove it from the dependency list -- but leave remain_reader
333:     // intact
334:     PetscCall(PetscDeviceContextSynchronize(sync_reader));
335:     PetscCall(CheckMapEqual({
336:       make_map_entry(x_id, PETSC_MEMORY_ACCESS_READ, remain_reader),
337:       make_map_entry(y_id, PETSC_MEMORY_ACCESS_READ, remain_reader),
338:     }));
339:     PetscCall(PetscDeviceContextSynchronize(remain_reader));
340:     PetscCall(CheckMapEqual({}));
341:     PetscFunctionReturn(PETSC_SUCCESS);
342:   };

344:   // Test that multiple readers can simultaneously read -- even if one of them is synchronized
345:   PetscCall(test_multiple_readers({dctx_a, dctx_b}, 0));
346:   PetscCall(test_multiple_readers({dctx_a, dctx_b}, 1));

348:   // Test that sync of unrelated ctx does not affect the map
349:   PetscCall(mem_read(dctx_a, x));
350:   PetscCall(mem_read(dctx_b, y));
351:   PetscCall(PetscDeviceContextSynchronize(dctx_c));
352:   PetscCall(CheckMapEqual({make_map_entry(x_id, PETSC_MEMORY_ACCESS_READ, dctx_a), make_map_entry(y_id, PETSC_MEMORY_ACCESS_READ, dctx_b)}));
353:   PetscCall(PetscDeviceContextSynchronize(dctx_a));
354:   PetscCall(PetscDeviceContextSynchronize(dctx_b));
355:   // Now the map is empty again
356:   PetscCall(CheckMapEqual({}));

358:   // Test another context writing over two reads
359:   PetscCall(mem_read(dctx_a, x));
360:   PetscCall(mem_read(dctx_b, x));
361:   // C writing should kick out both A and B
362:   PetscCall(mem_write(dctx_c, x));
363:   PetscCall(CheckMapEqual({make_map_entry(x_id, PETSC_MEMORY_ACCESS_WRITE, dctx_c)}));
364:   PetscCall(PetscDeviceContextSynchronize(dctx_c));
365:   PetscCall(CheckMapEqual({}));

367:   // Test that write and synchronize does not interfere with unrelated read
368:   PetscCall(mem_read_write(dctx_a, x));
369:   PetscCall(mem_read(dctx_a, y));
370:   PetscCall(mem_read_write(dctx_b, x));
371:   PetscCall(mem_read(dctx_b, y));
372:   // Synchronizing B here must clear everything *but* A's read on Y!
373:   PetscCall(PetscDeviceContextSynchronize(dctx_b));
374:   PetscCall(CheckMapEqual({make_map_entry(y_id, PETSC_MEMORY_ACCESS_READ, dctx_a)}));
375:   PetscCall(PetscDeviceContextSynchronize(dctx_a));
376:   // Now the map is empty again
377:   PetscCall(CheckMapEqual({}));

379:   // Test that implicit stream-dependencies are properly tracked
380:   PetscCall(mem_read(dctx_a, x));
381:   PetscCall(mem_read(dctx_b, y));
382:   // A waits for B
383:   PetscCall(PetscDeviceContextWaitForContext(dctx_a, dctx_b));
384:   // Because A waits on B, synchronizing A implicitly implies B read must have finished so the
385:   // map must be empty
386:   PetscCall(PetscDeviceContextSynchronize(dctx_a));
387:   PetscCall(CheckMapEqual({}));

389:   PetscCall(mem_write(dctx_a, x));
390:   PetscCall(CheckMapEqual({make_map_entry(x_id, PETSC_MEMORY_ACCESS_WRITE, dctx_a)}));
391:   PetscCall(PetscDeviceContextWaitForContext(dctx_b, dctx_a));
392:   PetscCall(PetscDeviceContextWaitForContext(dctx_c, dctx_b));
393:   // We have created the chain C -> B -> A, so synchronizing C should trickle down to synchronize and
394:   // remove A from the map
395:   PetscCall(PetscDeviceContextSynchronize(dctx_c));
396:   PetscCall(CheckMapEqual({}));

398:   // Test that superfluous stream-dependencies are properly ignored
399:   PetscCall(mem_read(dctx_a, x));
400:   PetscCall(mem_read(dctx_b, y));
401:   PetscCall(PetscDeviceContextWaitForContext(dctx_c, dctx_b));
402:   // C waited on B, so synchronizing C should remove B from the map but *not* remove A
403:   PetscCall(PetscDeviceContextSynchronize(dctx_c));
404:   PetscCall(CheckMapEqual({make_map_entry(x_id, PETSC_MEMORY_ACCESS_READ, dctx_a)}));
405:   PetscCall(PetscDeviceContextSynchronize(dctx_a));
406:   PetscCall(CheckMapEqual({}));

408:   // Test that read->write correctly wipes out the map
409:   PetscCall(mem_read(dctx_a, x));
410:   PetscCall(mem_read(dctx_b, x));
411:   PetscCall(mem_read(dctx_c, x));
412:   PetscCall(CheckMapEqual({make_map_entry(x_id, PETSC_MEMORY_ACCESS_READ, dctx_a, dctx_b, dctx_c)}));
413:   PetscCall(mem_write(dctx_a, x));
414:   PetscCall(CheckMapEqual({make_map_entry(x_id, PETSC_MEMORY_ACCESS_WRITE, dctx_a)}));
415:   PetscCall(PetscDeviceContextSynchronize(dctx_a));
416:   PetscCall(CheckMapEqual({}));

418:   PetscCall(PetscDeviceContextDestroy(&dctx_a));
419:   PetscCall(PetscDeviceContextDestroy(&dctx_b));
420:   PetscCall(PetscDeviceContextDestroy(&dctx_c));

422:   PetscCall(PetscContainerDestroy(&x));
423:   PetscCall(PetscContainerDestroy(&y));
424:   PetscCall(PetscContainerDestroy(&z));
425:   PetscCall(PetscPrintf(PETSC_COMM_WORLD, "EXIT_SUCCESS\n"));
426:   PetscCall(PetscFinalize());
427:   return 0;
428: }
429: #else // PETSC_CPP_VERSION > 11
430: int main(int argc, char *argv[])
431: {
432:   PetscFunctionBeginUser;
433:   PetscCall(PetscInitialize(&argc, &argv, nullptr, help));
434:   PetscCall(PetscPrintf(PETSC_COMM_WORLD, "EXIT_SUCCESS\n"));
435:   PetscCall(PetscFinalize());
436:   return 0;
437: }
438: #endif

440: /*TEST

442:   testset:
443:     requires: defined(PETSC_DEVICELANGUAGE_CXX)
444:     output_file: output/ExitSuccess.out
445:     test:
446:       requires: !device
447:       suffix: host_no_device
448:     test:
449:       requires: device
450:       args: -default_device_type host
451:       suffix: host_with_device
452:     test:
453:       requires: cuda
454:       args: -default_device_type cuda
455:       suffix: cuda
456:     test:
457:       requires: hip
458:       args: -default_device_type hip
459:       suffix: hip
460:     test:
461:       requires: sycl
462:       TODO: unclear if it is needed
463:       args: -default_device_type sycl
464:       suffix: sycl

466: TEST*/