/* @file  psMemory.h
 * @brief Contains the definitions for the memory management system
 *
 *  @brief Contains the definitions for the memory management system
 *
 *  This is the generic memory management system put inbetween the user's high
 *  level code and the OS-level memory allocation routines.  This system adds
 *  such features as callback routines for memory error events, tracing
 *  capabilities, and reference counting.
 *
 *  @author Robert DeSonia, MHPCC
 *  @author Robert Lupton, Princeton University
 *  @author Joshua Hoblitt, University of Hawaii
 *
 *  @ingroup MemoryManagement
 *
 *  @version $Revision: 1.65 $ $Name: not supported by cvs2svn $
 *  @date $Date: 2007-01-30 03:00:50 $
 *
 *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
 */

#ifndef PS_MEMORY_H
#define PS_MEMORY_H

/// @addtogroup SysUtils System Utilities
/// @{

#include <stdio.h>                     // needed for FILE
#include <pthread.h>                   // we need a mutex to make this stuff thread safe.
#include "psType.h"

/** @addtogroup MemoryManagement
 *  @{
 */

#define P_PS_MEMMAGIC (psU32)0xdeadbeef   // Magic number in psMemBlock header

/// typedef for memory identification numbers.  Guaranteed to be some variety of integer.
typedef unsigned long psMemId;

/// typedef for a memory block's reference count. Guaranteed to be some variety of integer.
typedef unsigned long psReferenceCount;

/// typedef for deallocator.
typedef void (*psFreeFunc) (void* ptr);

/** Book-keeping data for storage allocator.
 *  N.b. sizeof(psMemBlock) must be chosen such that if ptr is a pointer
 *  returned by malloc, then ((char *)ptr + sizeof(psMemBlock)) is properly
 *  aligned for all storage types.
 */

// The memory overhead of a psMemBlock + the trailing post can be checked in
// gdb with the following command:
//      p sizeof(psMemBlock) + sizeof(void*)
typedef struct psMemBlock
{
    const psU32 startblock;            ///< initialised to p_psMEMMAGIC
    struct psMemBlock* previousBlock;  ///< previous block in allocation list
    struct psMemBlock* nextBlock;      ///< next block allocation list
    psFreeFunc freeFunc;               ///< deallocator.  If NULL, use generic deallocation.
    size_t userMemorySize;             ///< the size of the user-portion of the memory block
    const psMemId id;                  ///< a unique ID for this allocation
    const pthread_t tid;               ///< set from pthread_self();
    const char *file;                  ///< set from __FILE__ in e.g. p_psAlloc
    const unsigned int lineno;         ///< set from __LINE__ in e.g. p_psAlloc
    const char *func;                  ///< set from __func__
    #ifdef HAVE_BACKTRACE

    const void **backtrace;                  ///< set from backtrace()
    const size_t backtraceSize;              ///< set from bracktrace()
    #endif // ifdef HAVE_BACKTRACE

    psReferenceCount refCounter;       ///< how many times pointer is referenced
    bool persistent;                   ///< marks if this non-user persistent data like error stack, etc.
    const psU32 endblock;              ///< initialised to p_psMEMMAGIC
}
psMemBlock;

/** prototype of a basic callback used by memory functions
 *
 *  @see psMemAllocCallbackSet
 */
typedef psMemId(*psMemAllocCallback) (
    const psMemBlock* ptr              ///< the psMemBlock just allocated
);

/** prototype of memory free callback used by memory functions
 *
 *  @see psMemFreeCallbackSet
 */
typedef psMemId(*psMemFreeCallback) (
    const psMemBlock* ptr              ///< the psMemBlock being freed
);

/** prototype of a callback used in error conditions
 *
 *  This callback should not try to call psAlloc or psFree.
 *
 *  @see psMemProblemCallbackSet
 */
