/* malloc.c -- Copyright 1989, 1995 Liam R. Quin.
 * All Rights Reserved.
 * This code is NOT in the public domain.
 * See the file COPYRIGHT for full details.
 */

/*
 * malloc.c -- wrapper routines for free/malloc, that can do checking and
 * print errors.
 *
 * $Id: malloc.c,v 1.19 96/08/14 16:57:34 lee Exp $
 */

#include "globals.h"
#include "error.h"

#include <stdio.h>

#ifdef HAVE_STDLIB_H
# include <stdlib.h>
#else
# include <malloc.h>
#endif

#include "emalloc.h"

#undef malloc
#undef emalloc

/* TODO: make What be a data structure:
 * typedef struct s_MemoryPool {
 *    char FreeBlock[]
 *    char *Data
 *    unsigned int nominalSize;
 *    char *Description;
 *    int Flags;
 * } t_MemoryPool;
 * where flags can include
 *    CanGrow
 *    Can Shrink
 *    ByteAlignment|PointerAlignment|BlockAlignment|DoubleAlignment
 *    FreeWhenEmpty
 *    UnmapWhenEmpty
 *    
 * Then, can use mmap() to allocate memory, and unmap it again when it's
 * done.  This would be a big win for the lqaddfile (LQC_AddWord) symbol
 * table, which might grow to 10 MBytes or more, and then shrink to
 * being mostly or entirely empty.
 */

typedef struct s_Reclaimer {
    unsigned long (* Reclaimer)(
#ifdef HAVE_PROTO
	void *privte_stuff,
	unsigned long AmountToFree
#endif
    );
    char *Argument;
    struct s_Reclaimer *Next;
} t_Reclaimer;

PRIVATE t_Reclaimer *Reclaimers = 0;

API void
LQU_AddMemoryReclaimFunction(argument, theFunction)
    char *argument;
    unsigned long (* theFunction)(
#ifdef HAVE_PROTO
	void *privte_stuff,
	unsigned long AmountToFree
#endif
    );
{
    t_Reclaimer **rp;

    for (rp = &Reclaimers; *rp; rp = &(*rp)->Next) {
	if ((*rp)->Reclaimer == theFunction) {
	    /* already there */
	    return;
	}
    }

    *rp = (t_Reclaimer *) emalloc("Reclaimer", sizeof(t_Reclaimer));
    (*rp)->Reclaimer = theFunction;
    (*rp)->Argument = argument;
    (*rp)->Next = 0;
}

PRIVATE void
ReclaimMemory(Amount)
    unsigned long Amount;
{
    t_Reclaimer *rp;

    for (rp = Reclaimers; rp; rp = rp->Next) {
	unsigned long saved;

	saved = (* rp->Reclaimer)(rp->Argument, Amount);

	if (saved <= Amount) {
	    Amount -= saved;
	} else {
	    return;
	}
    }
}

/* <Function>
 *   <Name>emalloc
 *   <Class>Utilities/Memory
 *   <Purpose>
 *      <P>Allocates the given number of bytes of memory and returns a pointer
 *	to it, using the system-supplied malloc function.
 *	<P>If there is not enough memory, a fatal error is generated.
 *	The What argument is included in any such error message, and should
 *	be a human-readable description of the error, as an aid to help the
 *	user understand exactly what failed.</P>
 *	<P>A future release of lq-text will have an improved memory allocation
 *	interface.</P>
 *   <Errors>
 *	A fatal (E_FATAL | E_MEMORY) error is produced if memory is
 *	exhausted.
 *   <SeeAlso>
 *	ecalloc
 *	efree
 *	Error
 * </Function>
 */
char *
emalloc(What, nbytes)
    CONST char *What;
    unsigned nbytes;
{
    char *Result;

    if (nbytes == 0) {
	nbytes = 4;
    }

    if ((Result = malloc(nbytes)) == (char *) 0) {
	ReclaimMemory(nbytes);
	if ((Result = malloc(nbytes)) == (char *) 0) {
	    Error(E_FATAL|E_MEMORY,
		"emalloc request for %u bytes for %s denied",
		nbytes, What
	    );
	}
    }

    return Result;
}

#ifdef MALLOCTRACE
char *
LQU_TraceMalloc(What, nbytes, FileName, Line)
    CONST char *What;
    unsigned int nbytes;
    CONST char *FileName;
    int Line;
{
    char *Result;

    if (nbytes == 0) {
	nbytes = 4;
    }
    if ((Result = malloc(nbytes)) == (char *) 0) {
	Error(E_FATAL|E_MEMORY|E_BUG,
	    "%s: %d: emalloc(%u) failed for %s.",
	    FileName, Line, nbytes, What
	);
    }

    (void) fprintf(stderr, "malloc %u > 0x%x %s %d %s\n", nbytes, Result, FileName, Line, What);
    return Result;
}
#endif

#undef ecalloc

