xref: /libCEED/interface/ceed.c (revision f8608ea82c72806ce37b46314218ab211cba2735)
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 immediately.
45   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 order that it is submitted to the device.
67   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 @ref CeedRequest to complete.
87 
88   Calling @ref CeedRequestWait() on a `NULL` request is a no-op.
89 
90   @param[in,out] req Address of @ref 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 @ref 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 @ref 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 @ref 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   CeedCheck(ierr == 0, NULL, CEED_ERROR_MAJOR, "Too many backends");
139   return CEED_ERROR_SUCCESS;
140 }
141 
142 /// @}
143 
144 /// ----------------------------------------------------------------------------
145 /// Ceed Backend API
146 /// ----------------------------------------------------------------------------
147 /// @addtogroup CeedBackend
148 /// @{
149 
150 /**
151   @brief Return value of `CEED_DEBUG` environment variable
152 
153   @param[in] ceed `Ceed` context
154 
155   @return Boolean value: true  - debugging mode enabled
156                          false - debugging mode disabled
157 
158   @ref Backend
159 **/
160 // LCOV_EXCL_START
161 bool CeedDebugFlag(const Ceed ceed) { return ceed->is_debug; }
162 // LCOV_EXCL_STOP
163 
164 /**
165   @brief Return value of `CEED_DEBUG` environment variable
166 
167   @return Boolean value: true  - debugging mode enabled
168                          false - debugging mode disabled
169 
170   @ref Backend
171 **/
172 // LCOV_EXCL_START
173 bool CeedDebugFlagEnv(void) { return getenv("CEED_DEBUG") || getenv("DEBUG") || getenv("DBG"); }
174 // LCOV_EXCL_STOP
175 
176 /**
177   @brief Print debugging information in color
178 
179   @param[in] color  Color to print
180   @param[in] format Printing format
181 
182   @ref Backend
183 **/
184 // LCOV_EXCL_START
185 void CeedDebugImpl256(const unsigned char color, const char *format, ...) {
186   va_list args;
187   va_start(args, format);
188   fflush(stdout);
189   if (color != CEED_DEBUG_COLOR_NONE) fprintf(stdout, "\033[38;5;%dm", color);
190   vfprintf(stdout, format, args);
191   if (color != CEED_DEBUG_COLOR_NONE) fprintf(stdout, "\033[m");
192   fprintf(stdout, "\n");
193   fflush(stdout);
194   va_end(args);
195 }
196 // LCOV_EXCL_STOP
197 
198 /**
199   @brief Allocate an array on the host; use @ref CeedMalloc().
200 
201   Memory usage can be tracked by the library.
202   This ensures sufficient alignment for vectorization and should be used for large allocations.
203 
204   @param[in]  n    Number of units to allocate
205   @param[in]  unit Size of each unit
206   @param[out] p    Address of pointer to hold the result
207 
208   @return An error code: 0 - success, otherwise - failure
209 
210   @ref Backend
211 
212   @sa CeedFree()
213 **/
214 int CeedMallocArray(size_t n, size_t unit, void *p) {
215   int ierr = posix_memalign((void **)p, CEED_ALIGN, n * unit);
216   CeedCheck(ierr == 0, NULL, CEED_ERROR_MAJOR, "posix_memalign failed to allocate %zd members of size %zd\n", n, unit);
217   return CEED_ERROR_SUCCESS;
218 }
219 
220 /**
221   @brief Allocate a cleared (zeroed) array on the host; use @ref CeedCalloc().
222 
223   Memory usage can be tracked by the library.
224 
225   @param[in]  n    Number of units to allocate
226   @param[in]  unit Size of each unit
227   @param[out] p    Address of pointer to hold the result
228 
229   @return An error code: 0 - success, otherwise - failure
230 
231   @ref Backend
232 
233   @sa CeedFree()
234 **/
235 int CeedCallocArray(size_t n, size_t unit, void *p) {
236   *(void **)p = calloc(n, unit);
237   CeedCheck(!n || !unit || *(void **)p, NULL, CEED_ERROR_MAJOR, "calloc failed to allocate %zd members of size %zd\n", n, unit);
238   return CEED_ERROR_SUCCESS;
239 }
240 
241 /**
242   @brief Reallocate an array on the host; use @ref CeedRealloc().
243 
244   Memory usage can be tracked by the library.
245 
246   @param[in]  n    Number of units to allocate
247   @param[in]  unit Size of each unit
248   @param[out] p    Address of pointer to hold the result
249 
250   @return An error code: 0 - success, otherwise - failure
251 
252   @ref Backend
253 
254   @sa CeedFree()
255 **/
256 int CeedReallocArray(size_t n, size_t unit, void *p) {
257   *(void **)p = realloc(*(void **)p, n * unit);
258   CeedCheck(!n || !unit || *(void **)p, NULL, CEED_ERROR_MAJOR, "realloc failed to allocate %zd members of size %zd\n", n, unit);
259   return CEED_ERROR_SUCCESS;
260 }
261 
262 /**
263   @brief Allocate a cleared string buffer on the host.
264 
265   Memory usage can be tracked by the library.
266 
267   @param[in]  source Pointer to string to be copied
268   @param[out] copy   Pointer to variable to hold newly allocated string copy
269 
270   @return An error code: 0 - success, otherwise - failure
271 
272   @ref Backend
273 
274   @sa CeedFree()
275 **/
276 int CeedStringAllocCopy(const char *source, char **copy) {
277   size_t len = strlen(source);
278   CeedCall(CeedCalloc(len + 1, copy));
279   memcpy(*copy, source, len);
280   return CEED_ERROR_SUCCESS;
281 }
282 
283 /** Free memory allocated using @ref CeedMalloc() or @ref CeedCalloc()
284 
285   @param[in,out] p Address of pointer to memory.
286                      This argument is of type `void*` to avoid needing a cast, but is the address of the pointer (which is zeroed) rather than the pointer.
287 
288   @return An error code: 0 - success, otherwise - failure
289 
290   @ref Backend
291 **/
292 int CeedFree(void *p) {
293   free(*(void **)p);
294   *(void **)p = NULL;
295   return CEED_ERROR_SUCCESS;
296 }
297 
298 /**
299   @brief Register a `Ceed` backend
300 
301   @param[in] prefix   Prefix of resources for this backend to respond to.
302                         For example, the reference backend responds to "/cpu/self".
303   @param[in] init     Initialization function called by @ref CeedInit() when the backend is selected to drive the requested resource
304   @param[in] priority Integer priority.
305                         Lower values are preferred in case the resource requested by @ref CeedInit() has non-unique best prefix match.
306 
307   @return An error code: 0 - success, otherwise - failure
308 
309   @ref Backend
310 **/
311 int CeedRegister(const char *prefix, int (*init)(const char *, Ceed), unsigned int priority) {
312   CeedDebugEnv("Backend Register: %s", prefix);
313   CeedRegisterImpl(prefix, init, priority);
314   return CEED_ERROR_SUCCESS;
315 }
316 
317 /**
318   @brief Return debugging status flag
319 
320   @param[in]  ceed     `Ceed` context to get debugging flag
321   @param[out] is_debug Variable to store debugging flag
322 
323   @return An error code: 0 - success, otherwise - failure
324 
325   @ref Backend
326 **/
327 int CeedIsDebug(Ceed ceed, bool *is_debug) {
328   *is_debug = ceed->is_debug;
329   return CEED_ERROR_SUCCESS;
330 }
331 
332 /**
333   @brief Get the root of the requested resource
334 
335   @param[in]  ceed          `Ceed` context to get resource name of
336   @param[in]  resource      Full user specified resource
337   @param[in]  delineator    Delineator to break `resource_root` and `resource_spec`
338   @param[out] resource_root Variable to store resource root
339 
340   @return An error code: 0 - success, otherwise - failure
341 
342   @ref Backend
343 **/
344 int CeedGetResourceRoot(Ceed ceed, const char *resource, const char *delineator, char **resource_root) {
345   char  *device_spec       = strstr(resource, delineator);
346   size_t resource_root_len = device_spec ? (size_t)(device_spec - resource) + 1 : strlen(resource) + 1;
347 
348   CeedCall(CeedCalloc(resource_root_len, resource_root));
349   memcpy(*resource_root, resource, resource_root_len - 1);
350   return CEED_ERROR_SUCCESS;
351 }
352 
353 /**
354   @brief Retrieve a parent `Ceed` context
355 
356   @param[in]  ceed   `Ceed` context to retrieve parent of
357   @param[out] parent Address to save the parent to
358 
359   @return An error code: 0 - success, otherwise - failure
360 
361   @ref Backend
362 **/
363 int CeedGetParent(Ceed ceed, Ceed *parent) {
364   if (ceed->parent) {
365     CeedCall(CeedGetParent(ceed->parent, parent));
366     return CEED_ERROR_SUCCESS;
367   }
368   *parent = ceed;
369   return CEED_ERROR_SUCCESS;
370 }
371 
372 /**
373   @brief Retrieve a delegate `Ceed` context
374 
375   @param[in]  ceed     `Ceed` context to retrieve delegate of
376   @param[out] delegate Address to save the delegate to
377 
378   @return An error code: 0 - success, otherwise - failure
379 
380   @ref Backend
381 **/
382 int CeedGetDelegate(Ceed ceed, Ceed *delegate) {
383   *delegate = ceed->delegate;
384   return CEED_ERROR_SUCCESS;
385 }
386 
387 /**
388   @brief Set a delegate `Ceed` context
389 
390   This function allows a `Ceed` context to set a delegate `Ceed` context.
391   All backend implementations default to the delegate `Ceed` context, unless overridden.
392 
393   @param[in]  ceed     `Ceed` context to set delegate of
394   @param[out] delegate Address to set the delegate to
395 
396   @return An error code: 0 - success, otherwise - failure
397 
398   @ref Backend
399 **/
400 int CeedSetDelegate(Ceed ceed, Ceed delegate) {
401   ceed->delegate   = delegate;
402   delegate->parent = ceed;
403   return CEED_ERROR_SUCCESS;
404 }
405 
406 /**
407   @brief Retrieve a delegate `Ceed` context for a specific object type
408 
409   @param[in]  ceed     `Ceed` context to retrieve delegate of
410   @param[out] delegate Address to save the delegate to
411   @param[in]  obj_name Name of the object type to retrieve delegate for
412 
413   @return An error code: 0 - success, otherwise - failure
414 
415   @ref Backend
416 **/
417 int CeedGetObjectDelegate(Ceed ceed, Ceed *delegate, const char *obj_name) {
418   // Check for object delegate
419   for (CeedInt i = 0; i < ceed->obj_delegate_count; i++) {
420     if (!strcmp(obj_name, ceed->obj_delegates->obj_name)) {
421       *delegate = ceed->obj_delegates->delegate;
422       return CEED_ERROR_SUCCESS;
423     }
424   }
425 
426   // Use default delegate if no object delegate
427   CeedCall(CeedGetDelegate(ceed, delegate));
428   return CEED_ERROR_SUCCESS;
429 }
430 
431 /**
432   @brief Set a delegate `Ceed` context for a specific object type
433 
434   This function allows a `Ceed` context to set a delegate `Ceed` context for a given type of `Ceed` object.
435   All backend implementations default to the delegate `Ceed` context for this object.
436   For example, `CeedSetObjectDelegate(ceed, delegate, "Basis")` uses delegate implementations for all `CeedBasis` backend functions.
437 
438   @param[in,out] ceed     `Ceed` context to set delegate of
439   @param[in]     delegate `Ceed` context to use for delegation
440   @param[in]     obj_name Name of the object type to set delegate for
441 
442   @return An error code: 0 - success, otherwise - failure
443 
444   @ref Backend
445 **/
446 int CeedSetObjectDelegate(Ceed ceed, Ceed delegate, const char *obj_name) {
447   CeedInt count = ceed->obj_delegate_count;
448 
449   // Malloc or Realloc
450   if (count) {
451     CeedCall(CeedRealloc(count + 1, &ceed->obj_delegates));
452   } else {
453     CeedCall(CeedCalloc(1, &ceed->obj_delegates));
454   }
455   ceed->obj_delegate_count++;
456 
457   // Set object delegate
458   ceed->obj_delegates[count].delegate = delegate;
459   CeedCall(CeedStringAllocCopy(obj_name, &ceed->obj_delegates[count].obj_name));
460 
461   // Set delegate parent
462   delegate->parent = ceed;
463   return CEED_ERROR_SUCCESS;
464 }
465 
466 /**
467   @brief Get the fallback resource for `CeedOperator`
468 
469   @param[in]  ceed     `Ceed` context
470   @param[out] resource Variable to store fallback resource
471 
472   @return An error code: 0 - success, otherwise - failure
473 
474   @ref Backend
475 **/
476 int CeedGetOperatorFallbackResource(Ceed ceed, const char **resource) {
477   *resource = (const char *)ceed->op_fallback_resource;
478   return CEED_ERROR_SUCCESS;
479 }
480 
481 /**
482   @brief Get the fallback `Ceed` for `CeedOperator`
483 
484   @param[in]  ceed          `Ceed` context
485   @param[out] fallback_ceed Variable to store fallback `Ceed`
486 
487   @return An error code: 0 - success, otherwise - failure
488 
489   @ref Backend
490 **/
491 int CeedGetOperatorFallbackCeed(Ceed ceed, Ceed *fallback_ceed) {
492   if (ceed->has_valid_op_fallback_resource) {
493     CeedDebug256(ceed, CEED_DEBUG_COLOR_SUCCESS, "---------- CeedOperator Fallback ----------\n");
494     CeedDebug(ceed, "Getting fallback from %s to %s\n", ceed->resource, ceed->op_fallback_resource);
495   }
496 
497   // Create fallback Ceed if uninitalized
498   if (!ceed->op_fallback_ceed && ceed->has_valid_op_fallback_resource) {
499     CeedDebug(ceed, "Creating fallback Ceed");
500 
501     Ceed        fallback_ceed;
502     const char *fallback_resource;
503 
504     CeedCall(CeedGetOperatorFallbackResource(ceed, &fallback_resource));
505     CeedCall(CeedInit(fallback_resource, &fallback_ceed));
506     fallback_ceed->op_fallback_parent = ceed;
507     fallback_ceed->Error              = ceed->Error;
508     ceed->op_fallback_ceed            = fallback_ceed;
509   }
510   *fallback_ceed = ceed->op_fallback_ceed;
511   return CEED_ERROR_SUCCESS;
512 }
513 
514 /**
515   @brief Set the fallback resource for `CeedOperator`.
516 
517   The current resource, if any, is freed by calling this function.
518   This string is freed upon the destruction of the `Ceed` context.
519 
520   @param[in,out] ceed     `Ceed` context
521   @param[in]     resource Fallback resource to set
522 
523   @return An error code: 0 - success, otherwise - failure
524 
525   @ref Backend
526 **/
527 int CeedSetOperatorFallbackResource(Ceed ceed, const char *resource) {
528   // Free old
529   CeedCall(CeedFree(&ceed->op_fallback_resource));
530 
531   // Set new
532   CeedCall(CeedStringAllocCopy(resource, (char **)&ceed->op_fallback_resource));
533 
534   // Check validity
535   ceed->has_valid_op_fallback_resource = ceed->op_fallback_resource && ceed->resource && strcmp(ceed->op_fallback_resource, ceed->resource);
536   return CEED_ERROR_SUCCESS;
537 }
538 
539 /**
540   @brief Flag `Ceed` context as deterministic
541 
542   @param[in]  ceed             `Ceed` to flag as deterministic
543   @param[out] is_deterministic Deterministic status to set
544 
545   @return An error code: 0 - success, otherwise - failure
546 
547   @ref Backend
548 **/
549 int CeedSetDeterministic(Ceed ceed, bool is_deterministic) {
550   ceed->is_deterministic = is_deterministic;
551   return CEED_ERROR_SUCCESS;
552 }
553 
554 /**
555   @brief Set a backend function.
556 
557   This function is used for a backend to set the function associated with the Ceed objects.
558   For example, `CeedSetBackendFunction(ceed, "Ceed", ceed, "VectorCreate", BackendVectorCreate)` sets the backend implementation of @ref CeedVectorCreate() and `CeedSetBackendFunction(ceed, "Basis", basis, "Apply", BackendBasisApply)` sets the backend implementation of @ref CeedBasisApply().
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 CeedSetBackendFunctionImpl(Ceed ceed, const char *type, void *object, const char *func_name, void (*f)(void)) {
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 = (int (*)(void))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 array.
651 
652   @param[out] n          Number of available resources
653   @param[out] resources  List of available resource names
654   @param[out] priorities Resource name prioritization values, lower is better
655 
656   @return An error code: 0 - success, otherwise - failure
657 
658   @ref User
659 **/
660 // LCOV_EXCL_START
661 int CeedRegistryGetList(size_t *n, char ***const resources, CeedInt **priorities) {
662   *n         = 0;
663   *resources = malloc(num_backends * sizeof(**resources));
664   CeedCheck(resources, NULL, CEED_ERROR_MAJOR, "malloc() failure");
665   if (priorities) {
666     *priorities = malloc(num_backends * sizeof(**priorities));
667     CeedCheck(priorities, NULL, CEED_ERROR_MAJOR, "malloc() failure");
668   }
669   for (size_t i = 0; i < num_backends; i++) {
670     // Only report compiled backends
671     if (backends[i].priority < CEED_MAX_BACKEND_PRIORITY) {
672       *resources[i] = backends[i].prefix;
673       if (priorities) *priorities[i] = backends[i].priority;
674       *n += 1;
675     }
676   }
677   CeedCheck(*n, NULL, CEED_ERROR_MAJOR, "No backends installed");
678   *resources = realloc(*resources, *n * sizeof(**resources));
679   CeedCheck(resources, NULL, CEED_ERROR_MAJOR, "realloc() failure");
680   if (priorities) {
681     *priorities = realloc(*priorities, *n * sizeof(**priorities));
682     CeedCheck(priorities, NULL, CEED_ERROR_MAJOR, "realloc() failure");
683   }
684   return CEED_ERROR_SUCCESS;
685 }
686 // LCOV_EXCL_STOP
687 
688 /**
689   @brief Initialize a `Ceed` context to use the specified resource.
690 
691   Note: Prefixing the resource with "help:" (e.g. "help:/cpu/self") will result in @ref CeedInt() printing the current libCEED version number and a list of current available backend resources to `stderr`.
692 
693   @param[in]  resource Resource to use, e.g., "/cpu/self"
694   @param[out] ceed     The library context
695 
696   @return An error code: 0 - success, otherwise - failure
697 
698   @ref User
699 
700   @sa CeedRegister() CeedDestroy()
701 **/
702 int CeedInit(const char *resource, Ceed *ceed) {
703   size_t match_len = 0, match_index = UINT_MAX, match_priority = CEED_MAX_BACKEND_PRIORITY, priority;
704 
705   // Find matching backend
706   CeedCheck(resource, NULL, CEED_ERROR_MAJOR, "No resource provided");
707   CeedCall(CeedRegisterAll());
708 
709   // Check for help request
710   const char *help_prefix = "help";
711   size_t      match_help  = 0;
712   while (match_help < 4 && resource[match_help] == help_prefix[match_help]) match_help++;
713   if (match_help == 4) {
714     fprintf(stderr, "libCEED version: %d.%d%d%s\n", CEED_VERSION_MAJOR, CEED_VERSION_MINOR, CEED_VERSION_PATCH,
715             CEED_VERSION_RELEASE ? "" : "+development");
716     fprintf(stderr, "Available backend resources:\n");
717     for (size_t i = 0; i < num_backends; i++) {
718       // Only report compiled backends
719       if (backends[i].priority < CEED_MAX_BACKEND_PRIORITY) fprintf(stderr, "  %s\n", backends[i].prefix);
720     }
721     fflush(stderr);
722     match_help = 5;  // Delineating character expected
723   } else {
724     match_help = 0;
725   }
726 
727   // Find best match, computed as number of matching characters from requested resource stem
728   size_t stem_length = 0;
729   while (resource[stem_length + match_help] && resource[stem_length + match_help] != ':') stem_length++;
730   for (size_t i = 0; i < num_backends; i++) {
731     size_t      n      = 0;
732     const char *prefix = backends[i].prefix;
733     while (prefix[n] && prefix[n] == resource[n + match_help]) n++;
734     priority = backends[i].priority;
735     if (n > match_len || (n == match_len && match_priority > priority)) {
736       match_len      = n;
737       match_priority = priority;
738       match_index    = i;
739     }
740   }
741   // Using Levenshtein distance to find closest match
742   if (match_len <= 1 || match_len != stem_length) {
743     // LCOV_EXCL_START
744     size_t lev_dis   = UINT_MAX;
745     size_t lev_index = UINT_MAX, lev_priority = CEED_MAX_BACKEND_PRIORITY;
746     for (size_t i = 0; i < num_backends; i++) {
747       const char *prefix        = backends[i].prefix;
748       size_t      prefix_length = strlen(backends[i].prefix);
749       size_t      min_len       = (prefix_length < stem_length) ? prefix_length : stem_length;
750       size_t      column[min_len + 1];
751       for (size_t j = 0; j <= min_len; j++) column[j] = j;
752       for (size_t j = 1; j <= min_len; j++) {
753         column[0] = j;
754         for (size_t k = 1, last_diag = j - 1; k <= min_len; k++) {
755           size_t old_diag = column[k];
756           size_t min_1    = (column[k] < column[k - 1]) ? column[k] + 1 : column[k - 1] + 1;
757           size_t min_2    = last_diag + (resource[k - 1] == prefix[j - 1] ? 0 : 1);
758           column[k]       = (min_1 < min_2) ? min_1 : min_2;
759           last_diag       = old_diag;
760         }
761       }
762       size_t n = column[min_len];
763       priority = backends[i].priority;
764       if (n < lev_dis || (n == lev_dis && lev_priority > priority)) {
765         lev_dis      = n;
766         lev_priority = priority;
767         lev_index    = i;
768       }
769     }
770     const char *prefix_lev = backends[lev_index].prefix;
771     size_t      lev_length = 0;
772     while (prefix_lev[lev_length] && prefix_lev[lev_length] != '\0') lev_length++;
773     size_t m = (lev_length < stem_length) ? lev_length : stem_length;
774     if (lev_dis + 1 >= m) return CeedError(NULL, CEED_ERROR_MAJOR, "No suitable backend: %s", resource);
775     else return CeedError(NULL, CEED_ERROR_MAJOR, "No suitable backend: %s\nClosest match: %s", resource, backends[lev_index].prefix);
776     // LCOV_EXCL_STOP
777   }
778 
779   // Setup Ceed
780   CeedCall(CeedCalloc(1, ceed));
781   CeedCall(CeedCalloc(1, &(*ceed)->jit_source_roots));
782   const char *ceed_error_handler = getenv("CEED_ERROR_HANDLER");
783   if (!ceed_error_handler) ceed_error_handler = "abort";
784   if (!strcmp(ceed_error_handler, "exit")) (*ceed)->Error = CeedErrorExit;
785   else if (!strcmp(ceed_error_handler, "store")) (*ceed)->Error = CeedErrorStore;
786   else (*ceed)->Error = CeedErrorAbort;
787   memcpy((*ceed)->err_msg, "No error message stored", 24);
788   (*ceed)->ref_count = 1;
789   (*ceed)->data      = NULL;
790 
791   // Set lookup table
792   FOffset f_offsets[] = {
793       CEED_FTABLE_ENTRY(Ceed, Error),
794       CEED_FTABLE_ENTRY(Ceed, SetStream),
795       CEED_FTABLE_ENTRY(Ceed, GetPreferredMemType),
796       CEED_FTABLE_ENTRY(Ceed, Destroy),
797       CEED_FTABLE_ENTRY(Ceed, VectorCreate),
798       CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreate),
799       CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreateAtPoints),
800       CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreateBlocked),
801       CEED_FTABLE_ENTRY(Ceed, BasisCreateTensorH1),
802       CEED_FTABLE_ENTRY(Ceed, BasisCreateH1),
803       CEED_FTABLE_ENTRY(Ceed, BasisCreateHdiv),
804       CEED_FTABLE_ENTRY(Ceed, BasisCreateHcurl),
805       CEED_FTABLE_ENTRY(Ceed, TensorContractCreate),
806       CEED_FTABLE_ENTRY(Ceed, QFunctionCreate),
807       CEED_FTABLE_ENTRY(Ceed, QFunctionContextCreate),
808       CEED_FTABLE_ENTRY(Ceed, OperatorCreate),
809       CEED_FTABLE_ENTRY(Ceed, OperatorCreateAtPoints),
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 @ref 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` context
987 
988   @param[in]  ceed             `Ceed` context
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` context
1002 
1003   @param[in,out] ceed            `Ceed` context
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`
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   @return An error code: 0 - success, otherwise - failure
1101 
1102   @ref Developer
1103 **/
1104 int CeedErrorImpl(Ceed ceed, const char *filename, int lineno, const char *func, int ecode, const char *format, ...) {
1105   va_list args;
1106   int     ret_val;
1107 
1108   va_start(args, format);
1109   if (ceed) {
1110     ret_val = ceed->Error(ceed, filename, lineno, func, ecode, format, &args);
1111   } else {
1112     // LCOV_EXCL_START
1113     const char *ceed_error_handler = getenv("CEED_ERROR_HANDLER");
1114     if (!ceed_error_handler) ceed_error_handler = "abort";
1115     if (!strcmp(ceed_error_handler, "return")) {
1116       ret_val = CeedErrorReturn(ceed, filename, lineno, func, ecode, format, &args);
1117     } else {
1118       // This function will not return
1119       ret_val = CeedErrorAbort(ceed, filename, lineno, func, ecode, format, &args);
1120     }
1121   }
1122   va_end(args);
1123   return ret_val;
1124   // LCOV_EXCL_STOP
1125 }
1126 
1127 /**
1128   @brief Error handler that returns without printing anything.
1129 
1130   Pass this to @ref CeedSetErrorHandler() to obtain this error handling behavior.
1131 
1132   @return An error code: 0 - success, otherwise - failure
1133 
1134   @ref Developer
1135 **/
1136 // LCOV_EXCL_START
1137 int CeedErrorReturn(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1138   return err_code;
1139 }
1140 // LCOV_EXCL_STOP
1141 
1142 /**
1143   @brief Error handler that stores the error message for future use and returns the error.
1144 
1145   Pass this to @ref CeedSetErrorHandler() to obtain this error handling behavior.
1146 
1147   @return An error code: 0 - success, otherwise - failure
1148 
1149   @ref Developer
1150 **/
1151 // LCOV_EXCL_START
1152 int CeedErrorStore(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1153   if (ceed->parent) return CeedErrorStore(ceed->parent, filename, line_no, func, err_code, format, args);
1154   if (ceed->op_fallback_parent) return CeedErrorStore(ceed->op_fallback_parent, filename, line_no, func, err_code, format, args);
1155 
1156   // Build message
1157   int len = snprintf(ceed->err_msg, CEED_MAX_RESOURCE_LEN, "%s:%d in %s(): ", filename, line_no, func);
1158   // Using pointer to va_list for better FFI, but clang-tidy can't verify va_list is initalized
1159   vsnprintf(ceed->err_msg + len, CEED_MAX_RESOURCE_LEN - len, format, *args);  // NOLINT
1160   return err_code;
1161 }
1162 // LCOV_EXCL_STOP
1163 
1164 /**
1165   @brief Error handler that prints to `stderr` and aborts
1166 
1167   Pass this to @ref CeedSetErrorHandler() to obtain this error handling behavior.
1168 
1169   @return An error code: 0 - success, otherwise - failure
1170 
1171   @ref Developer
1172 **/
1173 // LCOV_EXCL_START
1174 int CeedErrorAbort(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   vfprintf(stderr, format, *args);
1177   fprintf(stderr, "\n");
1178   abort();
1179   return err_code;
1180 }
1181 // LCOV_EXCL_STOP
1182 
1183 /**
1184   @brief Error handler that prints to `stderr` and exits.
1185 
1186   Pass this to @ref CeedSetErrorHandler() to obtain this error handling behavior.
1187 
1188   In contrast to @ref CeedErrorAbort(), this exits without a signal, so `atexit()` handlers (e.g., as used by gcov) are run.
1189 
1190   @return An error code: 0 - success, otherwise - failure
1191 
1192   @ref Developer
1193 **/
1194 int CeedErrorExit(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
1195   fprintf(stderr, "%s:%d in %s(): ", filename, line_no, func);
1196   // Using pointer to va_list for better FFI, but clang-tidy can't verify va_list is initalized
1197   vfprintf(stderr, format, *args);  // NOLINT
1198   fprintf(stderr, "\n");
1199   exit(err_code);
1200   return err_code;
1201 }
1202 
1203 /**
1204   @brief Set error handler
1205 
1206   A default error handler is set in @ref CeedInit().
1207   Use this function to change the error handler to @ref CeedErrorReturn(), @ref CeedErrorAbort(), or a user-defined error handler.
1208 
1209   @return An error code: 0 - success, otherwise - failure
1210 
1211   @ref Developer
1212 **/
1213 int CeedSetErrorHandler(Ceed ceed, CeedErrorHandler handler) {
1214   ceed->Error = handler;
1215   if (ceed->delegate) CeedSetErrorHandler(ceed->delegate, handler);
1216   for (CeedInt i = 0; i < ceed->obj_delegate_count; i++) CeedSetErrorHandler(ceed->obj_delegates[i].delegate, handler);
1217   return CEED_ERROR_SUCCESS;
1218 }
1219 
1220 /**
1221   @brief Get error message
1222 
1223   The error message is only stored when using the error handler @ref CeedErrorStore()
1224 
1225   @param[in]  ceed    `Ceed` context to retrieve error message
1226   @param[out] err_msg Char pointer to hold error message
1227 
1228   @return An error code: 0 - success, otherwise - failure
1229 
1230   @ref Developer
1231 **/
1232 int CeedGetErrorMessage(Ceed ceed, const char **err_msg) {
1233   if (ceed->parent) return CeedGetErrorMessage(ceed->parent, err_msg);
1234   if (ceed->op_fallback_parent) return CeedGetErrorMessage(ceed->op_fallback_parent, err_msg);
1235   *err_msg = ceed->err_msg;
1236   return CEED_ERROR_SUCCESS;
1237 }
1238 
1239 /**
1240   @brief Restore error message.
1241 
1242   The error message is only stored when using the error handler @ref CeedErrorStore().
1243 
1244   @param[in]  ceed    `Ceed` context to restore error message
1245   @param[out] err_msg Char pointer that holds error message
1246 
1247   @return An error code: 0 - success, otherwise - failure
1248 
1249   @ref Developer
1250 **/
1251 int CeedResetErrorMessage(Ceed ceed, const char **err_msg) {
1252   if (ceed->parent) return CeedResetErrorMessage(ceed->parent, err_msg);
1253   if (ceed->op_fallback_parent) return CeedResetErrorMessage(ceed->op_fallback_parent, err_msg);
1254   *err_msg = NULL;
1255   memcpy(ceed->err_msg, "No error message stored", 24);
1256   return CEED_ERROR_SUCCESS;
1257 }
1258 
1259 /**
1260   @brief Get libCEED library version information.
1261 
1262   libCEED version numbers have the form major.minor.patch.
1263   Non-release versions may contain unstable interfaces.
1264 
1265   @param[out] major   Major version of the library
1266   @param[out] minor   Minor version of the library
1267   @param[out] patch   Patch (subminor) version of the library
1268   @param[out] release True for releases; false for development branches
1269 
1270   The caller may pass `NULL` for any arguments that are not needed.
1271 
1272   @return An error code: 0 - success, otherwise - failure
1273 
1274   @ref Developer
1275 
1276   @sa CEED_VERSION_GE()
1277 */
1278 int CeedGetVersion(int *major, int *minor, int *patch, bool *release) {
1279   if (major) *major = CEED_VERSION_MAJOR;
1280   if (minor) *minor = CEED_VERSION_MINOR;
1281   if (patch) *patch = CEED_VERSION_PATCH;
1282   if (release) *release = CEED_VERSION_RELEASE;
1283   return CEED_ERROR_SUCCESS;
1284 }
1285 
1286 /**
1287   @brief Get libCEED scalar type, such as F64 or F32
1288 
1289   @param[out] scalar_type Type of libCEED scalars
1290 
1291   @return An error code: 0 - success, otherwise - failure
1292 
1293   @ref Developer
1294 */
1295 int CeedGetScalarType(CeedScalarType *scalar_type) {
1296   *scalar_type = CEED_SCALAR_TYPE;
1297   return CEED_ERROR_SUCCESS;
1298 }
1299 
1300 /// @}
1301