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