/*---------------------------------------------------------------------------
* The Memory Allocator wrapper.
*
*---------------------------------------------------------------------------
* This file #includes the source for the memory allocator selected in
* in config.h. If sysmalloc is selected, this file provides the basic
* xalloc()... implementation using malloc()... .
*
* It is the task of the memory allocator source included to provide
* simulations of malloc()... where required.
*---------------------------------------------------------------------------
*/
#include "driver.h"
#include <stddef.h>
#include <stdio.h>
#include "xalloc.h"
#include "interpret.h"
#include "main.h"
#include "simulate.h"
/*-------------------------------------------------------------------------*/
/* Minimum boundary between stack and heap, should be sufficient for
* error handling (which needs about 10..15KByte).
* If the gap falls below this value, an error is generated and the
* gap is no longer checked except for true overlap with the heap.
*/
#define HEAP_STACK_GAP (20480)
/* -- Global Variables/Arguments dealing with memory -- */
Bool out_of_memory = MY_FALSE; /* True: we are out of memory */
int malloc_privilege = MALLOC_USER; /* Privilege for next allocation */
char *reserved_user_area = NULL; /* Reserved memory areas */
char *reserved_master_area = NULL;
char *reserved_system_area = NULL;
mp_int reserved_user_size = RESERVED_USER_SIZE; /* The reserved sizes */
mp_int reserved_master_size = RESERVED_MASTER_SIZE;
mp_int reserved_system_size = RESERVED_SYSTEM_SIZE;
mp_int min_malloced = MIN_MALLOCED; /* Allocation limits */
mp_int min_small_malloced = MIN_SMALL_MALLOCED; /* Allocation limits */
mp_int max_malloced = MAX_MALLOCED;
int stack_direction = 0; /* 0: Unknown stack behaviour
* +1: Stack grows upward
* -1: Stack grows downward
*/
static int in_malloc = 0;
/* >0 when the core code in the allocator is executed.
* This variable serves as primitive safeguard against re-entrant
* calls to the allocator, for example by threads.
*/
/*-------------------------------------------------------------------------*/
/* Include the allocator source */
#ifdef MALLOC_smalloc
#include "smalloc.c"
#endif /* MALLOC_smalloc */
/* ======================================================================= */
#ifdef MALLOC_sysmalloc
static char * heap_end = NULL;
/*-------------------------------------------------------------------------*/
POINTER
xalloc (size_t size)
/* Allocate <size> bytes of memory like malloc() does.
* This function catches out of memory situations and tries to recover
* from them by using the reserved memory areas. If it totally runs
* out of memory, the program exit()s with code 3.
*/
{
char *p;
static int going_to_exit;
if (going_to_exit)
exit(3);
if (size == 0)
fatal("Tried to allocate 0 bytes.\n");
if (in_malloc++)
{
in_malloc = 0;
writes(1, "Multiple threads in smalloc()\n");
fatal("Multiple threads in smalloc()\n");
/* NOTREACHED */
return NULL;
}
{
static mp_int total_malloced = 0;
if (max_malloced > 0
&& (total_malloced += size + sizeof(p_int)) > max_malloced)
{
total_malloced -= size + sizeof(p_int);
p = NULL;
}
else
{
p = malloc(size);
}
}
if (p == NULL)
{
if (reserved_user_area) {
free(reserved_user_area);
reserved_user_area = NULL;
p = "Temporary out of MEMORY. Freeing reserve.\n";
write(1, p, strlen(p));
in_malloc--;
return xalloc(size); /* Try again */
}
if (malloc_privilege >= MALLOC_MASTER && reserved_master_area) {
free(reserved_master_area);
reserved_master_area = NULL;
p = "Temporary out of MEMORY. Freeing master reserve.\n";
write(1, p, strlen(p));
in_malloc--;
return xalloc(size); /* Try again */
}
if (malloc_privilege >= MALLOC_SYSTEM && reserved_system_area) {
free(reserved_system_area);
reserved_system_area = NULL;
p = "Temporary out of MEMORY. Freeing system reserve.\n";
write(1, p, strlen(p));
in_malloc--;
return xalloc(size); /* Try again */
}
/* We can hardly survive out of memory without the garbage collector */
going_to_exit = MY_TRUE;
p = "Totally out of MEMORY.\n";
write(1, p, strlen(p));
in_malloc--;
(void)dump_trace(MY_FALSE, NULL);
exit(2);
}
if (stack_direction > 0)
{
if (heap_end == NULL || heap_end < (char*)p)
heap_end = p;
assert_stack_gap();
}
else if (stack_direction < 0)
{
if (heap_end == NULL || heap_end > (char*)p + size)
heap_end = p;
assert_stack_gap();
}
in_malloc--;
return p;
} /* xalloc() */
/*-------------------------------------------------------------------------*/
void
dump_lpc_trace (int d, void *adr)
/* Stand in for the malloc lpc trace dump routine which the stdlib allocator
* lacks.
*/
{
#define WRITES(d, s) write((d), (s), strlen(s))
WRITES(d, "No malloc lpc trace.\n");
#undef WRITES
} /* dump_lpc_trace() */
/*-------------------------------------------------------------------------*/
void
dump_malloc_trace (int d, void *adr)
/* Stand in for the malloc trace dump routine which the stdlib allocator
* lacks.
*/
{
#define WRITES(d, s) write((d), (s), strlen(s))
WRITES(d, "No malloc trace.\n");
#undef WRITES
} /* dump_malloc_trace() */
#endif /* MALLOC_sysmalloc */
/* ======================================================================= */
/*-------------------------------------------------------------------------*/
void
get_stack_direction (void)
/* Find the direction of the stackgrowth and store the result (+1 or -1)
* into the global stack_direction.
*/
{
static char *addr = NULL; /* address of first `local', once known */
char local; /* to get stack address */
if (addr == NULL) /* initial call */
{
addr = &local;
get_stack_direction (); /* recurse once */
}
else /* second entry */
if (&local > addr)
stack_direction = 1; /* stack grew upward */
else
stack_direction = -1; /* stack grew downward */
} /* get_stack_direction() */
/*-------------------------------------------------------------------------*/
void
assert_stack_gap (void)
/* Test if the stack is far enough away from the heap area and throw
* an error if not.
*/
{
static enum { Initial, Normal, Error, Fatal } condition = Initial;
ptrdiff_t gap;
/* Don't check the gap after a Fatal error or if the system is
* not fully initialised yet.
*/
if (stack_direction == 0 || condition == Fatal || heap_end == NULL)
return;
/* On the first call, test if checking the gap actually makes sense.
* Reason: Some systems like Cygwin put the stack below the heap.
* If checking is not necessary, the 'condition' will be set to Fatal.
* TODO: Does Cygwin's Heap grow downwards?
*/
if (condition == Initial)
{
condition = Fatal; /* Prevent recursion */
if ((char *)heap_end > (char *)&gap)
{
printf("%s Heap/stack check disabled: heap %p > stack %p\n"
, time_stamp(), heap_end, &gap);
debug_message("%s Heap/stack check disabled: heap %p > stack %p\n"
, time_stamp(), heap_end, &gap);
/* Leave condition at 'Fatal' */
return;
}
/* Yup, we can check the gap */
condition = Normal;
}
/* First check if heap and stack overlap. We do this first to
* rule out negative 'gap' values which can be caused by overflows.
*/
gap = 0;
if (stack_direction < 0)
gap = (ptrdiff_t)((char *)heap_end >= (char *)&gap);
else
gap = (ptrdiff_t)((char *)&gap >= (char *)heap_end);
if (gap != 0)
{
/* Stack and Heap overlap: irrecoverable failure */
if (condition != Fatal)
{
condition = Fatal;
if (stack_direction < 0)
gap = (char *)heap_end - (char *)⪆
else
gap = (char *)&gap - (char *)heap_end;
fatal("Out of memory: Stack overlaps heap by %ld.\n"
, (long)gap);
/* NOTREACHED */
}
return; /* Recursive call during fatal() handling */
}
/* Heap and stack don't overlap - do the normal gap checking.
* Note that on machines with big address spaces the computation
* may overflow.
*/
if (stack_direction < 0)
gap = (char *)&gap - (char *)heap_end;
else
gap = (char *)heap_end - (char *)⪆
if (gap < 0)
gap = HEAP_STACK_GAP + 1;
/* If the gap is big enough, mark that condition and return */
if (gap >= HEAP_STACK_GAP)
{
condition = Normal;
return;
}
/* The gap is too small.
* Throw an error only if the condition was normal before,
* otherwise the error handling would again get an error.
*/
if (condition == Normal)
{
condition = Error;
error("Out of memory: Gap between stack and heap: %ld.\n"
, (long)gap);
/* NOTREACHED */
}
} /* assert_stack_gap() */
/*-------------------------------------------------------------------------*/
void
reserve_memory (void)
/* Reserve the memory blocks according to reserved_xxx_area, and the
* min_{small}_malloced limits.
*/
{
void * ptr;
/* First, check if max_malloced is a sensible value.
* We overestimate the requirement a bit...
*/
if (max_malloced > 0)
{
mp_int required_mem = 0;
mp_int required_reserve = 0;
if (reserved_user_size > 0) required_reserve += reserved_user_size;
if (reserved_master_size > 0) required_reserve += reserved_master_size;
if (reserved_system_size > 0) required_reserve += reserved_system_size;
if (min_malloced > 0)
{
if (min_malloced > required_reserve)
required_mem += min_malloced;
else
required_mem += required_reserve + min_malloced;
}
else
required_mem += required_reserve;
if (min_small_malloced > 0) required_mem += min_small_malloced;
if (max_malloced < required_mem)
{
printf("%s max_malloced is %ld bytes, "
"but driver requires %ld bytes.\n"
, time_stamp(), (long)max_malloced, (long)required_mem
);
debug_message("%s max_malloced is %ld bytes, "
"but driver requires %ld bytes.\n"
, time_stamp(), (long)max_malloced, (long)required_mem
);
max_malloced = required_mem;
}
}
if (min_malloced > 0)
{
ptr = xalloc(min_malloced);
if (ptr)
xfree(ptr);
else
{
printf("%s Failed to allocate MIN_MALLOCED block of %ld bytes.\n"
, time_stamp(), (long)min_malloced);
debug_message("%s Failed to allocate MIN_MALLOCED block of %ld bytes.\n"
, time_stamp(), (long)min_malloced);
}
}
#ifdef MALLOC_smalloc
if (min_small_malloced > 0)
small_chunk_size = min_small_malloced;
#endif /* MALLOC_smalloc */
if (reserved_system_size > 0)
{
reserved_system_area = xalloc((size_t)reserved_system_size);
if (reserved_system_area == NULL)
{
printf("%s Failed to allocate system reserve of %ld bytes.\n"
, time_stamp(), (long)reserved_system_area);
debug_message("%s Failed to allocate system reserve of %ld bytes.\n"
, time_stamp(), (long)reserved_system_area);
}
}
if (reserved_master_size > 0)
{
reserved_master_area = xalloc((size_t)reserved_master_size);
if (reserved_master_area == NULL)
{
printf("%s Failed to allocate master reserve of %ld bytes.\n"
, time_stamp(), (long)reserved_master_area);
debug_message("%s Failed to allocate master reserve of %ld bytes.\n"
, time_stamp(), (long)reserved_master_area);
}
}
if (reserved_user_size > 0)
{
reserved_user_area = xalloc((size_t)reserved_user_size);
if (reserved_user_area == NULL)
{
printf("%s Failed to allocate user reserve of %ld bytes.\n"
, time_stamp(), (long)reserved_user_area);
debug_message("%s Failed to allocate user reserve of %ld bytes.\n"
, time_stamp(), (long)reserved_user_area);
}
}
} /* reserve_memory() */
#ifndef string_copy
/*-------------------------------------------------------------------------*/
char *
string_copy (const char *str)
/* string_copy() acts like strdup(), but uses xalloc() instead of malloc().
* It also has the additional bonus that it can trace file/line of the
* calling place if MALLOC_TRACE is defined (and the allocator supports
* tracing).
*/
{
char *p;
p = xalloc(strlen(str)+1);
if (p) {
(void)strcpy(p, str);
}
return p;
}
#endif /* string_copy */
/***************************************************************************/