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