xref: /libCEED/interface/ceed.c (revision 898eb931305e6255a892582eacaf7552197fb10f)
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 `Ceed` for `CeedOperator`
628 
629   @param[in]  ceed          `Ceed` context
630   @param[out] fallback_ceed Variable to store fallback `Ceed`
631 
632   @return An error code: 0 - success, otherwise - failure
633 
634   @ref Backend
635 **/
636 int CeedGetOperatorFallbackCeed(Ceed ceed, Ceed *fallback_ceed) {
637   if (ceed->op_fallback_ceed) {
638     CeedDebug256(ceed, CEED_DEBUG_COLOR_SUCCESS, "---------- Ceed Fallback ----------\n");
639     CeedDebug(ceed, "Falling back from Ceed with backend %s at address %p to Ceed with backend %s at address %p", ceed->resource, ceed,
640               ceed->op_fallback_ceed->resource, ceed->op_fallback_ceed);
641   }
642 
643   *fallback_ceed = NULL;
644   if (ceed->op_fallback_ceed) CeedCall(CeedReferenceCopy(ceed->op_fallback_ceed, fallback_ceed));
645   return CEED_ERROR_SUCCESS;
646 }
647 
648 /**
649   @brief Set the fallback resource for `CeedOperator`.
650 
651   The current fallback, if any, is freed by calling this function.
652 
653   @param[in,out] ceed          `Ceed` context
654   @param[in]     fallback_ceed `Ceed` context to create fallback operators
655 
656   @return An error code: 0 - success, otherwise - failure
657 
658   @ref Backend
659 **/
660 int CeedSetOperatorFallbackCeed(Ceed ceed, Ceed fallback_ceed) {
661   CeedCall(CeedReferenceCopy(fallback_ceed, &ceed->op_fallback_ceed));
662   fallback_ceed->parent = ceed;
663   return CEED_ERROR_SUCCESS;
664 }
665 
666 /**
667   @brief Flag `Ceed` context as deterministic
668 
669   @param[in]  ceed             `Ceed` to flag as deterministic
670   @param[out] is_deterministic Deterministic status to set
671 
672   @return An error code: 0 - success, otherwise - failure
673 
674   @ref Backend
675 **/
676 int CeedSetDeterministic(Ceed ceed, bool is_deterministic) {
677   ceed->is_deterministic = is_deterministic;
678   return CEED_ERROR_SUCCESS;
679 }
680 
681 /**
682   @brief Set a backend function.
683 
684   This function is used for a backend to set the function associated with the Ceed objects.
685   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().
686   Note, the prefix 'Ceed' is not required for the object type ("Basis" vs "CeedBasis").
687 
688   @param[in]  ceed      `Ceed` context for error handling
689   @param[in]  type      Type of Ceed object to set function for
690   @param[out] object    Ceed object to set function for
691   @param[in]  func_name Name of function to set
692   @param[in]  f         Function to set
693 
694   @return An error code: 0 - success, otherwise - failure
695 
696   @ref Backend
697 **/
698 int CeedSetBackendFunctionImpl(Ceed ceed, const char *type, void *object, const char *func_name, void (*f)(void)) {
699   char lookup_name[CEED_MAX_RESOURCE_LEN + 1] = "";
700 
701   // Build lookup name
702   if (strcmp(type, "Ceed")) strncat(lookup_name, "Ceed", CEED_MAX_RESOURCE_LEN);
703   strncat(lookup_name, type, CEED_MAX_RESOURCE_LEN);
704   strncat(lookup_name, func_name, CEED_MAX_RESOURCE_LEN);
705 
706   // Find and use offset
707   for (CeedInt i = 0; ceed->f_offsets[i].func_name; i++) {
708     if (!strcmp(ceed->f_offsets[i].func_name, lookup_name)) {
709       size_t offset          = ceed->f_offsets[i].offset;
710       int (**fpointer)(void) = (int (**)(void))((char *)object + offset);  // *NOPAD*
711 
712       *fpointer = (int (*)(void))f;
713       return CEED_ERROR_SUCCESS;
714     }
715   }
716 
717   // LCOV_EXCL_START
718   return CeedError(ceed, CEED_ERROR_UNSUPPORTED, "Requested function '%s' was not found for CEED object '%s'", func_name, type);
719   // LCOV_EXCL_STOP
720 }
721 
722 /**
723   @brief Retrieve backend data for a `Ceed` context
724 
725   @param[in]  ceed `Ceed` context to retrieve data of
726   @param[out] data Address to save data to
727 
728   @return An error code: 0 - success, otherwise - failure
729 
730   @ref Backend
731 **/
732 int CeedGetData(Ceed ceed, void *data) {
733   *(void **)data = ceed->data;
734   return CEED_ERROR_SUCCESS;
735 }
736 
737 /**
738   @brief Set backend data for a `Ceed` context
739 
740   @param[in,out] ceed `Ceed` context to set data of
741   @param[in]     data Address of data to set
742 
743   @return An error code: 0 - success, otherwise - failure
744 
745   @ref Backend
746 **/
747 int CeedSetData(Ceed ceed, void *data) {
748   ceed->data = data;
749   return CEED_ERROR_SUCCESS;
750 }
751 
752 /**
753   @brief Increment the reference counter for a `Ceed` context
754 
755   @param[in,out] ceed `Ceed` context to increment the reference counter
756 
757   @return An error code: 0 - success, otherwise - failure
758 
759   @ref Backend
760 **/
761 int CeedReference(Ceed ceed) {
762   ceed->ref_count++;
763   return CEED_ERROR_SUCCESS;
764 }
765 
766 /**
767   @brief Computes the current memory usage of the work vectors in a `Ceed` context and prints to debug.abort
768 
769   @param[in]  ceed     `Ceed` context
770   @param[out] usage_mb Address of the variable where the MB of work vector usage will be stored
771 
772   @return An error code: 0 - success, otherwise - failure
773 
774   @ref Developer
775 **/
776 int CeedGetWorkVectorMemoryUsage(Ceed ceed, CeedScalar *usage_mb) {
777   if (!ceed->VectorCreate) {
778     Ceed delegate;
779 
780     CeedCall(CeedGetObjectDelegate(ceed, &delegate, "Vector"));
781     CeedCheck(delegate, ceed, CEED_ERROR_UNSUPPORTED, "Backend does not implement VectorCreate");
782     CeedCall(CeedGetWorkVectorMemoryUsage(delegate, usage_mb));
783     CeedCall(CeedDestroy(&delegate));
784     return CEED_ERROR_SUCCESS;
785   }
786   *usage_mb = 0.0;
787   if (ceed->work_vectors) {
788     for (CeedInt i = 0; i < ceed->work_vectors->num_vecs; i++) {
789       CeedSize vec_len;
790       CeedCall(CeedVectorGetLength(ceed->work_vectors->vecs[i], &vec_len));
791       *usage_mb += vec_len;
792     }
793     *usage_mb *= sizeof(CeedScalar) * 1e-6;
794     CeedDebug(ceed, "Resource {%s}: Work vectors memory usage: %" CeedInt_FMT " vectors, %g MB\n", ceed->resource, ceed->work_vectors->num_vecs,
795               *usage_mb);
796   }
797   return CEED_ERROR_SUCCESS;
798 }
799 
800 /**
801   @brief Clear inactive work vectors in a `Ceed` context below a minimum length.
802 
803   @param[in,out] ceed    `Ceed` context
804   @param[in]     min_len Minimum length of work vector to keep
805 
806   @return An error code: 0 - success, otherwise - failure
807 
808   @ref Backend
809 **/
810 int CeedClearWorkVectors(Ceed ceed, CeedSize min_len) {
811   if (!ceed->VectorCreate) {
812     Ceed delegate;
813 
814     CeedCall(CeedGetObjectDelegate(ceed, &delegate, "Vector"));
815     CeedCheck(delegate, ceed, CEED_ERROR_UNSUPPORTED, "Backend does not implement VectorCreate");
816     CeedCall(CeedClearWorkVectors(delegate, min_len));
817     CeedCall(CeedDestroy(&delegate));
818     return CEED_ERROR_SUCCESS;
819   }
820   if (!ceed->work_vectors) return CEED_ERROR_SUCCESS;
821   for (CeedInt i = 0; i < ceed->work_vectors->num_vecs; i++) {
822     if (ceed->work_vectors->is_in_use[i]) continue;
823     CeedSize vec_len;
824     CeedCall(CeedVectorGetLength(ceed->work_vectors->vecs[i], &vec_len));
825     if (vec_len < min_len) {
826       ceed->ref_count += 2;  // Note: increase ref_count to prevent Ceed destructor from triggering
827       CeedCall(CeedVectorDestroy(&ceed->work_vectors->vecs[i]));
828       ceed->ref_count -= 1;  // Note: restore ref_count
829       ceed->work_vectors->num_vecs--;
830       if (ceed->work_vectors->num_vecs > 0) {
831         ceed->work_vectors->vecs[i]                                 = ceed->work_vectors->vecs[ceed->work_vectors->num_vecs];
832         ceed->work_vectors->is_in_use[i]                            = ceed->work_vectors->is_in_use[ceed->work_vectors->num_vecs];
833         ceed->work_vectors->is_in_use[ceed->work_vectors->num_vecs] = false;
834         i--;
835       }
836     }
837   }
838   return CEED_ERROR_SUCCESS;
839 }
840 
841 /**
842   @brief Get a `CeedVector` for scratch work from a `Ceed` context.
843 
844   Note: This vector must be restored with @ref CeedRestoreWorkVector().
845 
846   @param[in]  ceed `Ceed` context
847   @param[in]  len  Minimum length of work vector
848   @param[out] vec  Address of the variable where `CeedVector` will be stored
849 
850   @return An error code: 0 - success, otherwise - failure
851 
852   @ref Backend
853 **/
854 int CeedGetWorkVector(Ceed ceed, CeedSize len, CeedVector *vec) {
855   CeedInt    i = 0;
856   CeedScalar usage_mb;
857 
858   if (!ceed->VectorCreate) {
859     Ceed delegate;
860 
861     CeedCall(CeedGetObjectDelegate(ceed, &delegate, "Vector"));
862     CeedCheck(delegate, ceed, CEED_ERROR_UNSUPPORTED, "Backend does not implement VectorCreate");
863     CeedCall(CeedGetWorkVector(delegate, len, vec));
864     CeedCall(CeedDestroy(&delegate));
865     return CEED_ERROR_SUCCESS;
866   }
867 
868   if (!ceed->work_vectors) CeedCall(CeedWorkVectorsCreate(ceed));
869 
870   // Search for big enough work vector
871   for (i = 0; i < ceed->work_vectors->num_vecs; i++) {
872     if (!ceed->work_vectors->is_in_use[i]) {
873       CeedSize work_len;
874 
875       CeedCall(CeedVectorGetLength(ceed->work_vectors->vecs[i], &work_len));
876       if (work_len >= len) break;
877     }
878   }
879   // Long enough vector was not found
880   if (i == ceed->work_vectors->num_vecs) {
881     if (ceed->work_vectors->max_vecs == 0) {
882       ceed->work_vectors->max_vecs = 1;
883       CeedCall(CeedCalloc(ceed->work_vectors->max_vecs, &ceed->work_vectors->vecs));
884       CeedCall(CeedCalloc(ceed->work_vectors->max_vecs, &ceed->work_vectors->is_in_use));
885     } else if (ceed->work_vectors->max_vecs == i) {
886       ceed->work_vectors->max_vecs *= 2;
887       CeedCall(CeedRealloc(ceed->work_vectors->max_vecs, &ceed->work_vectors->vecs));
888       CeedCall(CeedRealloc(ceed->work_vectors->max_vecs, &ceed->work_vectors->is_in_use));
889     }
890     ceed->work_vectors->num_vecs++;
891     CeedCallBackend(CeedVectorCreate(ceed, len, &ceed->work_vectors->vecs[i]));
892     ceed->ref_count--;  // Note: ref_count manipulation to prevent a ref-loop
893     if (ceed->is_debug) CeedGetWorkVectorMemoryUsage(ceed, &usage_mb);
894   }
895   // Return pointer to work vector
896   ceed->work_vectors->is_in_use[i] = true;
897   *vec                             = NULL;
898   CeedCall(CeedVectorReferenceCopy(ceed->work_vectors->vecs[i], vec));
899   ceed->ref_count++;  // Note: bump ref_count to account for external access
900   return CEED_ERROR_SUCCESS;
901 }
902 
903 /**
904   @brief Restore a `CeedVector` for scratch work from a `Ceed` context from @ref CeedGetWorkVector()
905 
906   @param[in]  ceed `Ceed` context
907   @param[out] vec  `CeedVector` to restore
908 
909   @return An error code: 0 - success, otherwise - failure
910 
911   @ref Backend
912 **/
913 int CeedRestoreWorkVector(Ceed ceed, CeedVector *vec) {
914   if (!ceed->VectorCreate) {
915     Ceed delegate;
916 
917     CeedCall(CeedGetObjectDelegate(ceed, &delegate, "Vector"));
918     CeedCheck(delegate, ceed, CEED_ERROR_UNSUPPORTED, "Backend does not implement VectorCreate");
919     CeedCall(CeedRestoreWorkVector(delegate, vec));
920     CeedCall(CeedDestroy(&delegate));
921     return CEED_ERROR_SUCCESS;
922   }
923 
924   for (CeedInt i = 0; i < ceed->work_vectors->num_vecs; i++) {
925     if (*vec == ceed->work_vectors->vecs[i]) {
926       CeedCheck(ceed->work_vectors->is_in_use[i], ceed, CEED_ERROR_ACCESS, "Work vector %" CeedSize_FMT " was not checked out but is being returned");
927       CeedCall(CeedVectorDestroy(vec));
928       ceed->work_vectors->is_in_use[i] = false;
929       ceed->ref_count--;  // Note: reduce ref_count again to prevent a ref-loop
930       return CEED_ERROR_SUCCESS;
931     }
932   }
933   // LCOV_EXCL_START
934   return CeedError(ceed, CEED_ERROR_MAJOR, "vec was not checked out via CeedGetWorkVector()");
935   // LCOV_EXCL_STOP
936 }
937 
938 /**
939   @brief Retrieve list of additional JiT source roots from `Ceed` context.
940 
941   Note: The caller is responsible for restoring `jit_source_roots` with @ref CeedRestoreJitSourceRoots().
942 
943   @param[in]  ceed             `Ceed` context
944   @param[out] num_source_roots Number of JiT source directories
945   @param[out] jit_source_roots Absolute paths to additional JiT source directories
946 
947   @return An error code: 0 - success, otherwise - failure
948 
949   @ref Backend
950 **/
951 int CeedGetJitSourceRoots(Ceed ceed, CeedInt *num_source_roots, const char ***jit_source_roots) {
952   Ceed ceed_parent;
953 
954   CeedCall(CeedGetParent(ceed, &ceed_parent));
955   *num_source_roots = ceed_parent->num_jit_source_roots;
956   *jit_source_roots = (const char **)ceed_parent->jit_source_roots;
957   ceed_parent->num_jit_source_roots_readers++;
958   CeedCall(CeedDestroy(&ceed_parent));
959   return CEED_ERROR_SUCCESS;
960 }
961 
962 /**
963   @brief Retrieve list of additional Rust source roots from `Ceed` context.
964 
965   Note: The caller is responsible for restoring `rust_source_roots` with @ref CeedRestoreRustSourceRoots().
966 
967   @param[in]  ceed             `Ceed` context
968   @param[out] num_source_roots Number of JiT source directories
969   @param[out] rust_source_roots Absolute paths to additional Rust source directories
970 
971   @return An error code: 0 - success, otherwise - failure
972 
973   @ref Backend
974 **/
975 int CeedGetRustSourceRoots(Ceed ceed, CeedInt *num_source_roots, const char ***rust_source_roots) {
976   Ceed ceed_parent;
977 
978   CeedCall(CeedGetParent(ceed, &ceed_parent));
979   *num_source_roots  = ceed_parent->num_rust_source_roots;
980   *rust_source_roots = (const char **)ceed_parent->rust_source_roots;
981   ceed_parent->num_rust_source_roots_readers++;
982   CeedCall(CeedDestroy(&ceed_parent));
983   return CEED_ERROR_SUCCESS;
984 }
985 
986 /**
987   @brief Restore list of additional JiT source roots from with @ref CeedGetJitSourceRoots()
988 
989   @param[in]  ceed             `Ceed` context
990   @param[out] jit_source_roots Absolute paths to additional JiT source directories
991 
992   @return An error code: 0 - success, otherwise - failure
993 
994   @ref Backend
995 **/
996 int CeedRestoreJitSourceRoots(Ceed ceed, const char ***jit_source_roots) {
997   Ceed ceed_parent;
998 
999   CeedCall(CeedGetParent(ceed, &ceed_parent));
1000   *jit_source_roots = NULL;
1001   ceed_parent->num_jit_source_roots_readers--;
1002   CeedCall(CeedDestroy(&ceed_parent));
1003   return CEED_ERROR_SUCCESS;
1004 }
1005 
1006 /**
1007   @brief Restore list of additional Rust source roots from with @ref CeedGetJitSourceRoots()
1008 
1009   @param[in]  ceed             `Ceed` context
1010   @param[out] rust_source_roots Absolute paths to additional Rust source directories
1011 
1012   @return An error code: 0 - success, otherwise - failure
1013 
1014   @ref Backend
1015 **/
1016 int CeedRestoreRustSourceRoots(Ceed ceed, const char ***rust_source_roots) {
1017   Ceed ceed_parent;
1018 
1019   CeedCall(CeedGetParent(ceed, &ceed_parent));
1020   *rust_source_roots = NULL;
1021   ceed_parent->num_rust_source_roots_readers--;
1022   CeedCall(CeedDestroy(&ceed_parent));
1023   return CEED_ERROR_SUCCESS;
1024 }
1025 
1026 /**
1027   @brief Retrieve list of additional JiT defines from `Ceed` context.
1028 
1029   Note: The caller is responsible for restoring `jit_defines` with @ref CeedRestoreJitDefines().
1030 
1031   @param[in]  ceed            `Ceed` context
1032   @param[out] num_jit_defines Number of JiT defines
1033   @param[out] jit_defines     Strings such as `foo=bar`, used as `-Dfoo=bar` in JiT
1034 
1035   @return An error code: 0 - success, otherwise - failure
1036 
1037   @ref Backend
1038 **/
1039 int CeedGetJitDefines(Ceed ceed, CeedInt *num_jit_defines, const char ***jit_defines) {
1040   Ceed ceed_parent;
1041 
1042   CeedCall(CeedGetParent(ceed, &ceed_parent));
1043   *num_jit_defines = ceed_parent->num_jit_defines;
1044   *jit_defines     = (const char **)ceed_parent->jit_defines;
1045   ceed_parent->num_jit_defines_readers++;
1046   CeedCall(CeedDestroy(&ceed_parent));
1047   return CEED_ERROR_SUCCESS;
1048 }
1049 
1050 /**
1051   @brief Restore list of additional JiT defines from with @ref CeedGetJitDefines()
1052 
1053   @param[in]  ceed        `Ceed` context
1054   @param[out] jit_defines String such as `foo=bar`, used as `-Dfoo=bar` in JiT
1055 
1056   @return An error code: 0 - success, otherwise - failure
1057 
1058   @ref Backend
1059 **/
1060 int CeedRestoreJitDefines(Ceed ceed, const char ***jit_defines) {
1061   Ceed ceed_parent;
1062 
1063   CeedCall(CeedGetParent(ceed, &ceed_parent));
1064   *jit_defines = NULL;
1065   ceed_parent->num_jit_defines_readers--;
1066   CeedCall(CeedDestroy(&ceed_parent));
1067   return CEED_ERROR_SUCCESS;
1068 }
1069 
1070 /// @}
1071 
1072 /// ----------------------------------------------------------------------------
1073 /// Ceed Public API
1074 /// ----------------------------------------------------------------------------
1075 /// @addtogroup CeedUser
1076 /// @{
1077 
1078 /**
1079   @brief Get the list of available resource names for `Ceed` contexts
1080 
1081   Note: The caller is responsible for `free()`ing the resources and priorities arrays, but should not `free()` the contents of the resources array.
1082 
1083   @param[out] n          Number of available resources
1084   @param[out] resources  List of available resource names
1085   @param[out] priorities Resource name prioritization values, lower is better
1086 
1087   @return An error code: 0 - success, otherwise - failure
1088 
1089   @ref User
1090 **/
1091 // LCOV_EXCL_START
1092 int CeedRegistryGetList(size_t *n, char ***const resources, CeedInt **priorities) {
1093   *n         = 0;
1094   *resources = malloc(num_backends * sizeof(**resources));
1095   CeedCheck(resources, NULL, CEED_ERROR_MAJOR, "malloc() failure");
1096   if (priorities) {
1097     *priorities = malloc(num_backends * sizeof(**priorities));
1098     CeedCheck(priorities, NULL, CEED_ERROR_MAJOR, "malloc() failure");
1099   }
1100   for (size_t i = 0; i < num_backends; i++) {
1101     // Only report compiled backends
1102     if (backends[i].priority < CEED_MAX_BACKEND_PRIORITY) {
1103       *resources[i] = backends[i].prefix;
1104       if (priorities) *priorities[i] = backends[i].priority;
1105       *n += 1;
1106     }
1107   }
1108   CeedCheck(*n, NULL, CEED_ERROR_MAJOR, "No backends installed");
1109   *resources = realloc(*resources, *n * sizeof(**resources));
1110   CeedCheck(resources, NULL, CEED_ERROR_MAJOR, "realloc() failure");
1111   if (priorities) {
1112     *priorities = realloc(*priorities, *n * sizeof(**priorities));
1113     CeedCheck(priorities, NULL, CEED_ERROR_MAJOR, "realloc() failure");
1114   }
1115   return CEED_ERROR_SUCCESS;
1116 }
1117 // LCOV_EXCL_STOP
1118 
1119 /**
1120   @brief Initialize a `Ceed` context to use the specified resource.
1121 
1122   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`.
1123 
1124   @param[in]  resource Resource to use, e.g., "/cpu/self"
1125   @param[out] ceed     The library context
1126 
1127   @return An error code: 0 - success, otherwise - failure
1128 
1129   @ref User
1130 
1131   @sa CeedRegister() CeedDestroy()
1132 **/
1133 int CeedInit(const char *resource, Ceed *ceed) {
1134   size_t match_len = 0, match_index = UINT_MAX, match_priority = CEED_MAX_BACKEND_PRIORITY, priority;
1135 
1136   // Find matching backend
1137   CeedCheck(resource, NULL, CEED_ERROR_MAJOR, "No resource provided");
1138   CeedCall(CeedRegisterAll());
1139 
1140   // Check for help request
1141   const char *help_prefix = "help";
1142   size_t      match_help  = 0;
1143   while (match_help < 4 && resource[match_help] == help_prefix[match_help]) match_help++;
1144   if (match_help == 4) {
1145     fprintf(stderr, "libCEED version: %d.%d%d%s\n", CEED_VERSION_MAJOR, CEED_VERSION_MINOR, CEED_VERSION_PATCH,
1146             CEED_VERSION_RELEASE ? "" : "+development");
1147     fprintf(stderr, "Available backend resources:\n");
1148     for (size_t i = 0; i < num_backends; i++) {
1149       // Only report compiled backends
1150       if (backends[i].priority < CEED_MAX_BACKEND_PRIORITY) fprintf(stderr, "  %s\n", backends[i].prefix);
1151     }
1152     fflush(stderr);
1153     match_help = 5;  // Delineating character expected
1154   } else {
1155     match_help = 0;
1156   }
1157 
1158   // Find best match, computed as number of matching characters from requested resource stem
1159   size_t stem_length = 0;
1160   while (resource[stem_length + match_help] && resource[stem_length + match_help] != ':') stem_length++;
1161   for (size_t i = 0; i < num_backends; i++) {
1162     size_t      n      = 0;
1163     const char *prefix = backends[i].prefix;
1164     while (prefix[n] && prefix[n] == resource[n + match_help]) n++;
1165     priority = backends[i].priority;
1166     if (n > match_len || (n == match_len && match_priority > priority)) {
1167       match_len      = n;
1168       match_priority = priority;
1169       match_index    = i;
1170     }
1171   }
1172   // Using Levenshtein distance to find closest match
1173   if (match_len <= 1 || match_len != stem_length) {
1174     // LCOV_EXCL_START
1175     size_t lev_dis   = UINT_MAX;
1176     size_t lev_index = UINT_MAX, lev_priority = CEED_MAX_BACKEND_PRIORITY;
1177     for (size_t i = 0; i < num_backends; i++) {
1178       const char *prefix        = backends[i].prefix;
1179       size_t      prefix_length = strlen(backends[i].prefix);
1180       size_t      min_len       = (prefix_length < stem_length) ? prefix_length : stem_length;
1181       size_t      column[min_len + 1];
1182       for (size_t j = 0; j <= min_len; j++) column[j] = j;
1183       for (size_t j = 1; j <= min_len; j++) {
1184         column[0] = j;
1185         for (size_t k = 1, last_diag = j - 1; k <= min_len; k++) {
1186           size_t old_diag = column[k];
1187           size_t min_1    = (column[k] < column[k - 1]) ? column[k] + 1 : column[k - 1] + 1;
1188           size_t min_2    = last_diag + (resource[k - 1] == prefix[j - 1] ? 0 : 1);
1189           column[k]       = (min_1 < min_2) ? min_1 : min_2;
1190           last_diag       = old_diag;
1191         }
1192       }
1193       size_t n = column[min_len];
1194       priority = backends[i].priority;
1195       if (n < lev_dis || (n == lev_dis && lev_priority > priority)) {
1196         lev_dis      = n;
1197         lev_priority = priority;
1198         lev_index    = i;
1199       }
1200     }
1201     const char *prefix_lev = backends[lev_index].prefix;
1202     size_t      lev_length = 0;
1203     while (prefix_lev[lev_length] && prefix_lev[lev_length] != '\0') lev_length++;
1204     size_t m = (lev_length < stem_length) ? lev_length : stem_length;
1205     if (lev_dis + 1 >= m) return CeedError(NULL, CEED_ERROR_MAJOR, "No suitable backend: %s", resource);
1206     else return CeedError(NULL, CEED_ERROR_MAJOR, "No suitable backend: %s\nClosest match: %s", resource, backends[lev_index].prefix);
1207     // LCOV_EXCL_STOP
1208   }
1209 
1210   // Setup Ceed
1211   CeedCall(CeedCalloc(1, ceed));
1212   CeedCall(CeedCalloc(1, &(*ceed)->jit_source_roots));
1213   CeedCall(CeedCalloc(1, &(*ceed)->rust_source_roots));
1214   const char *ceed_error_handler = getenv("CEED_ERROR_HANDLER");
1215   if (!ceed_error_handler) ceed_error_handler = "abort";
1216   if (!strcmp(ceed_error_handler, "exit")) (*ceed)->Error = CeedErrorExit;
1217   else if (!strcmp(ceed_error_handler, "store")) (*ceed)->Error = CeedErrorStore;
1218   else (*ceed)->Error = CeedErrorAbort;
1219   memcpy((*ceed)->err_msg, "No error message stored", 24);
1220   (*ceed)->ref_count = 1;
1221   (*ceed)->data      = NULL;
1222 
1223   // Set lookup table
1224   FOffset f_offsets[] = {
1225       CEED_FTABLE_ENTRY(Ceed, Error),
1226       CEED_FTABLE_ENTRY(Ceed, SetStream),
1227       CEED_FTABLE_ENTRY(Ceed, GetPreferredMemType),
1228       CEED_FTABLE_ENTRY(Ceed, Destroy),
1229       CEED_FTABLE_ENTRY(Ceed, VectorCreate),
1230       CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreate),
1231       CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreateAtPoints),
1232       CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreateBlocked),
1233       CEED_FTABLE_ENTRY(Ceed, BasisCreateTensorH1),
1234       CEED_FTABLE_ENTRY(Ceed, BasisCreateH1),
1235       CEED_FTABLE_ENTRY(Ceed, BasisCreateHdiv),
1236       CEED_FTABLE_ENTRY(Ceed, BasisCreateHcurl),
1237       CEED_FTABLE_ENTRY(Ceed, TensorContractCreate),
1238       CEED_FTABLE_ENTRY(Ceed, QFunctionCreate),
1239       CEED_FTABLE_ENTRY(Ceed, QFunctionContextCreate),
1240       CEED_FTABLE_ENTRY(Ceed, OperatorCreate),
1241       CEED_FTABLE_ENTRY(Ceed, OperatorCreateAtPoints),
1242       CEED_FTABLE_ENTRY(Ceed, CompositeOperatorCreate),
1243       CEED_FTABLE_ENTRY(CeedVector, HasValidArray),
1244       CEED_FTABLE_ENTRY(CeedVector, HasBorrowedArrayOfType),
1245       CEED_FTABLE_ENTRY(CeedVector, CopyStrided),
1246       CEED_FTABLE_ENTRY(CeedVector, SetArray),
1247       CEED_FTABLE_ENTRY(CeedVector, TakeArray),
1248       CEED_FTABLE_ENTRY(CeedVector, SetValue),
1249       CEED_FTABLE_ENTRY(CeedVector, SetValueStrided),
1250       CEED_FTABLE_ENTRY(CeedVector, SyncArray),
1251       CEED_FTABLE_ENTRY(CeedVector, GetArray),
1252       CEED_FTABLE_ENTRY(CeedVector, GetArrayRead),
1253       CEED_FTABLE_ENTRY(CeedVector, GetArrayWrite),
1254       CEED_FTABLE_ENTRY(CeedVector, RestoreArray),
1255       CEED_FTABLE_ENTRY(CeedVector, RestoreArrayRead),
1256       CEED_FTABLE_ENTRY(CeedVector, Norm),
1257       CEED_FTABLE_ENTRY(CeedVector, Scale),
1258       CEED_FTABLE_ENTRY(CeedVector, AXPY),
1259       CEED_FTABLE_ENTRY(CeedVector, AXPBY),
1260       CEED_FTABLE_ENTRY(CeedVector, PointwiseMult),
1261       CEED_FTABLE_ENTRY(CeedVector, Reciprocal),
1262       CEED_FTABLE_ENTRY(CeedVector, Destroy),
1263       CEED_FTABLE_ENTRY(CeedElemRestriction, Apply),
1264       CEED_FTABLE_ENTRY(CeedElemRestriction, ApplyUnsigned),
1265       CEED_FTABLE_ENTRY(CeedElemRestriction, ApplyUnoriented),
1266       CEED_FTABLE_ENTRY(CeedElemRestriction, ApplyAtPointsInElement),
1267       CEED_FTABLE_ENTRY(CeedElemRestriction, ApplyBlock),
1268       CEED_FTABLE_ENTRY(CeedElemRestriction, GetOffsets),
1269       CEED_FTABLE_ENTRY(CeedElemRestriction, GetOrientations),
1270       CEED_FTABLE_ENTRY(CeedElemRestriction, GetCurlOrientations),
1271       CEED_FTABLE_ENTRY(CeedElemRestriction, GetAtPointsElementOffset),
1272       CEED_FTABLE_ENTRY(CeedElemRestriction, Destroy),
1273       CEED_FTABLE_ENTRY(CeedBasis, Apply),
1274       CEED_FTABLE_ENTRY(CeedBasis, ApplyAdd),
1275       CEED_FTABLE_ENTRY(CeedBasis, ApplyAtPoints),
1276       CEED_FTABLE_ENTRY(CeedBasis, ApplyAddAtPoints),
1277       CEED_FTABLE_ENTRY(CeedBasis, Destroy),
1278       CEED_FTABLE_ENTRY(CeedTensorContract, Apply),
1279       CEED_FTABLE_ENTRY(CeedTensorContract, Destroy),
1280       CEED_FTABLE_ENTRY(CeedQFunction, Apply),
1281       CEED_FTABLE_ENTRY(CeedQFunction, SetCUDAUserFunction),
1282       CEED_FTABLE_ENTRY(CeedQFunction, SetHIPUserFunction),
1283       CEED_FTABLE_ENTRY(CeedQFunction, Destroy),
1284       CEED_FTABLE_ENTRY(CeedQFunctionContext, HasValidData),
1285       CEED_FTABLE_ENTRY(CeedQFunctionContext, HasBorrowedDataOfType),
1286       CEED_FTABLE_ENTRY(CeedQFunctionContext, SetData),
1287       CEED_FTABLE_ENTRY(CeedQFunctionContext, TakeData),
1288       CEED_FTABLE_ENTRY(CeedQFunctionContext, GetData),
1289       CEED_FTABLE_ENTRY(CeedQFunctionContext, GetDataRead),
1290       CEED_FTABLE_ENTRY(CeedQFunctionContext, RestoreData),
1291       CEED_FTABLE_ENTRY(CeedQFunctionContext, RestoreDataRead),
1292       CEED_FTABLE_ENTRY(CeedQFunctionContext, DataDestroy),
1293       CEED_FTABLE_ENTRY(CeedQFunctionContext, Destroy),
1294       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleQFunction),
1295       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleQFunctionUpdate),
1296       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleDiagonal),
1297       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleAddDiagonal),
1298       CEED_FTABLE_ENTRY(CeedOperator, LinearAssemblePointBlockDiagonal),
1299       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleAddPointBlockDiagonal),
1300       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleSymbolic),
1301       CEED_FTABLE_ENTRY(CeedOperator, LinearAssemble),
1302       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleSingle),
1303       CEED_FTABLE_ENTRY(CeedOperator, CreateFDMElementInverse),
1304       CEED_FTABLE_ENTRY(CeedOperator, Apply),
1305       CEED_FTABLE_ENTRY(CeedOperator, ApplyComposite),
1306       CEED_FTABLE_ENTRY(CeedOperator, ApplyAdd),
1307       CEED_FTABLE_ENTRY(CeedOperator, ApplyAddComposite),
1308       CEED_FTABLE_ENTRY(CeedOperator, ApplyJacobian),
1309       CEED_FTABLE_ENTRY(CeedOperator, Destroy),
1310       {NULL, 0}  // End of lookup table - used in SetBackendFunction loop
1311   };
1312 
1313   CeedCall(CeedCalloc(sizeof(f_offsets), &(*ceed)->f_offsets));
1314   memcpy((*ceed)->f_offsets, f_offsets, sizeof(f_offsets));
1315 
1316   // Record env variables CEED_DEBUG or DBG
1317   (*ceed)->is_debug = getenv("CEED_DEBUG") || getenv("DEBUG") || getenv("DBG");
1318 
1319   // Copy resource prefix, if backend setup successful
1320   CeedCall(CeedStringAllocCopy(backends[match_index].prefix, (char **)&(*ceed)->resource));
1321 
1322   // Set default JiT source root
1323   // Note: there will always be the default root for every Ceed but all additional paths are added to the top-most parent
1324   CeedCall(CeedAddJitSourceRoot(*ceed, (char *)CeedJitSourceRootDefault));
1325 
1326   // By default, make cuda compile without clang, use nvrtc instead
1327   // Note that this is overridden if a rust file is included (rust requires clang)
1328   const char *env = getenv("GPU_CLANG");
1329 
1330   if (env && strcmp(env, "1") == 0) {
1331     (*ceed)->cuda_compile_with_clang = true;
1332   } else {
1333     (*ceed)->cuda_compile_with_clang = false;
1334   }
1335 
1336   // Backend specific setup
1337   CeedCall(backends[match_index].init(&resource[match_help], *ceed));
1338   return CEED_ERROR_SUCCESS;
1339 }
1340 
1341 /**
1342   @brief Set the GPU stream for a `Ceed` context
1343 
1344   @param[in,out] ceed   `Ceed` context to set the stream
1345   @param[in]     handle Handle to GPU stream
1346 
1347   @return An error code: 0 - success, otherwise - failure
1348 
1349   @ref User
1350 **/
1351 int CeedSetStream(Ceed ceed, void *handle) {
1352   CeedCheck(handle, ceed, CEED_ERROR_INCOMPATIBLE, "Stream handle must be non-NULL");
1353   if (ceed->SetStream) {
1354     CeedCall(ceed->SetStream(ceed, handle));
1355   } else {
1356     Ceed delegate;
1357     CeedCall(CeedGetDelegate(ceed, &delegate));
1358 
1359     if (delegate) CeedCall(CeedSetStream(delegate, handle));
1360     else return CeedError(ceed, CEED_ERROR_UNSUPPORTED, "Backend does not support setting stream");
1361     CeedCall(CeedDestroy(&delegate));
1362   }
1363   return CEED_ERROR_SUCCESS;
1364 }
1365 
1366 /**
1367   @brief Copy the pointer to a `Ceed` context.
1368 
1369   Both pointers should be destroyed with @ref CeedDestroy().
1370 
1371   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.
1372         This `Ceed` context will be destroyed if `*ceed_copy` is the only reference to this `Ceed` context.
1373 
1374   @param[in]     ceed      `Ceed` context to copy reference to
1375   @param[in,out] ceed_copy Variable to store copied reference
1376 
1377   @return An error code: 0 - success, otherwise - failure
1378 
1379   @ref User
1380 **/
1381 int CeedReferenceCopy(Ceed ceed, Ceed *ceed_copy) {
1382   CeedCall(CeedReference(ceed));
1383   CeedCall(CeedDestroy(ceed_copy));
1384   *ceed_copy = ceed;
1385   return CEED_ERROR_SUCCESS;
1386 }
1387 
1388 /**
1389   @brief Get the full resource name for a `Ceed` context
1390 
1391   @param[in]  ceed     `Ceed` context to get resource name of
1392   @param[out] resource Variable to store resource name
1393 
1394   @return An error code: 0 - success, otherwise - failure
1395 
1396   @ref User
1397 **/
1398 int CeedGetResource(Ceed ceed, const char **resource) {
1399   *resource = (const char *)ceed->resource;
1400   return CEED_ERROR_SUCCESS;
1401 }
1402 
1403 /**
1404   @brief Return `Ceed` context preferred memory type
1405 
1406   @param[in]  ceed     `Ceed` context to get preferred memory type of
1407   @param[out] mem_type Address to save preferred memory type to
1408 
1409   @return An error code: 0 - success, otherwise - failure
1410 
1411   @ref User
1412 **/
1413 int CeedGetPreferredMemType(Ceed ceed, CeedMemType *mem_type) {
1414   if (ceed->GetPreferredMemType) {
1415     CeedCall(ceed->GetPreferredMemType(mem_type));
1416   } else {
1417     Ceed delegate;
1418     CeedCall(CeedGetDelegate(ceed, &delegate));
1419 
1420     if (delegate) {
1421       CeedCall(CeedGetPreferredMemType(delegate, mem_type));
1422     } else {
1423       *mem_type = CEED_MEM_HOST;
1424     }
1425     CeedCall(CeedDestroy(&delegate));
1426   }
1427   return CEED_ERROR_SUCCESS;
1428 }
1429 
1430 /**
1431   @brief Get deterministic status of `Ceed` context
1432 
1433   @param[in]  ceed             `Ceed` context
1434   @param[out] is_deterministic Variable to store deterministic status
1435 
1436   @return An error code: 0 - success, otherwise - failure
1437 
1438   @ref User
1439 **/
1440 int CeedIsDeterministic(Ceed ceed, bool *is_deterministic) {
1441   *is_deterministic = ceed->is_deterministic;
1442   return CEED_ERROR_SUCCESS;
1443 }
1444 
1445 /**
1446   @brief Set additional JiT source root for `Ceed` context
1447 
1448   @param[in,out] ceed            `Ceed` context
1449   @param[in]     jit_source_root Absolute path to additional JiT source directory
1450 
1451   @return An error code: 0 - success, otherwise - failure
1452 
1453   @ref User
1454 **/
1455 int CeedAddJitSourceRoot(Ceed ceed, const char *jit_source_root) {
1456   Ceed ceed_parent;
1457 
1458   CeedCall(CeedGetParent(ceed, &ceed_parent));
1459   CeedCheck(!ceed_parent->num_jit_source_roots_readers, ceed, CEED_ERROR_ACCESS, "Cannot add JiT source root, read access has not been restored");
1460 
1461   CeedInt index       = ceed_parent->num_jit_source_roots;
1462   size_t  path_length = strlen(jit_source_root);
1463 
1464   if (ceed_parent->num_jit_source_roots == ceed_parent->max_jit_source_roots) {
1465     if (ceed_parent->max_jit_source_roots == 0) ceed_parent->max_jit_source_roots = 1;
1466     ceed_parent->max_jit_source_roots *= 2;
1467     CeedCall(CeedRealloc(ceed_parent->max_jit_source_roots, &ceed_parent->jit_source_roots));
1468   }
1469   CeedCall(CeedCalloc(path_length + 1, &ceed_parent->jit_source_roots[index]));
1470   memcpy(ceed_parent->jit_source_roots[index], jit_source_root, path_length);
1471   ceed_parent->num_jit_source_roots++;
1472   CeedCall(CeedDestroy(&ceed_parent));
1473   return CEED_ERROR_SUCCESS;
1474 }
1475 
1476 /**
1477   @brief Set additional Rust source root for `Ceed` context for use in QFunction
1478 
1479   @param[in,out] ceed            `Ceed` context
1480   @param[in]     rust_source_root Absolute path to additional Rust source directory
1481 
1482   @return An error code: 0 - success, otherwise - failure
1483 
1484   @ref User
1485 **/
1486 int CeedAddRustSourceRoot(Ceed ceed, const char *rust_source_root) {
1487   Ceed ceed_parent;
1488 
1489   CeedCall(CeedGetParent(ceed, &ceed_parent));
1490   CeedCheck(!ceed_parent->num_rust_source_roots_readers, ceed, CEED_ERROR_ACCESS, "Cannot add Rust source root, read access has not been restored");
1491 
1492   CeedInt index       = ceed_parent->num_rust_source_roots;
1493   size_t  path_length = strlen(rust_source_root);
1494 
1495   if (ceed_parent->num_rust_source_roots == ceed_parent->max_rust_source_roots) {
1496     if (ceed_parent->max_rust_source_roots == 0) ceed_parent->max_rust_source_roots = 1;
1497     ceed_parent->max_rust_source_roots *= 2;
1498     CeedCall(CeedRealloc(ceed_parent->max_rust_source_roots, &ceed_parent->rust_source_roots));
1499   }
1500   CeedCall(CeedCalloc(path_length + 1, &ceed_parent->rust_source_roots[index]));
1501   memcpy(ceed_parent->rust_source_roots[index], rust_source_root, path_length);
1502   ceed_parent->num_rust_source_roots++;
1503   ceed_parent->cuda_compile_with_clang = true;
1504   ceed->cuda_compile_with_clang        = true;
1505   CeedCall(CeedDestroy(&ceed_parent));
1506   return CEED_ERROR_SUCCESS;
1507 }
1508 
1509 /**
1510   @brief Set additional JiT compiler define for `Ceed` context
1511 
1512   @param[in,out] ceed       `Ceed` context
1513   @param[in]     jit_define String such as `foo=bar`, used as `-Dfoo=bar` in JiT
1514 
1515   @return An error code: 0 - success, otherwise - failure
1516 
1517   @ref User
1518 **/
1519 int CeedAddJitDefine(Ceed ceed, const char *jit_define) {
1520   Ceed ceed_parent;
1521 
1522   CeedCall(CeedGetParent(ceed, &ceed_parent));
1523   CeedCheck(!ceed_parent->num_jit_defines_readers, ceed, CEED_ERROR_ACCESS, "Cannot add JiT define, read access has not been restored");
1524 
1525   CeedInt index         = ceed_parent->num_jit_defines;
1526   size_t  define_length = strlen(jit_define);
1527 
1528   if (ceed_parent->num_jit_defines == ceed_parent->max_jit_defines) {
1529     if (ceed_parent->max_jit_defines == 0) ceed_parent->max_jit_defines = 1;
1530     ceed_parent->max_jit_defines *= 2;
1531     CeedCall(CeedRealloc(ceed_parent->max_jit_defines, &ceed_parent->jit_defines));
1532   }
1533   CeedCall(CeedCalloc(define_length + 1, &ceed_parent->jit_defines[index]));
1534   memcpy(ceed_parent->jit_defines[index], jit_define, define_length);
1535   ceed_parent->num_jit_defines++;
1536   CeedCall(CeedDestroy(&ceed_parent));
1537   return CEED_ERROR_SUCCESS;
1538 }
1539 
1540 /**
1541   @brief View a `Ceed`
1542 
1543   @param[in] ceed   `Ceed` to view
1544   @param[in] stream Filestream to write to
1545 
1546   @return An error code: 0 - success, otherwise - failure
1547 
1548   @ref User
1549 **/
1550 int CeedView(Ceed ceed, FILE *stream) {
1551   CeedMemType mem_type;
1552 
1553   CeedCall(CeedGetPreferredMemType(ceed, &mem_type));
1554 
1555   fprintf(stream,
1556           "Ceed\n"
1557           "  Ceed Resource: %s\n"
1558           "  Preferred MemType: %s\n",
1559           ceed->resource, CeedMemTypes[mem_type]);
1560   return CEED_ERROR_SUCCESS;
1561 }
1562 
1563 /**
1564   @brief Destroy a `Ceed`
1565 
1566   @param[in,out] ceed Address of `Ceed` context to destroy
1567 
1568   @return An error code: 0 - success, otherwise - failure
1569 
1570   @ref User
1571 **/
1572 int CeedDestroy(Ceed *ceed) {
1573   if (!*ceed || --(*ceed)->ref_count > 0) {
1574     *ceed = NULL;
1575     return CEED_ERROR_SUCCESS;
1576   }
1577 
1578   CeedCheck(!(*ceed)->num_jit_source_roots_readers, *ceed, CEED_ERROR_ACCESS,
1579             "Cannot destroy ceed context, read access for JiT source roots has been granted");
1580   CeedCheck(!(*ceed)->num_jit_defines_readers, *ceed, CEED_ERROR_ACCESS, "Cannot add JiT source root, read access for JiT defines has been granted");
1581 
1582   if ((*ceed)->delegate) CeedCall(CeedDestroy(&(*ceed)->delegate));
1583 
1584   if ((*ceed)->obj_delegate_count > 0) {
1585     for (CeedInt i = 0; i < (*ceed)->obj_delegate_count; i++) {
1586       CeedCall(CeedDestroy(&((*ceed)->obj_delegates[i].delegate)));
1587       CeedCall(CeedFree(&(*ceed)->obj_delegates[i].obj_name));
1588     }
1589     CeedCall(CeedFree(&(*ceed)->obj_delegates));
1590   }
1591 
1592   if ((*ceed)->Destroy) CeedCall((*ceed)->Destroy(*ceed));
1593 
1594   for (CeedInt i = 0; i < (*ceed)->num_jit_source_roots; i++) {
1595     CeedCall(CeedFree(&(*ceed)->jit_source_roots[i]));
1596   }
1597   CeedCall(CeedFree(&(*ceed)->jit_source_roots));
1598 
1599   for (CeedInt i = 0; i < (*ceed)->num_jit_defines; i++) {
1600     CeedCall(CeedFree(&(*ceed)->jit_defines[i]));
1601   }
1602   CeedCall(CeedFree(&(*ceed)->jit_defines));
1603 
1604   for (CeedInt i = 0; i < (*ceed)->num_rust_source_roots; i++) {
1605     CeedCall(CeedFree(&(*ceed)->rust_source_roots[i]));
1606   }
1607   CeedCall(CeedFree(&(*ceed)->rust_source_roots));
1608 
1609   CeedCall(CeedFree(&(*ceed)->f_offsets));
1610   CeedCall(CeedFree(&(*ceed)->resource));
1611   CeedCall(CeedDestroy(&(*ceed)->op_fallback_ceed));
1612   CeedCall(CeedWorkVectorsDestroy(*ceed));
1613   CeedCall(CeedFree(ceed));
1614   return CEED_ERROR_SUCCESS;
1615 }
1616 
1617 // LCOV_EXCL_START
1618 const char *CeedErrorFormat(Ceed ceed, const char *format, va_list *args) {
1619   if (ceed->parent) return CeedErrorFormat(ceed->parent, format, args);
1620   // Using pointer to va_list for better FFI, but clang-tidy can't verify va_list is initalized
1621   vsnprintf(ceed->err_msg, CEED_MAX_RESOURCE_LEN, format, *args);  // NOLINT
1622   return ceed->err_msg;
1623 }
1624 // LCOV_EXCL_STOP
1625 
1626 /**
1627   @brief Error handling implementation; use @ref CeedError() instead.
1628 
1629   @return An error code: 0 - success, otherwise - failure
1630 
1631   @ref Developer
1632 **/
1633 int CeedErrorImpl(Ceed ceed, const char *filename, int lineno, const char *func, int ecode, const char *format, ...) {
1634   va_list args;
1635   int     ret_val;
1636 
1637   va_start(args, format);
1638   if (ceed) {
1639     ret_val = ceed->Error(ceed, filename, lineno, func, ecode, format, &args);
1640   } else {
1641     // LCOV_EXCL_START
1642     const char *ceed_error_handler = getenv("CEED_ERROR_HANDLER");
1643     if (!ceed_error_handler) ceed_error_handler = "abort";
1644     if (!strcmp(ceed_error_handler, "return")) {
1645       ret_val = CeedErrorReturn(ceed, filename, lineno, func, ecode, format, &args);
1646     } else {
1647       // This function will not return
1648       ret_val = CeedErrorAbort(ceed, filename, lineno, func, ecode, format, &args);
1649     }
1650   }
1651   va_end(args);
1652   return ret_val;
1653   // LCOV_EXCL_STOP
1654 }
1655 
1656 /**
1657   @brief Error handler that returns without printing anything.
1658 
1659   Pass this to @ref CeedSetErrorHandler() to obtain this error handling behavior.
1660 
1661   @return An error code: 0 - success, otherwise - failure
1662 
1663   @ref Developer
1664 **/
1665 // LCOV_EXCL_START
1666 int CeedErrorReturn(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1667   return err_code;
1668 }
1669 // LCOV_EXCL_STOP
1670 
1671 /**
1672   @brief Error handler that stores the error message for future use and returns the error.
1673 
1674   Pass this to @ref CeedSetErrorHandler() to obtain this error handling behavior.
1675 
1676   @return An error code: 0 - success, otherwise - failure
1677 
1678   @ref Developer
1679 **/
1680 // LCOV_EXCL_START
1681 int CeedErrorStore(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1682   if (ceed->parent) return CeedErrorStore(ceed->parent, filename, line_no, func, err_code, format, args);
1683 
1684   // Build message
1685   int len = snprintf(ceed->err_msg, CEED_MAX_RESOURCE_LEN, "%s:%d in %s(): ", filename, line_no, func);
1686   // Using pointer to va_list for better FFI, but clang-tidy can't verify va_list is initalized
1687   vsnprintf(ceed->err_msg + len, CEED_MAX_RESOURCE_LEN - len, format, *args);  // NOLINT
1688   return err_code;
1689 }
1690 // LCOV_EXCL_STOP
1691 
1692 /**
1693   @brief Error handler that prints to `stderr` and aborts
1694 
1695   Pass this to @ref CeedSetErrorHandler() to obtain this error handling behavior.
1696 
1697   @return An error code: 0 - success, otherwise - failure
1698 
1699   @ref Developer
1700 **/
1701 // LCOV_EXCL_START
1702 int CeedErrorAbort(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1703   fprintf(stderr, "%s:%d in %s(): ", filename, line_no, func);
1704   vfprintf(stderr, format, *args);
1705   fprintf(stderr, "\n");
1706   abort();
1707   return err_code;
1708 }
1709 // LCOV_EXCL_STOP
1710 
1711 /**
1712   @brief Error handler that prints to `stderr` and exits.
1713 
1714   Pass this to @ref CeedSetErrorHandler() to obtain this error handling behavior.
1715 
1716   In contrast to @ref CeedErrorAbort(), this exits without a signal, so `atexit()` handlers (e.g., as used by gcov) are run.
1717 
1718   @return An error code: 0 - success, otherwise - failure
1719 
1720   @ref Developer
1721 **/
1722 int CeedErrorExit(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1723   fprintf(stderr, "%s:%d in %s(): ", filename, line_no, func);
1724   // Using pointer to va_list for better FFI, but clang-tidy can't verify va_list is initalized
1725   vfprintf(stderr, format, *args);  // NOLINT
1726   fprintf(stderr, "\n");
1727   exit(err_code);
1728   return err_code;
1729 }
1730 
1731 /**
1732   @brief Set error handler
1733 
1734   A default error handler is set in @ref CeedInit().
1735   Use this function to change the error handler to @ref CeedErrorReturn(), @ref CeedErrorAbort(), or a user-defined error handler.
1736 
1737   @return An error code: 0 - success, otherwise - failure
1738 
1739   @ref Developer
1740 **/
1741 int CeedSetErrorHandler(Ceed ceed, CeedErrorHandler handler) {
1742   ceed->Error = handler;
1743   if (ceed->delegate) CeedSetErrorHandler(ceed->delegate, handler);
1744   for (CeedInt i = 0; i < ceed->obj_delegate_count; i++) CeedSetErrorHandler(ceed->obj_delegates[i].delegate, handler);
1745   return CEED_ERROR_SUCCESS;
1746 }
1747 
1748 /**
1749   @brief Get error message
1750 
1751   The error message is only stored when using the error handler @ref CeedErrorStore()
1752 
1753   @param[in]  ceed    `Ceed` context to retrieve error message
1754   @param[out] err_msg Char pointer to hold error message
1755 
1756   @return An error code: 0 - success, otherwise - failure
1757 
1758   @ref Developer
1759 **/
1760 int CeedGetErrorMessage(Ceed ceed, const char **err_msg) {
1761   if (ceed->parent) return CeedGetErrorMessage(ceed->parent, err_msg);
1762   *err_msg = ceed->err_msg;
1763   return CEED_ERROR_SUCCESS;
1764 }
1765 
1766 /**
1767   @brief Restore error message.
1768 
1769   The error message is only stored when using the error handler @ref CeedErrorStore().
1770 
1771   @param[in]  ceed    `Ceed` context to restore error message
1772   @param[out] err_msg Char pointer that holds error message
1773 
1774   @return An error code: 0 - success, otherwise - failure
1775 
1776   @ref Developer
1777 **/
1778 int CeedResetErrorMessage(Ceed ceed, const char **err_msg) {
1779   if (ceed->parent) return CeedResetErrorMessage(ceed->parent, err_msg);
1780   *err_msg = NULL;
1781   memcpy(ceed->err_msg, "No error message stored", 24);
1782   return CEED_ERROR_SUCCESS;
1783 }
1784 
1785 /**
1786   @brief Get libCEED library version information.
1787 
1788   libCEED version numbers have the form major.minor.patch.
1789   Non-release versions may contain unstable interfaces.
1790 
1791   @param[out] major   Major version of the library
1792   @param[out] minor   Minor version of the library
1793   @param[out] patch   Patch (subminor) version of the library
1794   @param[out] release True for releases; false for development branches
1795 
1796   The caller may pass `NULL` for any arguments that are not needed.
1797 
1798   @return An error code: 0 - success, otherwise - failure
1799 
1800   @ref Developer
1801 
1802   @sa CEED_VERSION_GE() CeedGetGitVersion() CeedGetBuildConfiguration()
1803 */
1804 int CeedGetVersion(int *major, int *minor, int *patch, bool *release) {
1805   if (major) *major = CEED_VERSION_MAJOR;
1806   if (minor) *minor = CEED_VERSION_MINOR;
1807   if (patch) *patch = CEED_VERSION_PATCH;
1808   if (release) *release = CEED_VERSION_RELEASE;
1809   return CEED_ERROR_SUCCESS;
1810 }
1811 
1812 /**
1813   @brief Get libCEED scalar type, such as F64 or F32
1814 
1815   @param[out] scalar_type Type of libCEED scalars
1816 
1817   @return An error code: 0 - success, otherwise - failure
1818 
1819   @ref Developer
1820 */
1821 int CeedGetScalarType(CeedScalarType *scalar_type) {
1822   *scalar_type = CEED_SCALAR_TYPE;
1823   return CEED_ERROR_SUCCESS;
1824 }
1825 
1826 /// @}
1827