typedef void (*psMemProblemCallback) (
    psMemBlock* ptr,                   ///< the pointer to the problematic memory block.
    const char *file,                  ///< the file in which the problem originated
    unsigned int lineno                ///< the line number in which the problem originated
);

/** prototype of a callback function used when memory runs out
 *
 *  @return psPtr pointer to requested buffer of the size size_t, or NULL if memory could not
 *          be found.
 *
 *  @see psMemExhaustedCallbackSet
 */
typedef psPtr (*psMemExhaustedCallback) (
    size_t size                        ///< the size of buffer required
);

/** Memory allocation.  This operates much like malloc(), but is guaranteed to return a non-NULL value.
 *
 *  @return psPtr pointer to the allocated buffer. This will not be NULL.
 *  @see psFree
 */
#ifdef DOXYGEN

psPtr psAlloc(
    size_t size                        ///< Size required
);

#else // #ifdef DOXYGEN

#ifdef __GNUC__
psPtr p_psAlloc(
    size_t size,                       ///< Size required
    const char *file,                  ///< File of caller
    unsigned int lineno,               ///< Line number of caller
    const char *func                   ///< Function name of caller
) __attribute__((malloc));
# else // __GNUC__

    psPtr p_psAlloc(
        size_t size,                       ///< Size required
        const char *file,                  ///< File of caller
        unsigned int lineno,               ///< Line number of caller
        const char *func                   ///< Function name of caller
    );
#endif // __GNUC__

/// Memory allocation. psAlloc sends file and line number to p_psAlloc.
#ifndef SWIG
#define psAlloc(size) \
p_psAlloc(size, __FILE__, __LINE__, __func__)
#endif // ! SWIG

#endif // ! DOXYGEN


/** Set the deallocator routine
 *
 *  A deallocator routine can optionally be assigned to a memory block to
 *  ensure that associated memory blocks also get freed, e.g., memory buffers
 *  referenced within a struct.
 *
 */
#ifdef DOXYGEN

void psMemSetDeallocator(
    psPtr ptr,                         ///< the memory block to operate on
    psFreeFunc freeFunc                ///< the function to be executed at deallocation
);

#else // ifdef DOXYGEN

void p_psMemSetDeallocator(
    psPtr ptr,                          ///< the memory block to operate on
    psFreeFunc freeFunc,                ///< the function to be executed at deallocation
    const char *file,                   ///< File of caller
    unsigned int lineno,                ///< Line number of caller
    const char *func                    ///< Function name of caller
);

#ifndef SWIG
#define psMemSetDeallocator(ptr, freeFunc) \
p_psMemSetDeallocator(ptr, freeFunc, __FILE__, __LINE__, __func__);
#endif // ! SWIG

#endif // ifdef DOXYGEN


/** Get the deallocator routine
 *
 *  This function returns the deallocator for a memory block.  A deallocator
 *  routine can optionally be assigned to a memory block to ensure that
 *  associated memory blocks also get freed, e.g., memory buffers referenced
 *  within a struct.
 *
 *  @return psFreeFunc    the routine to be called at deallocation.
 */
#ifdef DOXYGEN

psFreeFunc psMemGetDeallocator(
    const psPtr ptr                     ///< the memory block
);

#else // ifdef DOXYGEN

psFreeFunc p_psMemGetDeallocator(
    const psPtr ptr,                    ///< the memory block
    const char *file,                   ///< File of caller
    unsigned int lineno,                ///< Line number of caller
    const char *func                    ///< Function name of caller
);

#ifndef SWIG
#define psMemGetDeallocator(ptr) \
p_psMemGetDeallocator(ptr, __FILE__, __LINE__, __func__)
#endif // ! SWIG

#endif // ifdef DOXYGEN


/** Activate or Deactivate thread safety and mutex locking in the memory management.
 *
 *  psMemThreadSafety shall turn on thread safety in the memory management functions if
 *  safe is true, and deactivate all mutex locking in the memory management functions if
 *  safe is false.  The function shall return the previous value of the thread safety.
 *  Note that the default behaviour of the library shall be for the locking to be performed.
 *
 *  @return bool:       The previous value of the thread safety.
 */
