xref: /petsc/src/sys/objects/device/interface/dcontext.cxx (revision 6a4a1270853b5b1995c94de98f44d28bec57470a)
1 #include "petscdevice_interface_internal.hpp" /*I <petscdevice.h> I*/
2 #include <petsc/private/viewerimpl.h>         // _p_PetscViewer for PetscObjectCast()
3 
4 #include <petsc/private/cpp/object_pool.hpp>
5 #include <petsc/private/cpp/utility.hpp>
6 #include <petsc/private/cpp/array.hpp>
7 
8 #include <vector>
9 #include <string> // std::to_string among other things
10 
11 /* Define the allocator */
12 class PetscDeviceContextAllocator : public Petsc::AllocatorBase<PetscDeviceContext> {
13 public:
14   PETSC_CXX_COMPAT_DECL(PetscErrorCode create(PetscDeviceContext *dctx)) {
15     PetscFunctionBegin;
16     PetscCall(PetscHeaderCreate(*dctx, PETSC_DEVICE_CONTEXT_CLASSID, "PetscDeviceContext", "PetscDeviceContext", "Sys", PETSC_COMM_SELF, PetscDeviceContextDestroy, PetscDeviceContextView));
17     PetscCallCXX(PetscObjectCast(*dctx)->cpp = new CxxData());
18     PetscCall(reset(*dctx, false));
19     PetscFunctionReturn(0);
20   }
21 
22   PETSC_CXX_COMPAT_DECL(PetscErrorCode destroy(PetscDeviceContext dctx)) {
23     PetscFunctionBegin;
24     PetscAssert(!dctx->numChildren, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE, "Device context still has %" PetscInt_FMT " un-joined children, must call PetscDeviceContextJoin() with all children before destroying", dctx->numChildren);
25     PetscTryTypeMethod(dctx, destroy);
26     PetscCall(PetscDeviceDestroy(&dctx->device));
27     PetscCall(PetscFree(dctx->childIDs));
28     delete CxxDataCast(dctx);
29     PetscCall(PetscHeaderDestroy(&dctx));
30     PetscFunctionReturn(0);
31   }
32 
33   PETSC_CXX_COMPAT_DECL(PetscErrorCode reset(PetscDeviceContext dctx, bool zero = true)) {
34     PetscFunctionBegin;
35     if (zero) {
36       // reset the device if the user set it
37       if (auto &userset = dctx->usersetdevice) {
38         userset = PETSC_FALSE;
39         PetscTryTypeMethod(dctx, destroy);
40         PetscCall(PetscDeviceDestroy(&dctx->device));
41         PetscCall(PetscArrayzero(dctx->ops, 1));
42         dctx->data = nullptr;
43       }
44       PetscCall(PetscHeaderReset_Internal(PetscObjectCast(dctx)));
45       dctx->numChildren = 0;
46       dctx->setup       = PETSC_FALSE;
47       // don't deallocate the child array, rather just zero it out
48       PetscCall(PetscArrayzero(dctx->childIDs, dctx->maxNumChildren));
49       PetscCall(CxxDataCast(dctx)->clear());
50     }
51     dctx->streamType = PETSC_STREAM_DEFAULT_BLOCKING;
52     PetscFunctionReturn(0);
53   }
54 };
55 
56 static Petsc::ObjectPool<PetscDeviceContext, PetscDeviceContextAllocator> contextPool;
57 
58 /*@C
59   PetscDeviceContextCreate - Creates a `PetscDeviceContext`
60 
61   Not Collective
62 
63   Output Paramemter:
64 . dctx - The `PetscDeviceContext`
65 
66   Note:
67   Unlike almost every other PETSc class it is advised that most users use
68   `PetscDeviceContextDuplicate()` rather than this routine to create new contexts. Contexts of
69   different types are incompatible with one another; using `PetscDeviceContextDuplicate()`
70   ensures compatible types.
71 
72   DAG representation:
73 .vb
74   time ->
75 
76   |= CALL =| - dctx ->
77 .ve
78 
79   Level: beginner
80 
81 .N ASYNC_API
82 
83 .seealso: `PetscDeviceContextDuplicate()`, `PetscDeviceContextSetDevice()`,
84 `PetscDeviceContextSetStreamType()`, `PetscDeviceContextSetUp()`,
85 `PetscDeviceContextSetFromOptions()`, `PetscDeviceContextView()`, `PetscDeviceContextDestroy()`
86 @*/
87 PetscErrorCode PetscDeviceContextCreate(PetscDeviceContext *dctx) {
88   PetscFunctionBegin;
89   PetscValidPointer(dctx, 1);
90   PetscCall(PetscDeviceInitializePackage());
91   PetscCall(PetscLogEventBegin(DCONTEXT_Create, nullptr, nullptr, nullptr, nullptr));
92   PetscCall(contextPool.allocate(dctx));
93   PetscCall(PetscLogEventEnd(DCONTEXT_Create, nullptr, nullptr, nullptr, nullptr));
94   PetscFunctionReturn(0);
95 }
96 
97 /*@C
98   PetscDeviceContextDestroy - Frees a `PetscDeviceContext`
99 
100   Not Collective
101 
102   Input Parameters:
103 . dctx - The `PetscDeviceContext`
104 
105   Notes:
106   No implicit synchronization occurs due to this routine, all resources are released completely
107   asynchronously w.r.t. the host. If one needs to guarantee access to the data produced on
108   `dctx`'s stream the user is responsible for calling `PetscDeviceContextSynchronize()` before
109   calling this routine.
110 
111   DAG represetation:
112 .vb
113   time ->
114 
115   -> dctx - |= CALL =|
116 .ve
117 
118   Developer Notes:
119   `dctx` is never actually "destroyed" in the classical sense. It is returned to an ever
120   growing pool of `PetscDeviceContext`s. There are currently no limits on the size of the pool,
121   this should perhaps be implemented.
122 
123   Level: beginner
124 
125 .N ASYNC_API
126 
127 .seealso: `PetscDeviceContextCreate()`, `PetscDeviceContextSetDevice()`,
128 `PetscDeviceContextSetUp()`, `PetscDeviceContextSynchronize()`
129 @*/
130 PetscErrorCode PetscDeviceContextDestroy(PetscDeviceContext *dctx) {
131   PetscFunctionBegin;
132   PetscValidPointer(dctx, 1);
133   if (!*dctx) PetscFunctionReturn(0);
134   PetscCall(PetscLogEventBegin(DCONTEXT_Destroy, nullptr, nullptr, nullptr, nullptr));
135   if (--(PetscObjectCast(*dctx)->refct) <= 0) {
136     PetscCall(PetscDeviceContextCheckNotOrphaned_Internal(*dctx));
137     // std::move of the expression of the trivially-copyable type 'PetscDeviceContext' (aka
138     // '_n_PetscDeviceContext *') has no effect; remove std::move() [performance-move-const-arg]
139     // can't remove std::move, since reclaim only takes r-value reference
140     PetscCall(contextPool.deallocate(std::move(*dctx))); // NOLINT (performance-move-const-arg)
141   }
142   PetscCall(PetscLogEventEnd(DCONTEXT_Destroy, nullptr, nullptr, nullptr, nullptr));
143   *dctx = nullptr;
144   PetscFunctionReturn(0);
145 }
146 
147 /*@C
148   PetscDeviceContextSetStreamType - Set the implementation type of the underlying stream for a
149   `PetscDeviceContext`
150 
151   Not Collective
152 
153   Input Parameters:
154 + dctx - The `PetscDeviceContext`
155 - type - The `PetscStreamType`
156 
157   Notes:
158   See `PetscStreamType` in `include/petscdevicetypes.h` for more information on the available
159   types and their interactions. If the `PetscDeviceContext` was previously set up and stream
160   type was changed, you must call `PetscDeviceContextSetUp()` again after this routine.
161 
162   Level: beginner
163 
164 .seealso: `PetscStreamType`, `PetscDeviceContextGetStreamType()`, `PetscDeviceContextCreate()`,
165 `PetscDeviceContextSetUp()`, `PetscDeviceContextSetFromOptions()`
166 @*/
167 PetscErrorCode PetscDeviceContextSetStreamType(PetscDeviceContext dctx, PetscStreamType type) {
168   PetscFunctionBegin;
169   // do not use getoptionalnullcontext here since we do not want the user to change the stream
170   // type
171   PetscValidDeviceContext(dctx, 1);
172   PetscValidStreamType(type, 2);
173   // only need to do complex swapping if the object has already been setup
174   if (dctx->setup && (dctx->streamType != type)) {
175     dctx->setup = PETSC_FALSE;
176     PetscCall(PetscLogEventBegin(DCONTEXT_ChangeStream, dctx, nullptr, nullptr, nullptr));
177     PetscUseTypeMethod(dctx, changestreamtype, type);
178     PetscCall(PetscLogEventEnd(DCONTEXT_ChangeStream, dctx, nullptr, nullptr, nullptr));
179   }
180   dctx->streamType = type;
181   PetscFunctionReturn(0);
182 }
183 
184 /*@C
185   PetscDeviceContextGetStreamType - Get the implementation type of the underlying stream for a
186   `PetscDeviceContext`
187 
188   Not Collective
189 
190   Input Parameter:
191 . dctx - The `PetscDeviceContext`
192 
193   Output Parameter:
194 . type - The `PetscStreamType`
195 
196   Notes:
197   See `PetscStreamType` in `include/petscdevicetypes.h` for more information on the available
198   types and their interactions
199 
200   Level: beginner
201 
202 .seealso: `PetscDeviceContextSetStreamType()`, `PetscDeviceContextCreate()`,
203 `PetscDeviceContextSetFromOptions()`
204 @*/
205 PetscErrorCode PetscDeviceContextGetStreamType(PetscDeviceContext dctx, PetscStreamType *type) {
206   PetscFunctionBegin;
207   PetscCall(PetscDeviceContextGetOptionalNullContext_Internal(&dctx));
208   PetscValidIntPointer(type, 2);
209   *type = dctx->streamType;
210   PetscFunctionReturn(0);
211 }
212 
213 /*
214   Actual function to set the device.
215 
216   1. Repeatedly destroying and recreating internal data structures (like streams and events)
217      for recycled PetscDeviceContexts is not free. If done often, it does add up.
218   2. The vast majority of PetscDeviceContexts are created by PETSc either as children or
219      default contexts. The default contexts *never* change type, and the chilren are extremely
220      unlikely to (chances are if you fork once, you will fork again very soon).
221   3. The only time this calculus changes is if the user themselves sets the device type. In
222      this case we do not know what the user has changed, so must always wipe the slate clean.
223 
224   Thus we need to keep track whether the user explicitly sets the device contexts device.
225 */
226 static PetscErrorCode PetscDeviceContextSetDevice_Private(PetscDeviceContext dctx, PetscDevice device, PetscBool user_set) {
227   PetscFunctionBegin;
228   // do not use getoptionalnullcontext here since we do not want the user to change its device
229   PetscValidDeviceContext(dctx, 1);
230   PetscValidDevice(device, 2);
231   if (dctx->device && (dctx->device->id == device->id)) PetscFunctionReturn(0);
232   PetscCall(PetscLogEventBegin(DCONTEXT_SetDevice, dctx, nullptr, nullptr, nullptr));
233   if (const auto destroy = dctx->ops->destroy) PetscCall((*destroy)(dctx));
234   PetscCall(PetscDeviceDestroy(&dctx->device));
235   PetscCall(PetscMemzero(dctx->ops, sizeof(*dctx->ops)));
236   PetscCall((*device->ops->createcontext)(dctx));
237   PetscCall(PetscLogEventEnd(DCONTEXT_SetDevice, dctx, nullptr, nullptr, nullptr));
238   PetscCall(PetscDeviceReference_Internal(device));
239   dctx->device        = device;
240   dctx->setup         = PETSC_FALSE;
241   dctx->usersetdevice = user_set;
242   PetscFunctionReturn(0);
243 }
244 
245 PetscErrorCode PetscDeviceContextSetDefaultDeviceForType_Internal(PetscDeviceContext dctx, PetscDeviceType type) {
246   PetscDevice device;
247 
248   PetscFunctionBegin;
249   PetscCall(PetscDeviceGetDefaultForType_Internal(type, &device));
250   PetscCall(PetscDeviceContextSetDevice_Private(dctx, device, PETSC_FALSE));
251   PetscFunctionReturn(0);
252 }
253 
254 /*@C
255   PetscDeviceContextSetDevice - Set the underlying `PetscDevice` for a `PetscDeviceContext`
256 
257   Not Collective
258 
259   Input Parameters:
260 + dctx   - The `PetscDeviceContext`
261 - device - The `PetscDevice`
262 
263   Notes:
264   This routine is effectively `PetscDeviceContext`'s "set-type" (so every `PetscDeviceContext` must
265   also have an attached `PetscDevice`). Unlike the usual set-type semantics, it is not stricly
266   necessary to set a contexts device to enable usage, any created `PetscDeviceContext`s will
267   always come equipped with the "default" device.
268 
269   This routine is a no-op if `device` is already attached to `dctx`.
270 
271   This routine may (but is very unlikely to) initialize the backend device and may incur
272   synchronization.
273 
274   Level: intermediate
275 
276 .seealso: `PetscDeviceCreate()`, `PetscDeviceConfigure()`, `PetscDeviceContextGetDevice()`,
277 `PetscDeviceContextGetDeviceType()`
278 @*/
279 PetscErrorCode PetscDeviceContextSetDevice(PetscDeviceContext dctx, PetscDevice device) {
280   PetscFunctionBegin;
281   PetscCall(PetscDeviceContextSetDevice_Private(dctx, device, PETSC_TRUE));
282   PetscFunctionReturn(0);
283 }
284 
285 /*@C
286   PetscDeviceContextGetDevice - Get the underlying `PetscDevice` for a `PetscDeviceContext`
287 
288   Not Collective
289 
290   Input Parameter:
291 . dctx - the `PetscDeviceContext`
292 
293   Output Parameter:
294 . device - The `PetscDevice`
295 
296   Notes:
297   This is a borrowed reference, the user should not destroy `device`.
298 
299   Level: intermediate
300 
301 .seealso: `PetscDeviceContextSetDevice()`, `PetscDevice`, `PetscDeviceContextGetDeviceType()`
302 @*/
303 PetscErrorCode PetscDeviceContextGetDevice(PetscDeviceContext dctx, PetscDevice *device) {
304   PetscFunctionBegin;
305   PetscCall(PetscDeviceContextGetOptionalNullContext_Internal(&dctx));
306   PetscValidPointer(device, 2);
307   PetscAssert(dctx->device, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE, "PetscDeviceContext %" PetscInt64_FMT " has no attached PetscDevice to get", PetscObjectCast(dctx)->id);
308   *device = dctx->device;
309   PetscFunctionReturn(0);
310 }
311 
312 /*@C
313   PetscDeviceContextGetDeviceType - Get the `PetscDeviceType` for a `PetscDeviceContext`
314 
315   Not Collective
316 
317   Input Parameter:
318 . dctx - The `PetscDeviceContext`
319 
320   Output Parameter:
321 . type - The `PetscDeviceType`
322 
323   Notes:
324   This routine is a convenience shorthand for `PetscDeviceContextGetDevice()` ->
325   `PetscDeviceGetType()`.
326 
327   Level: beginner
328 
329 .seealso: `PetscDeviceType`, `PetscDeviceContextGetDevice()`, `PetscDeviceGetType()`, `PetscDevice`
330 @*/
331 PetscErrorCode PetscDeviceContextGetDeviceType(PetscDeviceContext dctx, PetscDeviceType *type) {
332   PetscDevice device = nullptr;
333 
334   PetscFunctionBegin;
335   PetscCall(PetscDeviceContextGetOptionalNullContext_Internal(&dctx));
336   PetscValidPointer(type, 2);
337   PetscCall(PetscDeviceContextGetDevice(dctx, &device));
338   PetscCall(PetscDeviceGetType(device, type));
339   PetscFunctionReturn(0);
340 }
341 
342 /*@C
343   PetscDeviceContextSetUp - Prepares a `PetscDeviceContext` for use
344 
345   Not Collective
346 
347   Input Parameter:
348 . dctx - The `PetscDeviceContext`
349 
350   Developer Notes:
351   This routine is usually the stage where a `PetscDeviceContext` acquires device-side data
352   structures such as streams, events, and (possibly) handles.
353 
354   Level: beginner
355 
356 .seealso: `PetscDeviceContextCreate()`, `PetscDeviceContextSetDevice()`,
357 `PetscDeviceContextDestroy()`, `PetscDeviceContextSetFromOptions()`
358 @*/
359 PetscErrorCode PetscDeviceContextSetUp(PetscDeviceContext dctx) {
360   PetscFunctionBegin;
361   PetscCall(PetscDeviceContextGetOptionalNullContext_Internal(&dctx));
362   if (dctx->setup) PetscFunctionReturn(0);
363   if (!dctx->device) {
364     const auto default_dtype = PETSC_DEVICE_DEFAULT();
365 
366     PetscCall(PetscInfo(dctx, "PetscDeviceContext %" PetscInt64_FMT " did not have an explicitly attached PetscDevice, using default with type %s\n", PetscObjectCast(dctx)->id, PetscDeviceTypes[default_dtype]));
367     PetscCall(PetscDeviceContextSetDefaultDeviceForType_Internal(dctx, default_dtype));
368   }
369   PetscCall(PetscLogEventBegin(DCONTEXT_SetUp, dctx, nullptr, nullptr, nullptr));
370   PetscUseTypeMethod(dctx, setup);
371   PetscCall(PetscLogEventEnd(DCONTEXT_SetUp, dctx, nullptr, nullptr, nullptr));
372   dctx->setup = PETSC_TRUE;
373   PetscFunctionReturn(0);
374 }
375 
376 static PetscErrorCode PetscDeviceContextDuplicate_Private(PetscDeviceContext dctx, PetscStreamType stype, PetscDeviceContext *dctxdup) {
377   PetscFunctionBegin;
378   PetscCall(PetscLogEventBegin(DCONTEXT_Duplicate, dctx, nullptr, nullptr, nullptr));
379   PetscCall(PetscDeviceContextCreate(dctxdup));
380   PetscCall(PetscDeviceContextSetStreamType(*dctxdup, stype));
381   if (const auto device = dctx->device) PetscCall(PetscDeviceContextSetDevice_Private(*dctxdup, device, dctx->usersetdevice));
382   PetscCall(PetscDeviceContextSetUp(*dctxdup));
383   PetscCall(PetscLogEventEnd(DCONTEXT_Duplicate, dctx, nullptr, nullptr, nullptr));
384   PetscFunctionReturn(0);
385 }
386 
387 /*@C
388   PetscDeviceContextDuplicate - Duplicates a `PetscDeviceContext` object
389 
390   Not Collective
391 
392   Input Parameter:
393 . dctx - The `PetscDeviceContext` to duplicate
394 
395   Output Parameter:
396 . dctxdup - The duplicated `PetscDeviceContext`
397 
398   Notes:
399   This is a shorthand method for creating a `PetscDeviceContext` with the exact same settings as
400   another. Note however that `dctxdup` does not share any of the underlying data with `dctx`,
401   (including its current stream-state) they are completely separate objects.
402 
403   There is no implied ordering between `dctx` or `dctxdup`.
404 
405   DAG representation:
406 .vb
407   time ->
408 
409   -> dctx - |= CALL =| - dctx ---->
410                        - dctxdup ->
411 .ve
412 
413   Level: beginner
414 
415 .seealso: `PetscDeviceContextCreate()`, `PetscDeviceContextSetDevice()`,
416 `PetscDeviceContextSetStreamType()`
417 @*/
418 PetscErrorCode PetscDeviceContextDuplicate(PetscDeviceContext dctx, PetscDeviceContext *dctxdup) {
419   auto stype = PETSC_STREAM_DEFAULT_BLOCKING;
420 
421   PetscFunctionBegin;
422   PetscCall(PetscDeviceContextGetOptionalNullContext_Internal(&dctx));
423   PetscValidPointer(dctxdup, 2);
424   PetscCall(PetscDeviceContextGetStreamType(dctx, &stype));
425   PetscCall(PetscDeviceContextDuplicate_Private(dctx, stype, dctxdup));
426   PetscFunctionReturn(0);
427 }
428 
429 /*@C
430   PetscDeviceContextQueryIdle - Returns whether or not a `PetscDeviceContext` is idle
431 
432   Not Collective
433 
434   Input Parameter:
435 . dctx - The `PetscDeviceContext`
436 
437   Output Parameter:
438 . idle - `PETSC_TRUE` if `dctx` has NO work, `PETSC_FALSE` if it has work
439 
440   Note:
441   This routine only refers a singular context and does NOT take any of its children into
442   account. That is, if `dctx` is idle but has dependents who do have work this routine still
443   returns `PETSC_TRUE`.
444 
445   Level: intermediate
446 
447 .seealso: `PetscDeviceContextCreate()`, `PetscDeviceContextWaitForContext()`, `PetscDeviceContextFork()`
448 @*/
449 PetscErrorCode PetscDeviceContextQueryIdle(PetscDeviceContext dctx, PetscBool *idle) {
450   PetscFunctionBegin;
451   PetscCall(PetscDeviceContextGetOptionalNullContext_Internal(&dctx));
452   PetscValidBoolPointer(idle, 2);
453   PetscCall(PetscLogEventBegin(DCONTEXT_QueryIdle, dctx, nullptr, nullptr, nullptr));
454   PetscUseTypeMethod(dctx, query, idle);
455   PetscCall(PetscLogEventEnd(DCONTEXT_QueryIdle, dctx, nullptr, nullptr, nullptr));
456   PetscCall(PetscInfo(dctx, "PetscDeviceContext ('%s', id %" PetscInt64_FMT ") %s idle\n", PetscObjectCast(dctx)->name ? PetscObjectCast(dctx)->name : "unnamed", PetscObjectCast(dctx)->id, *idle ? "was" : "was not"));
457   PetscFunctionReturn(0);
458 }
459 
460 /*@C
461   PetscDeviceContextWaitForContext - Make one context wait for another context to finish
462 
463   Not Collective
464 
465   Input Parameters:
466 + dctxa - The `PetscDeviceContext` object that is waiting
467 - dctxb - The `PetscDeviceContext` object that is being waited on
468 
469   Notes:
470   Serializes two `PetscDeviceContext`s. Serialization is performed asynchronously; the host
471   does not wait for the serialization to actually occur.
472 
473   This routine uses only the state of `dctxb` at the moment this routine was called, so any
474   future work queued will not affect `dctxa`. It is safe to pass the same context to both
475   arguments (in which case this routine does nothing).
476 
477   DAG representation:
478 .vb
479   time ->
480 
481   -> dctxa ---/- |= CALL =| - dctxa ->
482              /
483   -> dctxb -/------------------------>
484 .ve
485 
486   Level: beginner
487 
488 .N ASYNC_API
489 
490 .seealso: `PetscDeviceContextCreate()`, `PetscDeviceContextQueryIdle()`, `PetscDeviceContextJoin()`
491 @*/
492 PetscErrorCode PetscDeviceContextWaitForContext(PetscDeviceContext dctxa, PetscDeviceContext dctxb) {
493   PetscObject aobj;
494 
495   PetscFunctionBegin;
496   PetscCall(PetscDeviceContextGetOptionalNullContext_Internal(&dctxa));
497   PetscCall(PetscDeviceContextGetOptionalNullContext_Internal(&dctxb));
498   PetscCheckCompatibleDeviceContexts(dctxa, 1, dctxb, 2);
499   if (dctxa == dctxb) PetscFunctionReturn(0);
500   aobj = PetscObjectCast(dctxa);
501   PetscCall(PetscLogEventBegin(DCONTEXT_WaitForCtx, dctxa, dctxb, nullptr, nullptr));
502   PetscUseTypeMethod(dctxa, waitforcontext, dctxb);
503   PetscCallCXX(CxxDataCast(dctxa)->upstream[dctxb] = CxxData::parent_type(dctxb));
504   PetscCall(PetscLogEventEnd(DCONTEXT_WaitForCtx, dctxa, dctxb, nullptr, nullptr));
505   PetscCall(PetscInfo(dctxa, "dctx %" PetscInt64_FMT " waiting on dctx %" PetscInt64_FMT "\n", aobj->id, PetscObjectCast(dctxb)->id));
506   PetscCall(PetscObjectStateIncrease(aobj));
507   PetscFunctionReturn(0);
508 }
509 
510 /*@C
511   PetscDeviceContextForkWithStreamType - Create a set of dependent child contexts from a parent
512   context with a prescribed `PetscStreamType`
513 
514   Not Collective, Asynchronous
515 
516   Input Parameters:
517 + dctx  - The parent `PetscDeviceContext`
518 . stype - The prescribed `PetscStreamType`
519 - n     - The number of children to create
520 
521   Output Parameter:
522 . dsub - The created child context(s)
523 
524   Notes:
525   This routine creates `n` edges of a DAG from a source node which are causally dependent on the
526   source node. This causal dependency is established as-if by calling
527   `PetscDeviceContextWaitForContext()` on every child.
528 
529   `dsub` is allocated by this routine and has its lifetime bounded by `dctx`. That is, `dctx`
530   expects to free `dsub` (via `PetscDeviceContextJoin()`) before it itself is destroyed.
531 
532   This routine only accounts for work queued on `dctx` up until calling this routine, any
533   subsequent work enqueued on `dctx` has no effect on `dsub`.
534 
535   The `PetscStreamType` of `dctx` does not have to equal `stype`. In fact, it is often the case
536   that they are different. This is useful in cases where a routine can locally exploit stream
537   parallelism without needing to worry about what stream type the incoming `PetscDeviceContext`
538   carries.
539 
540   DAG representation:
541 .vb
542   time ->
543 
544   -> dctx - |= CALL =| -\----> dctx ------>
545                          \---> dsub[0] --->
546                           \--> ... ------->
547                            \-> dsub[n-1] ->
548 .ve
549 
550   Level: intermediate
551 
552 .N ASYNC_API
553 
554 .seealso: `PetscDeviceContextJoin()`, `PetscDeviceContextSynchronize()`,
555 `PetscDeviceContextQueryIdle()`, `PetscDeviceContextWaitForContext()`
556 @*/
557 PetscErrorCode PetscDeviceContextForkWithStreamType(PetscDeviceContext dctx, PetscStreamType stype, PetscInt n, PetscDeviceContext **dsub) {
558   // debugging only
559   std::string idList;
560   auto        ninput = n;
561 
562   PetscFunctionBegin;
563   PetscCall(PetscDeviceContextGetOptionalNullContext_Internal(&dctx));
564   PetscAssert(n >= 0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Number of contexts requested %" PetscInt_FMT " < 0", n);
565   PetscValidPointer(dsub, 4);
566   *dsub = nullptr;
567   /* reserve 4 chars per id, 2 for number and 2 for ', ' separator */
568   if (PetscDefined(USE_DEBUG_AND_INFO)) PetscCallCXX(idList.reserve(4 * n));
569   PetscCall(PetscLogEventBegin(DCONTEXT_Fork, dctx, nullptr, nullptr, nullptr));
570   /* update child totals */
571   dctx->numChildren += n;
572   /* now to find out if we have room */
573   if (dctx->numChildren > dctx->maxNumChildren) {
574     const auto numChildren    = dctx->numChildren;
575     auto      &maxNumChildren = dctx->maxNumChildren;
576     auto       numAllocated   = numChildren;
577 
578     /* no room, either from having too many kids or not having any */
579     if (auto &childIDs = dctx->childIDs) {
580       // the difference is backwards because we have not updated maxNumChildren yet
581       numAllocated -= maxNumChildren;
582       /* have existing children, must reallocate them */
583       PetscCall(PetscRealloc(numChildren * sizeof(*childIDs), &childIDs));
584       /* clear the extra memory since realloc doesn't do it for us */
585       PetscCall(PetscArrayzero(std::next(childIDs, maxNumChildren), numAllocated));
586     } else {
587       /* have no children */
588       PetscCall(PetscCalloc1(numChildren, &childIDs));
589     }
590     /* update total number of children */
591     maxNumChildren = numChildren;
592     PetscCall(PetscLogObjectMemory(PetscObjectCast(dctx), numAllocated * sizeof(*(dctx->childIDs))));
593   }
594   PetscCall(PetscMalloc1(n, dsub));
595   PetscCall(PetscLogObjectMemory(PetscObjectCast(dctx), n * sizeof(*dsub)));
596   for (PetscInt i = 0; ninput && (i < dctx->numChildren); ++i) {
597     auto &childID = dctx->childIDs[i];
598     /* empty child slot */
599     if (!childID) {
600       auto &childctx = (*dsub)[i];
601 
602       /* create the child context in the image of its parent */
603       PetscCall(PetscDeviceContextDuplicate_Private(dctx, stype, &childctx));
604       PetscCall(PetscDeviceContextWaitForContext(childctx, dctx));
605       /* register the child with its parent */
606       PetscCall(PetscObjectGetId(PetscObjectCast(childctx), &childID));
607       if (PetscDefined(USE_DEBUG_AND_INFO)) {
608         PetscCallCXX(idList += std::to_string(childID));
609         if (ninput != 1) PetscCallCXX(idList += ", ");
610       }
611       --ninput;
612     }
613   }
614   PetscCall(PetscLogEventEnd(DCONTEXT_Fork, dctx, nullptr, nullptr, nullptr));
615   PetscCall(PetscDebugInfo(dctx, "Forked %" PetscInt_FMT " children from parent %" PetscInt64_FMT " with IDs: %s\n", n, PetscObjectCast(dctx)->id, idList.c_str()));
616   PetscFunctionReturn(0);
617 }
618 
619 /*@C
620   PetscDeviceContextFork - Create a set of dependent child contexts from a parent context
621 
622   Not Collective, Asynchronous
623 
624   Input Parameters:
625 + dctx - The parent `PetscDeviceContext`
626 - n    - The number of children to create
627 
628   Output Parameter:
629 . dsub - The created child context(s)
630 
631   Notes:
632   Behaves identically to `PetscDeviceContextForkWithStreamType()` except that the prescribed
633   `PetscStreamType` is taken from `dctx`. In effect this routine is shorthand for\:
634 
635 .vb
636   PetscStreamType stype;
637 
638   PetscDeviceContextGetStreamType(dctx, &stype);
639   PetscDeviceContextForkWithStreamType(dctx, stype, ...);
640 .ve
641 
642   Level: beginner
643 
644 .N ASYNC_API
645 
646 .seealso: `PetscDeviceContextForkWithStreamType()`, `PetscDeviceContextJoin()`,
647 `PetscDeviceContextSynchronize()`, `PetscDeviceContextQueryIdle()`
648 @*/
649 PetscErrorCode PetscDeviceContextFork(PetscDeviceContext dctx, PetscInt n, PetscDeviceContext **dsub) {
650   auto stype = PETSC_STREAM_DEFAULT_BLOCKING;
651 
652   PetscFunctionBegin;
653   PetscCall(PetscDeviceContextGetOptionalNullContext_Internal(&dctx));
654   PetscCall(PetscDeviceContextGetStreamType(dctx, &stype));
655   PetscCall(PetscDeviceContextForkWithStreamType(dctx, stype, n, dsub));
656   PetscFunctionReturn(0);
657 }
658 
659 /*@C
660   PetscDeviceContextJoin - Converge a set of child contexts
661 
662   Not Collective, Asynchronous
663 
664   Input Parameters:
665 + dctx         - A `PetscDeviceContext` to converge on
666 . n            - The number of sub contexts to converge
667 . joinMode     - The type of join to perform
668 - dsub         - The sub contexts to converge
669 
670   Notes:
671   If `PetscDeviceContextFork()` creates `n` edges from a source node which all depend on the source
672   node, then this routine is the exact mirror. That is, it creates a node (represented in `dctx`)
673   which recieves `n` edges (and optionally destroys them) which is dependent on the completion
674   of all incoming edges.
675 
676   If `joinMode` is `PETSC_DEVICE_CONTEXT_JOIN_DESTROY`. All contexts in `dsub` will be
677   destroyed by this routine. Thus all sub contexts must have been created with the `dctx`
678   passed to this routine.
679 
680   If `joinMode` is `PETSC_DEVICE_CONTEXT_JOIN_SYNC`. All sub contexts will additionally wait on
681   `dctx` after converging. This has the effect of "synchronizing" the outgoing edges. Note the
682   sync suffix does NOT refer to the host, i.e. this routine does NOT call
683   `PetscDeviceSynchronize()`.
684 
685   If `joinMode` is `PETSC_DEVICE_CONTEXT_JOIN_NO_SYNC`. `dctx` waits for all sub contexts but
686   the sub contexts do not wait for one another or `dctx` afterwards.
687 
688   DAG representations:
689   If `joinMode` is `PETSC_DEVICE_CONTEXT_JOIN_DESTROY`
690 .vb
691   time ->
692 
693   -> dctx ---------/- |= CALL =| - dctx ->
694   -> dsub[0] -----/
695   ->  ... -------/
696   -> dsub[n-1] -/
697 .ve
698   If `joinMode` is `PETSC_DEVICE_CONTEXT_JOIN_SYNC`
699 .vb
700   time ->
701 
702   -> dctx ---------/- |= CALL =| -\----> dctx ------>
703   -> dsub[0] -----/                \---> dsub[0] --->
704   ->  ... -------/                  \--> ... ------->
705   -> dsub[n-1] -/                    \-> dsub[n-1] ->
706 .ve
707   If `joinMode` is `PETSC_DEVICE_CONTEXT_JOIN_NO_SYNC`
708 .vb
709   time ->
710 
711   -> dctx ----------/- |= CALL =| - dctx ->
712   -> dsub[0] ------/----------------------->
713   ->  ... --------/------------------------>
714   -> dsub[n-1] --/------------------------->
715 .ve
716 
717   Level: beginner
718 
719 .N ASYNC_API
720 
721 .seealso: `PetscDeviceContextFork()`, `PetscDeviceContextForkWithStreamType()`,
722 `PetscDeviceContextSynchronize()`, `PetscDeviceContextJoinMode`
723 @*/
724 PetscErrorCode PetscDeviceContextJoin(PetscDeviceContext dctx, PetscInt n, PetscDeviceContextJoinMode joinMode, PetscDeviceContext **dsub) {
725   // debugging only
726   std::string idList;
727 
728   PetscFunctionBegin;
729   PetscCall(PetscDeviceContextGetOptionalNullContext_Internal(&dctx));
730   /* validity of dctx is checked in the wait-for loop */
731   PetscValidPointer(dsub, 4);
732   PetscAssert(n >= 0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Number of contexts merged %" PetscInt_FMT " < 0", n);
733   /* reserve 4 chars per id, 2 for number and 2 for ', ' separator */
734   if (PetscDefined(USE_DEBUG_AND_INFO)) PetscCallCXX(idList.reserve(4 * n));
735   /* first dctx waits on all the incoming edges */
736   PetscCall(PetscLogEventBegin(DCONTEXT_Join, dctx, nullptr, nullptr, nullptr));
737   for (PetscInt i = 0; i < n; ++i) {
738     PetscCheckCompatibleDeviceContexts(dctx, 1, (*dsub)[i], 4);
739     PetscCall(PetscDeviceContextWaitForContext(dctx, (*dsub)[i]));
740     if (PetscDefined(USE_DEBUG_AND_INFO)) {
741       PetscCallCXX(idList += std::to_string(PetscObjectCast((*dsub)[i])->id));
742       if (i + 1 < n) PetscCallCXX(idList += ", ");
743     }
744   }
745 
746   /* now we handle the aftermath */
747   switch (joinMode) {
748   case PETSC_DEVICE_CONTEXT_JOIN_DESTROY: {
749     const auto children = dctx->childIDs;
750     const auto maxchild = dctx->maxNumChildren;
751     auto      &nchild   = dctx->numChildren;
752     PetscInt   j        = 0;
753 
754     PetscCheck(n <= nchild, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Trying to destroy %" PetscInt_FMT " children of a parent context that only has %" PetscInt_FMT " children, likely trying to restore to wrong parent", n, nchild);
755     /* update child count while it's still fresh in memory */
756     nchild -= n;
757     for (PetscInt i = 0; i < maxchild; ++i) {
758       if (children[i] && (children[i] == PetscObjectCast((*dsub)[j])->id)) {
759         /* child is one of ours, can destroy it */
760         PetscCall(PetscDeviceContextDestroy((*dsub) + j));
761         /* reset the child slot */
762         children[i] = 0;
763         if (++j == n) break;
764       }
765     }
766     /* gone through the loop but did not find every child */
767     PetscCheck(j == n, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "%" PetscInt_FMT " contexts still remain after destroy, this may be because you are trying to restore to the wrong parent context, or the device contexts are not in the same order as they were checked out out in", n - j);
768     PetscCall(PetscFree(*dsub));
769     PetscCall(PetscLogObjectMemory(PetscObjectCast(dctx), -n * sizeof(*dsub)));
770   } break;
771   case PETSC_DEVICE_CONTEXT_JOIN_SYNC:
772     for (PetscInt i = 0; i < n; ++i) PetscCall(PetscDeviceContextWaitForContext((*dsub)[i], dctx));
773   case PETSC_DEVICE_CONTEXT_JOIN_NO_SYNC: break;
774   default: SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Unknown PetscDeviceContextJoinMode given");
775   }
776   PetscCall(PetscLogEventEnd(DCONTEXT_Join, dctx, nullptr, nullptr, nullptr));
777 
778   PetscCall(PetscDebugInfo(dctx, "Joined %" PetscInt_FMT " ctxs to ctx %" PetscInt64_FMT ", mode %s with IDs: %s\n", n, PetscObjectCast(dctx)->id, PetscDeviceContextJoinModes[joinMode], idList.c_str()));
779   PetscFunctionReturn(0);
780 }
781 
782 /*@C
783   PetscDeviceContextSynchronize - Block the host until all work queued on a
784   `PetscDeviceContext` has finished
785 
786   Not Collective
787 
788   Input Parameters:
789 . dctx - The `PetscDeviceContext` to synchronize
790 
791   Notes:
792   The host will not return from this routine until `dctx` is idle. Any and all memory
793   operations queued on or otherwise associated with (either explicitly or implicitly via
794   dependencies) are guaranteed to have finished and be globally visible on return.
795 
796   In effect, this routine serves as memory and execution barrier.
797 
798   DAG representation:
799 .vb
800   time ->
801 
802   -> dctx - |= CALL =| - dctx ->
803 .ve
804 
805   Level: beginner
806 
807 .seealso: `PetscDeviceContextFork()`, `PetscDeviceContextJoin()`, `PetscDeviceContextQueryIdle()`
808 @*/
809 PetscErrorCode PetscDeviceContextSynchronize(PetscDeviceContext dctx) {
810   PetscFunctionBegin;
811   PetscCall(PetscDeviceContextGetOptionalNullContext_Internal(&dctx));
812   PetscCall(PetscLogEventBegin(DCONTEXT_Sync, dctx, nullptr, nullptr, nullptr));
813   /* if it isn't setup there is nothing to sync on */
814   if (dctx->setup) {
815     PetscCall((*dctx->ops->synchronize)(dctx));
816     PetscCall(PetscDeviceContextSyncClearMap_Internal(dctx));
817   }
818   PetscCall(PetscLogEventEnd(DCONTEXT_Sync, dctx, nullptr, nullptr, nullptr));
819   PetscFunctionReturn(0);
820 }
821 
822 /* every device type has a vector of null PetscDeviceContexts -- one for each device */
823 static auto nullContexts          = std::array<std::vector<PetscDeviceContext>, PETSC_DEVICE_MAX>{};
824 static auto nullContextsFinalizer = false;
825 
826 static PetscErrorCode PetscDeviceContextGetNullContextForDevice_Private(PetscBool user_set_device, PetscDevice device, PetscDeviceContext *dctx) {
827   PetscInt        devid;
828   PetscDeviceType dtype;
829 
830   PetscFunctionBegin;
831   PetscValidDevice(device, 2);
832   PetscValidPointer(dctx, 3);
833   if (PetscUnlikely(!nullContextsFinalizer)) {
834     const auto finalizer = [] {
835       PetscFunctionBegin;
836       for (auto &&dvec : nullContexts) {
837         for (auto &&dctx : dvec) PetscCall(PetscDeviceContextDestroy(&dctx));
838         PetscCallCXX(dvec.clear());
839       }
840       nullContextsFinalizer = false;
841       PetscFunctionReturn(0);
842     };
843 
844     nullContextsFinalizer = true;
845     PetscCall(PetscRegisterFinalize(std::move(finalizer)));
846   }
847   PetscCall(PetscDeviceGetDeviceId(device, &devid));
848   PetscCall(PetscDeviceGetType(device, &dtype));
849   {
850     auto &ctxlist = nullContexts[dtype];
851 
852     PetscCheck(devid >= 0, PETSC_COMM_SELF, PETSC_ERR_PLIB, "Device ID (%" PetscInt_FMT ") must be positive", devid);
853     // need to resize the container if not big enough because incrementing the iterator in
854     // std::next() (if we haven't initialized that ctx yet) may cause it to fall outside the
855     // current size of the container.
856     if (static_cast<std::size_t>(devid) >= ctxlist.size()) PetscCallCXX(ctxlist.resize(devid + 1));
857     if (PetscUnlikely(!ctxlist[devid])) {
858       // we have not seen this device before
859       PetscCall(PetscDeviceContextCreate(dctx));
860       PetscCall(PetscInfo(dctx, "Initializing null PetscDeviceContext (of type %s) for device %" PetscInt_FMT "\n", PetscDeviceTypes[dtype], devid));
861       {
862         const auto pobj   = PetscObjectCast(*dctx);
863         const auto name   = "null context " + std::to_string(devid);
864         const auto prefix = "null_context_" + std::to_string(devid) + '_';
865 
866         PetscCall(PetscObjectSetName(pobj, name.c_str()));
867         PetscCall(PetscObjectSetOptionsPrefix(pobj, prefix.c_str()));
868       }
869       PetscCall(PetscDeviceContextSetStreamType(*dctx, PETSC_STREAM_GLOBAL_BLOCKING));
870       PetscCall(PetscDeviceContextSetDevice_Private(*dctx, device, user_set_device));
871       PetscCall(PetscDeviceContextSetUp(*dctx));
872       // would use ctxlist.cbegin() but GCC 4.8 can't handle const iterator insert!
873       PetscCallCXX(ctxlist.insert(std::next(ctxlist.begin(), devid), *dctx));
874     } else *dctx = ctxlist[devid];
875   }
876   PetscFunctionReturn(0);
877 }
878 
879 /*
880   Gets the "NULL" context for the current PetscDeviceType and PetscDevice. NULL contexts are
881   guaranteed to always be globally blocking.
882 */
883 PetscErrorCode PetscDeviceContextGetNullContext_Internal(PetscDeviceContext *dctx) {
884   PetscDeviceContext gctx;
885   PetscDevice        gdev = nullptr;
886 
887   PetscFunctionBegin;
888   PetscValidPointer(dctx, 1);
889   PetscCall(PetscDeviceContextGetCurrentContext(&gctx));
890   PetscCall(PetscDeviceContextGetDevice(gctx, &gdev));
891   PetscCall(PetscDeviceContextGetNullContextForDevice_Private(gctx->usersetdevice, gdev, dctx));
892   PetscFunctionReturn(0);
893 }
894 
895 /*@C
896   PetscDeviceContextSetFromOptions - Configure a `PetscDeviceContext` from the options database
897 
898   Collective on `comm` or `dctx`
899 
900   Input Parameters:
901 + comm - MPI communicator on which to query the options database (optional)
902 - dctx - The `PetscDeviceContext` to configure
903 
904   Output Parameter:
905 . dctx - The `PetscDeviceContext`
906 
907   Options Database:
908 + -device_context_stream_type - type of stream to create inside the `PetscDeviceContext` -
909    `PetscDeviceContextSetStreamType()`
910 - -device_context_device_type - the type of `PetscDevice` to attach by default - `PetscDeviceType`
911 
912   Notes:
913   The user may pass `MPI_COMM_NULL` for `comm` in which case the communicator of `dctx` is
914   used (which is always `PETSC_COMM_SELF`).
915 
916   Level: beginner
917 
918 .seealso: `PetscDeviceContextSetStreamType()`, `PetscDeviceContextSetDevice()`,
919 `PetscDeviceContextView()`
920 @*/
921 PetscErrorCode PetscDeviceContextSetFromOptions(MPI_Comm comm, PetscDeviceContext dctx) {
922   const auto pobj     = PetscObjectCast(dctx);
923   auto       dtype    = std::make_pair(PETSC_DEVICE_DEFAULT(), PETSC_FALSE);
924   auto       stype    = std::make_pair(PETSC_DEVICE_CONTEXT_DEFAULT_STREAM_TYPE, PETSC_FALSE);
925   auto       old_comm = PETSC_COMM_SELF;
926 
927   PetscFunctionBegin;
928   // do not user getoptionalnullcontext here, the user is not allowed to set it from options!
929   PetscValidDeviceContext(dctx, 2);
930   /* set the device type first */
931   if (const auto device = dctx->device) PetscCall(PetscDeviceGetType(device, &dtype.first));
932   PetscCall(PetscDeviceContextGetStreamType(dctx, &stype.first));
933 
934   if (comm == MPI_COMM_NULL) {
935     PetscCall(PetscObjectGetComm(pobj, &comm));
936   } else {
937     // briefly set the communicator for dctx (it is always PETSC_COMM_SELF) so
938     // PetscObjectOptionsBegin() behaves as if dctx had comm
939     old_comm = Petsc::util::exchange(pobj->comm, comm);
940   }
941 
942   PetscObjectOptionsBegin(pobj);
943   PetscCall(PetscDeviceContextQueryOptions_Internal(PetscOptionsObject, dtype, stype));
944   PetscOptionsEnd();
945   // reset the comm (should be PETSC_COMM_SELF)
946   if (comm != MPI_COMM_NULL) pobj->comm = old_comm;
947   if (dtype.second) PetscCall(PetscDeviceContextSetDefaultDeviceForType_Internal(dctx, dtype.first));
948   if (stype.second) PetscCall(PetscDeviceContextSetStreamType(dctx, stype.first));
949   PetscCall(PetscDeviceContextSetUp(dctx));
950   PetscFunctionReturn(0);
951 }
952 
953 /*@C
954   PetscDeviceContextView - View a `PetscDeviceContext`
955 
956   Collective on `viewer`
957 
958   Input Parameters:
959 + dctx - The `PetscDeviceContext`
960 - viewer - The `PetscViewer` to view `dctx` with (may be `NULL`)
961 
962   Notes:
963   If `viewer` is `NULL`, `PETSC_VIEWER_STDOUT_WORLD` is used instead, in which case this
964   routine is collective on `PETSC_COMM_WORLD`.
965 
966   Level: beginner
967 
968 .seealso: `PetscDeviceContextViewFromOptions()`, `PetscDeviceView()`, `PETSC_VIEWER_STDOUT_WORLD`, `PetscDeviceContextCreate()`
969 @*/
970 PetscErrorCode PetscDeviceContextView(PetscDeviceContext dctx, PetscViewer viewer) {
971   PetscBool iascii;
972 
973   PetscFunctionBegin;
974   PetscCall(PetscDeviceContextGetOptionalNullContext_Internal(&dctx));
975   if (!viewer) PetscCall(PetscViewerASCIIGetStdout(PETSC_COMM_WORLD, &viewer));
976   PetscValidHeaderSpecific(viewer, PETSC_VIEWER_CLASSID, 2);
977   PetscCall(PetscObjectTypeCompare(PetscObjectCast(viewer), PETSCVIEWERASCII, &iascii));
978   if (iascii) {
979     auto        stype = PETSC_STREAM_DEFAULT_BLOCKING;
980     PetscViewer sub;
981 
982     PetscCall(PetscViewerGetSubViewer(viewer, PETSC_COMM_SELF, &sub));
983     PetscCall(PetscObjectPrintClassNamePrefixType(PetscObjectCast(dctx), sub));
984     PetscCall(PetscViewerASCIIPushTab(sub));
985     PetscCall(PetscDeviceContextGetStreamType(dctx, &stype));
986     PetscCall(PetscViewerASCIIPrintf(sub, "stream type: %s\n", PetscStreamTypes[stype]));
987     PetscCall(PetscViewerASCIIPrintf(sub, "children: %" PetscInt_FMT "\n", dctx->numChildren));
988     if (const auto nchild = dctx->numChildren) {
989       PetscCall(PetscViewerASCIIPushTab(sub));
990       for (PetscInt i = 0; i < nchild; ++i) {
991         if (i == nchild - 1) {
992           PetscCall(PetscViewerASCIIPrintf(sub, "%" PetscInt64_FMT, dctx->childIDs[i]));
993         } else {
994           PetscCall(PetscViewerASCIIPrintf(sub, "%" PetscInt64_FMT ", ", dctx->childIDs[i]));
995         }
996       }
997     }
998     PetscCall(PetscViewerASCIIPopTab(sub));
999     PetscCall(PetscViewerRestoreSubViewer(viewer, PETSC_COMM_SELF, &sub));
1000     PetscCall(PetscViewerFlush(viewer));
1001     PetscCall(PetscViewerASCIIPushTab(viewer));
1002   }
1003   if (const auto device = dctx->device) PetscCall(PetscDeviceView(device, viewer));
1004   if (iascii) PetscCall(PetscViewerASCIIPopTab(viewer));
1005   PetscFunctionReturn(0);
1006 }
1007 
1008 /*@C
1009   PetscDeviceContextViewFromOptions - View a `PetscDeviceContext` from options
1010 
1011   Input Parameters:
1012 + dctx - The `PetscDeviceContext` to view
1013 . obj  - Optional `PetscObject` to associate (may be `NULL`)
1014 - name - The command line option
1015 
1016   Level: beginner
1017 
1018 .seealso: `PetscDeviceContextView()`, `PetscObjectViewFromOptions()`, `PetscDeviceContextCreate()`
1019 @*/
1020 PetscErrorCode PetscDeviceContextViewFromOptions(PetscDeviceContext dctx, PetscObject obj, const char name[]) {
1021   PetscFunctionBegin;
1022   PetscCall(PetscDeviceContextGetOptionalNullContext_Internal(&dctx));
1023   if (obj) PetscValidHeader(obj, 2);
1024   PetscValidCharPointer(name, 3);
1025   PetscCall(PetscObjectViewFromOptions(PetscObjectCast(dctx), obj, name));
1026   PetscFunctionReturn(0);
1027 }
1028