xref: /petsc/src/sys/objects/device/tests/ex11.cxx (revision d52a580b706c59ca78066c1e38754e45b6b56e2b)
1 static const char help[] = "Tests PetscDeviceContextMarkIntentFromID().\n\n";
2 
3 #include "petscdevicetestcommon.h"
4 #include <petscviewer.h>
5 
6 #include <petsc/private/cpp/type_traits.hpp>
7 #include <petsc/private/cpp/array.hpp>
8 
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
14 
15 #include <petscmacros.h> // PETSC_CPP_VERSION
16 
17 #if PETSC_CPP_VERSION > 14
18 struct Marker {
19   PetscMemoryAccessMode mode{};
20 
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;
26 
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 };
34 
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);
39 
40 static PetscErrorCode MarkedObjectMapView(PetscViewer vwr, std::size_t nkeys, const PetscObjectId *keys, const PetscMemoryAccessMode *modes, const std::size_t *ndeps, const PetscEvent **dependencies)
41 {
42   PetscFunctionBegin;
43   if (!vwr) PetscCall(PetscViewerASCIIGetStdout(PETSC_COMM_WORLD, &vwr));
44   PetscCall(PetscViewerFlush(vwr));
45   PetscCall(PetscViewerASCIIPushSynchronized(vwr));
46   PetscCall(PetscViewerASCIISynchronizedPrintf(vwr, "Marked Object Map:\n"));
47   PetscCall(PetscViewerASCIIPushTab(vwr));
48   PetscCall(PetscViewerASCIISynchronizedPrintf(vwr, "size: %zu\n", nkeys));
49   PetscCall(PetscViewerASCIISynchronizedPrintf(vwr, "entries:\n"));
50   PetscCall(PetscViewerASCIIPushTab(vwr));
51   for (std::size_t i = 0; i < nkeys; ++i) {
52     PetscCall(PetscViewerASCIISynchronizedPrintf(vwr, "id %" PetscInt64_FMT " -> {\n", keys[i]));
53     PetscCall(PetscViewerASCIIPushTab(vwr));
54     PetscCall(PetscViewerASCIISynchronizedPrintf(vwr, "mode: %s\n", PetscMemoryAccessModeToString(modes[i])));
55     PetscCall(PetscViewerASCIISynchronizedPrintf(vwr, "dependencies:\n"));
56     PetscCall(PetscViewerASCIIPushTab(vwr));
57     for (std::size_t j = 0; j < ndeps[i]; ++j) {
58       const auto event = dependencies[i][j];
59 
60       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,
61                                                    reinterpret_cast<void *>(event->destroy)));
62     }
63     PetscCall(PetscViewerASCIIPopTab(vwr));
64     PetscCall(PetscViewerASCIIPopTab(vwr));
65     PetscCall(PetscViewerASCIISynchronizedPrintf(vwr, "}\n"));
66   }
67   PetscCall(PetscViewerASCIIPopTab(vwr));
68   PetscCall(PetscViewerASCIIPopTab(vwr));
69   PetscCall(PetscViewerFlush(vwr));
70   PetscCall(PetscViewerASCIIPopSynchronized(vwr));
71   PetscFunctionReturn(PETSC_SUCCESS);
72 }
73 
74 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, ...)
75 {
76   PetscFunctionBegin;
77   if (PetscUnlikely(!cond)) {
78     std::array<char, 2048> buf;
79     std::va_list           argp;
80     std::size_t            len;
81     PetscViewer            vwr;
82 
83     PetscCallCXX(buf.fill(0));
84     va_start(argp, format);
85     PetscCall(PetscVSNPrintf(buf.data(), buf.size(), format, &len, argp));
86     va_end(argp);
87     PetscCall(PetscViewerASCIIGetStdout(comm, &vwr));
88     if (dctx) PetscCall(PetscDeviceContextView(dctx, vwr));
89     PetscCall(MarkedObjectMapView(vwr, nkeys, keys, modes, ndeps, dependencies));
90     SETERRQ(comm, PETSC_ERR_PLIB, "Condition '%s' failed, marked object map in corrupt state: %s", cond_str, buf.data());
91   }
92   PetscFunctionReturn(PETSC_SUCCESS);
93 }
94 
95   #define CheckMarkedObjectMap(__cond__, ...) CheckMarkedObjectMap_Private((PetscBool)(!!(__cond__)), PetscStringize(__cond__), PETSC_COMM_SELF, dctx, nkeys, keys, modes, ndeps, const_cast<const PetscEvent **>(dependencies), __VA_ARGS__);
96 
97 static PetscErrorCode TestAllCombinations(PetscDeviceContext dctx, const std::vector<PetscContainer> &cont)
98 {
99   std::vector<PetscObjectId> cont_ids;
100   PetscObjectId              dctx_id;
101   PetscDeviceType            dtype;
102 
103   PetscFunctionBegin;
104   PetscCallCXX(cont_ids.reserve(cont.size()));
105   for (auto &&c : cont) {
106     PetscObjectId id;
107 
108     PetscCall(PetscObjectGetId((PetscObject)c, &id));
109     PetscCallCXX(cont_ids.emplace_back(id));
110   }
111   PetscCall(PetscObjectGetId(PetscObjectCast(dctx), &dctx_id));
112   PetscCall(PetscDeviceContextGetDeviceType(dctx, &dtype));
113   for (auto &&func_i : mark_funcs) {
114     for (auto &&func_j : mark_funcs) {
115       for (auto it = cont.cbegin(), next = std::next(it); it != cont.cend(); ++it, ++next) {
116         std::vector<int>       found_keys;
117         std::size_t            nkeys;
118         PetscObjectId         *keys;
119         PetscMemoryAccessMode *modes;
120         std::size_t           *ndeps;
121         PetscEvent           **dependencies;
122 
123         if (next >= cont.cend()) next = cont.cbegin();
124         PetscCall(func_i(dctx, *it));
125         PetscCall(func_j(dctx, *next));
126         PetscCall(PetscGetMarkedObjectMap_Internal(&nkeys, &keys, &modes, &ndeps, &dependencies));
127         PetscCallCXX(found_keys.resize(nkeys));
128         {
129           // The underlying marked object map is *unordered*, and hence the order in which we
130           // get the keys is not necessarily the same as the order of operations. This is
131           // confounded by the fact that k and knext are not necessarily "linear", i.e. k could
132           // be 2 while knext is 0. So we need to map these back to linear space so we can loop
133           // over them.
134           const auto keys_end           = keys + nkeys;
135           const auto num_expected_keys  = std::min(cont.size(), static_cast<std::size_t>(2));
136           const auto check_applied_mode = [&](PetscContainer container, PetscMemoryAccessMode mode) {
137             std::ptrdiff_t key_idx = 0;
138             PetscObjectId  actual_key;
139 
140             PetscFunctionBegin;
141             PetscCall(PetscObjectGetId((PetscObject)container, &actual_key));
142             // search the list of keys from the map for the selected key
143             key_idx = std::distance(keys, std::find(keys, keys_end, actual_key));
144             PetscCheck(key_idx >= 0, PETSC_COMM_SELF, PETSC_ERR_PLIB, "Key index %" PetscCount_FMT " < 0, this indicates keys_begin > keys_end?", key_idx);
145             found_keys[key_idx]++;
146             PetscCall(CheckMarkedObjectMap(key_idx < std::distance(keys, keys_end), "marked object map could not find expected key %" PetscInt64_FMT, actual_key));
147             // OK found it, now check the rest of the entries are as we expect them to be
148             PetscCall(CheckMarkedObjectMap(modes[key_idx] == mode, "unexpected mode %s, expected %s", PetscMemoryAccessModeToString(modes[key_idx]), PetscMemoryAccessModeToString(mode)));
149             PetscCall(CheckMarkedObjectMap(ndeps[key_idx] == 1, "unexpected number of dependencies %zu, expected 1", ndeps[key_idx]));
150             PetscCall(CheckMarkedObjectMap(dependencies[key_idx][0]->dtype == dtype, "unexpected device type on event: %s, expected %s", PetscDeviceTypes[dependencies[key_idx][0]->dtype], PetscDeviceTypes[dtype]));
151             PetscFunctionReturn(PETSC_SUCCESS);
152           };
153 
154           // if it == next, then even though we might num_expected_keys keys we never "look
155           // for" the missing key
156           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");
157           PetscCall(CheckMarkedObjectMap(nkeys == num_expected_keys, "marked object map has %zu keys expected %zu", nkeys, num_expected_keys));
158           // check that each function properly applied its mode, it == next if cont.size() = 1,
159           // i.e. testing identity
160           if (it != next) PetscCall(check_applied_mode(*it, func_i.mode));
161           PetscCall(check_applied_mode(*next, func_j.mode));
162         }
163         // Check that the map contained only keys we were looking for. Any extra keys will have
164         // zero find count
165         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)]));
166 
167         PetscCall(PetscRestoreMarkedObjectMap_Internal(nkeys, &keys, &modes, &ndeps, &dependencies));
168 
169         PetscCall(PetscDeviceContextSynchronize(dctx));
170         PetscCall(PetscGetMarkedObjectMap_Internal(&nkeys, &keys, &modes, &ndeps, &dependencies));
171         PetscCall(CheckMarkedObjectMap(nkeys == 0, "synchronizing device context did not empty dependency map, have %zu keys", nkeys));
172         PetscCall(PetscRestoreMarkedObjectMap_Internal(nkeys, &keys, &modes, &ndeps, &dependencies));
173       }
174     }
175   }
176   PetscCall(PetscDeviceContextSynchronize(dctx));
177   PetscFunctionReturn(PETSC_SUCCESS);
178 }
179 
180 template <typename... T>
181 PETSC_NODISCARD static std::pair<PetscObjectId, std::pair<PetscMemoryAccessMode, std::vector<PetscDeviceContext>>> make_map_entry(PetscObjectId id, PetscMemoryAccessMode mode, T &&...dctxs)
182 {
183   return {
184     id, {mode, {std::forward<T>(dctxs)...}}
185   };
186 }
187 
188 static PetscErrorCode CheckMapEqual(std::unordered_map<PetscObjectId, std::pair<PetscMemoryAccessMode, std::vector<PetscDeviceContext>>> expected_map)
189 {
190   std::size_t            nkeys;
191   PetscObjectId         *keys;
192   PetscMemoryAccessMode *modes;
193   std::size_t           *ndeps;
194   PetscEvent           **dependencies;
195   PetscDeviceContext     dctx = nullptr;
196 
197   PetscFunctionBegin;
198   PetscCall(PetscGetMarkedObjectMap_Internal(&nkeys, &keys, &modes, &ndeps, &dependencies));
199   {
200     const auto key_end = keys + nkeys;
201     auto       mode_it = modes;
202     auto       ndep_it = ndeps;
203     auto       dep_it  = dependencies;
204 
205     for (auto key_it = keys; key_it != key_end; ++key_it, ++mode_it, ++ndep_it, ++dep_it) {
206       const auto found_it = expected_map.find(*key_it);
207 
208       PetscCall(CheckMarkedObjectMap(found_it != expected_map.cend(), "marked object map did not contain key %" PetscInt64_FMT, *key_it));
209       {
210         // must do these here since found_it may be expected_map.cend()
211         const auto &expected_mode  = found_it->second.first;
212         const auto &expected_dctxs = found_it->second.second;
213         auto        sub_dep_it     = *dep_it;
214 
215         PetscCall(CheckMarkedObjectMap(expected_mode == *mode_it, "unexpected mode %s, expected %s", PetscMemoryAccessModeToString(expected_mode), PetscMemoryAccessModeToString(*mode_it)));
216         PetscCall(CheckMarkedObjectMap(expected_dctxs.size() == *ndep_it, "unexpected number of dependencies %zu, expected %zu", *ndep_it, expected_dctxs.size()));
217         // purposefully hide "dctx" with the loop variable, so we get more detailed output in
218         // the error message
219         for (auto &&dctx : expected_dctxs) {
220           const auto      event = *sub_dep_it;
221           PetscDeviceType dtype;
222           PetscObjectId   id;
223 
224           PetscCall(PetscDeviceContextGetDeviceType(dctx, &dtype));
225           PetscCall(PetscObjectGetId(PetscObjectCast(dctx), &id));
226           PetscCall(CheckMarkedObjectMap(event->dtype == dtype, "unexpected device type on event: %s, expected %s", PetscDeviceTypes[event->dtype], PetscDeviceTypes[dtype]));
227           PetscCall(CheckMarkedObjectMap(event->dctx_id == id, "unexpected dctx id on event: %" PetscInt64_FMT ", expected %" PetscInt64_FMT, event->dctx_id, id));
228           ++sub_dep_it;
229         }
230       }
231       // remove the found iterator from the map, this ensure we either run out of map (which is
232       // caught by the first check in the loop), or we run out of keys to check, which is
233       // caught in the end of the loop
234       PetscCallCXX(expected_map.erase(found_it));
235     }
236   }
237   PetscCall(CheckMarkedObjectMap(expected_map.empty(), "Not all keys in marked object map accounted for!"));
238   PetscCall(PetscRestoreMarkedObjectMap_Internal(nkeys, &keys, &modes, &ndeps, &dependencies));
239   PetscFunctionReturn(PETSC_SUCCESS);
240 }
241 
242 int main(int argc, char *argv[])
243 {
244   PetscContainer     x, y, z;
245   PetscObjectId      x_id, y_id, z_id;
246   PetscDeviceContext dctx_a, dctx_b, dctx_c;
247   auto               container_view   = PETSC_FALSE;
248   const auto         create_container = [&](PetscContainer *c, const char name[], PetscObjectId *id) {
249     PetscFunctionBegin;
250     PetscCall(PetscContainerCreate(PETSC_COMM_WORLD, c));
251     PetscCall(PetscObjectSetName((PetscObject)*c, name));
252     PetscCall(PetscObjectGetId((PetscObject)*c, id));
253     if (container_view) PetscCall(PetscPrintf(PETSC_COMM_WORLD, "Container '%s' -> id %" PetscInt64_FMT "\n", name, *id));
254     PetscFunctionReturn(PETSC_SUCCESS);
255   };
256   const auto sync_all = [&] {
257     PetscFunctionBegin;
258     for (auto &&ctx : {dctx_a, dctx_b, dctx_c}) PetscCall(PetscDeviceContextSynchronize(ctx));
259     PetscFunctionReturn(PETSC_SUCCESS);
260   };
261 
262   PetscFunctionBeginUser;
263   PetscCall(PetscInitialize(&argc, &argv, nullptr, help));
264 
265   PetscOptionsBegin(PETSC_COMM_WORLD, nullptr, "Test Options", "Sys");
266   PetscCall(PetscOptionsBool("-container_view", "View container names and ID's", nullptr, container_view, &container_view, nullptr));
267   PetscOptionsEnd();
268 
269   PetscCall(create_container(&x, "x", &x_id));
270   PetscCall(create_container(&y, "y", &y_id));
271   PetscCall(create_container(&z, "z", &z_id));
272 
273   PetscCall(PetscDeviceContextCreate(&dctx_a));
274   PetscCall(PetscObjectSetName(PetscObjectCast(dctx_a), "dctx_a"));
275   PetscCall(PetscDeviceContextSetStreamType(dctx_a, PETSC_STREAM_DEFAULT));
276   PetscCall(PetscDeviceContextSetFromOptions(PETSC_COMM_WORLD, dctx_a));
277   PetscCall(PetscDeviceContextDuplicate(dctx_a, &dctx_b));
278   PetscCall(PetscObjectSetName(PetscObjectCast(dctx_b), "dctx_b"));
279   PetscCall(PetscDeviceContextDuplicate(dctx_a, &dctx_c));
280   PetscCall(PetscObjectSetName(PetscObjectCast(dctx_c), "dctx_c"));
281   PetscCall(PetscDeviceContextViewFromOptions(dctx_a, nullptr, "-dctx_a_view"));
282   PetscCall(PetscDeviceContextViewFromOptions(dctx_b, nullptr, "-dctx_b_view"));
283   PetscCall(PetscDeviceContextViewFromOptions(dctx_c, nullptr, "-dctx_c_view"));
284 
285   // ensure they are all idle
286   PetscCall(sync_all());
287   PetscCall(CheckMapEqual({}));
288 
289   // do the bulk combination tests, these test only the very basic combinations for simple
290   // correctness
291   PetscCall(TestAllCombinations(dctx_a, {x}));
292   PetscCall(TestAllCombinations(dctx_a, {x, y, z}));
293 
294   // Now do some specific tests, these should test more complicated scenarios. First and
295   // foremost, ensure they are all idle, and that it does not change the map
296   PetscCall(sync_all());
297   // Map should be empty
298   PetscCall(CheckMapEqual({}));
299 
300   // Syncing again shouldn't magically fill the map back up
301   PetscCall(sync_all());
302   PetscCall(CheckMapEqual({}));
303 
304   const auto test_multiple_readers = [&](std::array<PetscDeviceContext, 2> readers, std::size_t sync_idx) {
305     // the reader which synchronizes
306     const auto sync_reader = readers[sync_idx];
307     // the reader that will remain in the map after sync_reader synchronizes
308     const auto remain_idx    = sync_idx + 1 >= readers.size() ? 0 : sync_idx + 1;
309     const auto remain_reader = readers[remain_idx];
310 
311     PetscFunctionBegin;
312     for (auto &&ctx : readers) PetscCall(mem_read(ctx, x));
313     for (auto &&ctx : readers) PetscCall(mem_read(ctx, y));
314     PetscCall(CheckMapEqual({
315       make_map_entry(x_id, PETSC_MEMORY_ACCESS_READ, readers[0], readers[1]),
316       make_map_entry(y_id, PETSC_MEMORY_ACCESS_READ, readers[0], readers[1]),
317     }));
318     // synchronizing sync_reader should remove it from the dependency list -- but leave remain_reader
319     // intact
320     PetscCall(PetscDeviceContextSynchronize(sync_reader));
321     PetscCall(CheckMapEqual({
322       make_map_entry(x_id, PETSC_MEMORY_ACCESS_READ, remain_reader),
323       make_map_entry(y_id, PETSC_MEMORY_ACCESS_READ, remain_reader),
324     }));
325     PetscCall(PetscDeviceContextSynchronize(remain_reader));
326     PetscCall(CheckMapEqual({}));
327     PetscFunctionReturn(PETSC_SUCCESS);
328   };
329 
330   // Test that multiple readers can simultaneously read -- even if one of them is synchronized
331   PetscCall(test_multiple_readers({dctx_a, dctx_b}, 0));
332   PetscCall(test_multiple_readers({dctx_a, dctx_b}, 1));
333 
334   // Test that sync of unrelated ctx does not affect the map
335   PetscCall(mem_read(dctx_a, x));
336   PetscCall(mem_read(dctx_b, y));
337   PetscCall(PetscDeviceContextSynchronize(dctx_c));
338   // clang-format off
339   PetscCall(CheckMapEqual({
340     make_map_entry(x_id, PETSC_MEMORY_ACCESS_READ, dctx_a),
341     make_map_entry(y_id, PETSC_MEMORY_ACCESS_READ, dctx_b)
342   }));
343   // clang-format on
344   PetscCall(PetscDeviceContextSynchronize(dctx_a));
345   PetscCall(PetscDeviceContextSynchronize(dctx_b));
346   // Now the map is empty again
347   PetscCall(CheckMapEqual({}));
348 
349   // Test another context writing over two reads
350   PetscCall(mem_read(dctx_a, x));
351   PetscCall(mem_read(dctx_b, x));
352   // C writing should kick out both A and B
353   PetscCall(mem_write(dctx_c, x));
354   PetscCall(CheckMapEqual({make_map_entry(x_id, PETSC_MEMORY_ACCESS_WRITE, dctx_c)}));
355   PetscCall(PetscDeviceContextSynchronize(dctx_c));
356   PetscCall(CheckMapEqual({}));
357 
358   // Test that write and synchronize does not interfere with unrelated read
359   PetscCall(mem_read_write(dctx_a, x));
360   PetscCall(mem_read(dctx_a, y));
361   PetscCall(mem_read_write(dctx_b, x));
362   PetscCall(mem_read(dctx_b, y));
363   // Synchronizing B here must clear everything *but* A's read on Y!
364   PetscCall(PetscDeviceContextSynchronize(dctx_b));
365   PetscCall(CheckMapEqual({make_map_entry(y_id, PETSC_MEMORY_ACCESS_READ, dctx_a)}));
366   PetscCall(PetscDeviceContextSynchronize(dctx_a));
367   // Now the map is empty again
368   PetscCall(CheckMapEqual({}));
369 
370   // Test that implicit stream-dependencies are properly tracked
371   PetscCall(mem_read(dctx_a, x));
372   PetscCall(mem_read(dctx_b, y));
373   // A waits for B
374   PetscCall(PetscDeviceContextWaitForContext(dctx_a, dctx_b));
375   // Because A waits on B, synchronizing A implicitly implies B read must have finished so the
376   // map must be empty
377   PetscCall(PetscDeviceContextSynchronize(dctx_a));
378   PetscCall(CheckMapEqual({}));
379 
380   PetscCall(mem_write(dctx_a, x));
381   PetscCall(CheckMapEqual({make_map_entry(x_id, PETSC_MEMORY_ACCESS_WRITE, dctx_a)}));
382   PetscCall(PetscDeviceContextWaitForContext(dctx_b, dctx_a));
383   PetscCall(PetscDeviceContextWaitForContext(dctx_c, dctx_b));
384   // We have created the chain C -> B -> A, so synchronizing C should trickle down to synchronize and
385   // remove A from the map
386   PetscCall(PetscDeviceContextSynchronize(dctx_c));
387   PetscCall(CheckMapEqual({}));
388 
389   // Test that superfluous stream-dependencies are properly ignored
390   PetscCall(mem_read(dctx_a, x));
391   PetscCall(mem_read(dctx_b, y));
392   PetscCall(PetscDeviceContextWaitForContext(dctx_c, dctx_b));
393   // C waited on B, so synchronizing C should remove B from the map but *not* remove A
394   PetscCall(PetscDeviceContextSynchronize(dctx_c));
395   PetscCall(CheckMapEqual({make_map_entry(x_id, PETSC_MEMORY_ACCESS_READ, dctx_a)}));
396   PetscCall(PetscDeviceContextSynchronize(dctx_a));
397   PetscCall(CheckMapEqual({}));
398 
399   // Test that read->write correctly wipes out the map
400   PetscCall(mem_read(dctx_a, x));
401   PetscCall(mem_read(dctx_b, x));
402   PetscCall(mem_read(dctx_c, x));
403   PetscCall(CheckMapEqual({make_map_entry(x_id, PETSC_MEMORY_ACCESS_READ, dctx_a, dctx_b, dctx_c)}));
404   PetscCall(mem_write(dctx_a, x));
405   PetscCall(CheckMapEqual({make_map_entry(x_id, PETSC_MEMORY_ACCESS_WRITE, dctx_a)}));
406   PetscCall(PetscDeviceContextSynchronize(dctx_a));
407   PetscCall(CheckMapEqual({}));
408 
409   PetscCall(PetscDeviceContextDestroy(&dctx_a));
410   PetscCall(PetscDeviceContextDestroy(&dctx_b));
411   PetscCall(PetscDeviceContextDestroy(&dctx_c));
412 
413   PetscCall(PetscContainerDestroy(&x));
414   PetscCall(PetscContainerDestroy(&y));
415   PetscCall(PetscContainerDestroy(&z));
416   PetscCall(PetscPrintf(PETSC_COMM_WORLD, "EXIT_SUCCESS\n"));
417   PetscCall(PetscFinalize());
418   return 0;
419 }
420 #else // PETSC_CPP_VERSION > 11
421 int main(int argc, char *argv[])
422 {
423   PetscFunctionBeginUser;
424   PetscCall(PetscInitialize(&argc, &argv, nullptr, help));
425   PetscCall(PetscPrintf(PETSC_COMM_WORLD, "EXIT_SUCCESS\n"));
426   PetscCall(PetscFinalize());
427   return 0;
428 }
429 #endif
430 
431 /*TEST
432 
433   testset:
434     requires: defined(PETSC_DEVICELANGUAGE_CXX)
435     output_file: output/ExitSuccess.out
436     test:
437       requires: !device
438       suffix: host_no_device
439     test:
440       requires: device
441       args: -default_device_type host
442       suffix: host_with_device
443     test:
444       requires: cuda
445       args: -default_device_type cuda
446       suffix: cuda
447     test:
448       requires: hip
449       args: -default_device_type hip
450       suffix: hip
451     test:
452       requires: sycl
453       args: -default_device_type sycl
454       suffix: sycl
455 
456 TEST*/
457