bool psMemSetThreadSafety(
    bool safe                          ///< boolean for turning on/off thread safety
);

/** Get the current state of thread safety and mutex locking in the memory management.
 *
 * psMemGetThreadSafety shall return the current state of thread safety in the memory management system.
 *
 *  @return bool:       The current state of thread safety.
 */
bool psMemGetThreadSafety(void);


/** Set the memory as persistent so that it is ignored when detecting memory leaks.
 *
 *  Used to mark a memory block as persistent data within the library,
 *  i.e., non user-level data used to hold psLib's state or cache data.  Such
 *  examples of this class of memory is psTrace's trace-levels and dynamic
 *  error codes.
 *
 *  Memory marked as persistent is excluded from memory leak checks.
 *
 */
#ifdef DOXYGEN

void psMemSetPersistent(
    psPtr ptr,                         ///< the memory block to operate on
    bool value,                        ///< true if memory is persistent, otherwise false
);

#else // #ifdef DOXYGEN

void p_psMemSetPersistent(
    psPtr ptr,                         ///< the memory block to operate on
    bool value,                        ///< true if memory is persistent, otherwise false
    const char *file,                  ///< File of caller
    unsigned int lineno,               ///< Line number of caller
    const char *func                   ///< Function name of caller
);

#ifndef SWIG
#define psMemSetPersistent(ptr, value) \
p_psMemSetPersistent(ptr, value, __FILE__, __LINE__, __func__);
#endif // ! SWIG

#endif // DOXYGEN


/** Set whether allocated memory is persistent
 *
 *  Set whether allocated memory is persistent. The defeault is false.
 *
 *  @return bool:       The previous value of whether all allocated memory is persistent
 */
bool p_psMemAllocatePersistent(bool is_persistent); ///< Should all memory allocated be persistent?


/** Get the memory's persistent flag.
 *
 *  Checks if a memory block has been marked as persistent by
 *  p_psMemSetPresistent.
 *
 *  Memory marked as persistent is excluded from memory leak checks.
 *
 *  @return bool    true if memory is marked persistent, otherwise false.
 */
#ifdef DOXYGEN

bool psMemGetPersistent(
    psPtr ptr,                         ///< the memory block to check.
);
#else // ifdef DOXYGEN

bool p_psMemGetPersistent(
    psPtr ptr,                         ///< the memory block to check.
    const char *file,                  ///< File of caller
    unsigned int lineno,               ///< Line number of caller
    const char *func                   ///< Function name of caller
);

#ifndef SWIG
#define psMemGetPersistent(ptr) \
p_MemGetPersistent(ptr, __FILE__, __LINE__, __func__);
#endif // ! SWIG

#endif // DOXYGEN


/** Memory re-allocation.  This operates much like realloc(), but is guaranteed to return a non-NULL value.
 *
 *  @return psPtr pointer to resized buffer. This will not be NULL.
 *  @see psAlloc, psFree
 */
#ifdef DOXYGEN

psPtr psRealloc(
    psPtr ptr,                         ///< Pointer to re-allocate
    size_t size                        ///< Size required
);
#else // #ifdef DOXYGEN

#ifdef __GNUC__
psPtr p_psRealloc(
    psPtr ptr,                         ///< Pointer to re-allocate
    size_t size,                       ///< Size required
    const char *file,                  ///< File of caller
    unsigned int lineno,               ///< Line number of caller
    const char *func                   ///< Function name of caller
) __attribute__((malloc));
# else // __GNUC__

    psPtr p_psRealloc(
        psPtr ptr,                         ///< Pointer to re-allocate
        size_t size,                       ///< Size required
        const char *file,                  ///< File of caller
        unsigned int lineno,               ///< Line number of caller
        const char *func                   ///< Function name of caller
    );
