xref: /libCEED/interface/ceed.c (revision 13d7dc18f45030012a8adfaa76169113ba58c950)
1 // Copyright (c) 2017-2022, 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) \
32   { #class #method, offsetof(struct class##_private, method) }
33 /// @endcond
34 
35 /// @file
36 /// Implementation of core components of Ceed library
37 
38 /// @addtogroup CeedUser
39 /// @{
40 
41 /**
42   @brief Request immediate completion
43 
44   This predefined constant is passed as the \ref CeedRequest argument to interfaces when the caller wishes for the operation to be performed
45 immediately. The code
46 
47   @code
48     CeedOperatorApply(op, ..., CEED_REQUEST_IMMEDIATE);
49   @endcode
50 
51   is semantically equivalent to
52 
53   @code
54     CeedRequest request;
55     CeedOperatorApply(op, ..., &request);
56     CeedRequestWait(&request);
57   @endcode
58 
59   @sa CEED_REQUEST_ORDERED
60 **/
61 CeedRequest *const CEED_REQUEST_IMMEDIATE = &ceed_request_immediate;
62 
63 /**
64   @brief Request ordered completion
65 
66   This predefined constant is passed as the \ref CeedRequest argument to interfaces when the caller wishes for the operation to be completed in the
67   order that it is submitted to the device. It is typically used in a construct such as:
68 
69   @code
70     CeedRequest request;
71     CeedOperatorApply(op1, ..., CEED_REQUEST_ORDERED);
72     CeedOperatorApply(op2, ..., &request);
73     // other optional work
74     CeedRequestWait(&request);
75   @endcode
76 
77   which allows the sequence to complete asynchronously but does not start `op2` until `op1` has completed.
78 
79   @todo The current implementation is overly strict, offering equivalent semantics to @ref CEED_REQUEST_IMMEDIATE.
80 
81   @sa CEED_REQUEST_IMMEDIATE
82  */
83 CeedRequest *const CEED_REQUEST_ORDERED = &ceed_request_ordered;
84 
85 /**
86   @brief Wait for a CeedRequest to complete.
87 
88   Calling CeedRequestWait on a NULL request is a no-op.
89 
90   @param req Address of CeedRequest to wait for; zeroed on completion.
91 
92   @return An error code: 0 - success, otherwise - failure
93 
94   @ref User
95 **/
96 int CeedRequestWait(CeedRequest *req) {
97   if (!*req) return CEED_ERROR_SUCCESS;
98   return CeedError(NULL, CEED_ERROR_UNSUPPORTED, "CeedRequestWait not implemented");
99 }
100 
101 /// @}
102 
103 /// ----------------------------------------------------------------------------
104 /// Ceed Library Internal Functions
105 /// ----------------------------------------------------------------------------
106 /// @addtogroup CeedDeveloper
107 /// @{
108 
109 /**
110   @brief Register a Ceed backend internally.
111 
112   Note: Backends should call `CeedRegister` instead.
113 
114   @param[in] prefix    Prefix of resources for this backend to respond to.
115                          For example, the reference backend responds to "/cpu/self".
116   @param[in] init      Initialization function called by CeedInit() when the backend is selected to drive the requested resource.
117   @param[in] priority  Integer priority.
118                          Lower values are preferred in case the resource requested by CeedInit() has non-unique best prefix match.
119 
120   @return An error code: 0 - success, otherwise - failure
121 
122   @ref Developer
123 **/
124 int CeedRegisterImpl(const char *prefix, int (*init)(const char *, Ceed), unsigned int priority) {
125   int ierr = 0;
126 
127   CeedPragmaCritical(CeedRegisterImpl) {
128     if (num_backends < sizeof(backends) / sizeof(backends[0])) {
129       strncpy(backends[num_backends].prefix, prefix, CEED_MAX_RESOURCE_LEN);
130       backends[num_backends].prefix[CEED_MAX_RESOURCE_LEN - 1] = 0;
131       backends[num_backends].init                              = init;
132       backends[num_backends].priority                          = priority;
133       num_backends++;
134     } else {
135       ierr = 1;
136     }
137   }
138   // LCOV_EXCL_START
139   CeedCheck(ierr == 0, NULL, CEED_ERROR_MAJOR, "Too many backends");
140   // LCOV_EXCL_STOP
141   return CEED_ERROR_SUCCESS;
142 }
143 
144 /// @}
145 
146 /// ----------------------------------------------------------------------------
147 /// Ceed Backend API
148 /// ----------------------------------------------------------------------------
149 /// @addtogroup CeedBackend
150 /// @{
151 
152 /**
153   @brief Return value of CEED_DEBUG environment variable
154 
155   @param[in] ceed Ceed context
156 
157   @return boolean value: true  - debugging mode enabled
158                          false - debugging mode disabled
159 
160   @ref Backend
161 **/
162 // LCOV_EXCL_START
163 bool CeedDebugFlag(const Ceed ceed) { return ceed->is_debug; }
164 // LCOV_EXCL_STOP
165 
166 /**
167   @brief Return value of CEED_DEBUG environment variable
168 
169   @return boolean value: true  - debugging mode enabled
170                          false - debugging mode disabled
171 
172   @ref Backend
173 **/
174 // LCOV_EXCL_START
175 bool CeedDebugFlagEnv(void) { return getenv("CEED_DEBUG") || getenv("DEBUG") || getenv("DBG"); }
176 // LCOV_EXCL_STOP
177 
178 /**
179   @brief Print debugging information in color
180 
181   @param color   Color to print
182   @param format  Printing format
183 
184   @ref Backend
185 **/
186 // LCOV_EXCL_START
187 void CeedDebugImpl256(const unsigned char color, const char *format, ...) {
188   va_list args;
189   va_start(args, format);
190   fflush(stdout);
191   if (color != CEED_DEBUG_COLOR_NONE) fprintf(stdout, "\033[38;5;%dm", color);
192   vfprintf(stdout, format, args);
193   if (color != CEED_DEBUG_COLOR_NONE) fprintf(stdout, "\033[m");
194   fprintf(stdout, "\n");
195   fflush(stdout);
196   va_end(args);
197 }
198 // LCOV_EXCL_STOP
199 
200 /**
201   @brief Allocate an array on the host; use CeedMalloc()
202 
203   Memory usage can be tracked by the library.
204   This ensures sufficient alignment for vectorization and should be used for large allocations.
205 
206   @param[in]  n    Number of units to allocate
207   @param[in]  unit Size of each unit
208   @param[out] p    Address of pointer to hold the result.
209 
210   @return An error code: 0 - success, otherwise - failure
211 
212   @sa CeedFree()
213 
214   @ref Backend
215 **/
216 int CeedMallocArray(size_t n, size_t unit, void *p) {
217   int ierr = posix_memalign((void **)p, CEED_ALIGN, n * unit);
218   CeedCheck(ierr == 0, NULL, CEED_ERROR_MAJOR, "posix_memalign failed to allocate %zd members of size %zd\n", n, unit);
219   return CEED_ERROR_SUCCESS;
220 }
221 
222 /**
223   @brief Allocate a cleared (zeroed) array on the host; use CeedCalloc()
224 
225   Memory usage can be tracked by the library.
226 
227   @param[in]  n    Number of units to allocate
228   @param[in]  unit Size of each unit
229   @param[out] p    Address of pointer to hold the result.
230 
231   @return An error code: 0 - success, otherwise - failure
232 
233   @sa CeedFree()
234 
235   @ref Backend
236 **/
237 int CeedCallocArray(size_t n, size_t unit, void *p) {
238   *(void **)p = calloc(n, unit);
239   CeedCheck(!n || !unit || *(void **)p, NULL, CEED_ERROR_MAJOR, "calloc failed to allocate %zd members of size %zd\n", n, unit);
240   return CEED_ERROR_SUCCESS;
241 }
242 
243 /**
244   @brief Reallocate an array on the host; use CeedRealloc()
245 
246   Memory usage can be tracked by the library.
247 
248   @param[in]  n    Number of units to allocate
249   @param[in]  unit Size of each unit
250   @param[out] p    Address of pointer to hold the result.
251 
252   @return An error code: 0 - success, otherwise - failure
253 
254   @sa CeedFree()
255 
256   @ref Backend
257 **/
258 int CeedReallocArray(size_t n, size_t unit, void *p) {
259   *(void **)p = realloc(*(void **)p, n * unit);
260   CeedCheck(!n || !unit || *(void **)p, NULL, CEED_ERROR_MAJOR, "realloc failed to allocate %zd members of size %zd\n", n, unit);
261   return CEED_ERROR_SUCCESS;
262 }
263 
264 /**
265   @brief Allocate a cleared string buffer on the host
266 
267   Memory usage can be tracked by the library.
268 
269   @param[in]  source Pointer to string to be copied
270   @param[out] copy   Pointer to variable to hold newly allocated string copy
271 
272   @return An error code: 0 - success, otherwise - failure
273 
274   @sa CeedFree()
275 
276   @ref Backend
277 **/
278 int CeedStringAllocCopy(const char *source, char **copy) {
279   size_t len = strlen(source);
280   CeedCall(CeedCalloc(len + 1, copy));
281   memcpy(*copy, source, len);
282   return CEED_ERROR_SUCCESS;
283 }
284 
285 /** Free memory allocated using CeedMalloc() or CeedCalloc()
286 
287   @param[in,out] p  address of pointer to memory.
288                       This argument is of type void* to avoid needing a cast, but is the address of the pointer (which is zeroed) rather than the
289 pointer.
290 **/
291 int CeedFree(void *p) {
292   free(*(void **)p);
293   *(void **)p = NULL;
294   return CEED_ERROR_SUCCESS;
295 }
296 
297 /**
298   @brief Register a Ceed backend
299 
300   @param[in] prefix   Prefix of resources for this backend to respond to.
301                         For example, the reference backend responds to "/cpu/self".
302   @param[in] init     Initialization function called by CeedInit() when the backend is selected to drive the requested resource.
303   @param[in] priority Integer priority.
304                         Lower values are preferred in case the resource requested by CeedInit() has non-unique best prefix match.
305 
306   @return An error code: 0 - success, otherwise - failure
307 
308   @ref Backend
309 **/
310 int CeedRegister(const char *prefix, int (*init)(const char *, Ceed), unsigned int priority) {
311   CeedDebugEnv("Backend Register: %s", prefix);
312   CeedRegisterImpl(prefix, init, priority);
313   return CEED_ERROR_SUCCESS;
314 }
315 
316 /**
317   @brief Return debugging status flag
318 
319   @param[in]  ceed     Ceed context to get debugging flag
320   @param[out] is_debug Variable to store debugging flag
321 
322   @return An error code: 0 - success, otherwise - failure
323 
324   @ref Backend
325 **/
326 int CeedIsDebug(Ceed ceed, bool *is_debug) {
327   *is_debug = ceed->is_debug;
328   return CEED_ERROR_SUCCESS;
329 }
330 
331 /**
332   @brief Get the root of the requested resource
333 
334   @param[in]  ceed          Ceed context to get resource name of
335   @param[in]  resource      ull user specified resource
336   @param[in]  delineator    Delineator to break resource_root and resource_spec
337   @param[out] resource_root Variable to store resource root
338 
339   @return An error code: 0 - success, otherwise - failure
340 
341   @ref Backend
342 **/
343 int CeedGetResourceRoot(Ceed ceed, const char *resource, const char *delineator, char **resource_root) {
344   char  *device_spec       = strstr(resource, delineator);
345   size_t resource_root_len = device_spec ? (size_t)(device_spec - resource) + 1 : strlen(resource) + 1;
346 
347   CeedCall(CeedCalloc(resource_root_len, resource_root));
348   memcpy(*resource_root, resource, resource_root_len - 1);
349   return CEED_ERROR_SUCCESS;
350 }
351 
352 /**
353   @brief Retrieve a parent Ceed context
354 
355   @param[in]  ceed   Ceed context to retrieve parent of
356   @param[out] parent Address to save the parent to
357 
358   @return An error code: 0 - success, otherwise - failure
359 
360   @ref Backend
361 **/
362 int CeedGetParent(Ceed ceed, Ceed *parent) {
363   if (ceed->parent) {
364     CeedCall(CeedGetParent(ceed->parent, parent));
365     return CEED_ERROR_SUCCESS;
366   }
367   *parent = ceed;
368   return CEED_ERROR_SUCCESS;
369 }
370 
371 /**
372   @brief Retrieve a delegate Ceed context
373 
374   @param[in]  ceed     Ceed context to retrieve delegate of
375   @param[out] delegate Address to save the delegate to
376 
377   @return An error code: 0 - success, otherwise - failure
378 
379   @ref Backend
380 **/
381 int CeedGetDelegate(Ceed ceed, Ceed *delegate) {
382   *delegate = ceed->delegate;
383   return CEED_ERROR_SUCCESS;
384 }
385 
386 /**
387   @brief Set a delegate Ceed context
388 
389   This function allows a Ceed context to set a delegate Ceed context.
390   All backend implementations default to the delegate Ceed context, unless overridden.
391 
392   @param[in]  ceed     Ceed context to set delegate of
393   @param[out] delegate Address to set the delegate to
394 
395   @return An error code: 0 - success, otherwise - failure
396 
397   @ref Backend
398 **/
399 int CeedSetDelegate(Ceed ceed, Ceed delegate) {
400   ceed->delegate   = delegate;
401   delegate->parent = ceed;
402   return CEED_ERROR_SUCCESS;
403 }
404 
405 /**
406   @brief Retrieve a delegate Ceed context for a specific object type
407 
408   @param[in]  ceed     Ceed context to retrieve delegate of
409   @param[out] delegate Address to save the delegate to
410   @param[in]  obj_name Name of the object type to retrieve delegate for
411 
412   @return An error code: 0 - success, otherwise - failure
413 
414   @ref Backend
415 **/
416 int CeedGetObjectDelegate(Ceed ceed, Ceed *delegate, const char *obj_name) {
417   // Check for object delegate
418   for (CeedInt i = 0; i < ceed->obj_delegate_count; i++) {
419     if (!strcmp(obj_name, ceed->obj_delegates->obj_name)) {
420       *delegate = ceed->obj_delegates->delegate;
421       return CEED_ERROR_SUCCESS;
422     }
423   }
424 
425   // Use default delegate if no object delegate
426   CeedCall(CeedGetDelegate(ceed, delegate));
427   return CEED_ERROR_SUCCESS;
428 }
429 
430 /**
431   @brief Set a delegate Ceed context for a specific object type
432 
433   This function allows a Ceed context to set a delegate Ceed context for a given type of Ceed object.
434   All backend implementations default to the delegate Ceed context for this object.
435   For example, CeedSetObjectDelegate(ceed, delegate, "Basis") uses delegate implementations for all CeedBasis backend functions.
436 
437   @param[in,out] ceed     Ceed context to set delegate of
438   @param[in]     delegate Ceed context to use for delegation
439   @param[in]     obj_name Name of the object type to set delegate for
440 
441   @return An error code: 0 - success, otherwise - failure
442 
443   @ref Backend
444 **/
445 int CeedSetObjectDelegate(Ceed ceed, Ceed delegate, const char *obj_name) {
446   CeedInt count = ceed->obj_delegate_count;
447 
448   // Malloc or Realloc
449   if (count) {
450     CeedCall(CeedRealloc(count + 1, &ceed->obj_delegates));
451   } else {
452     CeedCall(CeedCalloc(1, &ceed->obj_delegates));
453   }
454   ceed->obj_delegate_count++;
455 
456   // Set object delegate
457   ceed->obj_delegates[count].delegate = delegate;
458   CeedCall(CeedStringAllocCopy(obj_name, &ceed->obj_delegates[count].obj_name));
459 
460   // Set delegate parent
461   delegate->parent = ceed;
462   return CEED_ERROR_SUCCESS;
463 }
464 
465 /**
466   @brief Get the fallback resource for CeedOperators
467 
468   @param[in]  ceed     Ceed context
469   @param[out] resource Variable to store fallback resource
470 
471   @return An error code: 0 - success, otherwise - failure
472 
473   @ref Backend
474 **/
475 int CeedGetOperatorFallbackResource(Ceed ceed, const char **resource) {
476   *resource = (const char *)ceed->op_fallback_resource;
477   return CEED_ERROR_SUCCESS;
478 }
479 
480 /**
481   @brief Get the fallback Ceed for CeedOperators
482 
483   @param[in]  ceed          Ceed context
484   @param[out] fallback_ceed Variable to store fallback Ceed
485 
486   @return An error code: 0 - success, otherwise - failure
487 
488   @ref Backend
489 **/
490 int CeedGetOperatorFallbackCeed(Ceed ceed, Ceed *fallback_ceed) {
491   if (ceed->has_valid_op_fallback_resource) {
492     CeedDebug256(ceed, CEED_DEBUG_COLOR_SUCCESS, "---------- CeedOperator Fallback ----------\n");
493     CeedDebug(ceed, "Getting fallback from %s to %s\n", ceed->resource, ceed->op_fallback_resource);
494   }
495 
496   // Create fallback Ceed if uninitalized
497   if (!ceed->op_fallback_ceed && ceed->has_valid_op_fallback_resource) {
498     CeedDebug(ceed, "Creating fallback Ceed");
499 
500     Ceed        fallback_ceed;
501     const char *fallback_resource;
502 
503     CeedCall(CeedGetOperatorFallbackResource(ceed, &fallback_resource));
504     CeedCall(CeedInit(fallback_resource, &fallback_ceed));
505     fallback_ceed->op_fallback_parent = ceed;
506     fallback_ceed->Error              = ceed->Error;
507     ceed->op_fallback_ceed            = fallback_ceed;
508   }
509   *fallback_ceed = ceed->op_fallback_ceed;
510   return CEED_ERROR_SUCCESS;
511 }
512 
513 /**
514   @brief Set the fallback resource for CeedOperators.
515 
516   The current resource, if any, is freed by calling this function.
517   This string is freed upon the destruction of the Ceed context.
518 
519   @param[in,out] ceed     Ceed context
520   @param[in]     resource Fallback resource to set
521 
522   @return An error code: 0 - success, otherwise - failure
523 
524   @ref Backend
525 **/
526 int CeedSetOperatorFallbackResource(Ceed ceed, const char *resource) {
527   // Free old
528   CeedCall(CeedFree(&ceed->op_fallback_resource));
529 
530   // Set new
531   CeedCall(CeedStringAllocCopy(resource, (char **)&ceed->op_fallback_resource));
532 
533   // Check validity
534   ceed->has_valid_op_fallback_resource = ceed->op_fallback_resource && ceed->resource && strcmp(ceed->op_fallback_resource, ceed->resource);
535   return CEED_ERROR_SUCCESS;
536 }
537 
538 /**
539   @brief Flag Ceed context as deterministic
540 
541   @param[in]  ceed             Ceed to flag as deterministic
542   @param[out] is_deterministic Deterministic status to set
543 
544   @return An error code: 0 - success, otherwise - failure
545 
546   @ref Backend
547 **/
548 int CeedSetDeterministic(Ceed ceed, bool is_deterministic) {
549   ceed->is_deterministic = is_deterministic;
550   return CEED_ERROR_SUCCESS;
551 }
552 
553 /**
554   @brief Set a backend function
555 
556   This function is used for a backend to set the function associated with the Ceed objects.
557   For example, CeedSetBackendFunction(ceed, "Ceed", ceed, "VectorCreate", BackendVectorCreate) sets the backend implementation of 'CeedVectorCreate'
558 and CeedSetBackendFunction(ceed, "Basis", basis, "Apply", BackendBasisApply) sets the backend implementation of 'CeedBasisApply'.
559   Note, the prefix 'Ceed' is not required for the object type ("Basis" vs "CeedBasis").
560 
561   @param[in]  ceed      Ceed context for error handling
562   @param[in]  type      Type of Ceed object to set function for
563   @param[out] object    Ceed object to set function for
564   @param[in]  func_name Name of function to set
565   @param[in]  f         Function to set
566 
567   @return An error code: 0 - success, otherwise - failure
568 
569   @ref Backend
570 **/
571 int CeedSetBackendFunction(Ceed ceed, const char *type, void *object, const char *func_name, int (*f)()) {
572   char lookup_name[CEED_MAX_RESOURCE_LEN + 1] = "";
573 
574   // Build lookup name
575   if (strcmp(type, "Ceed")) strncat(lookup_name, "Ceed", CEED_MAX_RESOURCE_LEN);
576   strncat(lookup_name, type, CEED_MAX_RESOURCE_LEN);
577   strncat(lookup_name, func_name, CEED_MAX_RESOURCE_LEN);
578 
579   // Find and use offset
580   for (CeedInt i = 0; ceed->f_offsets[i].func_name; i++) {
581     if (!strcmp(ceed->f_offsets[i].func_name, lookup_name)) {
582       size_t offset          = ceed->f_offsets[i].offset;
583       int (**fpointer)(void) = (int (**)(void))((char *)object + offset);  // *NOPAD*
584 
585       *fpointer = f;
586       return CEED_ERROR_SUCCESS;
587     }
588   }
589 
590   // LCOV_EXCL_START
591   return CeedError(ceed, CEED_ERROR_UNSUPPORTED, "Requested function '%s' was not found for CEED object '%s'", func_name, type);
592   // LCOV_EXCL_STOP
593 }
594 
595 /**
596   @brief Retrieve backend data for a Ceed context
597 
598   @param[in]  ceed Ceed context to retrieve data of
599   @param[out] data Address to save data to
600 
601   @return An error code: 0 - success, otherwise - failure
602 
603   @ref Backend
604 **/
605 int CeedGetData(Ceed ceed, void *data) {
606   *(void **)data = ceed->data;
607   return CEED_ERROR_SUCCESS;
608 }
609 
610 /**
611   @brief Set backend data for a Ceed context
612 
613   @param[in,out] ceed Ceed context to set data of
614   @param[in]     data Address of data to set
615 
616   @return An error code: 0 - success, otherwise - failure
617 
618   @ref Backend
619 **/
620 int CeedSetData(Ceed ceed, void *data) {
621   ceed->data = data;
622   return CEED_ERROR_SUCCESS;
623 }
624 
625 /**
626   @brief Increment the reference counter for a Ceed context
627 
628   @param[in,out] ceed Ceed context to increment the reference counter
629 
630   @return An error code: 0 - success, otherwise - failure
631 
632   @ref Backend
633 **/
634 int CeedReference(Ceed ceed) {
635   ceed->ref_count++;
636   return CEED_ERROR_SUCCESS;
637 }
638 
639 /// @}
640 
641 /// ----------------------------------------------------------------------------
642 /// Ceed Public API
643 /// ----------------------------------------------------------------------------
644 /// @addtogroup CeedUser
645 /// @{
646 
647 /**
648   @brief Get the list of available resource names for Ceed contexts
649 
650   Note: The caller is responsible for `free()`ing the resources and priorities arrays, but should not `free()` the contents of the resources
651 array.
652 
653   @param[out] n          Number of available resources
654   @param[out] resources  List of available resource names
655   @param[out] priorities Resource name prioritization values, lower is better
656 
657   @return An error code: 0 - success, otherwise - failure
658 
659   @ref User
660 **/
661 // LCOV_EXCL_START
662 int CeedRegistryGetList(size_t *n, char ***const resources, CeedInt **priorities) {
663   *n         = 0;
664   *resources = malloc(num_backends * sizeof(**resources));
665   CeedCheck(resources, NULL, CEED_ERROR_MAJOR, "malloc() failure");
666   if (priorities) {
667     *priorities = malloc(num_backends * sizeof(**priorities));
668     CeedCheck(priorities, NULL, CEED_ERROR_MAJOR, "malloc() failure");
669   }
670   for (size_t i = 0; i < num_backends; i++) {
671     // Only report compiled backends
672     if (backends[i].priority < CEED_MAX_BACKEND_PRIORITY) {
673       *resources[i] = backends[i].prefix;
674       if (priorities) *priorities[i] = backends[i].priority;
675       *n += 1;
676     }
677   }
678   CeedCheck(*n, NULL, CEED_ERROR_MAJOR, "No backends installed");
679   *resources = realloc(*resources, *n * sizeof(**resources));
680   CeedCheck(resources, NULL, CEED_ERROR_MAJOR, "realloc() failure");
681   if (priorities) {
682     *priorities = realloc(*priorities, *n * sizeof(**priorities));
683     CeedCheck(priorities, NULL, CEED_ERROR_MAJOR, "realloc() failure");
684   }
685   return CEED_ERROR_SUCCESS;
686 }
687 // LCOV_EXCL_STOP
688 
689 /**
690   @brief Initialize a \ref Ceed context to use the specified resource.
691 
692   Note: Prefixing the resource with "help:" (e.g. "help:/cpu/self") will result in CeedInt printing the current libCEED version number and a
693 list of current available backend resources to stderr.
694 
695   @param[in]  resource Resource to use, e.g., "/cpu/self"
696   @param[out] ceed     The library context
697   @sa CeedRegister() CeedDestroy()
698 
699   @return An error code: 0 - success, otherwise - failure
700 
701   @ref User
702 **/
703 int CeedInit(const char *resource, Ceed *ceed) {
704   size_t match_len = 0, match_index = UINT_MAX, match_priority = CEED_MAX_BACKEND_PRIORITY, priority;
705 
706   // Find matching backend
707   CeedCheck(resource, NULL, CEED_ERROR_MAJOR, "No resource provided");
708   CeedCall(CeedRegisterAll());
709 
710   // Check for help request
711   const char *help_prefix = "help";
712   size_t      match_help  = 0;
713   while (match_help < 4 && resource[match_help] == help_prefix[match_help]) match_help++;
714   if (match_help == 4) {
715     fprintf(stderr, "libCEED version: %d.%d%d%s\n", CEED_VERSION_MAJOR, CEED_VERSION_MINOR, CEED_VERSION_PATCH,
716             CEED_VERSION_RELEASE ? "" : "+development");
717     fprintf(stderr, "Available backend resources:\n");
718     for (size_t i = 0; i < num_backends; i++) {
719       // Only report compiled backends
720       if (backends[i].priority < CEED_MAX_BACKEND_PRIORITY) fprintf(stderr, "  %s\n", backends[i].prefix);
721     }
722     fflush(stderr);
723     match_help = 5;  // Delineating character expected
724   } else {
725     match_help = 0;
726   }
727 
728   // Find best match, computed as number of matching characters from requested resource stem
729   size_t stem_length = 0;
730   while (resource[stem_length + match_help] && resource[stem_length + match_help] != ':') stem_length++;
731   for (size_t i = 0; i < num_backends; i++) {
732     size_t      n      = 0;
733     const char *prefix = backends[i].prefix;
734     while (prefix[n] && prefix[n] == resource[n + match_help]) n++;
735     priority = backends[i].priority;
736     if (n > match_len || (n == match_len && match_priority > priority)) {
737       match_len      = n;
738       match_priority = priority;
739       match_index    = i;
740     }
741   }
742   // Using Levenshtein distance to find closest match
743   if (match_len <= 1 || match_len != stem_length) {
744     // LCOV_EXCL_START
745     size_t lev_dis   = UINT_MAX;
746     size_t lev_index = UINT_MAX, lev_priority = CEED_MAX_BACKEND_PRIORITY;
747     for (size_t i = 0; i < num_backends; i++) {
748       const char *prefix        = backends[i].prefix;
749       size_t      prefix_length = strlen(backends[i].prefix);
750       size_t      min_len       = (prefix_length < stem_length) ? prefix_length : stem_length;
751       size_t      column[min_len + 1];
752       for (size_t j = 0; j <= min_len; j++) column[j] = j;
753       for (size_t j = 1; j <= min_len; j++) {
754         column[0] = j;
755         for (size_t k = 1, last_diag = j - 1; k <= min_len; k++) {
756           size_t old_diag = column[k];
757           size_t min_1    = (column[k] < column[k - 1]) ? column[k] + 1 : column[k - 1] + 1;
758           size_t min_2    = last_diag + (resource[k - 1] == prefix[j - 1] ? 0 : 1);
759           column[k]       = (min_1 < min_2) ? min_1 : min_2;
760           last_diag       = old_diag;
761         }
762       }
763       size_t n = column[min_len];
764       priority = backends[i].priority;
765       if (n < lev_dis || (n == lev_dis && lev_priority > priority)) {
766         lev_dis      = n;
767         lev_priority = priority;
768         lev_index    = i;
769       }
770     }
771     const char *prefix_lev = backends[lev_index].prefix;
772     size_t      lev_length = 0;
773     while (prefix_lev[lev_length] && prefix_lev[lev_length] != '\0') lev_length++;
774     size_t m = (lev_length < stem_length) ? lev_length : stem_length;
775     if (lev_dis + 1 >= m) return CeedError(NULL, CEED_ERROR_MAJOR, "No suitable backend: %s", resource);
776     else return CeedError(NULL, CEED_ERROR_MAJOR, "No suitable backend: %s\nClosest match: %s", resource, backends[lev_index].prefix);
777     // LCOV_EXCL_STOP
778   }
779 
780   // Setup Ceed
781   CeedCall(CeedCalloc(1, ceed));
782   CeedCall(CeedCalloc(1, &(*ceed)->jit_source_roots));
783   const char *ceed_error_handler = getenv("CEED_ERROR_HANDLER");
784   if (!ceed_error_handler) ceed_error_handler = "abort";
785   if (!strcmp(ceed_error_handler, "exit")) (*ceed)->Error = CeedErrorExit;
786   else if (!strcmp(ceed_error_handler, "store")) (*ceed)->Error = CeedErrorStore;
787   else (*ceed)->Error = CeedErrorAbort;
788   memcpy((*ceed)->err_msg, "No error message stored", 24);
789   (*ceed)->ref_count = 1;
790   (*ceed)->data      = NULL;
791 
792   // Set lookup table
793   FOffset f_offsets[] = {
794       CEED_FTABLE_ENTRY(Ceed, Error),
795       CEED_FTABLE_ENTRY(Ceed, SetStream),
796       CEED_FTABLE_ENTRY(Ceed, GetPreferredMemType),
797       CEED_FTABLE_ENTRY(Ceed, Destroy),
798       CEED_FTABLE_ENTRY(Ceed, VectorCreate),
799       CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreate),
800       CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreateAtPoints),
801       CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreateBlocked),
802       CEED_FTABLE_ENTRY(Ceed, BasisCreateTensorH1),
803       CEED_FTABLE_ENTRY(Ceed, BasisCreateH1),
804       CEED_FTABLE_ENTRY(Ceed, BasisCreateHdiv),
805       CEED_FTABLE_ENTRY(Ceed, BasisCreateHcurl),
806       CEED_FTABLE_ENTRY(Ceed, TensorContractCreate),
807       CEED_FTABLE_ENTRY(Ceed, QFunctionCreate),
808       CEED_FTABLE_ENTRY(Ceed, QFunctionContextCreate),
809       CEED_FTABLE_ENTRY(Ceed, OperatorCreate),
810       CEED_FTABLE_ENTRY(Ceed, CompositeOperatorCreate),
811       CEED_FTABLE_ENTRY(CeedVector, HasValidArray),
812       CEED_FTABLE_ENTRY(CeedVector, HasBorrowedArrayOfType),
813       CEED_FTABLE_ENTRY(CeedVector, SetArray),
814       CEED_FTABLE_ENTRY(CeedVector, TakeArray),
815       CEED_FTABLE_ENTRY(CeedVector, SetValue),
816       CEED_FTABLE_ENTRY(CeedVector, SyncArray),
817       CEED_FTABLE_ENTRY(CeedVector, GetArray),
818       CEED_FTABLE_ENTRY(CeedVector, GetArrayRead),
819       CEED_FTABLE_ENTRY(CeedVector, GetArrayWrite),
820       CEED_FTABLE_ENTRY(CeedVector, RestoreArray),
821       CEED_FTABLE_ENTRY(CeedVector, RestoreArrayRead),
822       CEED_FTABLE_ENTRY(CeedVector, Norm),
823       CEED_FTABLE_ENTRY(CeedVector, Scale),
824       CEED_FTABLE_ENTRY(CeedVector, AXPY),
825       CEED_FTABLE_ENTRY(CeedVector, AXPBY),
826       CEED_FTABLE_ENTRY(CeedVector, PointwiseMult),
827       CEED_FTABLE_ENTRY(CeedVector, Reciprocal),
828       CEED_FTABLE_ENTRY(CeedVector, Destroy),
829       CEED_FTABLE_ENTRY(CeedElemRestriction, Apply),
830       CEED_FTABLE_ENTRY(CeedElemRestriction, ApplyUnsigned),
831       CEED_FTABLE_ENTRY(CeedElemRestriction, ApplyUnoriented),
832       CEED_FTABLE_ENTRY(CeedElemRestriction, ApplyAtPointsInElement),
833       CEED_FTABLE_ENTRY(CeedElemRestriction, ApplyBlock),
834       CEED_FTABLE_ENTRY(CeedElemRestriction, GetOffsets),
835       CEED_FTABLE_ENTRY(CeedElemRestriction, GetOrientations),
836       CEED_FTABLE_ENTRY(CeedElemRestriction, GetCurlOrientations),
837       CEED_FTABLE_ENTRY(CeedElemRestriction, Destroy),
838       CEED_FTABLE_ENTRY(CeedBasis, Apply),
839       CEED_FTABLE_ENTRY(CeedBasis, ApplyAtPoints),
840       CEED_FTABLE_ENTRY(CeedBasis, Destroy),
841       CEED_FTABLE_ENTRY(CeedTensorContract, Apply),
842       CEED_FTABLE_ENTRY(CeedTensorContract, Destroy),
843       CEED_FTABLE_ENTRY(CeedQFunction, Apply),
844       CEED_FTABLE_ENTRY(CeedQFunction, SetCUDAUserFunction),
845       CEED_FTABLE_ENTRY(CeedQFunction, SetHIPUserFunction),
846       CEED_FTABLE_ENTRY(CeedQFunction, Destroy),
847       CEED_FTABLE_ENTRY(CeedQFunctionContext, HasValidData),
848       CEED_FTABLE_ENTRY(CeedQFunctionContext, HasBorrowedDataOfType),
849       CEED_FTABLE_ENTRY(CeedQFunctionContext, SetData),
850       CEED_FTABLE_ENTRY(CeedQFunctionContext, TakeData),
851       CEED_FTABLE_ENTRY(CeedQFunctionContext, GetData),
852       CEED_FTABLE_ENTRY(CeedQFunctionContext, GetDataRead),
853       CEED_FTABLE_ENTRY(CeedQFunctionContext, RestoreData),
854       CEED_FTABLE_ENTRY(CeedQFunctionContext, RestoreDataRead),
855       CEED_FTABLE_ENTRY(CeedQFunctionContext, DataDestroy),
856       CEED_FTABLE_ENTRY(CeedQFunctionContext, Destroy),
857       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleQFunction),
858       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleQFunctionUpdate),
859       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleDiagonal),
860       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleAddDiagonal),
861       CEED_FTABLE_ENTRY(CeedOperator, LinearAssemblePointBlockDiagonal),
862       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleAddPointBlockDiagonal),
863       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleSymbolic),
864       CEED_FTABLE_ENTRY(CeedOperator, LinearAssemble),
865       CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleSingle),
866       CEED_FTABLE_ENTRY(CeedOperator, CreateFDMElementInverse),
867       CEED_FTABLE_ENTRY(CeedOperator, Apply),
868       CEED_FTABLE_ENTRY(CeedOperator, ApplyComposite),
869       CEED_FTABLE_ENTRY(CeedOperator, ApplyAdd),
870       CEED_FTABLE_ENTRY(CeedOperator, ApplyAddComposite),
871       CEED_FTABLE_ENTRY(CeedOperator, ApplyJacobian),
872       CEED_FTABLE_ENTRY(CeedOperator, Destroy),
873       {NULL, 0}  // End of lookup table - used in SetBackendFunction loop
874   };
875 
876   CeedCall(CeedCalloc(sizeof(f_offsets), &(*ceed)->f_offsets));
877   memcpy((*ceed)->f_offsets, f_offsets, sizeof(f_offsets));
878 
879   // Set fallback for advanced CeedOperator functions
880   const char fallback_resource[] = "";
881   CeedCall(CeedSetOperatorFallbackResource(*ceed, fallback_resource));
882 
883   // Record env variables CEED_DEBUG or DBG
884   (*ceed)->is_debug = getenv("CEED_DEBUG") || getenv("DEBUG") || getenv("DBG");
885 
886   // Copy resource prefix, if backend setup successful
887   CeedCall(CeedStringAllocCopy(backends[match_index].prefix, (char **)&(*ceed)->resource));
888 
889   // Set default JiT source root
890   // Note: there will always be the default root for every Ceed but all additional paths are added to the top-most parent
891   CeedCall(CeedAddJitSourceRoot(*ceed, (char *)CeedJitSourceRootDefault));
892 
893   // Backend specific setup
894   CeedCall(backends[match_index].init(&resource[match_help], *ceed));
895   return CEED_ERROR_SUCCESS;
896 }
897 
898 /**
899   @brief Set the GPU stream for a Ceed context
900 
901   @param[in,out] ceed   Ceed context to set the stream
902   @param[in]     handle Handle to GPU stream
903 
904   @return An error code: 0 - success, otherwise - failure
905 
906   @ref User
907 **/
908 int CeedSetStream(Ceed ceed, void *handle) {
909   CeedCheck(handle, ceed, CEED_ERROR_INCOMPATIBLE, "Stream handle must be non-null");
910   if (ceed->SetStream) {
911     CeedCall(ceed->SetStream(ceed, handle));
912   } else {
913     Ceed delegate;
914     CeedCall(CeedGetDelegate(ceed, &delegate));
915 
916     if (delegate) CeedCall(CeedSetStream(delegate, handle));
917     else return CeedError(ceed, CEED_ERROR_UNSUPPORTED, "Backend does not support setting stream");
918   }
919   return CEED_ERROR_SUCCESS;
920 }
921 
922 /**
923   @brief Copy the pointer to a Ceed context.
924 
925   Both pointers should be destroyed with `CeedDestroy()`.
926 
927   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.
928         This Ceed context will be destroyed if `ceed_copy` is the only reference to this Ceed context.
929 
930   @param[in]     ceed      Ceed context to copy reference to
931   @param[in,out] ceed_copy Variable to store copied reference
932 
933   @return An error code: 0 - success, otherwise - failure
934 
935   @ref User
936 **/
937 int CeedReferenceCopy(Ceed ceed, Ceed *ceed_copy) {
938   CeedCall(CeedReference(ceed));
939   CeedCall(CeedDestroy(ceed_copy));
940   *ceed_copy = ceed;
941   return CEED_ERROR_SUCCESS;
942 }
943 
944 /**
945   @brief Get the full resource name for a Ceed context
946 
947   @param[in]  ceed     Ceed context to get resource name of
948   @param[out] resource Variable to store resource name
949 
950   @return An error code: 0 - success, otherwise - failure
951 
952   @ref User
953 **/
954 int CeedGetResource(Ceed ceed, const char **resource) {
955   *resource = (const char *)ceed->resource;
956   return CEED_ERROR_SUCCESS;
957 }
958 
959 /**
960   @brief Return Ceed context preferred memory type
961 
962   @param[in]  ceed     Ceed context to get preferred memory type of
963   @param[out] mem_type Address to save preferred memory type to
964 
965   @return An error code: 0 - success, otherwise - failure
966 
967   @ref User
968 **/
969 int CeedGetPreferredMemType(Ceed ceed, CeedMemType *mem_type) {
970   if (ceed->GetPreferredMemType) {
971     CeedCall(ceed->GetPreferredMemType(mem_type));
972   } else {
973     Ceed delegate;
974     CeedCall(CeedGetDelegate(ceed, &delegate));
975 
976     if (delegate) {
977       CeedCall(CeedGetPreferredMemType(delegate, mem_type));
978     } else {
979       *mem_type = CEED_MEM_HOST;
980     }
981   }
982   return CEED_ERROR_SUCCESS;
983 }
984 
985 /**
986   @brief Get deterministic status of Ceed
987 
988   @param[in]  ceed             Ceed
989   @param[out] is_deterministic Variable to store deterministic status
990 
991   @return An error code: 0 - success, otherwise - failure
992 
993   @ref User
994 **/
995 int CeedIsDeterministic(Ceed ceed, bool *is_deterministic) {
996   *is_deterministic = ceed->is_deterministic;
997   return CEED_ERROR_SUCCESS;
998 }
999 
1000 /**
1001   @brief Set additional JiT source root for Ceed
1002 
1003   @param[in,out] ceed            Ceed
1004   @param[in]     jit_source_root Absolute path to additional JiT source directory
1005 
1006   @return An error code: 0 - success, otherwise - failure
1007 
1008   @ref User
1009 **/
1010 int CeedAddJitSourceRoot(Ceed ceed, const char *jit_source_root) {
1011   Ceed ceed_parent;
1012 
1013   CeedCall(CeedGetParent(ceed, &ceed_parent));
1014 
1015   CeedInt index       = ceed_parent->num_jit_source_roots;
1016   size_t  path_length = strlen(jit_source_root);
1017 
1018   CeedCall(CeedRealloc(index + 1, &ceed_parent->jit_source_roots));
1019   CeedCall(CeedCalloc(path_length + 1, &ceed_parent->jit_source_roots[index]));
1020   memcpy(ceed_parent->jit_source_roots[index], jit_source_root, path_length);
1021   ceed_parent->num_jit_source_roots++;
1022   return CEED_ERROR_SUCCESS;
1023 }
1024 
1025 /**
1026   @brief View a Ceed
1027 
1028   @param[in] ceed   Ceed to view
1029   @param[in] stream Filestream to write to
1030 
1031   @return An error code: 0 - success, otherwise - failure
1032 
1033   @ref User
1034 **/
1035 int CeedView(Ceed ceed, FILE *stream) {
1036   CeedMemType mem_type;
1037 
1038   CeedCall(CeedGetPreferredMemType(ceed, &mem_type));
1039 
1040   fprintf(stream,
1041           "Ceed\n"
1042           "  Ceed Resource: %s\n"
1043           "  Preferred MemType: %s\n",
1044           ceed->resource, CeedMemTypes[mem_type]);
1045   return CEED_ERROR_SUCCESS;
1046 }
1047 
1048 /**
1049   @brief Destroy a Ceed context
1050 
1051   @param[in,out] ceed Address of Ceed context to destroy
1052 
1053   @return An error code: 0 - success, otherwise - failure
1054 
1055   @ref User
1056 **/
1057 int CeedDestroy(Ceed *ceed) {
1058   if (!*ceed || --(*ceed)->ref_count > 0) {
1059     *ceed = NULL;
1060     return CEED_ERROR_SUCCESS;
1061   }
1062   if ((*ceed)->delegate) CeedCall(CeedDestroy(&(*ceed)->delegate));
1063 
1064   if ((*ceed)->obj_delegate_count > 0) {
1065     for (CeedInt i = 0; i < (*ceed)->obj_delegate_count; i++) {
1066       CeedCall(CeedDestroy(&((*ceed)->obj_delegates[i].delegate)));
1067       CeedCall(CeedFree(&(*ceed)->obj_delegates[i].obj_name));
1068     }
1069     CeedCall(CeedFree(&(*ceed)->obj_delegates));
1070   }
1071 
1072   if ((*ceed)->Destroy) CeedCall((*ceed)->Destroy(*ceed));
1073 
1074   for (CeedInt i = 0; i < (*ceed)->num_jit_source_roots; i++) {
1075     CeedCall(CeedFree(&(*ceed)->jit_source_roots[i]));
1076   }
1077   CeedCall(CeedFree(&(*ceed)->jit_source_roots));
1078 
1079   CeedCall(CeedFree(&(*ceed)->f_offsets));
1080   CeedCall(CeedFree(&(*ceed)->resource));
1081   CeedCall(CeedDestroy(&(*ceed)->op_fallback_ceed));
1082   CeedCall(CeedFree(&(*ceed)->op_fallback_resource));
1083   CeedCall(CeedFree(ceed));
1084   return CEED_ERROR_SUCCESS;
1085 }
1086 
1087 // LCOV_EXCL_START
1088 const char *CeedErrorFormat(Ceed ceed, const char *format, va_list *args) {
1089   if (ceed->parent) return CeedErrorFormat(ceed->parent, format, args);
1090   if (ceed->op_fallback_parent) return CeedErrorFormat(ceed->op_fallback_parent, format, args);
1091   // Using pointer to va_list for better FFI, but clang-tidy can't verify va_list is initalized
1092   vsnprintf(ceed->err_msg, CEED_MAX_RESOURCE_LEN, format, *args);  // NOLINT
1093   return ceed->err_msg;
1094 }
1095 // LCOV_EXCL_STOP
1096 
1097 /**
1098   @brief Error handling implementation; use \ref CeedError instead.
1099 
1100   @ref Developer
1101 **/
1102 int CeedErrorImpl(Ceed ceed, const char *filename, int lineno, const char *func, int ecode, const char *format, ...) {
1103   va_list args;
1104   int     ret_val;
1105 
1106   va_start(args, format);
1107   if (ceed) {
1108     ret_val = ceed->Error(ceed, filename, lineno, func, ecode, format, &args);
1109   } else {
1110     // LCOV_EXCL_START
1111     const char *ceed_error_handler = getenv("CEED_ERROR_HANDLER");
1112     if (!ceed_error_handler) ceed_error_handler = "abort";
1113     if (!strcmp(ceed_error_handler, "return")) ret_val = CeedErrorReturn(ceed, filename, lineno, func, ecode, format, &args);
1114     else
1115       // This function will not return
1116       ret_val = CeedErrorAbort(ceed, filename, lineno, func, ecode, format, &args);
1117   }
1118   va_end(args);
1119   return ret_val;
1120   // LCOV_EXCL_STOP
1121 }
1122 
1123 /**
1124   @brief Error handler that returns without printing anything.
1125 
1126   Pass this to CeedSetErrorHandler() to obtain this error handling behavior.
1127 
1128   @ref Developer
1129 **/
1130 // LCOV_EXCL_START
1131 int CeedErrorReturn(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1132   return err_code;
1133 }
1134 // LCOV_EXCL_STOP
1135 
1136 /**
1137   @brief Error handler that stores the error message for future use and returns the error.
1138 
1139   Pass this to CeedSetErrorHandler() to obtain this error handling behavior.
1140 
1141   @ref Developer
1142 **/
1143 // LCOV_EXCL_START
1144 int CeedErrorStore(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1145   if (ceed->parent) return CeedErrorStore(ceed->parent, filename, line_no, func, err_code, format, args);
1146   if (ceed->op_fallback_parent) return CeedErrorStore(ceed->op_fallback_parent, filename, line_no, func, err_code, format, args);
1147 
1148   // Build message
1149   int len = snprintf(ceed->err_msg, CEED_MAX_RESOURCE_LEN, "%s:%d in %s(): ", filename, line_no, func);
1150   // Using pointer to va_list for better FFI, but clang-tidy can't verify va_list is initalized
1151   vsnprintf(ceed->err_msg + len, CEED_MAX_RESOURCE_LEN - len, format, *args);  // NOLINT
1152   return err_code;
1153 }
1154 // LCOV_EXCL_STOP
1155 
1156 /**
1157   @brief Error handler that prints to stderr and aborts
1158 
1159   Pass this to CeedSetErrorHandler() to obtain this error handling behavior.
1160 
1161   @ref Developer
1162 **/
1163 // LCOV_EXCL_START
1164 int CeedErrorAbort(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1165   fprintf(stderr, "%s:%d in %s(): ", filename, line_no, func);
1166   vfprintf(stderr, format, *args);
1167   fprintf(stderr, "\n");
1168   abort();
1169   return err_code;
1170 }
1171 // LCOV_EXCL_STOP
1172 
1173 /**
1174   @brief Error handler that prints to stderr and exits
1175 
1176   Pass this to CeedSetErrorHandler() to obtain this error handling behavior.
1177 
1178   In contrast to CeedErrorAbort(), this exits without a signal, so atexit() handlers (e.g., as used by gcov) are run.
1179 
1180   @ref Developer
1181 **/
1182 int CeedErrorExit(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1183   fprintf(stderr, "%s:%d in %s(): ", filename, line_no, func);
1184   // Using pointer to va_list for better FFI, but clang-tidy can't verify va_list is initalized
1185   vfprintf(stderr, format, *args);  // NOLINT
1186   fprintf(stderr, "\n");
1187   exit(err_code);
1188   return err_code;
1189 }
1190 
1191 /**
1192   @brief Set error handler
1193 
1194   A default error handler is set in CeedInit().
1195   Use this function to change the error handler to CeedErrorReturn(), CeedErrorAbort(), or a user-defined error handler.
1196 
1197   @ref Developer
1198 **/
1199 int CeedSetErrorHandler(Ceed ceed, CeedErrorHandler handler) {
1200   ceed->Error = handler;
1201   if (ceed->delegate) CeedSetErrorHandler(ceed->delegate, handler);
1202   for (CeedInt i = 0; i < ceed->obj_delegate_count; i++) CeedSetErrorHandler(ceed->obj_delegates[i].delegate, handler);
1203   return CEED_ERROR_SUCCESS;
1204 }
1205 
1206 /**
1207   @brief Get error message
1208 
1209   The error message is only stored when using the error handler CeedErrorStore()
1210 
1211   @param[in]  ceed    Ceed context to retrieve error message
1212   @param[out] err_msg Char pointer to hold error message
1213 
1214   @ref Developer
1215 **/
1216 int CeedGetErrorMessage(Ceed ceed, const char **err_msg) {
1217   if (ceed->parent) return CeedGetErrorMessage(ceed->parent, err_msg);
1218   if (ceed->op_fallback_parent) return CeedGetErrorMessage(ceed->op_fallback_parent, err_msg);
1219   *err_msg = ceed->err_msg;
1220   return CEED_ERROR_SUCCESS;
1221 }
1222 
1223 /**
1224   @brief Restore error message
1225 
1226   The error message is only stored when using the error handler CeedErrorStore()
1227 
1228   @param[in]  ceed    Ceed context to restore error message
1229   @param[out] err_msg Char pointer that holds error message
1230 
1231   @ref Developer
1232 **/
1233 int CeedResetErrorMessage(Ceed ceed, const char **err_msg) {
1234   if (ceed->parent) return CeedResetErrorMessage(ceed->parent, err_msg);
1235   if (ceed->op_fallback_parent) return CeedResetErrorMessage(ceed->op_fallback_parent, err_msg);
1236   *err_msg = NULL;
1237   memcpy(ceed->err_msg, "No error message stored", 24);
1238   return CEED_ERROR_SUCCESS;
1239 }
1240 
1241 /**
1242   @brief Get libCEED library version info
1243 
1244   libCEED version numbers have the form major.minor.patch.
1245   Non-release versions may contain unstable interfaces.
1246 
1247   @param[out] major   Major version of the library
1248   @param[out] minor   Minor version of the library
1249   @param[out] patch   Patch (subminor) version of the library
1250   @param[out] release True for releases; false for development branches
1251 
1252   The caller may pass NULL for any arguments that are not needed.
1253 
1254   @sa CEED_VERSION_GE()
1255 
1256   @ref Developer
1257 */
1258 int CeedGetVersion(int *major, int *minor, int *patch, bool *release) {
1259   if (major) *major = CEED_VERSION_MAJOR;
1260   if (minor) *minor = CEED_VERSION_MINOR;
1261   if (patch) *patch = CEED_VERSION_PATCH;
1262   if (release) *release = CEED_VERSION_RELEASE;
1263   return 0;
1264 }
1265 
1266 /**
1267   @brief Get libCEED scalar type, such as F64 or F32
1268 
1269   @param[out] scalar_type Type of libCEED scalars
1270 
1271   @ref Developer
1272 */
1273 int CeedGetScalarType(CeedScalarType *scalar_type) {
1274   *scalar_type = CEED_SCALAR_TYPE;
1275   return 0;
1276 }
1277 
1278 /// @}
1279