/** @file  psMemory.h
 *
 *  @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
 *
 *  @ingroup MemoryManagement
 *
 *  @version $Revision: 1.51 $ $Name: not supported by cvs2svn $
 *  @date $Date: 2005-10-01 02:22:13 $
 *
 *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
 */

#ifndef PS_MEMORY_H
#define PS_MEMORY_H

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

#include "psType.h"

/** @addtogroup MemoryManagement
 *  @{
 */

/**
 *  @addtogroup memCallback Memory Callbacks
 *
 *  Routines dealing with the creating and setting of memory management callback functions.
 */

/**
 *  @addtogroup memTracing Memory Tracing
 *
 *  Routines dealing with memory tracing and corruption checking.
 */

/**
 *  @addtogroup memRefCount Reference Count
 *
 *  Routines dealing with the reference counting of allocated buffers.
 */

/// 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.
 */
typedef struct psMemBlock
{
    const void* 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 char *file;                  ///< set from __FILE__ in e.g. p_psAlloc
    const unsigned int lineno;         ///< set from __LINE__ in e.g. p_psAlloc
    pthread_mutex_t refCounterMutex;   ///< mutex to ensure exclusive access to reference counter
    psReferenceCount refCounter;       ///< how many times pointer is referenced
    bool persistent;                   ///< marks if this non-user persistent data like error stack, etc.
    const void* endblock;              ///< initialised to p_psMEMMAGIC
}
psMemBlock;

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

/** prototype of memory free callback used by memory functions
 *
 *  @see psMemFreeCallbackSet
 *  @ingroup memCallback
 */
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
 *  @ingroup memCallback
 */
typedef void (*psMemProblemCallback) (
    psMemBlock* ptr,                   ///< the pointer to the problematic memory block.
    const char *filename,                    ///< 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
 *  @ingroup memCallback
 */
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
psPtr p_psAlloc(
    size_t size,                       ///< Size required
    const char *filename,              ///< File of call
    unsigned int lineno                ///< Line number of call
);

/// Memory allocation. psAlloc sends file and line number to p_psAlloc.
#ifndef SWIG
#define psAlloc(size) p_psAlloc(size, __FILE__, __LINE__)
#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.
 *
 */
void psMemSetDeallocator(
    psPtr ptr,                         ///< the memory block to operate on
    psFreeFunc freeFunc                ///< the function to be executed at deallocation
);

/** 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.
 */
psFreeFunc psMemGetDeallocator(
    const psPtr ptr                          ///< the memory block
);

/** Checks the deallocator to see if the pointer matches the desired datatype.
 *
 *  @return bool:       True if type matches, otherwise false.
 */
bool psMemCheckType(
    psDataType type,                   ///< The desired psDataType to match
    psPtr ptr                          ///< The desired pointer to match
);


/** 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.
 *
 */
void p_psMemSetPersistent(
    psPtr ptr,                         ///< the memory block to operate on
    bool value                         ///< true if memory is persistent, otherwise false
);

/** 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.
 */
bool p_psMemGetPersistent(
    psPtr ptr                          ///< the memory block to check.
);


/** 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

psPtr p_psRealloc(
    psPtr ptr,                         ///< Pointer to re-allocate
    size_t size,                       ///< Size required
    const char *filename,              ///< File of call
    unsigned int lineno                ///< Line number of call
);

/// Memory re-allocation.  psRealloc sends file and line number to p_psRealloc.
#ifndef SWIG
#define psRealloc(ptr, size) p_psRealloc(ptr, size, __FILE__, __LINE__)
#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
void p_psFree(
    psPtr ptr,                         ///< Pointer to free
    const char *filename,              ///< File of call
    unsigned int lineno                ///< Line number of call
);

/// Free memory.  psFree sends file and line number to p_psFree.
#ifndef SWIG
//#define psFree(ptr) { p_psFree((psPtr)ptr, __FILE__, __LINE__); *(void**)&(ptr) = NULL; }
#define psFree(ptr) { p_psFree((psPtr)ptr, __FILE__, __LINE__); }
#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
 *  @ingroup memTracing
 */
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
);

/** 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
 *
 *  @ingroup memTracing
 */
int psMemCheckCorruption(
    bool abort_on_error                ///< Abort on detecting corruption?
);

/** Return reference counter
 *
 *  @return psReferenceCount
 *
 *  @ingroup memRefCount
 */
psReferenceCount psMemGetRefCounter(
    const psPtr ptr                    ///< Pointer to get refCounter for
);

/** Increment reference counter and return the pointer
 *
 *  @return psPtr
 *
 *  @ingroup memRefCount
 */
#ifdef DOXYGEN
psPtr psMemIncrRefCounter(
    const psPtr ptr                    ///< Pointer to increment refCounter, and return
);
#else
psPtr p_psMemIncrRefCounter(
    psPtr vptr,                        ///< Pointer to increment refCounter, and return
    const char *file,                  ///< File of call
    psS32 lineno                       ///< Line number of call
);

#ifndef SWIG
#define psMemIncrRefCounter(vptr) p_psMemIncrRefCounter(vptr, __FILE__, __LINE__)
#endif // !SWIG

#endif // !DOXYGEN

/** Decrement reference counter and return the pointer
 *
 *  @ingroup memRefCount
 *
 *  @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 vptr,                        ///< Pointer to decrement refCounter, and return
    const char *file,                  ///< File of call
    psS32 lineno                       ///< Line number of call
);

#ifndef SWIG
#define psMemDecrRefCounter(vptr) p_psMemDecrRefCounter(vptr, __FILE__, __LINE__)
#endif // !SWIG

#endif // !DOXYGEN

/** Set reference counter and return the pointer
 *
 *  @ingroup memRefCount
 *
 *  @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

/** Set callback for problems.
 *
 *  At various occasions, the memory manager can check the state of the memory
 *  stack. If any of these checks discover that the memory stack is corrupted,
 *  the psMemProblemCallback is called.
 
 *  @ingroup memCallback
 *
 *  @return psMemProblemCallback       old psMemProblemCallback function
 */
psMemProblemCallback psMemProblemCallbackSet(
    psMemProblemCallback func          ///< Function to run at memory problem detection
);

/** 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.
 *
 *  @ingroup memCallback
 *
 *  @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.
 *
 *  @ingroup memCallback
 *
 *  @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.
 *
 *  @ingroup memCallback
 *
 *  @return psMemFreeCallback          old psMemFreeCallback function
 */
psMemFreeCallback psMemFreeCallbackSet(
    psMemFreeCallback func             ///< Function to run at memory free of specific mem block
);

/** get next memory ID
 *
 *  @ingroup memCallback
 *
 *  @return psMemId                 the next memory ID to be used
 */
psMemId psMemGetId(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.
 *
 *  @ingroup memCallback
 *
 *  @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.
 *
 *  @ingroup memCallback
 *
 *  @return psMemId                 the old p_psMemFreeID
 *
 *  @see psMemFreeCallbackSet
 */
psMemId psMemFreeCallbackSetID(
    psMemId id                         ///< ID to set
);

//@} End of Memory Management Functions

#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