#endif // __GNUC__

/// Memory re-allocation.  psRealloc sends file and line number to p_psRealloc.
#ifndef SWIG
#define psRealloc(ptr, size) \
p_psRealloc(ptr, size, __FILE__, __LINE__, __func__)
#endif // ! SWIG

#endif // ! DOXYGEN


/** Free memory.  This operates much like free().
 *
 *  @see psAlloc, psRealloc
 */
#ifdef DOXYGEN
void psFree(
    psPtr ptr                          ///< Pointer to free, if NULL, function returns immediately.
);
#else // #ifdef DOXYGEN

/// Free memory.  psFree sends file and line number to p_psFree.
#ifndef SWIG
#define            psFree(ptr) \
p_psMemDecrRefCounter((psPtr *)ptr, __FILE__, __LINE__, __func__);
#endif // ! SWIG

#endif // ! DOXYGEN


/** Check for memory leaks.  This scans for allocated memory buffers not freed with an ID not less than id0.
 *  This is used to check for memory leaks by:
 *      -# before a block of code to be checked, store the current ID count via psGetMemId
 *      -# after the block of code to be checked, call this function using the ID stored above.  If all
 *         memory in the block that was allocated has been freed, this call should output nothing and
 *         return 0.
 *
 *  If memory leaks are found, the Memory Problem callback will be called as well.
 *
 *  @return int  number of memory blocks found as 'leaks', i.e., the number of currently allocated memory
 *              blocks above id0 that have not been freed.
 *  @see psAlloc, psFree, psgetMemId, psMemProblemCallbackSet
 */
#ifdef DOXYGEN
int psMemCheckLeaks(
    psMemId id0,                       ///< don't list blocks with id < id0
    psMemBlock ***array,               ///< pointer to array of pointers to leaked blocks, or NULL
    FILE * fd,                         ///< print list of leaks to fd (or NULL)
    bool persistence                   ///< make check across all object even persistent ones
);
#else // ifdef DOXYGEN
int p_psMemCheckLeaks(
    psMemId id0,                       ///< don't list blocks with id < id0
    psMemBlock ***array,               ///< pointer to array of pointers to leaked blocks, or NULL
    FILE * fd,                         ///< print list of leaks to fd (or NULL)
    bool persistence,                  ///< make check across all object even persistent ones
    const char *file,                   ///< File of caller
    unsigned int lineno,                ///< Line number of caller
    const char *func                    ///< Function name of caller
);
#ifndef SWIG
#define psMemCheckLeaks(id0, array, fd, persistence) \
p_psMemCheckLeaks(id0, array, fd, persistence, __FILE__, __LINE__, __func__)
#endif // ifndef SWIG
#endif // ifdef DOXYGEN


/** Check for memory corruption.  Scans all currently allocated memory buffers and checks for corruptions,
 *  i.e., invalid markers that signify a buffer under/overflow.
 *
 *  @return int
 *
 */
#ifdef DOXYGEN
int psMemCheckCorruption(
    FILE *output,                       ///< FILE to write corrupted blocks too
    bool abort_on_error                 ///< Abort on detecting corruption?
);
#else // ifdef DOXYGEN
int p_psMemCheckCorruption(
    FILE *output,                       ///< FILE to write corrupted blocks too
    bool abort_on_error,                ///< Abort on detecting corruption?
    const char *file,                   ///< File of caller
    unsigned int lineno,                ///< Line number of caller
    const char *func                    ///< Function name of caller
);
#ifndef SWIG
#define psMemCheckCorruption(output, abort_on_error) \
p_psMemCheckCorruption(output, abort_on_error, __FILE__, __LINE__, __func__)
#endif // ifndef SWIG
#endif // ifdef DOXYGEN


/** Return reference counter
 *
 *  @return psReferenceCount
 *
 */
#ifdef DOXYGEN

psReferenceCount psMemGetRefCounter(
    const psPtr ptr                    ///< Pointer to get refCounter for
);

