xref: /libCEED/interface/ceed.c (revision 802d760a181830887ae39e389e664dcc61030cbc)
1 // Copyright (c) 2017-2025, Lawrence Livermore National Security, LLC and other CEED contributors.
2 // All Rights Reserved. See the top-level LICENSE and NOTICE files for details.
3 //
4 // SPDX-License-Identifier: BSD-2-Clause
5 //
6 // This file is part of CEED:  http://github.com/ceed
7 
8 #define _POSIX_C_SOURCE 200112
9 #include <ceed-impl.h>
10 #include <ceed.h>
11 #include <ceed/backend.h>
12 #include <limits.h>
13 #include <stdarg.h>
14 #include <stdbool.h>
15 #include <stddef.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 
20 /// @cond DOXYGEN_SKIP
21 static CeedRequest ceed_request_immediate;
22 static CeedRequest ceed_request_ordered;
23 
24 static struct {
25   char prefix[CEED_MAX_RESOURCE_LEN];
26   int (*init)(const char *resource, Ceed f);
27   unsigned int priority;
28 } backends[32];
29 static size_t num_backends;
30 
31 #define CEED_FTABLE_ENTRY(class, method) {#class #method, offsetof(struct class##_private, method)}
32 /// @endcond
33 
34 /// @file
35 /// Implementation of core components of Ceed library
36 
37 /// @addtogroup CeedUser
38 /// @{
39 
40 /**
41   @brief Request immediate completion
42 
43   This predefined constant is passed as the @ref CeedRequest argument to interfaces when the caller wishes for the operation to be performed immediately.
44   The code
45 
46   @code
47     CeedOperatorApply(op, ..., CEED_REQUEST_IMMEDIATE);
48   @endcode
49 
50   is semantically equivalent to
51 
52   @code
53     CeedRequest request;
54     CeedOperatorApply(op, ..., &request);
55     CeedRequestWait(&request);
56   @endcode
57 
58   @sa CEED_REQUEST_ORDERED
59 **/
60 CeedRequest *const CEED_REQUEST_IMMEDIATE = &ceed_request_immediate;
61 
62 /**
63   @brief Request ordered completion
64 
65   This predefined constant is passed as the @ref CeedRequest argument to interfaces when the caller wishes for the operation to be completed in the order that it is submitted to the device.
66   It is typically used in a construct such as:
67 
68   @code
69     CeedRequest request;
70     CeedOperatorApply(op1, ..., CEED_REQUEST_ORDERED);
71     CeedOperatorApply(op2, ..., &request);
72     // other optional work
73     CeedRequestWait(&request);
74   @endcode
75 
76   which allows the sequence to complete asynchronously but does not start `op2` until `op1` has completed.
77 
78   @todo The current implementation is overly strict, offering equivalent semantics to @ref CEED_REQUEST_IMMEDIATE.
79 
80   @sa CEED_REQUEST_IMMEDIATE
81  */
82 CeedRequest *const CEED_REQUEST_ORDERED = &ceed_request_ordered;
83 
84 /**
85   @brief Wait for a @ref CeedRequest to complete.
86 
87   Calling @ref CeedRequestWait() on a `NULL` request is a no-op.
88 
89   @param[in,out] req Address of @ref CeedRequest to wait for; zeroed on completion.
90 
91   @return An error code: 0 - success, otherwise - failure
92 
93   @ref User
94 **/
95 int CeedRequestWait(CeedRequest *req) {
96   if (!*req) return CEED_ERROR_SUCCESS;
97   return CeedError(NULL, CEED_ERROR_UNSUPPORTED, "CeedRequestWait not implemented");
98 }
99 
100 /// @}
101 
102 /// ----------------------------------------------------------------------------
103 /// Ceed Library Internal Functions
104 /// ----------------------------------------------------------------------------
105 /// @addtogroup CeedDeveloper
106 /// @{
107 
108 /**
109   @brief Register a Ceed backend internally.
110 
111   Note: Backends should call @ref CeedRegister() instead.
112 
113   @param[in] prefix   Prefix of resources for this backend to respond to.
114                         For example, the reference backend responds to "/cpu/self".
115   @param[in] init     Initialization function called by @ref CeedInit() when the backend is selected to drive the requested resource
116   @param[in] priority Integer priority.
117                         Lower values are preferred in case the resource requested by @ref CeedInit() has non-unique best prefix match.
118 
119   @return An error code: 0 - success, otherwise - failure
120 
121   @ref Developer
122 **/
123 int CeedRegisterImpl(const char *prefix, int (*init)(const char *, Ceed), unsigned int priority) {
124   int ierr = 0;
125 
126   CeedPragmaCritical(CeedRegisterImpl) {
127     if (num_backends < sizeof(backends) / sizeof(backends[0])) {
128       strncpy(backends[num_backends].prefix, prefix, CEED_MAX_RESOURCE_LEN);
129       backends[num_backends].prefix[CEED_MAX_RESOURCE_LEN - 1] = 0;
130       backends[num_backends].init                              = init;
131       backends[num_backends].priority                          = priority;
132       num_backends++;
133     } else {
134       ierr = 1;
135     }
136   }
137   CeedCheck(ierr == 0, NULL, CEED_ERROR_MAJOR, "Too many backends");
138   return CEED_ERROR_SUCCESS;
139 }
140 
141 /**
142   @brief Create a work vector space for a `ceed`
143 
144   @param[in,out] ceed `Ceed` to create work vector space for
145 
146   @return An error code: 0 - success, otherwise - failure
147 
148   @ref Developer
149 **/
150 static int CeedWorkVectorsCreate(Ceed ceed) {
151   CeedCall(CeedCalloc(1, &ceed->work_vectors));
152   return CEED_ERROR_SUCCESS;
153 }
154 
155 /**
156   @brief Destroy a work vector space for a `ceed`
157 
158   @param[in,out] ceed `Ceed` to destroy work vector space for
159 
160   @return An error code: 0 - success, otherwise - failure
161 
162   @ref Developer
163 **/
164 static int CeedWorkVectorsDestroy(Ceed ceed) {
165   if (!ceed->work_vectors) return CEED_ERROR_SUCCESS;
166   for (CeedSize i = 0; i < ceed->work_vectors->num_vecs; i++) {
167     CeedCheck(!ceed->work_vectors->is_in_use[i], ceed, CEED_ERROR_ACCESS, "Work vector %" CeedSize_FMT " checked out but not returned");
168     ceed->ref_count += 2;  // Note: increase ref_count to prevent Ceed destructor from triggering again
169     CeedCall(CeedVectorDestroy(&ceed->work_vectors->vecs[i]));
170     ceed->ref_count -= 1;  // Note: restore ref_count
171   }
172   CeedCall(CeedFree(&ceed->work_vectors->is_in_use));
173   CeedCall(CeedFree(&ceed->work_vectors->vecs));
174   CeedCall(CeedFree(&ceed->work_vectors));
175   return CEED_ERROR_SUCCESS;
176 }
177 
178 /// @}
179 
180 /// ----------------------------------------------------------------------------
181 /// Ceed Backend API
182 /// ----------------------------------------------------------------------------
183 /// @addtogroup CeedBackend
184 /// @{
185 
186 /**
187   @brief Return value of `CEED_DEBUG` environment variable
188 
189   @param[in] ceed `Ceed` context
190 
191   @return Boolean value: true  - debugging mode enabled
192                          false - debugging mode disabled
193 
194   @ref Backend
195 **/
196 // LCOV_EXCL_START
197 bool CeedDebugFlag(const Ceed ceed) { return ceed->is_debug; }
198 // LCOV_EXCL_STOP
199 
200 /**
201   @brief Return value of `CEED_DEBUG` environment variable
202 
203   @return Boolean value: true  - debugging mode enabled
204                          false - debugging mode disabled
205 
206   @ref Backend
207 **/
208 // LCOV_EXCL_START
209 bool CeedDebugFlagEnv(void) { return getenv("CEED_DEBUG") || getenv("DEBUG") || getenv("DBG"); }
210 // LCOV_EXCL_STOP
211 
212 /**
213   @brief Print debugging information in color
214 
215   @param[in] color  Color to print
216   @param[in] format Printing format
217 
218   @ref Backend
219 **/
220 // LCOV_EXCL_START
221 void CeedDebugImpl256(const unsigned char color, const char *format, ...) {
222   va_list args;
223   va_start(args, format);
224   fflush(stdout);
225   if (color != CEED_DEBUG_COLOR_NONE) fprintf(stdout, "\033[38;5;%dm", color);
226   vfprintf(stdout, format, args);
227   if (color != CEED_DEBUG_COLOR_NONE) fprintf(stdout, "\033[m");
228   fprintf(stdout, "\n");
229   fflush(stdout);
230   va_end(args);
231 }
232 // LCOV_EXCL_STOP
233 
234 /**
235   @brief Allocate an array on the host; use @ref CeedMalloc().
236 
237   Memory usage can be tracked by the library.
238   This ensures sufficient alignment for vectorization and should be used for large allocations.
239 
240   @param[in]  n    Number of units to allocate
241   @param[in]  unit Size of each unit
242   @param[out] p    Address of pointer to hold the result
243 
244   @return An error code: 0 - success, otherwise - failure
245 
246   @ref Backend
247 
248   @sa CeedFree()
249 **/
250 int CeedMallocArray(size_t n, size_t unit, void *p) {
251   int ierr = posix_memalign((void **)p, CEED_ALIGN, n * unit);
252   CeedCheck(ierr == 0, NULL, CEED_ERROR_MAJOR, "posix_memalign failed to allocate %zd members of size %zd\n", n, unit);
253   return CEED_ERROR_SUCCESS;
254 }
255 
256 /**
257   @brief Allocate a cleared (zeroed) array on the host; use @ref CeedCalloc().
258 
259   Memory usage can be tracked by the library.
260 
261   @param[in]  n    Number of units to allocate
262   @param[in]  unit Size of each unit
263   @param[out] p    Address of pointer to hold the result
264 
265   @return An error code: 0 - success, otherwise - failure
266 
267   @ref Backend
268 
269   @sa CeedFree()
270 **/
271 int CeedCallocArray(size_t n, size_t unit, void *p) {
272   *(void **)p = calloc(n, unit);
273   CeedCheck(!n || !unit || *(void **)p, NULL, CEED_ERROR_MAJOR, "calloc failed to allocate %zd members of size %zd\n", n, unit);
274   return CEED_ERROR_SUCCESS;
275 }
276 
277 /**
278   @brief Reallocate an array on the host; use @ref CeedRealloc().
279 
280   Memory usage can be tracked by the library.
281 
282   @param[in]  n    Number of units to allocate
283   @param[in]  unit Size of each unit
284   @param[out] p    Address of pointer to hold the result
285 
286   @return An error code: 0 - success, otherwise - failure
287 
288   @ref Backend
289 
290   @sa CeedFree()
291 **/
292 int CeedReallocArray(size_t n, size_t unit, void *p) {
293   *(void **)p = realloc(*(void **)p, n * unit);
294   CeedCheck(!n || !unit || *(void **)p, NULL, CEED_ERROR_MAJOR, "realloc failed to allocate %zd members of size %zd\n", n, unit);
295   return CEED_ERROR_SUCCESS;
296 }
297 
298 /**
299   @brief Allocate a cleared string buffer on the host.
300 
301   Memory usage can be tracked by the library.
302 
303   @param[in]  source Pointer to string to be copied
304   @param[out] copy   Pointer to variable to hold newly allocated string copy
305 
306   @return An error code: 0 - success, otherwise - failure
307 
308   @ref Backend
309 
310   @sa CeedFree()
311 **/
312 int CeedStringAllocCopy(const char *source, char **copy) {
313   size_t len = strlen(source);
314   CeedCall(CeedCalloc(len + 1, copy));
315   memcpy(*copy, source, len);
316   return CEED_ERROR_SUCCESS;
317 }
318 
319 /** Free memory allocated using @ref CeedMalloc() or @ref CeedCalloc()
320 
321   @param[in,out] p Address of pointer to memory.
322                      This argument is of type `void*` to avoid needing a cast, but is the address of the pointer (which is zeroed) rather than the pointer.
323 
324   @return An error code: 0 - success, otherwise - failure
325 
326   @ref Backend
327 **/
328 int CeedFree(void *p) {
329   free(*(void **)p);
330   *(void **)p = NULL;
331   return CEED_ERROR_SUCCESS;
332 }
333 
334 /** Internal helper to manage handoff of user `source_array` to backend with proper @ref CeedCopyMode behavior.
335 
336   @param[in]     source_array          Source data provided by user
337   @param[in]     copy_mode             Copy mode for the data
338   @param[in]     num_values            Number of values to handle
339   @param[in]     size_unit             Size of array element in bytes
340   @param[in,out] target_array_owned    Pointer to location to allocated or hold owned data, may be freed if already allocated
341   @param[out]    target_array_borrowed Pointer to location to hold borrowed data
342   @param[out]    target_array          Pointer to location for data
343 
344   @return An error code: 0 - success, otherwise - failure
345 
346   @ref Backend
347 **/
348 static inline int CeedSetHostGenericArray(const void *source_array, CeedCopyMode copy_mode, size_t size_unit, CeedSize num_values,
349                                           void *target_array_owned, void *target_array_borrowed, void *target_array) {
350   switch (copy_mode) {
351     case CEED_COPY_VALUES:
352       if (!*(void **)target_array) {
353         if (*(void **)target_array_borrowed) {
354           *(void **)target_array = *(void **)target_array_borrowed;
355         } else {
356           if (!*(void **)target_array_owned) CeedCall(CeedCallocArray(num_values, size_unit, target_array_owned));
357           *(void **)target_array = *(void **)target_array_owned;
358         }
359       }
360       if (source_array) memcpy(*(void **)target_array, source_array, size_unit * num_values);
361       break;
362     case CEED_OWN_POINTER:
363       CeedCall(CeedFree(target_array_owned));
364       *(void **)target_array_owned    = (void *)source_array;
365       *(void **)target_array_borrowed = NULL;
366       *(void **)target_array          = *(void **)target_array_owned;
367       break;
368     case CEED_USE_POINTER:
369       CeedCall(CeedFree(target_array_owned));
370       *(void **)target_array_owned    = NULL;
371       *(void **)target_array_borrowed = (void *)source_array;
372       *(void **)target_array          = *(void **)target_array_borrowed;
373   }
374   return CEED_ERROR_SUCCESS;
375 }
376 
377 /** Manage handoff of user `bool` `source_array` to backend with proper @ref CeedCopyMode behavior.
378 
379   @param[in]     source_array          Source data provided by user
380   @param[in]     copy_mode             Copy mode for the data
381   @param[in]     num_values            Number of values to handle
382   @param[in,out] target_array_owned    Pointer to location to allocated or hold owned data, may be freed if already allocated
383   @param[out]    target_array_borrowed Pointer to location to hold borrowed data
384   @param[out]    target_array          Pointer to location for data
385 
386   @return An error code: 0 - success, otherwise - failure
387 
388   @ref Backend
389 **/
390 int CeedSetHostBoolArray(const bool *source_array, CeedCopyMode copy_mode, CeedSize num_values, const bool **target_array_owned,
391                          const bool **target_array_borrowed, const bool **target_array) {
392   CeedCall(CeedSetHostGenericArray(source_array, copy_mode, sizeof(bool), num_values, target_array_owned, target_array_borrowed, target_array));
393   return CEED_ERROR_SUCCESS;
394 }
395 
396 /** Manage handoff of user `CeedInt8` `source_array` to backend with proper @ref CeedCopyMode behavior.
397 
398   @param[in]     source_array          Source data provided by user
399   @param[in]     copy_mode             Copy mode for the data
400   @param[in]     num_values            Number of values to handle
401   @param[in,out] target_array_owned    Pointer to location to allocated or hold owned data, may be freed if already allocated
402   @param[out]    target_array_borrowed Pointer to location to hold borrowed data
403   @param[out]    target_array          Pointer to location for data
404 
405   @return An error code: 0 - success, otherwise - failure
406 
407   @ref Backend
408 **/
409 int CeedSetHostCeedInt8Array(const CeedInt8 *source_array, CeedCopyMode copy_mode, CeedSize num_values, const CeedInt8 **target_array_owned,
410                              const CeedInt8 **target_array_borrowed, const CeedInt8 **target_array) {
411   CeedCall(CeedSetHostGenericArray(source_array, copy_mode, sizeof(CeedInt8), num_values, target_array_owned, target_array_borrowed, target_array));
412   return CEED_ERROR_SUCCESS;
413 }
414 
415 /** Manage handoff of user `CeedInt` `source_array` to backend with proper @ref CeedCopyMode behavior.
416 
417   @param[in]     source_array          Source data provided by user
418   @param[in]     copy_mode             Copy mode for the data
419   @param[in]     num_values            Number of values to handle
420   @param[in,out] target_array_owned    Pointer to location to allocated or hold owned data, may be freed if already allocated
421   @param[out]    target_array_borrowed Pointer to location to hold borrowed data
422   @param[out]    target_array          Pointer to location for data
423 
424   @return An error code: 0 - success, otherwise - failure
425 
426   @ref Backend
427 **/
428 int CeedSetHostCeedIntArray(const CeedInt *source_array, CeedCopyMode copy_mode, CeedSize num_values, const CeedInt **target_array_owned,
429                             const CeedInt **target_array_borrowed, const CeedInt **target_array) {
430   CeedCall(CeedSetHostGenericArray(source_array, copy_mode, sizeof(CeedInt), num_values, target_array_owned, target_array_borrowed, target_array));
431   return CEED_ERROR_SUCCESS;
432 }
433 
434 /** Manage handoff of user `CeedScalar` `source_array` to backend with proper @ref CeedCopyMode behavior.
435 
436   @param[in]     source_array          Source data provided by user
437   @param[in]     copy_mode             Copy mode for the data
438   @param[in]     num_values            Number of values to handle
439   @param[in,out] target_array_owned    Pointer to location to allocated or hold owned data, may be freed if already allocated
440   @param[out]    target_array_borrowed Pointer to location to hold borrowed data
441   @param[out]    target_array          Pointer to location for data
442 
443   @return An error code: 0 - success, otherwise - failure
444 
445   @ref Backend
446 **/
447 int CeedSetHostCeedScalarArray(const CeedScalar *source_array, CeedCopyMode copy_mode, CeedSize num_values, const CeedScalar **target_array_owned,
448                                const CeedScalar **target_array_borrowed, const CeedScalar **target_array) {
449   CeedCall(CeedSetHostGenericArray(source_array, copy_mode, sizeof(CeedScalar), num_values, target_array_owned, target_array_borrowed, target_array));
450   return CEED_ERROR_SUCCESS;
451 }
452 
453 /**
454   @brief Register a `Ceed` backend
455 
456   @param[in] prefix   Prefix of resources for this backend to respond to.
457                         For example, the reference backend responds to "/cpu/self".
458   @param[in] init     Initialization function called by @ref CeedInit() when the backend is selected to drive the requested resource
459   @param[in] priority Integer priority.
460                         Lower values are preferred in case the resource requested by @ref CeedInit() has non-unique best prefix match.
461 
462   @return An error code: 0 - success, otherwise - failure
463 
464   @ref Backend
465 **/
466 int CeedRegister(const char *prefix, int (*init)(const char *, Ceed), unsigned int priority) {
467   CeedDebugEnv("Backend Register: %s", prefix);
468   CeedRegisterImpl(prefix, init, priority);
469   return CEED_ERROR_SUCCESS;
470 }
471 
472 /**
473   @brief Return debugging status flag
474 
475   @param[in]  ceed     `Ceed` context to get debugging flag
476   @param[out] is_debug Variable to store debugging flag
477 
478   @return An error code: 0 - success, otherwise - failure
479 
480   @ref Backend
481 **/
482 int CeedIsDebug(Ceed ceed, bool *is_debug) {
483   *is_debug = ceed->is_debug;
484   return CEED_ERROR_SUCCESS;
485 }
486 
487 /**
488   @brief Get the root of the requested resource.
489 
490   Note: Caller is responsible for calling @ref CeedFree() on the `resource_root`.
491 
492   @param[in]  ceed          `Ceed` context to get resource name of
493   @param[in]  resource      Full user specified resource
494   @param[in]  delineator    Delineator to break `resource_root` and `resource_spec`
495   @param[out] resource_root Variable to store resource root
496 
497   @return An error code: 0 - success, otherwise - failure
498 
499   @ref Backend
500 **/
501 int CeedGetResourceRoot(Ceed ceed, const char *resource, const char *delineator, char **resource_root) {
502   char  *device_spec       = strstr(resource, delineator);
503   size_t resource_root_len = device_spec ? (size_t)(device_spec - resource) + 1 : strlen(resource) + 1;
504 
505   CeedCall(CeedCalloc(resource_root_len, resource_root));
506   memcpy(*resource_root, resource, resource_root_len - 1);
507   return CEED_ERROR_SUCCESS;
508 }
509 
510 /**
511   @brief Retrieve a parent `Ceed` context
512 
513   @param[in]  ceed   `Ceed` context to retrieve parent of
514   @param[out] parent Address to save the parent to
515 
516   @return An error code: 0 - success, otherwise - failure
517 
518   @ref Backend
519 **/
520 int CeedGetParent(Ceed ceed, Ceed *parent) {
521   if (ceed->parent) {
522     CeedCall(CeedGetParent(ceed->parent, parent));
523     return CEED_ERROR_SUCCESS;
524   }
525   *parent = NULL;
526   CeedCall(CeedReferenceCopy(ceed, parent));
527   return CEED_ERROR_SUCCESS;
528 }
529 
530 /**
531   @brief Retrieve a delegate `Ceed` context
532 
533   @param[in]  ceed     `Ceed` context to retrieve delegate of
534   @param[out] delegate Address to save the delegate to
535 
536   @return An error code: 0 - success, otherwise - failure
537 
538   @ref Backend
539 **/
540 int CeedGetDelegate(Ceed ceed, Ceed *delegate) {
541   *delegate = NULL;
542   if (ceed->delegate) CeedCall(CeedReferenceCopy(ceed->delegate, delegate));
543   return CEED_ERROR_SUCCESS;
544 }
545 
546 /**
547   @brief Set a delegate `Ceed` context
548 
549   This function allows a `Ceed` context to set a delegate `Ceed` context.
550   All backend implementations default to the delegate `Ceed` context, unless overridden.
551 
552   @param[in]  ceed     `Ceed` context to set delegate of
553   @param[out] delegate Address to set the delegate to
554 
555   @return An error code: 0 - success, otherwise - failure
556 
557   @ref Backend
558 **/
559 int CeedSetDelegate(Ceed ceed, Ceed delegate) {
560   CeedCall(CeedReferenceCopy(delegate, &ceed->delegate));
561   delegate->parent = ceed;
562   return CEED_ERROR_SUCCESS;
563 }
564 
565 /**
566   @brief Retrieve a delegate `Ceed` context for a specific object type
567 
568   @param[in]  ceed     `Ceed` context to retrieve delegate of
569   @param[out] delegate Address to save the delegate to
570   @param[in]  obj_name Name of the object type to retrieve delegate for
571 
572   @return An error code: 0 - success, otherwise - failure
573 
574   @ref Backend
575 **/
576 int CeedGetObjectDelegate(Ceed ceed, Ceed *delegate, const char *obj_name) {
577   // Check for object delegate
578   for (CeedInt i = 0; i < ceed->obj_delegate_count; i++) {
579     if (!strcmp(obj_name, ceed->obj_delegates->obj_name)) {
580       *delegate = NULL;
581       CeedCall(CeedReferenceCopy(ceed->obj_delegates->delegate, delegate));
582       return CEED_ERROR_SUCCESS;
583     }
584   }
585 
586   // Use default delegate if no object delegate
587   CeedCall(CeedGetDelegate(ceed, delegate));
588   return CEED_ERROR_SUCCESS;
589 }
590 
591 /**
592   @brief Set a delegate `Ceed` context for a specific object type
593 
594   This function allows a `Ceed` context to set a delegate `Ceed` context for a given type of `Ceed` object.
595   All backend implementations default to the delegate `Ceed` context for this object.
596   For example, `CeedSetObjectDelegate(ceed, delegate, "Basis")` uses delegate implementations for all `CeedBasis` backend functions.
597 
598   @param[in,out] ceed     `Ceed` context to set delegate of
599   @param[in]     delegate `Ceed` context to use for delegation
600   @param[in]     obj_name Name of the object type to set delegate for
601 
602   @return An error code: 0 - success, otherwise - failure
603 
604   @ref Backend
605 **/
606 int CeedSetObjectDelegate(Ceed ceed, Ceed delegate, const char *obj_name) {
607   CeedInt count = ceed->obj_delegate_count;
608 
609   // Malloc or Realloc
610   if (count) {
611     CeedCall(CeedRealloc(count + 1, &ceed->obj_delegates));
612   } else {
613     CeedCall(CeedCalloc(1, &ceed->obj_delegates));
614   }
615   ceed->obj_delegate_count++;
616 
617   // Set object delegate
618   CeedCall(CeedReferenceCopy(delegate, &ceed->obj_delegates[count].delegate));
619   CeedCall(CeedStringAllocCopy(obj_name, &ceed->obj_delegates[count].obj_name));
620 
621   // Set delegate parent
622   delegate->parent = ceed;
623   return CEED_ERROR_SUCCESS;
624 }
625 
626 /**
627   @brief Get the fallback resource for `CeedOperator`
628 
629   @param[in]  ceed     `Ceed` context
630   @param[out] resource Variable to store fallback resource
631 
632   @return An error code: 0 - success, otherwise - failure
633 
634   @ref Backend
635 **/
636 int CeedGetOperatorFallbackResource(Ceed ceed, const char **resource) {
637   *resource = (const char *)ceed->op_fallback_resource;
638   return CEED_ERROR_SUCCESS;
639 }
640 
641 /**
642   @brief Get the fallback `Ceed` for `CeedOperator`
643 
644   @param[in]  ceed          `Ceed` context
645   @param[out] fallback_ceed Variable to store fallback `Ceed`
646 
647   @return An error code: 0 - success, otherwise - failure
648 
649   @ref Backend
650 **/
651 int CeedGetOperatorFallbackCeed(Ceed ceed, Ceed *fallback_ceed) {
652   if (ceed->has_valid_op_fallback_resource) {
653     CeedDebug256(ceed, CEED_DEBUG_COLOR_SUCCESS, "---------- Ceed Fallback ----------\n");
654     CeedDebug(ceed, "Falling back from Ceed with backend %s at address %p to Ceed with backend %s", ceed->resource, ceed, ceed->op_fallback_resource);
655   }
656 
657   // Create fallback Ceed if uninitialized
658   if (!ceed->op_fallback_ceed && ceed->has_valid_op_fallback_resource) {
659     CeedDebug(ceed, "Creating fallback Ceed");
660 
661     Ceed        fallback_ceed;
662     const char *fallback_resource;
663 
664     CeedCall(CeedGetOperatorFallbackResource(ceed, &fallback_resource));
665     CeedCall(CeedInit(fallback_resource, &fallback_ceed));
666     fallback_ceed->op_fallback_parent = ceed;
667     fallback_ceed->Error              = ceed->Error;
668     ceed->op_fallback_ceed            = fallback_ceed;
669     {
670       const char **jit_source_roots;
671       CeedInt      num_jit_source_roots = 0;
672 
673       CeedCall(CeedGetJitSourceRoots(ceed, &num_jit_source_roots, &jit_source_roots));
674       for (CeedInt i = 0; i < num_jit_source_roots; i++) {
675         CeedCall(CeedAddJitSourceRoot(fallback_ceed, jit_source_roots[i]));
676       }
677       CeedCall(CeedRestoreJitSourceRoots(ceed, &jit_source_roots));
678     }
679     {
680       const char **jit_defines;
681       CeedInt      num_jit_defines = 0;
682 
683       CeedCall(CeedGetJitDefines(ceed, &num_jit_defines, &jit_defines));
684       for (CeedInt i = 0; i < num_jit_defines; i++) {
685         CeedCall(CeedAddJitSourceRoot(fallback_ceed, jit_defines[i]));
686       }
687       CeedCall(CeedRestoreJitDefines(ceed, &jit_defines));
688     }
689   }
690   *fallback_ceed = NULL;
691   CeedDebug(ceed, "Fallback Ceed with backend %s at address %p\n", ceed->op_fallback_resource, ceed->op_fallback_ceed);
692   if (ceed->op_fallback_ceed) CeedCall(CeedReferenceCopy(ceed->op_fallback_ceed, fallback_ceed));
693   return CEED_ERROR_SUCCESS;
694 }
695 
696 /**
697   @brief Set the fallback resource for `CeedOperator`.
698 
699   The current resource, if any, is freed by calling this function.
700   This string is freed upon the destruction of the `Ceed` context.
701 
702   @param[in,out] ceed     `Ceed` context
703   @param[in]     resource Fallback resource to set
704 
705   @return An error code: 0 - success, otherwise - failure
706 
707   @ref Backend
708 **/
709 int CeedSetOperatorFallbackResource(Ceed ceed, const char *resource) {
710   // Free old
711   CeedCall(CeedFree(&ceed->op_fallback_resource));
712 
713   // Set new
714   CeedCall(CeedStringAllocCopy(resource, (char **)&ceed->op_fallback_resource));
715 
716   // Check validity
717   ceed->has_valid_op_fallback_resource = ceed->op_fallback_resource && ceed->resource && strcmp(ceed->op_fallback_resource, ceed->resource);
718   return CEED_ERROR_SUCCESS;
719 }
720 
721 /**
722   @brief Flag `Ceed` context as deterministic
723 
724   @param[in]  ceed             `Ceed` to flag as deterministic
725   @param[out] is_deterministic Deterministic status to set
726 
727   @return An error code: 0 - success, otherwise - failure
728 
729   @ref Backend
730 **/
731 int CeedSetDeterministic(Ceed ceed, bool is_deterministic) {
732   ceed->is_deterministic = is_deterministic;
733   return CEED_ERROR_SUCCESS;
734 }
735 
736 /**
737   @brief Set a backend function.
738 
739   This function is used for a backend to set the function associated with the Ceed objects.
740   For example, `CeedSetBackendFunction(ceed, "Ceed", ceed, "VectorCreate", BackendVectorCreate)` sets the backend implementation of @ref CeedVectorCreate() and `CeedSetBackendFunction(ceed, "Basis", basis, "Apply", BackendBasisApply)` sets the backend implementation of @ref CeedBasisApply().
741   Note, the prefix 'Ceed' is not required for the object type ("Basis" vs "CeedBasis").
742 
743   @param[in]  ceed      `Ceed` context for error handling
744   @param[in]  type      Type of Ceed object to set function for
745   @param[out] object    Ceed object to set function for
746   @param[in]  func_name Name of function to set
747   @param[in]  f         Function to set
748 
749   @return An error code: 0 - success, otherwise - failure
750 
751   @ref Backend
752 **/
753 int CeedSetBackendFunctionImpl(Ceed ceed, const char *type, void *object, const char *func_name, void (*f)(void)) {
754   char lookup_name[CEED_MAX_RESOURCE_LEN + 1] = "";
755 
756   // Build lookup name
757   if (strcmp(type, "Ceed")) strncat(lookup_name, "Ceed", CEED_MAX_RESOURCE_LEN);
758   strncat(lookup_name, type, CEED_MAX_RESOURCE_LEN);
759   strncat(lookup_name, func_name, CEED_MAX_RESOURCE_LEN);
760 
761   // Find and use offset
762   for (CeedInt i = 0; ceed->f_offsets[i].func_name; i++) {
763     if (!strcmp(ceed->f_offsets[i].func_name, lookup_name)) {
764       size_t offset          = ceed->f_offsets[i].offset;
765       int (**fpointer)(void) = (int (**)(void))((char *)object + offset);  // *NOPAD*
766 
767       *fpointer = (int (*)(void))f;
768       return CEED_ERROR_SUCCESS;
769     }
770   }
771 
772   // LCOV_EXCL_START
773   return CeedError(ceed, CEED_ERROR_UNSUPPORTED, "Requested function '%s' was not found for CEED object '%s'", func_name, type);
774   // LCOV_EXCL_STOP
775 }
776 
777 /**
778   @brief Retrieve backend data for a `Ceed` context
779 
780   @param[in]  ceed `Ceed` context to retrieve data of
781   @param[out] data Address to save data to
782 
783   @return An error code: 0 - success, otherwise - failure
784 
785   @ref Backend
786 **/
787 int CeedGetData(Ceed ceed, void *data) {
788   *(void **)data = ceed->data;
789   return CEED_ERROR_SUCCESS;
790 }
791 
792 /**
793   @brief Set backend data for a `Ceed` context
794 
795   @param[in,out] ceed `Ceed` context to set data of
796   @param[in]     data Address of data to set
797 
798   @return An error code: 0 - success, otherwise - failure
799 
800   @ref Backend
801 **/
802 int CeedSetData(Ceed ceed, void *data) {
803   ceed->data = data;
804   return CEED_ERROR_SUCCESS;
805 }
806 
807 /**
808   @brief Increment the reference counter for a `Ceed` context
809 
810   @param[in,out] ceed `Ceed` context to increment the reference counter
811 
812   @return An error code: 0 - success, otherwise - failure
813 
814   @ref Backend
815 **/
816 int CeedReference(Ceed ceed) {
817   ceed->ref_count++;
818   return CEED_ERROR_SUCCESS;
819 }
820 
821 /**
822   @brief Computes the current memory usage of the work vectors in a `Ceed` context and prints to debug.abort
823 
824   @param[in]  ceed     `Ceed` context
825   @param[out] usage_mb Address of the variable where the MB of work vector usage will be stored
826 
827   @return An error code: 0 - success, otherwise - failure
828 
829   @ref Developer
830 **/
831 int CeedGetWorkVectorMemoryUsage(Ceed ceed, CeedScalar *usage_mb) {
832   if (!ceed->VectorCreate) {
833     Ceed delegate;
834 
835     CeedCall(CeedGetObjectDelegate(ceed, &delegate, "Vector"));
836     CeedCheck(delegate, ceed, CEED_ERROR_UNSUPPORTED, "Backend does not implement VectorCreate");
837     CeedCall(CeedGetWorkVectorMemoryUsage(delegate, usage_mb));
838     CeedCall(CeedDestroy(&delegate));
839     return CEED_ERROR_SUCCESS;
840   }
841   *usage_mb = 0.0;
842   if (ceed->work_vectors) {
843     for (CeedInt i = 0; i < ceed->work_vectors->num_vecs; i++) {
844       CeedSize vec_len;
845       CeedCall(CeedVectorGetLength(ceed->work_vectors->vecs[i], &vec_len));
846       *usage_mb += vec_len;
847     }
848     *usage_mb *= sizeof(CeedScalar) * 1e-6;
849     CeedDebug(ceed, "Resource {%s}: Work vectors memory usage: %" CeedInt_FMT " vectors, %g MB\n", ceed->resource, ceed->work_vectors->num_vecs,
850               *usage_mb);
851   }
852   return CEED_ERROR_SUCCESS;
853 }
854 
855 /**
856   @brief Clear inactive work vectors in a `Ceed` context below a minimum length.
857 
858   @param[in,out] ceed    `Ceed` context
859   @param[in]     min_len Minimum length of work vector to keep
860 
861   @return An error code: 0 - success, otherwise - failure
862 
863   @ref Backend
864 **/
865 int CeedClearWorkVectors(Ceed ceed, CeedSize min_len) {
866   if (!ceed->VectorCreate) {
867     Ceed delegate;
868 
869     CeedCall(CeedGetObjectDelegate(ceed, &delegate, "Vector"));
870     CeedCheck(delegate, ceed, CEED_ERROR_UNSUPPORTED, "Backend does not implement VectorCreate");
871     CeedCall(CeedClearWorkVectors(delegate, min_len));
872     CeedCall(CeedDestroy(&delegate));
873     return CEED_ERROR_SUCCESS;
874   }
875   if (!ceed->work_vectors) return CEED_ERROR_SUCCESS;
876   for (CeedInt i = 0; i < ceed->work_vectors->num_vecs; i++) {
877     if (ceed->work_vectors->is_in_use[i]) continue;
878     CeedSize vec_len;
879     CeedCall(CeedVectorGetLength(ceed->work_vectors->vecs[i], &vec_len));
880     if (vec_len < min_len) {
881       ceed->ref_count += 2;  // Note: increase ref_count to prevent Ceed destructor from triggering
882       CeedCall(CeedVectorDestroy(&ceed->work_vectors->vecs[i]));
883       ceed->ref_count -= 1;  // Note: restore ref_count
884       ceed->work_vectors->num_vecs--;
885       if (ceed->work_vectors->num_vecs > 0) {
886         ceed->work_vectors->vecs[i]                                 = ceed->work_vectors->vecs[ceed->work_vectors->num_vecs];
887         ceed->work_vectors->is_in_use[i]                            = ceed->work_vectors->is_in_use[ceed->work_vectors->num_vecs];
888         ceed->work_vectors->is_in_use[ceed->work_vectors->num_vecs] = false;
889         i--;
890       }
891     }
892   }
893   return CEED_ERROR_SUCCESS;
894 }
895 
896 /**
897   @brief Get a `CeedVector` for scratch work from a `Ceed` context.
898 
899   Note: This vector must be restored with @ref CeedRestoreWorkVector().
900 
901   @param[in]  ceed `Ceed` context
902   @param[in]  len  Minimum length of work vector
903   @param[out] vec  Address of the variable where `CeedVector` will be stored
904 
905   @return An error code: 0 - success, otherwise - failure
906 
907   @ref Backend
908 **/
909 int CeedGetWorkVector(Ceed ceed, CeedSize len, CeedVector *vec) {
910   CeedInt    i = 0;
911   CeedScalar usage_mb;
912 
913   if (!ceed->VectorCreate) {
914     Ceed delegate;
915 
916     CeedCall(CeedGetObjectDelegate(ceed, &delegate, "Vector"));
917     CeedCheck(delegate, ceed, CEED_ERROR_UNSUPPORTED, "Backend does not implement VectorCreate");
918     CeedCall(CeedGetWorkVector(delegate, len, vec));
919     CeedCall(CeedDestroy(&delegate));
920     return CEED_ERROR_SUCCESS;
921   }
922 
923   if (!ceed->work_vectors) CeedCall(CeedWorkVectorsCreate(ceed));
924 
925   // Search for big enough work vector
926   for (i = 0; i < ceed->work_vectors->num_vecs; i++) {
927     if (!ceed->work_vectors->is_in_use[i]) {
928       CeedSize work_len;
929 
930       CeedCall(CeedVectorGetLength(ceed->work_vectors->vecs[i], &work_len));
931       if (work_len >= len) break;
932     }
933   }
934   // Long enough vector was not found
935   if (i == ceed->work_vectors->num_vecs) {
936     if (ceed->work_vectors->max_vecs == 0) {
937       ceed->work_vectors->max_vecs = 1;
938       CeedCall(CeedCalloc(ceed->work_vectors->max_vecs, &ceed->work_vectors->vecs));
939       CeedCall(CeedCalloc(ceed->work_vectors->max_vecs, &ceed->work_vectors->is_in_use));
940     } else if (ceed->work_vectors->max_vecs == i) {
941       ceed->work_vectors->max_vecs *= 2;
942       CeedCall(CeedRealloc(ceed->work_vectors->max_vecs, &ceed->work_vectors->vecs));
943       CeedCall(CeedRealloc(ceed->work_vectors->max_vecs, &ceed->work_vectors->is_in_use));
944     }
945     ceed->work_vectors->num_vecs++;
946     CeedCallBackend(CeedVectorCreate(ceed, len, &ceed->work_vectors->vecs[i]));
947     ceed->ref_count--;  // Note: ref_count manipulation to prevent a ref-loop
948     if (ceed->is_debug) CeedGetWorkVectorMemoryUsage(ceed, &usage_mb);
949   }
950   // Return pointer to work vector
951   ceed->work_vectors->is_in_use[i] = true;
952   *vec                             = NULL;
953   CeedCall(CeedVectorReferenceCopy(ceed->work_vectors->vecs[i], vec));
954   ceed->ref_count++;  // Note: bump ref_count to account for external access
955   return CEED_ERROR_SUCCESS;
956 }
957 
958 /**
959   @brief Restore a `CeedVector` for scratch work from a `Ceed` context from @ref CeedGetWorkVector()
960 
961   @param[in]  ceed `Ceed` context
962   @param[out] vec  `CeedVector` to restore
963 
964   @return An error code: 0 - success, otherwise - failure
965 
966   @ref Backend
967 **/
968 int CeedRestoreWorkVector(Ceed ceed, CeedVector *vec) {
969   if (!ceed->VectorCreate) {
970     Ceed delegate;
971 
972     CeedCall(CeedGetObjectDelegate(ceed, &delegate, "Vector"));
973     CeedCheck(delegate, ceed, CEED_ERROR_UNSUPPORTED, "Backend does not implement VectorCreate");
974     CeedCall(CeedRestoreWorkVector(delegate, vec));
975     CeedCall(CeedDestroy(&delegate));
976     return CEED_ERROR_SUCCESS;
977   }
978 
979   for (CeedInt i = 0; i < ceed->work_vectors->num_vecs; i++) {
980     if (*vec == ceed->work_vectors->vecs[i]) {
981       CeedCheck(ceed->work_vectors->is_in_use[i], ceed, CEED_ERROR_ACCESS, "Work vector %" CeedSize_FMT " was not checked out but is being returned");
982       CeedCall(CeedVectorDestroy(vec));
983       ceed->work_vectors->is_in_use[i] = false;
984       ceed->ref_count--;  // Note: reduce ref_count again to prevent a ref-loop
985       return CEED_ERROR_SUCCESS;
986     }
987   }
988   // LCOV_EXCL_START
989   return CeedError(ceed, CEED_ERROR_MAJOR, "vec was not checked out via CeedGetWorkVector()");
990   // LCOV_EXCL_STOP
991 }
992 
993 /**
994   @brief Retrieve list of additional JiT source roots from `Ceed` context.
995 
996   Note: The caller is responsible for restoring `jit_source_roots` with @ref CeedRestoreJitSourceRoots().
997 
998   @param[in]  ceed             `Ceed` context
999   @param[out] num_source_roots Number of JiT source directories
1000   @param[out] jit_source_roots Absolute paths to additional JiT source directories
1001 
1002   @return An error code: 0 - success, otherwise - failure
1003 
1004   @ref Backend
1005 **/
1006 int CeedGetJitSourceRoots(Ceed ceed, CeedInt *num_source_roots, const char ***jit_source_roots) {
1007   Ceed ceed_parent;
1008 
1009   CeedCall(CeedGetParent(ceed, &ceed_parent));
1010   *num_source_roots = ceed_parent->num_jit_source_roots;
1011   *jit_source_roots = (const char **)ceed_parent->jit_source_roots;
1012   ceed_parent->num_jit_source_roots_readers++;
1013   CeedCall(CeedDestroy(&ceed_parent));
1014   return CEED_ERROR_SUCCESS;
1015 }
1016 
1017 /**
1018   @brief Restore list of additional JiT source roots from with @ref CeedGetJitSourceRoots()
1019 
1020   @param[in]  ceed             `Ceed` context
1021   @param[out] jit_source_roots Absolute paths to additional JiT source directories
1022 
1023   @return An error code: 0 - success, otherwise - failure
1024 
1025   @ref Backend
1026 **/
1027 int CeedRestoreJitSourceRoots(Ceed ceed, const char ***jit_source_roots) {
1028   Ceed ceed_parent;
1029 
1030   CeedCall(CeedGetParent(ceed, &ceed_parent));
1031   *jit_source_roots = NULL;
1032   ceed_parent->num_jit_source_roots_readers--;
1033   CeedCall(CeedDestroy(&ceed_parent));
1034   return CEED_ERROR_SUCCESS;
1035 }
1036 
1037 /**
1038   @brief Retrieve list of additional JiT defines from `Ceed` context.
1039 
1040   Note: The caller is responsible for restoring `jit_defines` with @ref CeedRestoreJitDefines().
1041 
1042   @param[in]  ceed            `Ceed` context
1043   @param[out] num_jit_defines Number of JiT defines
1044   @param[out] jit_defines     Strings such as `foo=bar`, used as `-Dfoo=bar` in JiT
1045 
1046   @return An error code: 0 - success, otherwise - failure
1047 
1048   @ref Backend
1049 **/
1050 int CeedGetJitDefines(Ceed ceed, CeedInt *num_jit_defines, const char ***jit_defines) {
1051   Ceed ceed_parent;
1052 
1053   CeedCall(CeedGetParent(ceed, &ceed_parent));
1054   *num_jit_defines = ceed_parent->num_jit_defines;
1055   *jit_defines     = (const char **)ceed_parent->jit_defines;
1056   ceed_parent->num_jit_defines_readers++;
1057   CeedCall(CeedDestroy(&ceed_parent));
1058   return CEED_ERROR_SUCCESS;
1059 }
1060 
1061 /**
1062   @brief Restore list of additional JiT defines from with @ref CeedGetJitDefines()
1063 
1064   @param[in]  ceed        `Ceed` context
1065   @param[out] jit_defines String such as `foo=bar`, used as `-Dfoo=bar` in JiT
1066 
1067   @return An error code: 0 - success, otherwise - failure
1068 
1069   @ref Backend
1070 **/
1071 int CeedRestoreJitDefines(Ceed ceed, const char ***jit_defines) {
1072   Ceed ceed_parent;
1073 
1074   CeedCall(CeedGetParent(ceed, &ceed_parent));
1075   *jit_defines = NULL;
1076   ceed_parent->num_jit_defines_readers--;
1077   CeedCall(CeedDestroy(&ceed_parent));
1078   return CEED_ERROR_SUCCESS;
1079 }
1080 
1081 /// @}
1082 
1083 /// ----------------------------------------------------------------------------
1084 /// Ceed Public API
1085 /// ----------------------------------------------------------------------------
1086 /// @addtogroup CeedUser
1087 /// @{
1088 
1089 /**
1090   @brief Get the list of available resource names for `Ceed` contexts
1091 
1092   Note: The caller is responsible for `free()`ing the resources and priorities arrays, but should not `free()` the contents of the resources array.
1093 
1094   @param[out] n          Number of available resources
1095   @param[out] resources  List of available resource names
1096   @param[out] priorities Resource name prioritization values, lower is better
1097 
1098   @return An error code: 0 - success, otherwise - failure
1099 
1100   @ref User
1101 **/
1102 // LCOV_EXCL_START
1103 int CeedRegistryGetList(size_t *n, char ***const resources, CeedInt **priorities) {
1104   *n         = 0;
1105   *resources = malloc(num_backends * sizeof(**resources));
1106   CeedCheck(resources, NULL, CEED_ERROR_MAJOR, "malloc() failure");
1107   if (priorities) {
1108     *priorities = malloc(num_backends * sizeof(**priorities));
1109     CeedCheck(priorities, NULL, CEED_ERROR_MAJOR, "malloc() failure");
1110   }
1111   for (size_t i = 0; i < num_backends; i++) {
1112     // Only report compiled backends
1113     if (backends[i].priority < CEED_MAX_BACKEND_PRIORITY) {
1114       *resources[i] = backends[i].prefix;
1115       if (priorities) *priorities[i] = backends[i].priority;
1116       *n += 1;
1117     }
1118   }
1119   CeedCheck(*n, NULL, CEED_ERROR_MAJOR, "No backends installed");
1120   *resources = realloc(*resources, *n * sizeof(**resources));
1121   CeedCheck(resources, NULL, CEED_ERROR_MAJOR, "realloc() failure");
1122   if (priorities) {
1123     *priorities = realloc(*priorities, *n * sizeof(**priorities));
1124     CeedCheck(priorities, NULL, CEED_ERROR_MAJOR, "realloc() failure");
1125   }
1126   return CEED_ERROR_SUCCESS;
1127 }
1128 // LCOV_EXCL_STOP
1129 
1130 /**
1131   @brief Initialize a `Ceed` context to use the specified resource.
1132 
1133   Note: Prefixing the resource with "help:" (e.g. "help:/cpu/self") will result in @ref CeedInt() printing the current libCEED version number and a list of current available backend resources to `stderr`.
1134 
1135   @param[in]  resource Resource to use, e.g., "/cpu/self"
1136   @param[out] ceed     The library context
1137 
1138   @return An error code: 0 - success, otherwise - failure
1139 
1140   @ref User
1141 
1142   @sa CeedRegister() CeedDestroy()
1143 **/
1144 int CeedInit(const char *resource, Ceed *ceed) {
1145   size_t match_len = 0, match_index = UINT_MAX, match_priority = CEED_MAX_BACKEND_PRIORITY, priority;
1146 
1147   // Find matching backend
1148   CeedCheck(resource, NULL, CEED_ERROR_MAJOR, "No resource provided");
1149   CeedCall(CeedRegisterAll());
1150 
1151   // Check for help request
1152   const char *help_prefix = "help";
1153   size_t      match_help  = 0;
1154   while (match_help < 4 && resource[match_help] == help_prefix[match_help]) match_help++;
1155   if (match_help == 4) {
1156     fprintf(stderr, "libCEED version: %d.%d%d%s\n", CEED_VERSION_MAJOR, CEED_VERSION_MINOR, CEED_VERSION_PATCH,
1157             CEED_VERSION_RELEASE ? "" : "+development");
1158     fprintf(stderr, "Available backend resources:\n");
1159     for (size_t i = 0; i < num_backends; i++) {
1160       // Only report compiled backends
1161       if (backends[i].priority < CEED_MAX_BACKEND_PRIORITY) fprintf(stderr, "  %s\n", backends[i].prefix);
1162     }
1163     fflush(stderr);
1164     match_help = 5;  // Delineating character expected
1165   } else {
1166     match_help = 0;
1167   }
1168 
1169   // Find best match, computed as number of matching characters from requested resource stem
1170   size_t stem_length = 0;
1171   while (resource[stem_length + match_help] && resource[stem_length + match_help] != ':') stem_length++;
1172   for (size_t i = 0; i < num_backends; i++) {
1173     size_t      n      = 0;
1174     const char *prefix = backends[i].prefix;
1175     while (prefix[n] && prefix[n] == resource[n + match_help]) n++;
1176     priority = backends[i].priority;
1177     if (n > match_len || (n == match_len && match_priority > priority)) {
1178       match_len      = n;
1179       match_priority = priority;
1180       match_index    = i;
1181     }
1182   }
1183   // Using Levenshtein distance to find closest match
1184   if (match_len <= 1 || match_len != stem_length) {
1185     // LCOV_EXCL_START
1186     size_t lev_dis   = UINT_MAX;
1187     size_t lev_index = UINT_MAX, lev_priority = CEED_MAX_BACKEND_PRIORITY;
1188     for (size_t i = 0; i < num_backends; i++) {
1189       const char *prefix        = backends[i].prefix;
1190       size_t      prefix_length = strlen(backends[i].prefix);
1191       size_t      min_len       = (prefix_length < stem_length) ? prefix_length : stem_length;
1192       size_t      column[min_len + 1];
1193       for (size_t j = 0; j <= min_len; j++) column[j] = j;
1194       for (size_t j = 1; j <= min_len; j++) {
1195         column[0] = j;
1196         for (size_t k = 1, last_diag = j - 1; k <= min_len; k++) {
1197           size_t old_diag = column[k];
1198           size_t min_1    = (column[k] < column[k - 1]) ? column[k] + 1 : column[k - 1] + 1;
1199           size_t min_2    = last_diag + (resource[k - 1] == prefix[j - 1] ? 0 : 1);
1200           column[k]       = (min_1 < min_2) ? min_1 : min_2;
1201           last_diag       = old_diag;
1202         }
1203       }
1204       size_t n = column[min_len];
1205       priority = backends[i].priority;
1206       if (n < lev_dis || (n == lev_dis && lev_priority > priority)) {
1207         lev_dis      = n;
1208         lev_priority = priority;
1209         lev_index    = i;
1210       }
1211     }
1212     const char *prefix_lev = backends[lev_index].prefix;
1213     size_t      lev_length = 0;
1214     while (prefix_lev[lev_length] && prefix_lev[lev_length] != '\0') lev_length++;
1215     size_t m = (lev_length < stem_length) ? lev_length : stem_length;
1216     if (lev_dis + 1 >= m) return CeedError(NULL, CEED_ERROR_MAJOR, "No suitable backend: %s", resource);
1217     else return CeedError(NULL, CEED_ERROR_MAJOR, "No suitable backend: %s\nClosest match: %s", resource, backends[lev_index].prefix);
1218     // LCOV_EXCL_STOP
1219   }
1220 
1221   // Setup Ceed
1222   CeedCall(CeedCalloc(1, ceed));
1223   CeedCall(CeedCalloc(1, &(*ceed)->jit_source_roots));
1224   const char *ceed_error_handler = getenv("CEED_ERROR_HANDLER");
1225   if (!ceed_error_handler) ceed_error_handler = "abort";
1226   if (!strcmp(ceed_error_handler, "exit")) (*ceed)->Error = CeedErrorExit;
1227   else if (!strcmp(ceed_error_handler, "store")) (*ceed)->Error = CeedErrorStore;
1228   else (*ceed)->Error = CeedErrorAbort;
1229   memcpy((*ceed)->err_msg, "No error message stored", 24);
1230   (*ceed)->ref_count = 1;
1231   (*ceed)->data      = NULL;
1232 
1233   // Set lookup table
1234   FOffset f_offsets[] = {
1235       CEED_FTABLE_ENTRY(Ceed, Error),
1236       CEED_FTABLE_ENTRY(Ceed, SetStream),
1237       CEED_FTABLE_ENTRY(Ceed, GetPreferredMemType),
1238       CEED_FTABLE_ENTRY(Ceed, Destroy),
1239       CEED_FTABLE_ENTRY(Ceed, VectorCreate),
1240       CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreate),
1241       CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreateAtPoints),
1242       CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreateBlocked),
1243       CEED_FTABLE_ENTRY(Ceed, BasisCreateTensorH1),
1244       CEED_FTABLE_ENTRY(Ceed, BasisCreateH1),
1245       CEED_FTABLE_ENTRY(Ceed, BasisCreateHdiv),
1246       CEED_FTABLE_ENTRY(Ceed, BasisCreateHcurl),
1247       CEED_FTABLE_ENTRY(Ceed, TensorContractCreate),
1248       CEED_FTABLE_ENTRY(Ceed, QFunctionCreate),
1249       CEED_FTABLE_ENTRY(Ceed, QFunctionContextCreate),
1250       CEED_FTABLE_ENTRY(Ceed, OperatorCreate),
1251       CEED_FTABLE_ENTRY(Ceed, OperatorCreateAtPoints),
1252       CEED_FTABLE_ENTRY(Ceed, CompositeOperatorCreate),
1253       CEED_FTABLE_ENTRY(CeedVector, HasValidArray),
1254       CEED_FTABLE_ENTRY(CeedVector, HasBorrowedArrayOfType),
1255       CEED_FTABLE_ENTRY(CeedVector, CopyStrided),
1256       CEED_FTABLE_ENTRY(CeedVector, SetArray),
1257       CEED_FTABLE_ENTRY(CeedVector, TakeArray),
1258       CEED_FTABLE_ENTRY(CeedVector, SetValue),
1259       CEED_FTABLE_ENTRY(CeedVector, SetValueStrided),
1260       CEED_FTABLE_ENTRY(CeedVector, SyncArray),
1261       CEED_FTABLE_ENTRY(CeedVector, GetArray),
1262       CEED_FTABLE_ENTRY(CeedVector, GetArrayRead),
1263       CEED_FTABLE_ENTRY(CeedVector, GetArrayWrite),
1264       CEED_FTABLE_ENTRY(CeedVector, RestoreArray),
1265       CEED_FTABLE_ENTRY(CeedVector, RestoreArrayRead),
1266       CEED_FTABLE_ENTRY(CeedVector, Norm),
1267       CEED_FTABLE_ENTRY(CeedVector, Scale),
1268       CEED_FTABLE_ENTRY(CeedVector, AXPY),
1269       CEED_FTABLE_ENTRY(CeedVector, AXPBY),
1270       CEED_FTABLE_ENTRY(CeedVector, PointwiseMult),
1271       CEED_FTABLE_ENTRY(CeedVector, Reciprocal),
1272       CEED_FTABLE_ENTRY(CeedVector, Destroy),
1273       CEED_FTABLE_ENTRY(CeedElemRestriction, Apply),
1274       CEED_FTABLE_ENTRY(CeedElemRestriction, ApplyUnsigned),
1275       CEED_FTABLE_ENTRY(CeedElemRestriction, ApplyUnoriented),
1276       CEED_FTABLE_ENTRY(CeedElemRestriction, ApplyAtPointsInElement),
1277       CEED_FTABLE_ENTRY(CeedElemRestriction, ApplyBlock),
1278       CEED_FTABLE_ENTRY(CeedElemRestriction, GetOffsets),
1279       CEED_FTABLE_ENTRY(CeedElemRestriction, GetOrientations),
1280       CEED_FTABLE_ENTRY(CeedElemRestriction, GetCurlOrientations),
1281       CEED_FTABLE_ENTRY(CeedElemRestriction, GetAtPointsElementOffset),
1282       CEED_FTABLE_ENTRY(CeedElemRestriction, Destroy),
1283       CEED_FTABLE_ENTRY(CeedBasis, Apply),
1284       CEED_FTABLE_ENTRY(CeedBasis, ApplyAdd),
1285       CEED_FTABLE_ENTRY(CeedBasis, ApplyAtPoints),
1286       CEED_FTABLE_ENTRY(CeedBasis, ApplyAddAtPoints),
1287       CEED_FTABLE_ENTRY(CeedBasis, Destroy),
1288       CEED_FTABLE_ENTRY(CeedTensorContract, Apply),
1289       CEED_FTABLE_ENTRY(CeedTensorContract, Destroy),
1290       CEED_FTABLE_ENTRY(CeedQFunction, Apply),
1291       CEED_FTABLE_ENTRY(CeedQFunction, SetCUDAUserFunction),
1292       CEED_FTABLE_ENTRY(CeedQFunction, SetHIPUserFunction),
1293       CEED_FTABLE_ENTRY(CeedQFunction, Destroy),
1294       CEED_FTABLE_ENTRY(CeedQFunctionContext, HasValidData),
1295       CEED_FTABLE_ENTRY(CeedQFunctionContext, HasBorrowedDataOfType),
1296       CEED_FTABLE_ENTRY(CeedQFunctionContext, SetData),
1297       CEED_FTABLE_ENTRY(CeedQFunctionContext, TakeData),
1298       CEED_FTABLE_ENTRY(CeedQFunctionContext, GetData),
1299       CEED_FTABLE_ENTRY(CeedQFunctionContext, GetDataRead),
1300       CEED_FTABLE_ENTRY(CeedQFunctionContext, RestoreData),
1301       CEED_FTABLE_ENTRY(CeedQFunctionContext, RestoreDataRead),
1302       CEED_FTABLE_ENTRY(CeedQFunctionContext, DataDestroy),
1303       CEED_FTABLE_ENTRY(CeedQFunctionContext, Destroy),
1304       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleQFunction),
1305       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleQFunctionUpdate),
1306       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleDiagonal),
1307       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleAddDiagonal),
1308       CEED_FTABLE_ENTRY(CeedOperator, LinearAssemblePointBlockDiagonal),
1309       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleAddPointBlockDiagonal),
1310       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleSymbolic),
1311       CEED_FTABLE_ENTRY(CeedOperator, LinearAssemble),
1312       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleSingle),
1313       CEED_FTABLE_ENTRY(CeedOperator, CreateFDMElementInverse),
1314       CEED_FTABLE_ENTRY(CeedOperator, Apply),
1315       CEED_FTABLE_ENTRY(CeedOperator, ApplyComposite),
1316       CEED_FTABLE_ENTRY(CeedOperator, ApplyAdd),
1317       CEED_FTABLE_ENTRY(CeedOperator, ApplyAddComposite),
1318       CEED_FTABLE_ENTRY(CeedOperator, ApplyJacobian),
1319       CEED_FTABLE_ENTRY(CeedOperator, Destroy),
1320       {NULL, 0}  // End of lookup table - used in SetBackendFunction loop
1321   };
1322 
1323   CeedCall(CeedCalloc(sizeof(f_offsets), &(*ceed)->f_offsets));
1324   memcpy((*ceed)->f_offsets, f_offsets, sizeof(f_offsets));
1325 
1326   // Set fallback for advanced CeedOperator functions
1327   const char fallback_resource[] = "";
1328   CeedCall(CeedSetOperatorFallbackResource(*ceed, fallback_resource));
1329 
1330   // Record env variables CEED_DEBUG or DBG
1331   (*ceed)->is_debug = getenv("CEED_DEBUG") || getenv("DEBUG") || getenv("DBG");
1332 
1333   // Copy resource prefix, if backend setup successful
1334   CeedCall(CeedStringAllocCopy(backends[match_index].prefix, (char **)&(*ceed)->resource));
1335 
1336   // Set default JiT source root
1337   // Note: there will always be the default root for every Ceed but all additional paths are added to the top-most parent
1338   CeedCall(CeedAddJitSourceRoot(*ceed, (char *)CeedJitSourceRootDefault));
1339 
1340   // Backend specific setup
1341   CeedCall(backends[match_index].init(&resource[match_help], *ceed));
1342   return CEED_ERROR_SUCCESS;
1343 }
1344 
1345 /**
1346   @brief Set the GPU stream for a `Ceed` context
1347 
1348   @param[in,out] ceed   `Ceed` context to set the stream
1349   @param[in]     handle Handle to GPU stream
1350 
1351   @return An error code: 0 - success, otherwise - failure
1352 
1353   @ref User
1354 **/
1355 int CeedSetStream(Ceed ceed, void *handle) {
1356   CeedCheck(handle, ceed, CEED_ERROR_INCOMPATIBLE, "Stream handle must be non-NULL");
1357   if (ceed->SetStream) {
1358     CeedCall(ceed->SetStream(ceed, handle));
1359   } else {
1360     Ceed delegate;
1361     CeedCall(CeedGetDelegate(ceed, &delegate));
1362 
1363     if (delegate) CeedCall(CeedSetStream(delegate, handle));
1364     else return CeedError(ceed, CEED_ERROR_UNSUPPORTED, "Backend does not support setting stream");
1365     CeedCall(CeedDestroy(&delegate));
1366   }
1367   return CEED_ERROR_SUCCESS;
1368 }
1369 
1370 /**
1371   @brief Copy the pointer to a `Ceed` context.
1372 
1373   Both pointers should be destroyed with @ref CeedDestroy().
1374 
1375   Note: If the value of `*ceed_copy` passed to this function is non-`NULL`, then it is assumed that `*ceed_copy` is a pointer to a `Ceed` context.
1376         This `Ceed` context will be destroyed if `*ceed_copy` is the only reference to this `Ceed` context.
1377 
1378   @param[in]     ceed      `Ceed` context to copy reference to
1379   @param[in,out] ceed_copy Variable to store copied reference
1380 
1381   @return An error code: 0 - success, otherwise - failure
1382 
1383   @ref User
1384 **/
1385 int CeedReferenceCopy(Ceed ceed, Ceed *ceed_copy) {
1386   CeedCall(CeedReference(ceed));
1387   CeedCall(CeedDestroy(ceed_copy));
1388   *ceed_copy = ceed;
1389   return CEED_ERROR_SUCCESS;
1390 }
1391 
1392 /**
1393   @brief Get the full resource name for a `Ceed` context
1394 
1395   @param[in]  ceed     `Ceed` context to get resource name of
1396   @param[out] resource Variable to store resource name
1397 
1398   @return An error code: 0 - success, otherwise - failure
1399 
1400   @ref User
1401 **/
1402 int CeedGetResource(Ceed ceed, const char **resource) {
1403   *resource = (const char *)ceed->resource;
1404   return CEED_ERROR_SUCCESS;
1405 }
1406 
1407 /**
1408   @brief Return `Ceed` context preferred memory type
1409 
1410   @param[in]  ceed     `Ceed` context to get preferred memory type of
1411   @param[out] mem_type Address to save preferred memory type to
1412 
1413   @return An error code: 0 - success, otherwise - failure
1414 
1415   @ref User
1416 **/
1417 int CeedGetPreferredMemType(Ceed ceed, CeedMemType *mem_type) {
1418   if (ceed->GetPreferredMemType) {
1419     CeedCall(ceed->GetPreferredMemType(mem_type));
1420   } else {
1421     Ceed delegate;
1422     CeedCall(CeedGetDelegate(ceed, &delegate));
1423 
1424     if (delegate) {
1425       CeedCall(CeedGetPreferredMemType(delegate, mem_type));
1426     } else {
1427       *mem_type = CEED_MEM_HOST;
1428     }
1429     CeedCall(CeedDestroy(&delegate));
1430   }
1431   return CEED_ERROR_SUCCESS;
1432 }
1433 
1434 /**
1435   @brief Get deterministic status of `Ceed` context
1436 
1437   @param[in]  ceed             `Ceed` context
1438   @param[out] is_deterministic Variable to store deterministic status
1439 
1440   @return An error code: 0 - success, otherwise - failure
1441 
1442   @ref User
1443 **/
1444 int CeedIsDeterministic(Ceed ceed, bool *is_deterministic) {
1445   *is_deterministic = ceed->is_deterministic;
1446   return CEED_ERROR_SUCCESS;
1447 }
1448 
1449 /**
1450   @brief Set additional JiT source root for `Ceed` context
1451 
1452   @param[in,out] ceed            `Ceed` context
1453   @param[in]     jit_source_root Absolute path to additional JiT source directory
1454 
1455   @return An error code: 0 - success, otherwise - failure
1456 
1457   @ref User
1458 **/
1459 int CeedAddJitSourceRoot(Ceed ceed, const char *jit_source_root) {
1460   Ceed ceed_parent;
1461 
1462   CeedCall(CeedGetParent(ceed, &ceed_parent));
1463   CeedCheck(!ceed_parent->num_jit_source_roots_readers, ceed, CEED_ERROR_ACCESS, "Cannot add JiT source root, read access has not been restored");
1464 
1465   CeedInt index       = ceed_parent->num_jit_source_roots;
1466   size_t  path_length = strlen(jit_source_root);
1467 
1468   if (ceed_parent->num_jit_source_roots == ceed_parent->max_jit_source_roots) {
1469     if (ceed_parent->max_jit_source_roots == 0) ceed_parent->max_jit_source_roots = 1;
1470     ceed_parent->max_jit_source_roots *= 2;
1471     CeedCall(CeedRealloc(ceed_parent->max_jit_source_roots, &ceed_parent->jit_source_roots));
1472   }
1473   CeedCall(CeedCalloc(path_length + 1, &ceed_parent->jit_source_roots[index]));
1474   memcpy(ceed_parent->jit_source_roots[index], jit_source_root, path_length);
1475   ceed_parent->num_jit_source_roots++;
1476   CeedCall(CeedDestroy(&ceed_parent));
1477   return CEED_ERROR_SUCCESS;
1478 }
1479 
1480 /**
1481   @brief Set additional JiT compiler define for `Ceed` context
1482 
1483   @param[in,out] ceed       `Ceed` context
1484   @param[in]     jit_define String such as `foo=bar`, used as `-Dfoo=bar` in JiT
1485 
1486   @return An error code: 0 - success, otherwise - failure
1487 
1488   @ref User
1489 **/
1490 int CeedAddJitDefine(Ceed ceed, const char *jit_define) {
1491   Ceed ceed_parent;
1492 
1493   CeedCall(CeedGetParent(ceed, &ceed_parent));
1494   CeedCheck(!ceed_parent->num_jit_defines_readers, ceed, CEED_ERROR_ACCESS, "Cannot add JiT define, read access has not been restored");
1495 
1496   CeedInt index         = ceed_parent->num_jit_defines;
1497   size_t  define_length = strlen(jit_define);
1498 
1499   if (ceed_parent->num_jit_defines == ceed_parent->max_jit_defines) {
1500     if (ceed_parent->max_jit_defines == 0) ceed_parent->max_jit_defines = 1;
1501     ceed_parent->max_jit_defines *= 2;
1502     CeedCall(CeedRealloc(ceed_parent->max_jit_defines, &ceed_parent->jit_defines));
1503   }
1504   CeedCall(CeedCalloc(define_length + 1, &ceed_parent->jit_defines[index]));
1505   memcpy(ceed_parent->jit_defines[index], jit_define, define_length);
1506   ceed_parent->num_jit_defines++;
1507   CeedCall(CeedDestroy(&ceed_parent));
1508   return CEED_ERROR_SUCCESS;
1509 }
1510 
1511 /**
1512   @brief View a `Ceed`
1513 
1514   @param[in] ceed   `Ceed` to view
1515   @param[in] stream Filestream to write to
1516 
1517   @return An error code: 0 - success, otherwise - failure
1518 
1519   @ref User
1520 **/
1521 int CeedView(Ceed ceed, FILE *stream) {
1522   CeedMemType mem_type;
1523 
1524   CeedCall(CeedGetPreferredMemType(ceed, &mem_type));
1525 
1526   fprintf(stream,
1527           "Ceed\n"
1528           "  Ceed Resource: %s\n"
1529           "  Preferred MemType: %s\n",
1530           ceed->resource, CeedMemTypes[mem_type]);
1531   return CEED_ERROR_SUCCESS;
1532 }
1533 
1534 /**
1535   @brief Destroy a `Ceed`
1536 
1537   @param[in,out] ceed Address of `Ceed` context to destroy
1538 
1539   @return An error code: 0 - success, otherwise - failure
1540 
1541   @ref User
1542 **/
1543 int CeedDestroy(Ceed *ceed) {
1544   if (!*ceed || --(*ceed)->ref_count > 0) {
1545     *ceed = NULL;
1546     return CEED_ERROR_SUCCESS;
1547   }
1548 
1549   CeedCheck(!(*ceed)->num_jit_source_roots_readers, *ceed, CEED_ERROR_ACCESS,
1550             "Cannot destroy ceed context, read access for JiT source roots has been granted");
1551   CeedCheck(!(*ceed)->num_jit_defines_readers, *ceed, CEED_ERROR_ACCESS, "Cannot add JiT source root, read access for JiT defines has been granted");
1552 
1553   if ((*ceed)->delegate) CeedCall(CeedDestroy(&(*ceed)->delegate));
1554 
1555   if ((*ceed)->obj_delegate_count > 0) {
1556     for (CeedInt i = 0; i < (*ceed)->obj_delegate_count; i++) {
1557       CeedCall(CeedDestroy(&((*ceed)->obj_delegates[i].delegate)));
1558       CeedCall(CeedFree(&(*ceed)->obj_delegates[i].obj_name));
1559     }
1560     CeedCall(CeedFree(&(*ceed)->obj_delegates));
1561   }
1562 
1563   if ((*ceed)->Destroy) CeedCall((*ceed)->Destroy(*ceed));
1564 
1565   for (CeedInt i = 0; i < (*ceed)->num_jit_source_roots; i++) {
1566     CeedCall(CeedFree(&(*ceed)->jit_source_roots[i]));
1567   }
1568   CeedCall(CeedFree(&(*ceed)->jit_source_roots));
1569 
1570   for (CeedInt i = 0; i < (*ceed)->num_jit_defines; i++) {
1571     CeedCall(CeedFree(&(*ceed)->jit_defines[i]));
1572   }
1573   CeedCall(CeedFree(&(*ceed)->jit_defines));
1574 
1575   CeedCall(CeedFree(&(*ceed)->f_offsets));
1576   CeedCall(CeedFree(&(*ceed)->resource));
1577   CeedCall(CeedDestroy(&(*ceed)->op_fallback_ceed));
1578   CeedCall(CeedFree(&(*ceed)->op_fallback_resource));
1579   CeedCall(CeedWorkVectorsDestroy(*ceed));
1580   CeedCall(CeedFree(ceed));
1581   return CEED_ERROR_SUCCESS;
1582 }
1583 
1584 // LCOV_EXCL_START
1585 const char *CeedErrorFormat(Ceed ceed, const char *format, va_list *args) {
1586   if (ceed->parent) return CeedErrorFormat(ceed->parent, format, args);
1587   if (ceed->op_fallback_parent) return CeedErrorFormat(ceed->op_fallback_parent, format, args);
1588   // Using pointer to va_list for better FFI, but clang-tidy can't verify va_list is initalized
1589   vsnprintf(ceed->err_msg, CEED_MAX_RESOURCE_LEN, format, *args);  // NOLINT
1590   return ceed->err_msg;
1591 }
1592 // LCOV_EXCL_STOP
1593 
1594 /**
1595   @brief Error handling implementation; use @ref CeedError() instead.
1596 
1597   @return An error code: 0 - success, otherwise - failure
1598 
1599   @ref Developer
1600 **/
1601 int CeedErrorImpl(Ceed ceed, const char *filename, int lineno, const char *func, int ecode, const char *format, ...) {
1602   va_list args;
1603   int     ret_val;
1604 
1605   va_start(args, format);
1606   if (ceed) {
1607     ret_val = ceed->Error(ceed, filename, lineno, func, ecode, format, &args);
1608   } else {
1609     // LCOV_EXCL_START
1610     const char *ceed_error_handler = getenv("CEED_ERROR_HANDLER");
1611     if (!ceed_error_handler) ceed_error_handler = "abort";
1612     if (!strcmp(ceed_error_handler, "return")) {
1613       ret_val = CeedErrorReturn(ceed, filename, lineno, func, ecode, format, &args);
1614     } else {
1615       // This function will not return
1616       ret_val = CeedErrorAbort(ceed, filename, lineno, func, ecode, format, &args);
1617     }
1618   }
1619   va_end(args);
1620   return ret_val;
1621   // LCOV_EXCL_STOP
1622 }
1623 
1624 /**
1625   @brief Error handler that returns without printing anything.
1626 
1627   Pass this to @ref CeedSetErrorHandler() to obtain this error handling behavior.
1628 
1629   @return An error code: 0 - success, otherwise - failure
1630 
1631   @ref Developer
1632 **/
1633 // LCOV_EXCL_START
1634 int CeedErrorReturn(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1635   return err_code;
1636 }
1637 // LCOV_EXCL_STOP
1638 
1639 /**
1640   @brief Error handler that stores the error message for future use and returns the error.
1641 
1642   Pass this to @ref CeedSetErrorHandler() to obtain this error handling behavior.
1643 
1644   @return An error code: 0 - success, otherwise - failure
1645 
1646   @ref Developer
1647 **/
1648 // LCOV_EXCL_START
1649 int CeedErrorStore(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1650   if (ceed->parent) return CeedErrorStore(ceed->parent, filename, line_no, func, err_code, format, args);
1651   if (ceed->op_fallback_parent) return CeedErrorStore(ceed->op_fallback_parent, filename, line_no, func, err_code, format, args);
1652 
1653   // Build message
1654   int len = snprintf(ceed->err_msg, CEED_MAX_RESOURCE_LEN, "%s:%d in %s(): ", filename, line_no, func);
1655   // Using pointer to va_list for better FFI, but clang-tidy can't verify va_list is initalized
1656   vsnprintf(ceed->err_msg + len, CEED_MAX_RESOURCE_LEN - len, format, *args);  // NOLINT
1657   return err_code;
1658 }
1659 // LCOV_EXCL_STOP
1660 
1661 /**
1662   @brief Error handler that prints to `stderr` and aborts
1663 
1664   Pass this to @ref CeedSetErrorHandler() to obtain this error handling behavior.
1665 
1666   @return An error code: 0 - success, otherwise - failure
1667 
1668   @ref Developer
1669 **/
1670 // LCOV_EXCL_START
1671 int CeedErrorAbort(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1672   fprintf(stderr, "%s:%d in %s(): ", filename, line_no, func);
1673   vfprintf(stderr, format, *args);
1674   fprintf(stderr, "\n");
1675   abort();
1676   return err_code;
1677 }
1678 // LCOV_EXCL_STOP
1679 
1680 /**
1681   @brief Error handler that prints to `stderr` and exits.
1682 
1683   Pass this to @ref CeedSetErrorHandler() to obtain this error handling behavior.
1684 
1685   In contrast to @ref CeedErrorAbort(), this exits without a signal, so `atexit()` handlers (e.g., as used by gcov) are run.
1686 
1687   @return An error code: 0 - success, otherwise - failure
1688 
1689   @ref Developer
1690 **/
1691 int CeedErrorExit(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1692   fprintf(stderr, "%s:%d in %s(): ", filename, line_no, func);
1693   // Using pointer to va_list for better FFI, but clang-tidy can't verify va_list is initalized
1694   vfprintf(stderr, format, *args);  // NOLINT
1695   fprintf(stderr, "\n");
1696   exit(err_code);
1697   return err_code;
1698 }
1699 
1700 /**
1701   @brief Set error handler
1702 
1703   A default error handler is set in @ref CeedInit().
1704   Use this function to change the error handler to @ref CeedErrorReturn(), @ref CeedErrorAbort(), or a user-defined error handler.
1705 
1706   @return An error code: 0 - success, otherwise - failure
1707 
1708   @ref Developer
1709 **/
1710 int CeedSetErrorHandler(Ceed ceed, CeedErrorHandler handler) {
1711   ceed->Error = handler;
1712   if (ceed->delegate) CeedSetErrorHandler(ceed->delegate, handler);
1713   for (CeedInt i = 0; i < ceed->obj_delegate_count; i++) CeedSetErrorHandler(ceed->obj_delegates[i].delegate, handler);
1714   return CEED_ERROR_SUCCESS;
1715 }
1716 
1717 /**
1718   @brief Get error message
1719 
1720   The error message is only stored when using the error handler @ref CeedErrorStore()
1721 
1722   @param[in]  ceed    `Ceed` context to retrieve error message
1723   @param[out] err_msg Char pointer to hold error message
1724 
1725   @return An error code: 0 - success, otherwise - failure
1726 
1727   @ref Developer
1728 **/
1729 int CeedGetErrorMessage(Ceed ceed, const char **err_msg) {
1730   if (ceed->parent) return CeedGetErrorMessage(ceed->parent, err_msg);
1731   if (ceed->op_fallback_parent) return CeedGetErrorMessage(ceed->op_fallback_parent, err_msg);
1732   *err_msg = ceed->err_msg;
1733   return CEED_ERROR_SUCCESS;
1734 }
1735 
1736 /**
1737   @brief Restore error message.
1738 
1739   The error message is only stored when using the error handler @ref CeedErrorStore().
1740 
1741   @param[in]  ceed    `Ceed` context to restore error message
1742   @param[out] err_msg Char pointer that holds error message
1743 
1744   @return An error code: 0 - success, otherwise - failure
1745 
1746   @ref Developer
1747 **/
1748 int CeedResetErrorMessage(Ceed ceed, const char **err_msg) {
1749   if (ceed->parent) return CeedResetErrorMessage(ceed->parent, err_msg);
1750   if (ceed->op_fallback_parent) return CeedResetErrorMessage(ceed->op_fallback_parent, err_msg);
1751   *err_msg = NULL;
1752   memcpy(ceed->err_msg, "No error message stored", 24);
1753   return CEED_ERROR_SUCCESS;
1754 }
1755 
1756 /**
1757   @brief Get libCEED library version information.
1758 
1759   libCEED version numbers have the form major.minor.patch.
1760   Non-release versions may contain unstable interfaces.
1761 
1762   @param[out] major   Major version of the library
1763   @param[out] minor   Minor version of the library
1764   @param[out] patch   Patch (subminor) version of the library
1765   @param[out] release True for releases; false for development branches
1766 
1767   The caller may pass `NULL` for any arguments that are not needed.
1768 
1769   @return An error code: 0 - success, otherwise - failure
1770 
1771   @ref Developer
1772 
1773   @sa CEED_VERSION_GE() CeedGetGitVersion() CeedGetBuildConfiguration()
1774 */
1775 int CeedGetVersion(int *major, int *minor, int *patch, bool *release) {
1776   if (major) *major = CEED_VERSION_MAJOR;
1777   if (minor) *minor = CEED_VERSION_MINOR;
1778   if (patch) *patch = CEED_VERSION_PATCH;
1779   if (release) *release = CEED_VERSION_RELEASE;
1780   return CEED_ERROR_SUCCESS;
1781 }
1782 
1783 /**
1784   @brief Get libCEED scalar type, such as F64 or F32
1785 
1786   @param[out] scalar_type Type of libCEED scalars
1787 
1788   @return An error code: 0 - success, otherwise - failure
1789 
1790   @ref Developer
1791 */
1792 int CeedGetScalarType(CeedScalarType *scalar_type) {
1793   *scalar_type = CEED_SCALAR_TYPE;
1794   return CEED_ERROR_SUCCESS;
1795 }
1796 
1797 /// @}
1798