/* <Function>
 *   <Name>ecalloc
 *   <Class>Utilities/Memory
 *   <Purpose>
 *      <P>Allocates sufficient memory to hold the given Number of objects
 *	of the given Size, after taking alignment constraints into account;
 *	the system-supplied calloc function is used.</P>
 *	<P>If there is not enough memory, a fatal error is generated.
 *	The What argument is included in any such error message, and should
 *	be a human-readable description of the error, as an aid to help the
 *	user understand exactly what failed.</P>
 *	<P>A future release of lq-text will have an improved memory allocation
 *	interface.</P>
 *   <Errors>
 *	A fatal (E_FATAL | E_MEMORY) error is produced if memory is
 *	exhausted.
 *   <SeeAlso>
 *	emalloc
 *	efree
 *	Error
 * </Function>
 */
char *
ecalloc(What, Number, Size)
    CONST char *What;
    unsigned int Number;
    unsigned int Size;
{
    char *Result;

    if ((long) Number <= 0 || Size <= 0) {
	fprintf(stderr, "%s: warning: ecalloc(%u x %u) seems a little odd.\n",
			progname, Number, Size);
    }
    if ((Result = calloc(Number, Size)) == (char *) 0) {
	ReclaimMemory(Number * Size);
	if ((Result = calloc(Number, Size)) == (char *) 0) {
	    Error(E_FATAL|E_MEMORY, "ecalloc request for (%u x %u) failed",
			Number, Size);
	}
    }

    return Result;
}

#ifdef MALLOCTRACE
API char *
LQU_TraceCalloc(What, Number, Size, FileName, Line)
    CONST char *What;
    unsigned int Number;
    unsigned int Size;
    CONST char *FileName;
    int Line;
{
    char *Result;

    if ((long) Number <= 0) {
	fprintf(stderr, "%s: %s: %d: warning: ecalloc(%u x %u) strange!\n",
			progname, FileName, Line, Number, Size);
    }
    if ((Result = calloc(Number, Size)) == (char *) 0) {
	Error(E_FATAL|E_MEMORY|E_BUG, "%s: %d: ecalloc(%u x %u) failed.",
			FileName, Line, Number, Size);
	exit(1);
    }

    return Result;
}
#endif

#undef erealloc

/* <Function>
 *   <Name>erealloc
 *   <Class>Utilities/Memory
 *   <Purpose>
 *      <P>Changes the size of the given Object, either by extending the
 *	area of memory allocated to it or by allocating a new area,
 *	copying the data and freeing the original storage area.</P>
 *	<P>If insufficient memory is available, a fatal (E_FATAL) error
 *	is produced, which includes the given What argument as a textual
 *	(human-readable) description of the object.</P>
 *	<P>The system-supplied realloc function is used.</P>
 *   <Returns>
 *	A pointer to the newly sized object; in most implementations this
 *	will almost always be a new copy of the object.
 *	<P>A future release of lq-text will have an improved memory allocation
 *	interface.</P>
 *   <Errors>
 *	A fatal (E_FATAL | E_MEMORY) error is produced if memory is
 *	exhausted.
 *   <SeeAlso>
 *	emalloc
 *	efree
 *	Error
 * </Function>
 */
char *
erealloc(Object, NewSize)
    char *Object;
    unsigned int NewSize;
{
    char *Result;

    if (NewSize == 0) {
	NewSize = 4;
    }

    if ((Result = realloc(Object, NewSize)) == (char *) 0) {
	Error(E_FATAL|E_MEMORY, "erealloc to change 0x%x to %u bytes failed",
	    Object, NewSize
	);
    }

    return Result;
}

#ifdef MALLOCTRACE
char *
LQU_TraceRealloc(Object, NewSize, FileName, Line)
    CONST char *Object;
    unsigned int NewSize;
    CONST char *FileName;
    int Line;
{
    char *Result;

    if (NewSize == 0) {
	NewSize = 4;
    }

    if ((Result = realloc(Object, NewSize)) == (char *) 0) {
	Error(E_FATAL|E_BUG|E_MEMORY,
	    "%s: %d: erealloc(0x%x, %u) failed.",
	    FileName, Line, Object, NewSize
	);
	exit(1);
    }

    (void) fprintf(stderr, "realloc 0x%x %u > 0x%x %s %d\n", Object, NewSize, Result, FileName, Line);
    return Result;
}
#endif

#undef efree

/* <Function>
 *   <Name>efree
 *   <Class>Utilities/Memory
 *   <Purpose>
 *      <P>Returns the memory used by an object to the system, using the
 *	system-provided free function.</P>
 *	<P>A future release of lq-text will have an improved memory allocation
 *	interface.</P>
 *   <Errors>
 *	A warning (E_WARN) is produced a NULL pointer is passed as an argument.
 * </Function>
 */
void
efree(String)
    char *String;
{
    if (!String) {
	Error(E_WARN, "efree: call to free(0) ignored");
	return;
    }

    (void) free(String);
}

#ifdef MALLOCTRACE
API void
LQU_TraceFree(String, FileName, Line)
    char *String;
    CONST char *FileName;
    int Line;
{
    if (!String) {
	(void) Error(E_FATAL|E_BUG,  "%s: %d: Warning: free(0) ignored",
			progname, FileName, Line);
	return;
    }
    (void) fprintf(stderr, "free 0x%x %s %d\n", String, FileName, Line);

    (void) free(String);
}
#endif