#else // ifdef DOXYGEN

psReferenceCount p_psMemGetRefCounter(
    const psPtr ptr,                   ///< Pointer to get refCounter for
    const char *file,                  ///< File of call
    unsigned int lineno,               ///< Line number of call
    const char *func                   ///< Function name of caller
);

#ifndef SWIG
#define psMemGetRefCounter(ptr) \
p_psMemGetRefCounter(ptr, __FILE__, __LINE__, __func__)
#endif // !SWIG

#endif // !DOXYGEN


/** Increment reference counter and return the pointer
 *
 *  @return psPtr
 *
 */
#ifdef DOXYGEN

psPtr psMemIncrRefCounter(
    const psPtr ptr                    ///< Pointer to increment refCounter, and return
);
#else // ifdef DOXYGEN

psPtr p_psMemIncrRefCounter(
    const psPtr ptr,                  ///< Pointer to increment refCounter, and return
    const char *file,                  ///< File of call
    unsigned int lineno,               ///< Line number of call
    const char *func                   ///< Function name of caller
);

#ifndef SWIG
#define psMemIncrRefCounter(ptr) \
p_psMemIncrRefCounter(ptr, __FILE__, __LINE__, __func__)
#endif // !SWIG

#endif // !DOXYGEN


/** Decrement reference counter and return the pointer
 *
 *
 *  @return psPtr    the pointer deremented in refCount, or NULL if pointer is
 *                   fully dereferenced.
 */
#ifdef DOXYGEN

psPtr psMemDecrRefCounter(
    psPtr ptr                         ///< Pointer to decrement refCounter, and return
);

#else // DOXYGEN

psPtr p_psMemDecrRefCounter(
    psPtr ptr,                        ///< Pointer to decrement refCounter, and return
    const char *file,                  ///< File of call
    unsigned int lineno,               ///< Line number of call
    const char *func                   ///< Function name of caller
);

#ifndef SWIG
#define psMemDecrRefCounter(ptr) \
p_psMemDecrRefCounter(ptr, __FILE__, __LINE__, __func__)
#endif // !SWIG

#endif // !DOXYGEN

#if 0 // psMemSetRefCounter
/** Set reference counter and return the pointer
 *
 *  @return psPtr    the pointer with refCount set, or NULL if pointer is
 *                   fully dereferenced.
 */
#ifdef DOXYGEN
psPtr psMemSetRefCounter(
    psPtr ptr,                        ///< Pointer to decrement refCounter, and return
    psReferenceCount count            ///< New reference count
);
#else // DOXYGEN
psPtr p_psMemSetRefCounter(
    psPtr vptr,                        ///< Pointer to decrement refCounter, and return
    psReferenceCount count,            ///< New reference count
    const char *file,                  ///< File of call
    psS32 lineno                       ///< Line number of call
);

#ifndef SWIG
#define psMemSetRefCounter(vptr, count) p_psMemSetRefCounter(vptr, count, __FILE__, __LINE__)
#endif // !SWIG

#endif // !DOXYGEN
#endif // psMemSetRefCounter

/** Set callback for out-of-memory.
 *
 *  If not enough memory is available to satisfy a request by psAlloc or
 *  psRealloc, these functions attempt to find an alternative solution by
 *  calling the psMemExhaustedCallback, a function which may be set by the
 *  programmer in appropriate circumstances, rather than immediately fail.
 *  The typical use of such a feature may be when a program needs a large
 *  chunk of memory to do an operation, but the exact size is not critical.
 *  This feature gives the programmer the opportunity to make a smaller
 *  request and try again, limiting the size of the operating buffer.
 *
 *  @return psMemExhaustedCallback     old psMemExhaustedCallback function
 */
psMemExhaustedCallback psMemExhaustedCallbackSet(
    psMemExhaustedCallback func        ///< Function to run at memory exhaustion
);

