xref: /libCEED/interface/ceed.c (revision 1f71eee278f25962fbc709e94b21d99c0bc8cbc2)
1 // Copyright (c) 2017, Lawrence Livermore National Security, LLC. Produced at
2 // the Lawrence Livermore National Laboratory. LLNL-CODE-734707. All Rights
3 // reserved. See files LICENSE and NOTICE for details.
4 //
5 // This file is part of CEED, a collection of benchmarks, miniapps, software
6 // libraries and APIs for efficient high-order finite element and spectral
7 // element discretizations for exascale applications. For more information and
8 // source code availability see http://github.com/ceed.
9 //
10 // The CEED research is supported by the Exascale Computing Project 17-SC-20-SC,
11 // a collaborative effort of two U.S. Department of Energy organizations (Office
12 // of Science and the National Nuclear Security Administration) responsible for
13 // the planning and preparation of a capable exascale ecosystem, including
14 // software, applications, hardware, advanced system engineering and early
15 // testbed platforms, in support of the nation's exascale computing imperative.
16 
17 #define _POSIX_C_SOURCE 200112
18 #include <ceed-impl.h>
19 #include <ceed-backend.h>
20 #include <limits.h>
21 #include <stdarg.h>
22 #include <stddef.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 
27 /// @cond DOXYGEN_SKIP
28 static CeedRequest ceed_request_immediate;
29 static CeedRequest ceed_request_ordered;
30 
31 static struct {
32   char prefix[CEED_MAX_RESOURCE_LEN];
33   int (*init)(const char *resource, Ceed f);
34   unsigned int priority;
35 } backends[32];
36 static size_t num_backends;
37 
38 #define CEED_FTABLE_ENTRY(class, method) \
39   {#class #method, offsetof(struct class ##_private, method)}
40 /// @endcond
41 
42 /// @file
43 /// Implementation of core components of Ceed library
44 ///
45 /// @addtogroup Ceed
46 /// @{
47 
48 /**
49   @brief Request immediate completion
50 
51   This predefined constant is passed as the \ref CeedRequest argument to
52   interfaces when the caller wishes for the operation to be performed
53   immediately.  The code
54 
55   @code
56     CeedOperatorApply(op, ..., CEED_REQUEST_IMMEDIATE);
57   @endcode
58 
59   is semantically equivalent to
60 
61   @code
62     CeedRequest request;
63     CeedOperatorApply(op, ..., &request);
64     CeedRequestWait(&request);
65   @endcode
66 
67   @sa CEED_REQUEST_ORDERED
68 **/
69 CeedRequest *const CEED_REQUEST_IMMEDIATE = &ceed_request_immediate;
70 
71 /**
72   @brief Request ordered completion
73 
74   This predefined constant is passed as the \ref CeedRequest argument to
75   interfaces when the caller wishes for the operation to be completed in the
76   order that it is submitted to the device.  It is typically used in a construct
77   such as
78 
79   @code
80     CeedRequest request;
81     CeedOperatorApply(op1, ..., CEED_REQUEST_ORDERED);
82     CeedOperatorApply(op2, ..., &request);
83     // other optional work
84     CeedWait(&request);
85   @endcode
86 
87   which allows the sequence to complete asynchronously but does not start
88   `op2` until `op1` has completed.
89 
90   @todo The current implementation is overly strict, offering equivalent
91   semantics to CEED_REQUEST_IMMEDIATE.
92 
93   @sa CEED_REQUEST_IMMEDIATE
94  */
95 CeedRequest *const CEED_REQUEST_ORDERED = &ceed_request_ordered;
96 
97 /**
98   @brief Error handling implementation; use \ref CeedError instead.
99 
100   @ref Developer
101 **/
102 int CeedErrorImpl(Ceed ceed, const char *filename, int lineno, const char *func,
103                   int ecode, const char *format, ...) {
104   va_list args;
105   int retval;
106   va_start(args, format);
107   if (ceed) {
108     retval = ceed->Error(ceed, filename, lineno, func, ecode, format, args);
109   } else {
110     // This function doesn't actually return
111     retval = CeedErrorAbort(ceed, filename, lineno, func, ecode, format, args);
112   }
113   va_end(args);
114   return retval;
115 }
116 
117 /**
118   @brief Error handler that returns without printing anything.
119 
120   Pass this to CeedSetErrorHandler() to obtain this error handling behavior.
121 
122   @ref Developer
123 **/
124 int CeedErrorReturn(Ceed ceed, const char *filename, int lineno,
125                     const char *func, int ecode, const char *format,
126                     va_list args) {
127   return ecode;
128 }
129 
130 /**
131   @brief Error handler that prints to stderr and aborts
132 
133   Pass this to CeedSetErrorHandler() to obtain this error handling behavior.
134 
135   @ref Developer
136 **/
137 int CeedErrorAbort(Ceed ceed, const char *filename, int lineno,
138                    const char *func, int ecode,
139                    const char *format, va_list args) {
140   fprintf(stderr, "%s:%d in %s(): ", filename, lineno, func);
141   vfprintf(stderr, format, args);
142   fprintf(stderr, "\n");
143   abort();
144   return ecode;
145 }
146 
147 /**
148   @brief Error handler that prints to stderr and exits
149 
150   Pass this to CeedSetErrorHandler() to obtain this error handling behavior.
151 
152   In contrast to CeedErrorAbort(), this exits without a signal, so atexit()
153   handlers (e.g., as used by gcov) are run.
154 
155   @ref Developer
156 **/
157 int CeedErrorExit(Ceed ceed, const char *filename, int lineno,
158                   const char *func, int ecode,
159                   const char *format, va_list args) {
160   fprintf(stderr, "%s:%d in %s(): ", filename, lineno, func);
161   vfprintf(stderr, format, args);
162   fprintf(stderr, "\n");
163   exit(ecode);
164   return ecode;
165 }
166 
167 /**
168   @brief Set error handler
169 
170   A default error handler is set in CeedInit().  Use this function to change
171   the error handler to CeedErrorReturn(), CeedErrorAbort(), or a user-defined
172   error handler.
173 
174   @ref Developer
175 **/
176 int CeedSetErrorHandler(Ceed ceed,
177                         int (eh)(Ceed, const char *, int, const char *,
178                                  int, const char *, va_list)) {
179   ceed->Error = eh;
180   return 0;
181 }
182 
183 /**
184   @brief Register a Ceed backend
185 
186   @param prefix   Prefix of resources for this backend to respond to.  For
187                     example, the reference backend responds to "/cpu/self".
188   @param init     Initialization function called by CeedInit() when the backend
189                     is selected to drive the requested resource.
190   @param priority Integer priority.  Lower values are preferred in case the
191                     resource requested by CeedInit() has non-unique best prefix
192                     match.
193 
194   @return An error code: 0 - success, otherwise - failure
195 
196   @ref Advanced
197 **/
198 int CeedRegister(const char *prefix,
199                  int (*init)(const char *, Ceed), unsigned int priority) {
200   if (num_backends >= sizeof(backends) / sizeof(backends[0]))
201     // LCOV_EXCL_START
202     return CeedError(NULL, 1, "Too many backends");
203   // LCOV_EXCL_STOP
204 
205   strncpy(backends[num_backends].prefix, prefix, CEED_MAX_RESOURCE_LEN);
206   backends[num_backends].prefix[CEED_MAX_RESOURCE_LEN-1] = 0;
207   backends[num_backends].init = init;
208   backends[num_backends].priority = priority;
209   num_backends++;
210   return 0;
211 }
212 
213 /**
214   @brief Allocate an array on the host; use CeedMalloc()
215 
216   Memory usage can be tracked by the library.  This ensures sufficient
217     alignment for vectorization and should be used for large allocations.
218 
219   @param n Number of units to allocate
220   @param unit Size of each unit
221   @param p Address of pointer to hold the result.
222 
223   @return An error code: 0 - success, otherwise - failure
224 
225   @sa CeedFree()
226 
227   @ref Advanced
228 **/
229 int CeedMallocArray(size_t n, size_t unit, void *p) {
230   int ierr = posix_memalign((void **)p, CEED_ALIGN, n*unit);
231   if (ierr)
232     // LCOV_EXCL_START
233     return CeedError(NULL, ierr,
234                      "posix_memalign failed to allocate %zd members of size %zd\n",
235                      n, unit);
236   // LCOV_EXCL_STOP
237 
238   return 0;
239 }
240 
241 /**
242   @brief Allocate a cleared (zeroed) array on the host; use CeedCalloc()
243 
244   Memory usage can be tracked by the library.
245 
246   @param n Number of units to allocate
247   @param unit Size of each unit
248   @param p Address of pointer to hold the result.
249 
250   @return An error code: 0 - success, otherwise - failure
251 
252   @sa CeedFree()
253 
254   @ref Advanced
255 **/
256 int CeedCallocArray(size_t n, size_t unit, void *p) {
257   *(void **)p = calloc(n, unit);
258   if (n && unit && !*(void **)p)
259     // LCOV_EXCL_START
260     return CeedError(NULL, 1,
261                      "calloc failed to allocate %zd members of size %zd\n",
262                      n, unit);
263   // LCOV_EXCL_STOP
264 
265   return 0;
266 }
267 
268 /**
269   @brief Reallocate an array on the host; use CeedRealloc()
270 
271   Memory usage can be tracked by the library.
272 
273   @param n Number of units to allocate
274   @param unit Size of each unit
275   @param p Address of pointer to hold the result.
276 
277   @return An error code: 0 - success, otherwise - failure
278 
279   @sa CeedFree()
280 
281   @ref Advanced
282 **/
283 int CeedReallocArray(size_t n, size_t unit, void *p) {
284   *(void **)p = realloc(*(void **)p, n*unit);
285   if (n && unit && !*(void **)p)
286     // LCOV_EXCL_START
287     return CeedError(NULL, 1,
288                      "realloc failed to allocate %zd members of size %zd\n",
289                      n, unit);
290   // LCOV_EXCL_STOP
291 
292   return 0;
293 }
294 
295 /// Free memory allocated using CeedMalloc() or CeedCalloc()
296 ///
297 /// @param p address of pointer to memory.  This argument is of type void* to
298 /// avoid needing a cast, but is the address of the pointer (which is zeroed)
299 /// rather than the pointer.
300 int CeedFree(void *p) {
301   free(*(void **)p);
302   *(void **)p = NULL;
303   return 0;
304 }
305 
306 /**
307   @brief Wait for a CeedRequest to complete.
308 
309   Calling CeedRequestWait on a NULL request is a no-op.
310 
311   @param req Address of CeedRequest to wait for; zeroed on completion.
312 
313   @return An error code: 0 - success, otherwise - failure
314 
315   @ref Advanced
316 **/
317 int CeedRequestWait(CeedRequest *req) {
318   if (!*req) return 0;
319   return CeedError(NULL, 2, "CeedRequestWait not implemented");
320 }
321 
322 /**
323   @brief Initialize a \ref Ceed to use the specified resource.
324 
325   @param resource  Resource to use, e.g., "/cpu/self"
326   @param ceed The library context
327   @sa CeedRegister() CeedDestroy()
328 
329   @return An error code: 0 - success, otherwise - failure
330 
331   @ref Basic
332 **/
333 int CeedInit(const char *resource, Ceed *ceed) {
334   int ierr;
335   size_t matchlen = 0, matchidx = UINT_MAX, matchpriority = UINT_MAX, priority;
336 
337   // Find matching backend
338   if (!resource) return CeedError(NULL, 1, "No resource provided");
339   for (size_t i=0; i<num_backends; i++) {
340     size_t n;
341     const char *prefix = backends[i].prefix;
342     for (n = 0; prefix[n] && prefix[n] == resource[n]; n++) {}
343     priority = backends[i].priority;
344     if (n > matchlen || (n == matchlen && matchpriority > priority)) {
345       matchlen = n;
346       matchpriority = priority;
347       matchidx = i;
348     }
349   }
350   if (!matchlen) return CeedError(NULL, 1, "No suitable backend");
351 
352   // Setup Ceed
353   ierr = CeedCalloc(1,ceed); CeedChk(ierr);
354   const char *ceed_error_handler = getenv("CEED_ERROR_HANDLER");
355   if (!ceed_error_handler) ceed_error_handler = "abort";
356   if (!strcmp(ceed_error_handler, "exit"))
357     (*ceed)->Error = CeedErrorExit;
358   else
359     (*ceed)->Error = CeedErrorAbort;
360   (*ceed)->refcount = 1;
361   (*ceed)->data = NULL;
362 
363   // Set lookup table
364   foffset foffsets[] = {
365     CEED_FTABLE_ENTRY(Ceed, Error),
366     CEED_FTABLE_ENTRY(Ceed, GetPreferredMemType),
367     CEED_FTABLE_ENTRY(Ceed, Destroy),
368     CEED_FTABLE_ENTRY(Ceed, VectorCreate),
369     CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreate),
370     CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreateBlocked),
371     CEED_FTABLE_ENTRY(Ceed, BasisCreateTensorH1),
372     CEED_FTABLE_ENTRY(Ceed, BasisCreateH1),
373     CEED_FTABLE_ENTRY(Ceed, TensorContractCreate),
374     CEED_FTABLE_ENTRY(Ceed, QFunctionCreate),
375     CEED_FTABLE_ENTRY(Ceed, OperatorCreate),
376     CEED_FTABLE_ENTRY(Ceed, CompositeOperatorCreate),
377     CEED_FTABLE_ENTRY(CeedVector, SetArray),
378     CEED_FTABLE_ENTRY(CeedVector, SetValue),
379     CEED_FTABLE_ENTRY(CeedVector, GetArray),
380     CEED_FTABLE_ENTRY(CeedVector, GetArrayRead),
381     CEED_FTABLE_ENTRY(CeedVector, RestoreArray),
382     CEED_FTABLE_ENTRY(CeedVector, RestoreArrayRead),
383     CEED_FTABLE_ENTRY(CeedVector, Destroy),
384     CEED_FTABLE_ENTRY(CeedElemRestriction, Apply),
385     CEED_FTABLE_ENTRY(CeedElemRestriction, ApplyBlock),
386     CEED_FTABLE_ENTRY(CeedElemRestriction, Destroy),
387     CEED_FTABLE_ENTRY(CeedBasis, Apply),
388     CEED_FTABLE_ENTRY(CeedBasis, Destroy),
389     CEED_FTABLE_ENTRY(CeedTensorContract, Apply),
390     CEED_FTABLE_ENTRY(CeedTensorContract, Destroy),
391     CEED_FTABLE_ENTRY(CeedQFunction, Apply),
392     CEED_FTABLE_ENTRY(CeedQFunction, Destroy),
393     CEED_FTABLE_ENTRY(CeedOperator, Apply),
394     CEED_FTABLE_ENTRY(CeedOperator, ApplyJacobian),
395     CEED_FTABLE_ENTRY(CeedOperator, Destroy),
396     {NULL, 0} // End of lookup table - used in SetBackendFunction loop
397   };
398 
399   ierr = CeedCalloc(sizeof(foffsets), &(*ceed)->foffsets); CeedChk(ierr);
400   memcpy((*ceed)->foffsets, foffsets, sizeof(foffsets));
401 
402   // Backend specific setup
403   ierr = backends[matchidx].init(resource, *ceed); CeedChk(ierr);
404 
405   return 0;
406 }
407 
408 /**
409   @brief Retrieve a parent CEED
410 
411   @param ceed           Ceed to retrieve parent of
412   @param[out] parent    Address to save the parent to
413 
414   @return An error code: 0 - success, otherwise - failure
415 
416   @ref Developer
417 **/
418 int CeedGetParent(Ceed ceed, Ceed *parent) {
419   int ierr;
420   if (ceed->parent) {
421     ierr = CeedGetParent(ceed->parent, parent); CeedChk(ierr);
422     return 0;
423   }
424   *parent = ceed;
425   return 0;
426 }
427 
428 /**
429   @brief Retrieve a delegate CEED
430 
431   @param ceed           Ceed to retrieve delegate of
432   @param[out] delegate  Address to save the delegate to
433 
434   @return An error code: 0 - success, otherwise - failure
435 
436   @ref Developer
437 **/
438 int CeedGetDelegate(Ceed ceed, Ceed *delegate) {
439   *delegate = ceed->delegate;
440   return 0;
441 }
442 
443 /**
444   @brief Set a delegate CEED
445 
446   This function allows a CEED to set a delegate CEED. All backend
447   implementations default to the delegate CEED, unless overridden.
448 
449   @param ceed           Ceed to set delegate of
450   @param[out] delegate  Address to set the delegate to
451 
452   @return An error code: 0 - success, otherwise - failure
453 
454   @ref Advanced
455 **/
456 int CeedSetDelegate(Ceed ceed, Ceed delegate) {
457   ceed->delegate = delegate;
458   delegate->parent = ceed;
459   return 0;
460 }
461 
462 /**
463   @brief Retrieve a delegate CEED for a specific object type
464 
465   @param ceed           Ceed to retrieve delegate of
466   @param[out] delegate  Address to save the delegate to
467   @param[in] objname    Name of the object type to retrieve delegate for
468 
469   @return An error code: 0 - success, otherwise - failure
470 
471   @ref Developer
472 **/
473 int CeedGetObjectDelegate(Ceed ceed, Ceed *delegate, const char *objname) {
474   CeedInt ierr;
475 
476   // Check for object delegate
477   for (CeedInt i=0; i<ceed->objdelegatecount; i++) {
478     if (!strcmp(objname, ceed->objdelegates->objname)) {
479       *delegate = ceed->objdelegates->delegate;
480       return 0;
481     }
482   }
483 
484   // Use default delegate if no object delegate
485   ierr = CeedGetDelegate(ceed, delegate); CeedChk(ierr);
486 
487   return 0;
488 }
489 
490 /**
491   @brief Set a delegate CEED for a specific object type
492 
493   This function allows a CEED to set a delegate CEED for a given type of
494   CEED object. All backend implementations default to the delegate CEED for
495   this object. For example,
496     CeedSetObjectDelegate(ceed, refceed, "Basis")
497   uses refceed implementations for all CeedBasis backend functions.
498 
499   @param ceed           Ceed to set delegate of
500   @param[out] delegate  Address to set the delegate to
501   @param[in] objname    Name of the object type to set delegate for
502 
503   @return An error code: 0 - success, otherwise - failure
504 
505   @ref Advanced
506 **/
507 int CeedSetObjectDelegate(Ceed ceed, Ceed delegate, const char *objname) {
508   CeedInt ierr;
509   CeedInt count = ceed->objdelegatecount;
510 
511   // Malloc or Realloc
512   if (count) {
513     ierr = CeedRealloc(count+1, &ceed->objdelegates);
514     CeedChk(ierr);
515   } else {
516     ierr = CeedCalloc(1, &ceed->objdelegates); CeedChk(ierr);
517   }
518   ceed->objdelegatecount++;
519 
520   // Set object delegate
521   ceed->objdelegates[count].delegate = delegate;
522   size_t slen = strlen(objname) + 1;
523   ierr = CeedMalloc(slen, &ceed->objdelegates[count].objname); CeedChk(ierr);
524   memcpy(ceed->objdelegates[count].objname, objname, slen);
525 
526   // Set delegate parent
527   delegate->parent = ceed;
528 
529   return 0;
530 }
531 
532 /**
533   @brief Return Ceed preferred memory type
534 
535   @param ceed           Ceed to get preferred memory type of
536   @param[out] type      Address to save preferred memory type to
537 
538   @return An error code: 0 - success, otherwise - failure
539 
540   @ref Basic
541 **/
542 int CeedGetPreferredMemType(Ceed ceed, CeedMemType *type) {
543   int ierr;
544 
545   if (ceed->GetPreferredMemType) {
546     ierr = ceed->GetPreferredMemType(type); CeedChk(ierr);
547   } else {
548     Ceed delegate;
549     ierr = CeedGetDelegate(ceed, &delegate); CeedChk(ierr);
550 
551     if (delegate) {
552       ierr = CeedGetPreferredMemType(delegate, type); CeedChk(ierr);
553     } else {
554       *type = CEED_MEM_HOST;
555     }
556   }
557 
558   return 0;
559 }
560 
561 /**
562   @brief Set a backend function
563 
564   This function is used for a backend to set the function associated with
565   the CEED objects. For example,
566     CeedSetBackendFunction(ceed, "Ceed", ceed, "VectorCreate", BackendVectorCreate)
567   sets the backend implementation of 'CeedVectorCreate' and
568     CeedSetBackendFunction(ceed, "Basis", basis, "Apply", BackendBasisApply)
569   sets the backend implementation of 'CeedBasisApply'. Note, the prefix 'Ceed'
570   is not required for the object type ("Basis" vs "CeedBasis").
571 
572   @param ceed           Ceed for error handling
573   @param type           Type of Ceed object to set function for
574   @param[out] object    Ceed object to set function for
575   @param fname          Name of function to set
576   @param f              Function to set
577 
578   @return An error code: 0 - success, otherwise - failure
579 
580   @ref Advanced
581 **/
582 int CeedSetBackendFunction(Ceed ceed,
583                            const char *type, void *object,
584                            const char *fname, int (*f)()) {
585   char lookupname[CEED_MAX_RESOURCE_LEN+1] = "";
586 
587   // Build lookup name
588   if (strcmp(type, "Ceed"))
589     strncat (lookupname, "Ceed", CEED_MAX_RESOURCE_LEN);
590   strncat(lookupname, type, CEED_MAX_RESOURCE_LEN);
591   strncat(lookupname, fname, CEED_MAX_RESOURCE_LEN);
592 
593   // Find and use offset
594   for (CeedInt i = 0; ceed->foffsets[i].fname; i++) {
595     if (!strcmp(ceed->foffsets[i].fname, lookupname)) {
596       size_t offset = ceed->foffsets[i].offset;
597       int (**fpointer)(void) = (int (* *)(void))((char *)object + offset);
598       *fpointer = f;
599       return 0;
600     }
601   }
602 
603   // LCOV_EXCL_START
604   return CeedError(ceed, 1,
605                    "Requested function '%s' was not found for CEED object '%s'",
606                    fname, type);
607   // LCOV_EXCL_STOP
608 }
609 
610 /**
611   @brief Retrieve backend data for a CEED
612 
613   @param ceed           Ceed to retrieve data of
614   @param[out] data      Address to save data to
615 
616   @return An error code: 0 - success, otherwise - failure
617 
618   @ref Advanced
619 **/
620 int CeedGetData(Ceed ceed, void* *data) {
621   *data = ceed->data;
622   return 0;
623 }
624 
625 /**
626   @brief Set backend data for a CEED
627 
628   @param ceed           Ceed to set data of
629   @param data           Address of data to set
630 
631   @return An error code: 0 - success, otherwise - failure
632 
633   @ref Advanced
634 **/
635 int CeedSetData(Ceed ceed, void* *data) {
636   ceed->data = *data;
637   return 0;
638 }
639 
640 /**
641   @brief Destroy a Ceed context
642 
643   @param ceed Address of Ceed context to destroy
644 
645   @return An error code: 0 - success, otherwise - failure
646 
647   @ref Basic
648 **/
649 int CeedDestroy(Ceed *ceed) {
650   int ierr;
651 
652   if (!*ceed || --(*ceed)->refcount > 0) return 0;
653   if ((*ceed)->delegate) {
654     ierr = CeedDestroy(&(*ceed)->delegate); CeedChk(ierr);
655   }
656   if ((*ceed)->objdelegatecount > 0) {
657     for (int i=0; i<(*ceed)->objdelegatecount; i++) {
658       ierr = CeedDestroy(&((*ceed)->objdelegates[i].delegate)); CeedChk(ierr);
659       ierr = CeedFree(&(*ceed)->objdelegates[i].objname); CeedChk(ierr);
660     }
661     ierr = CeedFree(&(*ceed)->objdelegates); CeedChk(ierr);
662   }
663   if ((*ceed)->Destroy) {
664     ierr = (*ceed)->Destroy(*ceed); CeedChk(ierr);
665   }
666   ierr = CeedFree(&(*ceed)->foffsets); CeedChk(ierr);
667   ierr = CeedFree(ceed); CeedChk(ierr);
668   return 0;
669 }
670 
671 /// @}
672