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