/** Set call back for when a particular memory block is allocated
 *
 *  A private variable, p_psMemAllocID, can be used to trace the allocation
 *  and freeing of specific memory blocks. If p_psMemAllocID is set and a
 *  memory block with that ID is allocated, psMemAllocCallback is called
 *  just before memory is returned to the calling function.
 *
 *  @return psMemAllocCallback      old psMemAllocCallback function
 */
psMemAllocCallback psMemAllocCallbackSet(
    psMemAllocCallback func            ///< Function to run at memory allocation of specific mem block
);

/** Set call back for when a particular memory block is freed
 *
 *  A private variable, p_psMemFreeID, can be used to trace the freeing of
 *  specific memory blocks. If p_psMemFreeID is set and the memory block with
 *  the ID is about to be freed, the psMemFreeCallback callback is called just
 *  before the memory block is freed.
 *
 *  @return psMemFreeCallback          old psMemFreeCallback function
 */
psMemFreeCallback psMemFreeCallbackSet(
    psMemFreeCallback func             ///< Function to run at memory free of specific mem block
);

/** get next memory ID
 *
 *  @return psMemId                 the next memory ID to be used
 */
psMemId psMemGetId(void);

/** get the last memory ID used
 *
 *  @return psMemId                 the last memory ID used
 */
psMemId psMemGetLastId(void);

/** set p_psMemAllocID to specific id
 *
 *  A private variable, p_psMemAllocID, can be used to trace the allocation
 *  and freeing of specific memory blocks. If p_psMemAllocID is set and a
 *  memory block with that ID is allocated, psMemAllocCallback is called
 *  just before memory is returned to the calling function.
 *
 *  @return psMemId
 *
 *  @see psMemAllocCallbackSet
 */
psMemId psMemAllocCallbackSetID(
    psMemId id                         ///< ID to set
);

/** set p_psMemFreeID to id
 *
 *  A private variable, p_psMemFreeID, can be used to trace the freeing of
 *  specific memory blocks. If p_psMemFreeID is set and the memory block with
 *  the ID is about to be freed, the psMemFreeCallback callback is called just
 *  before the memory block is freed.
 *
 *  @return psMemId                 the old p_psMemFreeID
 *
 *  @see psMemFreeCallbackSet
 */
psMemId psMemFreeCallbackSetID(
    psMemId id                         ///< ID to set
);


/** return statistics on memory usage
 *
 * @return the total amount of memory owned by psLib; if non-NULL also provide
 * a breakdown into allocated and allocated-and-persistent
 */
size_t psMemStats(const bool print, ///< print details as they're found?
                  size_t *allocated, ///< memory that's currently allocated (but not persistent)
                  size_t *persistent); ///< persistent memory that's currently allocated

/** print detailed information about a psMemBlock
 *
 * This function prints a detailed description of a psMemBlock to output.
 *
 * @return the return status of fprintf()
 */
int psMemBlockPrint(
    FILE *output,                       ///< FILE to write information too
    const psMemBlock *memBlock          ///< psMemBlock to be examined
);


/// @} end of SysUtils

#ifndef DOXYGEN

/*
 * Ensure that any program using malloc/realloc/free will fail to compile
 */
#ifndef PS_ALLOW_MALLOC
#ifdef __GNUC__
#pragma GCC poison malloc realloc calloc free
#else // __GNUC__
#define malloc(S)       _Pragma("error Use of malloc is not allowed.  Use psAlloc instead.")
#define realloc(P,S)    _Pragma("error Use of realloc is not allowed.  Use psRealloc instead.")
#define calloc(S)       _Pragma("error Use of calloc is not allowed.  Use psAlloc instead.")
#define free(P)         _Pragma("error Use of free is not allowed.  Use psFree instead.")
#endif // ! __GNUC__
#endif // #ifndef PS_ALLOW_MALLOC

#endif // #ifndef DOXYGEN
#endif // #ifndef PS_MEMORY_H
