/* ========================================================================== */
/* === KLU_memory =========================================================== */
/* ========================================================================== */

/* KLU memory management routines:
 *
 * KLU_malloc                   malloc wrapper
 * KLU_free                     free wrapper
 * KLU_realloc                  realloc wrapper
 */

#include "klu_internal.h"

/* ========================================================================== */
/* === KLU_add_size_t ======================================================= */
/* ========================================================================== */

/* Safely compute a+b, and check for size_t overflow */

size_t KLU_add_size_t (size_t a, size_t b, Int *ok)
{
    (*ok) = (*ok) && ((a + b) >= MAX (a,b)) ;
    return ((*ok) ? (a + b) : ((size_t) -1)) ;
}

/* ========================================================================== */
/* === KLU_mult_size_t ====================================================== */
/* ========================================================================== */

/* Safely compute a*k, where k should be small, and check for size_t overflow */

size_t KLU_mult_size_t (size_t a, size_t k, Int *ok)
{
    size_t i, s = 0 ;
    for (i = 0 ; i < k ; i++)
    {
        s = KLU_add_size_t (s, a, ok) ;
    }
    return ((*ok) ? s : ((size_t) -1)) ;
}

/* ========================================================================== */
/* === KLU_malloc =========================================================== */
/* ========================================================================== */

/* Wrapper around malloc routine (mxMalloc for a mexFunction).  Allocates
 * space of size MAX(1,n)*size, where size is normally a sizeof (...).
 *
 * This routine and KLU_realloc do not set Common->status to KLU_OK on success,
 * so that a sequence of KLU_malloc's or KLU_realloc's can be used.  If any of
 * them fails, the Common->status will hold the most recent error status.
 *
 * Usage, for a pointer to Int:
 *
 *      p = KLU_malloc (n, sizeof (Int), Common)
 *
 * Uses a pointer to the malloc routine (or its equivalent) defined in Common.
 */

void *KLU_malloc        /* returns pointer to the newly malloc'd block */
(
    /* ---- input ---- */
    size_t n,           /* number of items */
    size_t size,        /* size of each item */
    /* --------------- */
    KLU_common *Common
)
{
    void *p ;
    size_t s ;
    Int ok = TRUE ;

    if (Common == NULL)
    {
        p = NULL ;
    }
    else if (size == 0)
    {
        /* size must be > 0 */
        Common->status = KLU_INVALID ;
        p = NULL ;
    }
    else if (n >= INT_MAX)
    {
        /* object is too big to allocate; p[i] where i is an Int will not
         * be enough. */
        Common->status = KLU_TOO_LARGE ;
        p = NULL ;
    }
    else
    {
        /* call malloc, or its equivalent */
        s = KLU_mult_size_t (MAX (1,n), size, &ok) ;
        p = ok ? ((Common->malloc_memory) (s)) : NULL ;
        if (p == NULL)
        {
            /* failure: out of memory */
            Common->status = KLU_OUT_OF_MEMORY ;
        }
        else
        {
            Common->memusage += s ;
            Common->mempeak = MAX (Common->mempeak, Common->memusage) ;
        }
    }
    return (p) ;
}


/* ========================================================================== */
/* === KLU_free ============================================================= */
/* ========================================================================== */

/* Wrapper around free routine (mxFree for a mexFunction).  Returns NULL,
 * which can be assigned to the pointer being freed, as in:
 *
 *      p = KLU_free (p, n, sizeof (int), Common) ;
 */

void *KLU_free          /* always returns NULL */
(
    /* ---- in/out --- */
    void *p,            /* block of memory to free */
    /* ---- input --- */
    size_t n,           /* size of block to free, in # of items */
    size_t size,        /* size of each item */
    /* --------------- */
    KLU_common *Common
)
{
    size_t s ;
    Int ok = TRUE ;
    if (p != NULL && Common != NULL)
    {
        /* only free the object if the pointer is not NULL */
        /* call free, or its equivalent */
        (Common->free_memory) (p) ;
        s = KLU_mult_size_t (MAX (1,n), size, &ok) ;
        Common->memusage -= s ;
    }
    /* return NULL, and the caller should assign this to p.  This avoids
     * freeing the same pointer twice. */
    return (NULL) ;
}


/* ========================================================================== */
/* === KLU_realloc ========================================================== */
/* ========================================================================== */

/* Wrapper around realloc routine (mxRealloc for a mexFunction).  Given a
 * pointer p to a block allocated by KLU_malloc, it changes the size of the
 * block pointed to by p to be MAX(1,nnew)*size in size.  It may return a
 * pointer different than p.  This should be used as (for a pointer to Int):
 *
 *      p = KLU_realloc (nnew, nold, sizeof (Int), p, Common) ;
 *
 * If p is NULL, this is the same as p = KLU_malloc (...).
 * A size of nnew=0 is treated as nnew=1.
 *
 * If the realloc fails, p is returned unchanged and Common->status is set
 * to KLU_OUT_OF_MEMORY.  If successful, Common->status is not modified,
 * and p is returned (possibly changed) and pointing to a large block of memory.
 *
 * Uses a pointer to the realloc routine (or its equivalent) defined in Common.
 */

void *KLU_realloc       /* returns pointer to reallocated block */
(
    /* ---- input ---- */
    size_t nnew,        /* requested # of items in reallocated block */
    size_t nold,        /* old # of items */
    size_t size,        /* size of each item */
    /* ---- in/out --- */
    void *p,            /* block of memory to realloc */
    /* --------------- */
    KLU_common *Common
)
{
    void *pnew ;
    size_t snew, sold ;
    Int ok = TRUE ;

    if (Common == NULL)
    {
        p = NULL ;
    }
    else if (size == 0)
    {
        /* size must be > 0 */
        Common->status = KLU_INVALID ;
        p = NULL ;
    }
    else if (p == NULL)
    {
        /* A fresh object is being allocated. */
        p = KLU_malloc (nnew, size, Common) ;
    }
    else if (nnew >= INT_MAX)
    {
        /* failure: nnew is too big.  Do not change p */
        Common->status = KLU_TOO_LARGE ;
    }
    else
    {
        /* The object exists, and is changing to some other nonzero size. */
        /* call realloc, or its equivalent */
        snew = KLU_mult_size_t (MAX (1,nnew), size, &ok) ;
        sold = KLU_mult_size_t (MAX (1,nold), size, &ok) ;
        pnew = ok ? ((Common->realloc_memory) (p, snew)) : NULL ;
        if (pnew == NULL)
        {
            /* Do not change p, since it still points to allocated memory */
            Common->status = KLU_OUT_OF_MEMORY ;
        }
        else
        {
            /* success: return the new p and change the size of the block */
            Common->memusage += (snew - sold) ;
            Common->mempeak = MAX (Common->mempeak, Common->memusage) ;
            p = pnew ;
        }
    }
    return (p) ;
}
