1 /*============================================================================
2  * Base memory allocation wrappers with optional tracing
3  *============================================================================*/
4 
5 /*
6   This file is part of Code_Saturne, a general-purpose CFD tool.
7 
8   Copyright (C) 1998-2021 EDF S.A.
9 
10   This program is free software; you can redistribute it and/or modify it under
11   the terms of the GNU General Public License as published by the Free Software
12   Foundation; either version 2 of the License, or (at your option) any later
13   version.
14 
15   This program is distributed in the hope that it will be useful, but WITHOUT
16   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17   FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
18   details.
19 
20   You should have received a copy of the GNU General Public License along with
21   this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
22   Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 */
24 
25 /*----------------------------------------------------------------------------*/
26 
27 /*
28   Define _GNU_SOURCE if necessary before including any headers, to ensure
29   the correct feature macros are defined first.
30 */
31 
32 #if defined(__linux__)
33 #  define _GNU_SOURCE
34 #endif
35 
36 #include "cs_defs.h"
37 
38 /*-----------------------------------------------------------------------------*/
39 
40 /*
41  * Standard C library headers
42  */
43 
44 #include <assert.h>
45 #include <errno.h>
46 #include <stdlib.h>
47 #include <stdio.h>
48 #include <string.h>
49 #include <stdlib.h>
50 
51 /*
52  * Optional library and BFT headers
53  */
54 
55 #include "bft_error.h"
56 #include "bft_mem_usage.h"
57 #include "bft_mem.h"
58 #include "bft_printf.h"
59 
60 /*-----------------------------------------------------------------------------*/
61 
62 BEGIN_C_DECLS
63 
64 /*=============================================================================
65  * Additional doxygen documentation
66  *============================================================================*/
67 
68 /*!
69   \file bft_mem.c
70         Base memory allocation wrappers with optional tracing.
71 
72   The memory managment function provided here provide optional logging,
73   and tracking of non-freed pointers.
74 
75   Since in most of the intended applications, failure to allocate memory
76   is considered fatal, failed allocations from these functions are
77   considedered as errors, which are fatal by default but can be handled
78   differently if an appropriate error handler is defined. So additional
79   checking of the return values in the calling code is not needed.
80 
81   The functions provided here are otherwise based on the matching C library
82   functions.
83 */
84 
85 /*-------------------------------------------------------------------------------
86  * Local macro documentation
87  *-----------------------------------------------------------------------------*/
88 
89 /*! \fn BFT_MALLOC(_ptr, _ni, _type)
90  * \brief Allocate memory for _ni elements of type _type.
91  *
92  * This macro calls bft_mem_malloc(), automatically setting the
93  * allocated variable name and source file name and line arguments.
94  *
95  * \param [out] _ptr  pointer to allocated memory.
96  * \param [in]  _ni   number of elements.
97  * \param [in]  _type element type.
98  */
99 
100 /*! \fn BFT_REALLOC(_ptr, _ni, _type)
101  * \brief Reallocate memory for _ni elements of type _type.
102  *
103  * This macro calls bft_mem_realloc(), automatically setting the
104  * allocated variable name and source file name and line arguments.
105  *
106  * \param [in, out] _ptr  pointer to allocated memory.
107  * \param [in]      _ni   number of elements.
108  * \param [in]      _type element type.
109  */
110 
111 /*! \fn BFT_FREE(_ptr)
112  * \brief Free allocated memory.
113  *
114  * This macro calls bft_mem_free(), automatically setting the
115  * allocated variable name and source file name and line arguments.
116  *
117  * The freed pointer is set to NULL to avoid accidental reuse.
118  *
119  * \param [in, out] _ptr  pointer to allocated memory.
120  */
121 
122 /*! \fn BFT_MEMALIGN(_ptr, _align, _ni, _type)
123  * \brief Allocate aligned memory for _ni elements of type _type.
124  *
125  * This macro calls bft_mem_memalign(), automatically setting the
126  * allocated variable name and source file name and line arguments.
127  *
128  * \param [out] _ptr   pointer to allocated memory.
129  * \param [in]  _align alignment.
130  * \param [in]  _ni    number of elements.
131  * \param [in]  _type  element type.
132  */
133 
134 /*! \cond DOXYGEN_SHOULD_SKIP_THIS */
135 
136 /*-------------------------------------------------------------------------------
137  * Local macro definitions
138  *-----------------------------------------------------------------------------*/
139 
140 /* Directory name separator
141    (historically, '/' for Unix/Linux, '\' for Windows, ':' for Mac
142    but '/' should work for all on modern systems) */
143 
144 #define DIR_SEPARATOR '/'
145 
146 /*-------------------------------------------------------------------------------
147  * Local type definitions
148  *-----------------------------------------------------------------------------*/
149 
150 /*
151  * Structure defining an allocated memory block (for memory tracing)
152  */
153 
154 struct _bft_mem_block_t {
155 
156   void    *p_bloc;  /* Allocated memory block start adress */
157   size_t   size;    /* Allocated memory block length */
158 
159 };
160 
161 /*-----------------------------------------------------------------------------
162  * Local function prototypes
163  *-----------------------------------------------------------------------------*/
164 
165 /*
166  * Default memory error handler.
167  *
168  * Memory status is written to stderr, and the general error handler
169  * used by bft_error() is then called (which results in the
170  * termination of the current process).
171  *
172  * parameters:
173  *   file_name:      <-- name of source file from which error handler called.
174  *   line_num:       <-- line of source file from which error handler called.
175  *   sys_error_code: <-- error code if error in system or libc call, 0 otherwise.
176  *   format:         <-- format string, as printf() and family.
177  *   arg_ptr:        <-> variable argument list based on format string.
178  */
179 
180 static void
181 _bft_mem_error_handler_default(const char  *file_name,
182                                int          line_num,
183                                int          sys_error_code,
184                                const char  *format,
185                                va_list      arg_ptr);
186 
187 /*-----------------------------------------------------------------------------
188  * Local static variable definitions
189  *-----------------------------------------------------------------------------*/
190 
191 static int  _bft_mem_global_initialized = 0;
192 
193 static FILE *_bft_mem_global_file = NULL;
194 
195 static struct _bft_mem_block_t  *_bft_mem_global_block_array = NULL;
196 
197 static unsigned long  _bft_mem_global_block_nbr = 0 ;
198 static unsigned long  _bft_mem_global_block_max = 512 ;
199 
200 static size_t  _bft_mem_global_alloc_cur = 0;
201 static size_t  _bft_mem_global_alloc_max = 0;
202 
203 static size_t  _bft_mem_global_n_allocs = 0;
204 static size_t  _bft_mem_global_n_reallocs = 0;
205 static size_t  _bft_mem_global_n_frees = 0;
206 
207 static bft_error_handler_t  *_bft_mem_error_handler
208                               = (_bft_mem_error_handler_default);
209 
210 static bft_mem_get_size_t  *_bft_alt_get_size_func = NULL;
211 static bft_mem_realloc_t   *_bft_alt_realloc_func = NULL;
212 static bft_mem_free_t      *_bft_alt_free_func = NULL;
213 
214 #if defined(HAVE_OPENMP)
215 static omp_lock_t _bft_mem_lock;
216 #endif
217 
218 /*-----------------------------------------------------------------------------
219  * Local function definitions
220  *-----------------------------------------------------------------------------*/
221 
222 /*
223  * Given a character string representing a file name, returns
224  * pointer to that part of the string corresponding to the base name.
225  *
226  * parameters:
227  *   file_name:      <-- full name of source file.
228  *
229  * return:
230  *   pointer to part of file name corresponding to base name.
231  */
232 
233 static const char *
_bft_mem_basename(const char * file_name)234 _bft_mem_basename(const char  *file_name)
235 {
236   int i;
237 
238   if (file_name == NULL)
239     return NULL;
240 
241   for (i = strlen(file_name) - 1;
242        i > 0 && file_name[i] != DIR_SEPARATOR;
243        i--);
244 
245   if (file_name[i] == DIR_SEPARATOR)
246     i++;
247 
248   return (file_name + i);
249 }
250 
251 /*
252  * Determines values associated with an array representing a
253  * long integer
254  *
255  * parameters:
256  *   counter: <-- counter to update.
257  *   value:   --> counter values in output unit [out].
258  *   unit:    --> counter value unit : ' ', 'k', 'm', 'g', 't', or 'p'
259  *                for bytes, Kilobytes, Megabytes, Gigabytes, Terabytes,
260  *                or Petabytes [out].
261  */
262 
263 static void
_bft_mem_size_val(const size_t counter,unsigned long value[2],char * unit)264 _bft_mem_size_val(const size_t    counter,
265                   unsigned long   value[2],
266                   char           *unit)
267 {
268   int i;
269   size_t _counter[2];
270   const char units[] = {' ', 'k', 'm', 'g', 't', 'p', 'e'};
271 
272   for (i = 0, _counter[0] = counter, _counter[1] = 0;
273        _counter[0] >= 1024 && i < 6;
274        i++) {
275     _counter[1] = _counter[0] % 1024;
276     _counter[0] /= 1024;
277   }
278 
279   value[0] = _counter[0];
280   value[1] = _counter[1];
281   *unit = units[i];
282 }
283 
284 /*
285  * Memory usage summary.
286  */
287 
_bft_mem_summary(FILE * f)288 static void _bft_mem_summary(FILE  *f)
289 {
290   char unit;
291   unsigned long value[2];
292   size_t mem_usage;
293 
294   if (f == NULL)
295     return;
296 
297   fprintf(f, "\n\n");
298   fprintf(f, "Memory allocation summary\n"
299           "-------------------------\n\n");
300 
301   /* Available memory usage information */
302 
303   _bft_mem_size_val(_bft_mem_global_alloc_cur, value, &unit);
304   fprintf(f,
305           "Theoretical current allocated memory:   %8lu.%lu %cB\n",
306           value[0], value[1], unit);
307 
308   _bft_mem_size_val(_bft_mem_global_alloc_max, value, &unit);
309   fprintf(f,
310           "Theoretical maximum allocated memory:   %8lu.%lu %cB\n",
311           value[0], value[1], unit);
312 
313   fprintf(f,
314           "\n"
315           "Number of allocations:   %lu\n"
316           "          reallocations: %lu\n"
317           "          frees:         %lu\n\n",
318           (unsigned long)_bft_mem_global_n_allocs,
319           (unsigned long)_bft_mem_global_n_reallocs,
320           (unsigned long)_bft_mem_global_n_frees);
321 
322   if (bft_mem_usage_initialized() == 1) {
323 
324     /* Maximum measured memory */
325 
326     mem_usage = bft_mem_usage_max_pr_size();
327     if (mem_usage > 0) {
328       fprintf(f,
329               "Maximum program memory measure:  %8lu kB\n",
330               (unsigned long)mem_usage);
331     }
332 
333     /* Current measured memory */
334 
335     mem_usage = bft_mem_usage_pr_size();
336     if (mem_usage > 0)
337       fprintf(f,
338               "Current program memory measure:   %8lu kB\n",
339               (unsigned long)mem_usage);
340   }
341 
342 }
343 
344 /*
345  * Default memory error handler.
346  *
347  * Memory status is written to stderr (after bft_print_flush() is called),
348  * and the general error handler used by bft_error() is then called
349  * (which results in the termination of the current process).
350  *
351  * parameters:
352  *   file_name:      <-- name of source file from which error handler called.
353  *   line_num:       <-- line of source file from which error handler called.
354  *   sys_error_code: <-- error code if error in system or libc call, 0 otherwise.
355  *   format:         <-- format string, as printf() and family.
356  *   arg_ptr:        <-> variable argument list based on format string.
357  */
358 
359 static void
_bft_mem_error_handler_default(const char * file_name,int line_num,int sys_error_code,const char * format,va_list arg_ptr)360 _bft_mem_error_handler_default(const char  *file_name,
361                                int          line_num,
362                                int          sys_error_code,
363                                const char  *format,
364                                va_list      arg_ptr)
365 {
366   bft_error_handler_t * general_err_handler;
367 
368   bft_printf_flush();
369 
370   _bft_mem_summary(stderr);
371 
372   general_err_handler = bft_error_handler_get();
373   general_err_handler(file_name, line_num, sys_error_code, format, arg_ptr);
374 }
375 
376 /*
377  * Calls the error handler (set by bft_mem_error_handler_set() or default).
378  *
379  * With the default error handler, an error message is output to stderr,
380  * (after bft_print_flush() is called), and the general error handler used
381  * by bft_error() is then called (which results in the termination of the
382  * current process or process group).
383  *
384  * parameters:
385  *   file_name      <-- name of source file from which failed bft_mem_...()
386  *                      function was called.
387  *   line_num       <-- line of source file from which failed bft_mem_...()
388  *                      function was called.
389  *   sys_error_code <-- error code if error in system or libc call,
390  *                      0 otherwise.
391  *   format         <-- format string, as printf() and family.
392  *   ...            <-- variable arguments based on format string.
393  */
394 
395 static void
_bft_mem_error(const char * file_name,int line_num,int sys_error_code,const char * format,...)396 _bft_mem_error(const char  *file_name,
397                int          line_num,
398                int          sys_error_code,
399                const char  *format,
400                ...)
401 {
402   va_list  arg_ptr;
403 
404   if (_bft_mem_global_file != NULL) {
405     _bft_mem_summary(_bft_mem_global_file);
406     fflush(_bft_mem_global_file);
407   }
408 
409   va_start(arg_ptr, format);
410 
411   _bft_mem_error_handler(file_name, line_num, sys_error_code, format, arg_ptr);
412 
413   va_end(arg_ptr);
414 }
415 
416 /*
417  * Call error function when pointer is not found in allocation info.
418  *
419  * parameters:
420  *   p: <-- allocated block's start adress.
421  *
422  * returns:
423  *   corresponding _bft_mem_block structure.
424  */
425 
426 static void
_bft_mem_block_info_error(const void * p)427 _bft_mem_block_info_error(const void  *p)
428 {
429   _bft_mem_error(__FILE__, __LINE__, 0,
430                  _("Adress [%p] does not correspond to "
431                    "the beginning of an allocated block."),
432                  p);
433 }
434 
435 /*
436  * Return the _bft_mem_block structure corresponding to a given
437  * allocated block.
438  *
439  * parameters:
440  *   p_in: <-- allocated block's start adress.
441  *
442  * returns:
443  *   corresponding _bft_mem_block structure.
444  */
445 
446 static struct _bft_mem_block_t *
_bft_mem_block_info(const void * p_get)447 _bft_mem_block_info(const void *p_get)
448 {
449   struct _bft_mem_block_t  *pinfo = NULL;
450   unsigned long idx;
451 
452   if (_bft_mem_global_block_array != NULL) {
453 
454     for (idx = _bft_mem_global_block_nbr - 1;
455          idx > 0 && (_bft_mem_global_block_array + idx)->p_bloc != p_get;
456          idx--);
457 
458     if ((_bft_mem_global_block_array + idx)->p_bloc != p_get)
459       _bft_mem_block_info_error(p_get);
460     else {
461       pinfo = _bft_mem_global_block_array + idx;
462       assert(p_get == pinfo->p_bloc);
463     }
464 
465   }
466 
467   return pinfo;
468 }
469 
470 /*
471  * Return the _bft_mem_block structure corresponding to a given
472  * allocated block if available.
473  *
474  * parameters:
475  *   p_in: <-- allocated block's start adress.
476  *
477  * returns:
478  *   corresponding _bft_mem_block structure.
479  */
480 
481 static struct _bft_mem_block_t *
_bft_mem_block_info_try(const void * p_get)482 _bft_mem_block_info_try(const void *p_get)
483 {
484   struct _bft_mem_block_t  *pinfo = NULL;
485 
486   if (_bft_mem_global_block_array != NULL) {
487 
488     unsigned long idx = _bft_mem_global_block_nbr - 1;
489     while (idx > 0 && (_bft_mem_global_block_array + idx)->p_bloc != p_get)
490       idx--;
491 
492     if ((_bft_mem_global_block_array + idx)->p_bloc == p_get) {
493       pinfo = _bft_mem_global_block_array + idx;
494       assert(p_get == pinfo->p_bloc);
495     }
496 
497   }
498 
499   return pinfo;
500 }
501 
502 /*
503  * Return the size of a given allocated block.
504  *
505  * parameters:
506  *   p_in: <-- allocated block's start adress.
507  *
508  * returns:
509  *   block size.
510  */
511 
512 static size_t
_bft_mem_block_size(const void * p_in)513 _bft_mem_block_size(const void  *p_in)
514 {
515   struct _bft_mem_block_t *pinfo = _bft_mem_block_info(p_in);
516 
517   if (pinfo != NULL)
518     return pinfo->size;
519   else
520     return 0;
521 }
522 
523 /*
524  * Fill a _bft_mem_block_t structure for an allocated pointer.
525  */
526 
527 static void
_bft_mem_block_malloc(void * p_new,const size_t size_new)528 _bft_mem_block_malloc(void          *p_new,
529                       const size_t   size_new)
530 {
531   struct _bft_mem_block_t *pinfo;
532 
533   assert(size_new != 0);
534 
535   if (_bft_mem_global_block_array == NULL)
536     return;
537 
538   if (_bft_mem_global_block_nbr >= _bft_mem_global_block_max) {
539 
540     _bft_mem_global_block_max *= 2;
541     _bft_mem_global_block_array
542       = (struct _bft_mem_block_t *) realloc(_bft_mem_global_block_array,
543                                             sizeof(struct _bft_mem_block_t)
544                                             * _bft_mem_global_block_max);
545 
546     if (_bft_mem_global_block_array == NULL) {
547       _bft_mem_error(__FILE__, __LINE__, errno,
548                      _("Memory allocation failure"));
549       return;
550     }
551 
552   }
553 
554   _bft_mem_global_block_nbr += 1;
555 
556   pinfo = _bft_mem_global_block_array + _bft_mem_global_block_nbr - 1;
557 
558   /* Start adress and size of allocated block */
559 
560   pinfo->p_bloc = p_new;
561   pinfo->size   = size_new;
562 }
563 
564 /*
565  * Update a _bft_mem_block_t structure for an reallocated pointer.
566  */
567 
568 static void
_bft_mem_block_realloc(const void * p_old,void * p_new,size_t size_new)569 _bft_mem_block_realloc(const void    *p_old,
570                        void          *p_new,
571                        size_t         size_new)
572 {
573   struct _bft_mem_block_t *pinfo;
574 
575   assert(size_new != 0);
576 
577   pinfo = _bft_mem_block_info(p_old);
578 
579   if (pinfo != NULL) {
580     pinfo->p_bloc = p_new;
581     pinfo->size   = size_new;
582   }
583 }
584 
585 /*
586  * Free a _bft_mem_block_t structure for a freed pointer.
587  */
588 
589 static void
_bft_mem_block_free(const void * p_free)590 _bft_mem_block_free(const void *p_free)
591 {
592   struct _bft_mem_block_t *pinfo, *pmove;
593   unsigned long idx;
594 
595   if (_bft_mem_global_block_array == NULL)
596     return;
597 
598   for (idx = _bft_mem_global_block_nbr - 1;
599        idx > 0 && (_bft_mem_global_block_array + idx)->p_bloc != p_free;
600        idx--);
601 
602   if ((_bft_mem_global_block_array + idx)->p_bloc != p_free)
603     _bft_mem_error(__FILE__, __LINE__, 0,
604                    _("Adress [%10p] does not correspond to "
605                      "the beginning of an allocated block."),
606                    p_free);
607 
608   else {
609 
610     /* We move the contents of the array's final block to the position
611        of the freed block, and shorten the array's useful part by one. */
612 
613     pinfo = _bft_mem_global_block_array + idx;
614     pmove = _bft_mem_global_block_array + _bft_mem_global_block_nbr - 1;
615     pinfo->p_bloc = pmove->p_bloc;
616     pinfo->size   = pmove->size;
617 
618     _bft_mem_global_block_nbr -= 1;
619 
620   }
621 }
622 
623 /*! (DOXYGEN_SHOULD_SKIP_THIS) \endcond */
624 
625 /*============================================================================
626  * Public function definitions
627  *============================================================================*/
628 
629 /*!
630  * \brief Initialize memory handling.
631  *
632  * This function should be called before any other bft_mem_...()
633  * function. To activate memory allocation logging, a logfile
634  * name should be given as an argument. The resulting file will
635  * be a regular, local file. If this file cannot be opened for
636  * some reason, logging is silently de-activated.
637  *
638  * \param log_file_name name of optional log_file (if NULL, no log).
639  */
640 
641 void
bft_mem_init(const char * log_file_name)642 bft_mem_init(const char *log_file_name)
643 {
644   size_t alloc_size;
645 
646 #if defined(HAVE_OPENMP)
647   if (omp_in_parallel()) {
648     if (omp_get_thread_num() != 0)
649       return;
650   }
651   omp_init_lock(&_bft_mem_lock);
652 #endif
653 
654   if (_bft_mem_global_initialized == 1) {
655     _bft_mem_error(__FILE__, __LINE__, 0,
656                    _("bft_mem_init() has already been called"));
657   }
658   _bft_mem_global_initialized = 1;
659 
660   alloc_size = sizeof(struct _bft_mem_block_t) * _bft_mem_global_block_max;
661 
662   _bft_mem_global_block_array
663     = malloc(sizeof(struct _bft_mem_block_t) * _bft_mem_global_block_max);
664 
665   if (_bft_mem_global_block_array == NULL) {
666     _bft_mem_error(__FILE__, __LINE__, errno,
667                    _("Failure to allocate \"%s\" (%lu bytes)"),
668                    "_bft_mem_global_block_array", (unsigned long)alloc_size);
669     return;
670   }
671 
672   if (log_file_name != NULL) {
673 
674     _bft_mem_global_file = fopen(log_file_name, "w");
675 
676     /*
677       If the file could not be opened, we do not abort, as it is not
678       absolutely necessary. We silently continue.
679       (We could warn the user, but this would require either using
680       bft_printf(), which we prefer to keep independent of the bft_mem_...()
681       functions to avoid evental crossed definitions when user-defined, or
682       "warning handling" similar to error handling, with a possibility
683       of user-defined warning handlers, as we are not sure if the calling
684       code uses stderr (especially in a distributed environment). This
685       is probably not worth the bother.
686     */
687 
688     if (_bft_mem_global_file == NULL)
689       fprintf(stderr,
690               _("Failure to open memory log file \"%s\"\n"),
691               log_file_name);
692 
693   }
694 
695   /* Log file header */
696 
697   if (_bft_mem_global_file != NULL) {
698 
699     fprintf(_bft_mem_global_file,
700             "       :     FILE NAME              : LINE  :"
701             "  POINTER NAME                          : N BYTES   :"
702             " (+- N BYTES) : TOTAL BYTES  : [    ADRESS]\n"
703             "-------:----------------------------:-------:"
704             "----------------------------------------:-----------:"
705             "-----------------------------:--------------");
706 
707   }
708 
709 }
710 
711 /*!
712  * \brief End memory handling.
713  *
714  * This function should be called after all other bft_mem_...()
715  * functions. In case of memory allocation logging, it
716  * writes final information to the log file and closes is.
717  */
718 
bft_mem_end(void)719 void bft_mem_end(void)
720 {
721   if (_bft_mem_global_initialized == 0)
722     return;
723 
724 #if defined(HAVE_OPENMP)
725   if (omp_in_parallel()) {
726     if (omp_get_thread_num() != 0)
727       return;
728   }
729   omp_destroy_lock(&_bft_mem_lock);
730 #endif
731 
732   _bft_mem_global_initialized = 0;
733 
734   if (_bft_mem_global_file != NULL) {
735 
736     unsigned long  non_free = 0;
737     struct _bft_mem_block_t  *pinfo;
738 
739     /* Memory usage summary */
740 
741     _bft_mem_summary(_bft_mem_global_file);
742 
743     /* List of non-freed pointers */
744 
745     if (_bft_mem_global_block_array != NULL) {
746 
747       fprintf(_bft_mem_global_file, "List of non freed pointers:\n");
748 
749       for (pinfo = _bft_mem_global_block_array;
750            pinfo < _bft_mem_global_block_array + _bft_mem_global_block_nbr;
751            pinfo++) {
752 
753         fprintf(_bft_mem_global_file,"[%10p]\n", pinfo->p_bloc);
754         non_free++;
755 
756       }
757 
758       fprintf(_bft_mem_global_file,
759               "Number of non freed pointers remaining: %lu\n",
760               non_free);
761 
762     }
763 
764     fclose(_bft_mem_global_file);
765   }
766 
767   /* Reset defaults in case of later initialization */
768 
769   if (_bft_mem_global_block_array != NULL) {
770     free(_bft_mem_global_block_array);
771     _bft_mem_global_block_array = NULL;
772   }
773 
774   _bft_mem_global_block_nbr   = 0 ;
775   _bft_mem_global_block_max   = 512 ;
776 
777   _bft_mem_global_alloc_cur = 0;
778   _bft_mem_global_alloc_max = 0;
779 
780   _bft_mem_global_n_allocs = 0;
781   _bft_mem_global_n_reallocs = 0;
782   _bft_mem_global_n_frees = 0;
783 
784 }
785 
786 /*!
787  * \brief Indicates if bft_mem_...() functions are initialized.
788  *
789  * \returns 1 if bft_mem_init has been called, 0 otherwise.
790  */
791 
792 int
bft_mem_initialized(void)793 bft_mem_initialized(void)
794 {
795   return _bft_mem_global_initialized;
796 }
797 
798 /*!
799  * \brief Allocate memory for ni elements of size bytes.
800  *
801  * This function calls malloc(), but adds tracing capabilities, and
802  * automatically calls the bft_error() errorhandler if it fails to
803  * allocate the required memory.
804  *
805  * \param [in] ni        number of elements.
806  * \param [in] size      element size.
807  * \param [in] var_name  allocated variable name string.
808  * \param [in] file_name name of calling source file.
809  * \param [in] line_num  line number in calling source file.
810  *
811  * \returns pointer to allocated memory.
812  */
813 
814 void *
bft_mem_malloc(size_t ni,size_t size,const char * var_name,const char * file_name,int line_num)815 bft_mem_malloc(size_t       ni,
816                size_t       size,
817                const char  *var_name,
818                const char  *file_name,
819                int          line_num)
820 {
821   void       *p_loc;
822   size_t      alloc_size = ni * size;
823 
824   if (ni == 0)
825     return NULL;
826 
827   /* Allocate memory and check return */
828 
829   p_loc = malloc(alloc_size);
830 
831   if (p_loc == NULL) {
832     _bft_mem_error(file_name, line_num, errno,
833                    _("Failure to allocate \"%s\" (%lu bytes)"),
834                    var_name, (unsigned long)alloc_size);
835     return NULL;
836   }
837   else if (_bft_mem_global_initialized == 0)
838     return p_loc;
839 
840   /* Memory allocation counting */
841 
842   {
843 #if defined(HAVE_OPENMP)
844     int in_parallel = omp_in_parallel();
845     if (in_parallel)
846       omp_set_lock(&_bft_mem_lock);
847 #endif
848 
849     _bft_mem_global_alloc_cur += alloc_size;
850 
851     if (_bft_mem_global_alloc_max < _bft_mem_global_alloc_cur)
852       _bft_mem_global_alloc_max = _bft_mem_global_alloc_cur;
853 
854     if (_bft_mem_global_file != NULL) {
855       fprintf(_bft_mem_global_file, "\n  alloc: %-27s:%6d : %-39s: %9lu",
856               _bft_mem_basename(file_name), line_num,
857               var_name, (unsigned long)alloc_size);
858       fprintf(_bft_mem_global_file, " : (+%9lu) : %12lu : [%10p]",
859               (unsigned long)alloc_size,
860               (unsigned long)_bft_mem_global_alloc_cur,
861               p_loc);
862       fflush(_bft_mem_global_file);
863     }
864 
865     _bft_mem_block_malloc(p_loc, alloc_size);
866 
867     _bft_mem_global_n_allocs += 1;
868 
869 #if defined(HAVE_OPENMP)
870     if (in_parallel)
871       omp_unset_lock(&_bft_mem_lock);
872 #endif
873   }
874 
875   /* Return pointer to allocated memory */
876 
877   return p_loc;
878 }
879 
880 /*!
881  * \brief Reallocate memory for ni elements of size bytes.
882  *
883  * This function calls realloc(), but adds tracing capabilities, and
884  * automatically calls the bft_error() errorhandler if it fails to
885  * allocate the required memory.
886  *
887  * \param [in] ptr       pointer to previous memory location
888  *                       (if NULL, bft_alloc() called).
889  * \param [in] ni        number of elements.
890  * \param [in] size      element size.
891  * \param [in] var_name  allocated variable name string.
892  * \param [in] file_name name of calling source file.
893  * \param [in] line_num  line number in calling source file.
894  *
895  * \returns pointer to reallocated memory.
896  */
897 
898 void *
bft_mem_realloc(void * ptr,size_t ni,size_t size,const char * var_name,const char * file_name,int line_num)899 bft_mem_realloc(void        *ptr,
900                 size_t       ni,
901                 size_t       size,
902                 const char  *var_name,
903                 const char  *file_name,
904                 int          line_num)
905 {
906   void      *p_loc;
907 
908   size_t old_size = 0;
909   size_t new_size = ni * size;
910 
911 #if defined(HAVE_OPENMP)
912   int in_parallel = 0;
913 #endif
914 
915   /*
916     Behave as bft_mem_malloc() if the previous pointer is equal to NULL.
917     Note that the operation will then appear as a first allocation
918     ('alloc') in the _bft_mem_global_file trace file.
919   */
920 
921   if (ptr == NULL)
922     return bft_mem_malloc(ni,
923                           size,
924                           var_name,
925                           file_name,
926                           line_num);
927 
928   /*
929     Behave as bft_mem_free() if the requested size is zero.
930     Note that in this case, the operation will appear as 'free'
931     in the _bft_mem_global_file trace file.
932   */
933 
934   else if (ni == 0)
935     return bft_mem_free(ptr,
936                         var_name,
937                         file_name,
938                         line_num);
939 
940   /* When possible, get previous size to compute difference. */
941 
942   if (_bft_mem_global_initialized) {
943 
944 #if defined(HAVE_OPENMP)
945     in_parallel = omp_in_parallel();
946     if (in_parallel)
947       omp_set_lock(&_bft_mem_lock);
948 #endif
949 
950     struct _bft_mem_block_t *pinfo = _bft_mem_block_info_try(ptr);
951     if (pinfo != NULL)
952       old_size = pinfo->size;
953 
954 #if defined(HAVE_OPENMP)
955     if (in_parallel)
956       omp_unset_lock(&_bft_mem_lock);
957 #endif
958 
959     if (pinfo == NULL) {
960       if (_bft_alt_get_size_func != NULL) {
961         old_size = _bft_alt_get_size_func(ptr);
962         if (old_size > 0)
963           return _bft_alt_realloc_func(ptr, ni, size,
964                                      var_name, file_name, line_num);
965       }
966       _bft_mem_block_info_error(ptr);
967     }
968 
969     /* If the old size is known to equal the new size,
970        nothing needs to be done. */
971 
972     if (new_size == old_size)
973       return ptr;
974 
975   }
976 
977   /* In the general case, we have a true reallocation. */
978 
979   p_loc = realloc(ptr, new_size);
980 
981   if (p_loc == NULL) {
982     _bft_mem_error(file_name, line_num, errno,
983                    _("Failure to reallocate \"%s\" (%lu bytes)"),
984                    var_name, (unsigned long)new_size);
985     return NULL;
986   }
987   else if (_bft_mem_global_initialized == 0)
988     return p_loc;
989 
990   {
991 #if defined(HAVE_OPENMP)
992     if (in_parallel)
993       omp_set_lock(&_bft_mem_lock);
994 #endif
995 
996     /* FIXME: size_diff overestimated when _bft_mem_global_initialized == 0,
997        so bft_mem_size_current/bft_mem_size_max will return incorrect values
998        in this case.
999        Maybe these functions should simply return 0 in the case. */
1000 
1001     long size_diff = new_size - old_size;
1002 
1003     _bft_mem_global_alloc_cur += size_diff;
1004 
1005     if (size_diff > 0) {
1006       if (_bft_mem_global_alloc_max < _bft_mem_global_alloc_cur)
1007         _bft_mem_global_alloc_max = _bft_mem_global_alloc_cur;
1008     }
1009 
1010     if (_bft_mem_global_file != NULL) {
1011       char sgn = (size_diff > 0) ? '+' : '-';
1012       fprintf(_bft_mem_global_file, "\nrealloc: %-27s:%6d : %-39s: %9lu",
1013               _bft_mem_basename(file_name), line_num,
1014               var_name, (unsigned long)new_size);
1015       fprintf(_bft_mem_global_file, " : (%c%9lu) : %12lu : [%10p]",
1016               sgn,
1017               (unsigned long) ((size_diff > 0) ? size_diff : -size_diff),
1018               (unsigned long)_bft_mem_global_alloc_cur,
1019               p_loc);
1020       fflush(_bft_mem_global_file);
1021     }
1022 
1023     _bft_mem_block_realloc(ptr, p_loc, new_size);
1024 
1025     _bft_mem_global_n_reallocs += 1;
1026 
1027 #if defined(HAVE_OPENMP)
1028     if (in_parallel)
1029       omp_unset_lock(&_bft_mem_lock);
1030 #endif
1031 
1032     return p_loc;
1033   }
1034 
1035   return NULL; /* Avoid a compiler warning */
1036 }
1037 
1038 /*!
1039  * \brief Free allocated memory.
1040  *
1041  * This function calls free(), but adds tracing capabilities, and
1042  * automatically calls the bft_error() errorhandler if it fails to
1043  * free the corresponding memory. In case of a NULL pointer argument,
1044  * the function simply returns.
1045  *
1046  * \param [in] ptr       pointer to previous memory location
1047  *                       (if NULL, bft_alloc() called).
1048  * \param [in] var_name  allocated variable name string
1049  * \param [in] file_name name of calling source file
1050  * \param [in] line_num  line number in calling source file
1051  *
1052  * \returns NULL pointer.
1053  */
1054 
1055 void *
bft_mem_free(void * ptr,const char * var_name,const char * file_name,int line_num)1056 bft_mem_free(void        *ptr,
1057              const char  *var_name,
1058              const char  *file_name,
1059              int          line_num)
1060 {
1061   /* NULL pointer case (non-allocated location) */
1062 
1063   if (ptr == NULL)
1064     return NULL;
1065 
1066   /* General case (free allocated memory) */
1067 
1068   if (_bft_mem_global_initialized != 0) {
1069 
1070 #if defined(HAVE_OPENMP)
1071     int in_parallel = omp_in_parallel();
1072     if (in_parallel)
1073       omp_set_lock(&_bft_mem_lock);
1074 #endif
1075 
1076     struct _bft_mem_block_t *pinfo = _bft_mem_block_info_try(ptr);
1077 
1078     if (pinfo != NULL) {
1079 
1080       size_t  size_info = pinfo->size;
1081 
1082       _bft_mem_global_alloc_cur -= size_info;
1083 
1084       if (_bft_mem_global_file != NULL) {
1085         fprintf(_bft_mem_global_file,"\n   free: %-27s:%6d : %-39s: %9lu",
1086                 _bft_mem_basename(file_name), line_num,
1087                 var_name, (unsigned long)size_info);
1088         fprintf(_bft_mem_global_file, " : (-%9lu) : %12lu : [%10p]",
1089                 (unsigned long)size_info,
1090                 (unsigned long)_bft_mem_global_alloc_cur,
1091                 ptr);
1092         fflush(_bft_mem_global_file);
1093       }
1094 
1095       _bft_mem_block_free(ptr);
1096 
1097       _bft_mem_global_n_frees += 1;
1098 
1099     }
1100 
1101 #if defined(HAVE_OPENMP)
1102     if (in_parallel)
1103       omp_unset_lock(&_bft_mem_lock);
1104 #endif
1105 
1106     if (pinfo == NULL) {
1107       if (_bft_alt_get_size_func != NULL) {
1108         size_t size_info = _bft_alt_get_size_func(ptr);
1109         if (size_info > 0) {
1110           _bft_alt_free_func(ptr, var_name, file_name, line_num);
1111           return NULL;
1112         }
1113       }
1114       _bft_mem_block_info_error(ptr);
1115     }
1116 
1117   }
1118 
1119   free(ptr);
1120 
1121   return NULL;
1122 }
1123 
1124 /*!
1125  * \brief Allocate aligned memory for ni elements of size bytes.
1126  *
1127  * This function calls posix_memalign() if available, but adds tracing
1128  * capabilities, and automatically calls the bft_error() errorhandler if
1129  * it fails to allocate the required memory.
1130  *
1131  * The associated function bft_mem_have_memalign() indicates if this
1132  * type of allocation may be used on this system.
1133  *
1134  * \param [in]  alignment alignment.
1135  * \param [in]  ni        number of elements.
1136  * \param [in]  size      element size.
1137  * \param [in]  var_name  allocated variable name string.
1138  * \param [in]  file_name name of calling source file.
1139  * \param [in]  line_num  line number in calling source file.
1140  *
1141  * \returns pointer to allocated memory.
1142  */
1143 
1144 void *
bft_mem_memalign(size_t alignment,size_t ni,size_t size,const char * var_name,const char * file_name,int line_num)1145 bft_mem_memalign(size_t       alignment,
1146                  size_t       ni,
1147                  size_t       size,
1148                  const char  *var_name,
1149                  const char  *file_name,
1150                  int          line_num)
1151 {
1152 #if defined(HAVE_POSIX_MEMALIGN)
1153 
1154   int         retval;
1155   void       *p_loc;
1156   size_t      alloc_size = ni * size;
1157 
1158   if (ni == 0)
1159     return NULL;
1160 
1161   /* Allocate memory and check return */
1162 
1163   retval = posix_memalign(&p_loc, alignment, alloc_size);
1164 
1165   if (retval != 0) {
1166     switch (retval) {
1167     case EINVAL:
1168       _bft_mem_error(file_name, line_num, 0,
1169                      _("Alignment %lu for \"%s\" not a power of 2\n"
1170                        "or a multiple of sizeof(void *) = %lu"),
1171                      (unsigned long)alignment, var_name,
1172                      (unsigned long)(sizeof(void *)));
1173       break;
1174     default:
1175       _bft_mem_error(file_name, line_num, 0,
1176                      _("Failure to allocate \"%s\" (%lu bytes)"),
1177                      var_name, (unsigned long)alloc_size);
1178     }
1179     return NULL;
1180   }
1181   else if (_bft_mem_global_initialized == 0)
1182     return p_loc;
1183 
1184   /* Memory allocation counting */
1185 
1186   {
1187 #if defined(HAVE_OPENMP)
1188     int in_parallel = omp_in_parallel();
1189     if (in_parallel)
1190       omp_set_lock(&_bft_mem_lock);
1191 #endif
1192 
1193     _bft_mem_global_alloc_cur += alloc_size;
1194 
1195     if (_bft_mem_global_alloc_max < _bft_mem_global_alloc_cur)
1196       _bft_mem_global_alloc_max = _bft_mem_global_alloc_cur;
1197 
1198     if (_bft_mem_global_file != NULL) {
1199       fprintf(_bft_mem_global_file, "\n  alloc: %-27s:%6d : %-39s: %9lu",
1200               _bft_mem_basename(file_name), line_num,
1201               var_name, (unsigned long)alloc_size);
1202       fprintf(_bft_mem_global_file, " : (+%9lu) : %12lu : [%10p]",
1203               (unsigned long)alloc_size,
1204               (unsigned long)_bft_mem_global_alloc_cur,
1205               p_loc);
1206       fflush(_bft_mem_global_file);
1207     }
1208 
1209     _bft_mem_block_malloc(p_loc, alloc_size);
1210 
1211     _bft_mem_global_n_allocs += 1;
1212 
1213 #if defined(HAVE_OPENMP)
1214     if (in_parallel)
1215       omp_unset_lock(&_bft_mem_lock);
1216 #endif
1217   }
1218 
1219   /* Return pointer to allocated memory */
1220 
1221   return p_loc;
1222 
1223 #else
1224 
1225   _bft_mem_error(file_name, line_num, errno,
1226                  _("No aligned allocation function available on this system"));
1227 
1228   return NULL;
1229 
1230 #endif
1231 }
1232 
1233 /*!
1234  * \brief Return block size associated with a given pointer.
1235  *
1236  * bft_mem_init() must have beed called before this function can be used.
1237  *
1238  * \param [in] ptr  pointer to previous memory location
1239  *
1240  * \returns size of associated memory block.
1241  */
1242 
1243 size_t
bft_mem_get_block_size(void * ptr)1244 bft_mem_get_block_size(void  *ptr)
1245 {
1246   size_t block_size = 0;
1247 
1248   if (_bft_mem_global_initialized) {
1249 
1250 #if defined(HAVE_OPENMP)
1251     int in_parallel = omp_in_parallel();
1252     if (in_parallel)
1253       omp_set_lock(&_bft_mem_lock);
1254 #endif
1255 
1256     block_size = _bft_mem_block_size(ptr);
1257 
1258   }
1259   else
1260     _bft_mem_error(__FILE__, __LINE__, 0,
1261                    _("%s: should not be called before %s\n"),
1262                    __func__, "bft_mem_init");
1263 
1264   return block_size;
1265 }
1266 
1267 /*!
1268  * \brief Return current theoretical dynamic memory allocated.
1269  *
1270  * \return current memory handled through bft_mem_...() (in kB).
1271  */
1272 
1273 size_t
bft_mem_size_current(void)1274 bft_mem_size_current(void)
1275 {
1276   return (_bft_mem_global_alloc_cur / 1024);
1277 }
1278 
1279 /*!
1280  * \brief Return maximum theoretical dynamic memory allocated.
1281  *
1282  * \return maximum memory handled through bft_mem_...() (in kB).
1283  */
1284 
1285 size_t
bft_mem_size_max(void)1286 bft_mem_size_max(void)
1287 {
1288   return (_bft_mem_global_alloc_max / 1024);
1289 }
1290 
1291 /*!
1292  * \brief Indicate if a memory aligned allocation variant is available.
1293  *
1294  * If no such function is available, bft_mem_memalign() will always fail.
1295  *
1296  * \returns 1 if memory aligned allocation is possible, 0 otherwise.
1297  */
1298 
1299 int
bft_mem_have_memalign(void)1300 bft_mem_have_memalign(void)
1301 {
1302 #if defined(HAVE_POSIX_MEMALIGN)
1303   return 1;
1304 #else
1305   return 0;
1306 #endif
1307 }
1308 
1309 /*!
1310  * \brief Returns the error handler associated with the bft_mem_...() functions.
1311  *
1312  * \return pointer to the error handler function.
1313  */
1314 
1315 bft_error_handler_t *
bft_mem_error_handler_get(void)1316 bft_mem_error_handler_get(void)
1317 {
1318   return _bft_mem_error_handler;
1319 }
1320 
1321 /*!
1322  * \brief Associates an error handler with the bft_mem_...() functions.
1323  *
1324  * With the default error handler, an error message is output to stderr,
1325  * (after bft_print_flush() is called), and the general error handler used
1326  * by bft_error() is then called (which results in the termination of the
1327  * current process or process group).
1328  *
1329  * \param handler pointer to the error handler function [in].
1330  */
1331 
1332 void
bft_mem_error_handler_set(bft_error_handler_t * handler)1333 bft_mem_error_handler_set(bft_error_handler_t *handler)
1334 {
1335   _bft_mem_error_handler = handler;
1336 }
1337 
1338 /*
1339  * Associates alternative functions with the bft_mem_...() functions.
1340  *
1341  * When memory allocated with another mechanism is reallocated or
1342  * freed using a bft_mem_... function, this allows trying the
1343  * matching alternative function rather than throwing an error.
1344  *
1345  * Though using matching methods is recommended, this allows handling
1346  * compatibility between methods which might be used in different parts
1347  * of the code.
1348  *
1349  * parameter:
1350  *   realloc_func <-- pointer to alternative reallocation function.
1351  *   realloc_func <-- pointer to alternative free function.
1352  */
1353 
1354 void
bft_mem_alternative_set(bft_mem_get_size_t * get_size_func,bft_mem_realloc_t * realloc_func,bft_mem_free_t * free_func)1355 bft_mem_alternative_set(bft_mem_get_size_t  *get_size_func,
1356                         bft_mem_realloc_t   *realloc_func,
1357                         bft_mem_free_t      *free_func)
1358 {
1359   _bft_alt_get_size_func = get_size_func;
1360   _bft_alt_realloc_func = realloc_func;
1361   _bft_alt_free_func = free_func;
1362 }
1363 
1364 /*----------------------------------------------------------------------------*/
1365 
1366 END_C_DECLS
1367