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