psyclpc/src/xalloc.c

1679 lines
45 KiB
C

/*---------------------------------------------------------------------------
* 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 <ctype.h>
#include <stddef.h>
#include <stdio.h>
#include "xalloc.h"
#include "backend.h"
#include "gcollect.h"
#include "interpret.h"
#include "simulate.h"
#include "exec.h"
#include "object.h"
#include "mstrings.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 (10 * ERROR_FMT_LEN)
/* A counter type for statistics and its functions.
*/
typedef struct { unsigned long counter, size; } t_stat;
/* A counter type for statistics and its functions: */
static inline void count_add(t_stat *a, unsigned long b) {
a->size += b;
}
static inline void count(t_stat *a, unsigned long b) {
count_add(a, b);
if (b < 0)
--a->counter;
else
++a->counter;
}
static inline void count_up(t_stat *a, unsigned long b) {
count_add(a, b);
++a->counter;
}
static inline void count_up_n(t_stat *a, unsigned long b, unsigned long c) {
count_add(a, b * c);
a->counter += b;
}
static inline void count_back(t_stat *a, unsigned long b) {
count_add(a, -b);
--a->counter;
}
static inline void count_back_n(t_stat *a, unsigned long b, unsigned long c) {
count_add(a, -(b * c));
a->counter -= b;
}
typedef p_uint word_t;
/* Our 'word' type.
*/
/*-------------------------------------------------------------------------*/
/* The extra xalloc header fields.
* GC's write_malloc_trace() expects XM_FILE and XM_LINE at the end
* of the header.
* TODO: Let the GC use the symbolic constants.
*/
#ifdef MALLOC_LPC_TRACE
# define XM_OBJ 0 /* (object_t*) the allocating object */
# define XM_PROG 1 /* (int32) allocating program's id-number */
# define XM_PC 2 /* (bytecode_p) inter_pc at the allocation */
# ifdef MALLOC_TRACE
# define XM_OVERHEAD (5)
# define XM_FILE 3 /* (const char*) allocating source file */
# define XM_LINE 4 /* (word_t) allocating line in source file */
# else
# define XM_OVERHEAD (3)
# endif /* MALLOC_TRACE */
#else /* !MALLOC_LPC_TRACE */
# ifdef MALLOC_TRACE
# define XM_OVERHEAD (2)
# define XM_FILE 0 /* (const char*) allocating source file */
# define XM_LINE 1 /* (word_t) allocating line in source file */
# else
# define XM_OVERHEAD (0)
# endif /* MALLOC_TRACE */
#endif /* MALLOC_LPC_TRACE */
#define XM_OVERHEAD_SIZE (XM_OVERHEAD * sizeof(word_t))
/*-------------------------------------------------------------------------*/
/* -- 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 char * initial_stack = NULL; /* The stack at the start of the program */
#if 0
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.
*/
#endif
static int going_to_exit = MY_FALSE;
/* When the allocator detected a fatal error, it sets this
* variable before starting the fatal() handling in order to
* prevent recursions.
*/
#ifdef MALLOC_SBRK_TRACE
static size_t mdb_size;
static object_t *mdb_object;
# if defined(MALLOC_TRACE)
static const char * mdb_file;
static int mdb_line;
# endif
/* Persistent copy of the latest memory request information,
* so that the SBRK_TRACE can print it.
*/
#endif /* MALLOC_SBRK_TRACE */
/* --- Statistics --- */
static t_stat xalloc_stat = {0,0};
/* Total number and size of allocations done by the driver (incl overhead).
*/
#if defined(REPLACE_MALLOC) && defined(SBRK_OK)
static t_stat clib_alloc_stat = {0,0};
/* Number and size of allocations done through the clib emulation
* functions (incl overhead).
*/
#endif
/*-------------------------------------------------------------------------*/
/* Forward declarations */
#ifdef MALLOC_LPC_TRACE
static void write_lpc_trace (int d, word_t *p, int oneline);
#endif
#ifdef GC_SUPPORT
static void print_block (int d, word_t *block);
#endif /* GC_SUPPORT */
#ifdef MALLOC_SBRK_TRACE
/*-------------------------------------------------------------------------*/
static void
mem_debug_log (const char * name, p_int size)
/* Mem debug log function. Log the given <size> for function <name>
* to stdout together with the original allocation request size.
*/
{
#if defined(MALLOC_TRACE)
dprintf4(1, "%s %s(%d) for %d"
, (p_int)current_time_stamp, (p_int)name
, (p_int)size, (p_int)mdb_size
);
dprintf3(1, " , '%s':%d , obj %s\n"
, (p_int)mdb_file, (p_int)mdb_line
, (p_int)(mdb_object ? ( mdb_object->name ? get_txt(mdb_object->name) : "<?>"): "<null>")
);
#else
dprintf4(1, "%s %s(%d) for %d"
, (p_int)current_time_stamp, (p_int)name
, (p_int)size, (p_int)mdb_size
);
dprintf1(1, " , obj %s\n"
, (p_int)(mdb_object ? ( mdb_object->name ? get_txt(mdb_object->name) : "<?>"): "<null>")
);
#endif /* MALLOC_TRACE */
} /* mem_debug_log() */
/*-------------------------------------------------------------------------*/
static void
mdb_log_sbrk (p_int size)
/* esbrk() log function: esbrk() is called to allocate <size> bytes
* from the system. Log this information to stdout together with
* the original allocation request size.
*/
{
mem_debug_log("esbrk", size);
} /* mdb_log_sbrk() */
#else
#define mdb_log_sbrk(size) NOOP
#endif /* MALLOC_SBRK_TRACE */
/*-------------------------------------------------------------------------*/
/* Include the allocator source.
*
* The allocator is free to use any information found in xalloc.
* In return, the allocator has to provide this interface:
*
* static POINTER mem_alloc(size_t size)
* to allocate a memory block
*
* static void mem_free(POINTER p)
* to deallocate a memory block
*
* static POINTER mem_realloc (POINTER p, size_t size)
* reallocate a block.
*
* static void * mem_increment_size (void *vp, size_t size)
* Increase a block size in place. If not possible, return NULL.
*
* static static size_t mem_block_size (POINTER p)
* Return the size of an allocated block.
* If this value is not available, implement this function as a dummy,
* but also #define NO_MEM_BLOCK_SIZE.
* For Garbage Collection or replacing malloc() this function must be
* valid!
*
* static static size_t mem_overhead ()
* Return the size of the allocators internal overhead.
*
* static void mem_mark_permanent (POINTER p)
* static void mem_mark_collectable (POINTER p)
* Mark a block as permanent resp. collectable
*
* void mem_consolidate (bool force)
* Do whatever consolidation is useful.
* <force> is true after a GC, and false when called from the backend.
*
* void mem_dump_data (strbuf_t *sbuf)
* void mem_dump_extdata (strbuf_t *sbuf)
* void mem_dinfo_data (svalue_t *svp, int value)
* Return the statistics data.
*
* Bool mem_dump_memory (int fd)
* Dump the memory layout to <fd>, or return FALSE.
* If <fd> is -1, just return TRUE or FALSE (this is used to check if
* the allocator supports memory dumps).
*
#ifdef MALLOC_EXT_STATISTICS
* void mem_update_stats (void)
* Update whatever extended statistics are available.
* Called every backend cycle or so to allow for the calculation of
* averages over time.
*
#endif
#ifdef GC_SUPPORT
* static void mem_clear_ref (POINTER p)
* static void mem_mark_ref (POINTER p)
* static Bool mem_test_ref (POINTER p)
* Clear, set, test the 'referenced' marker.
*
* void mem_clear_ref_flags()
* Clear all 'referenced' markers.
*
* void mem_free_unrefed_memory()
* Free all memory marked as 'unreferenced'.
* This routine also has to accordingly adjust xalloc_stat.
*
#ifdef MALLOC_TRACE
* static Bool mem_is_freed (POINTER p, size_t minsize)
* Return true if <p> is a free block.
#endif
#endif
*
* #define MEM_ALIGN
* the alignment guaranteed by the allocator
* #define REPLACE_MALLOC
* if the allocator's mem_alloc()/mem_free() can be used to replace the
* libc allocation routines (ie. the allocator doesn't use malloc()
* itself). The actual replacement is provided by xalloc.
* #define MEM_MAIN_THREADSAFE
* The allocator is safe to use from the main thread.
* #define MEM_THREADSAFE
* The allocator is altogether threadsafe.
*/
#if defined(MALLOC_smalloc)
# include "smalloc.c"
#elif defined(MALLOC_slaballoc)
# include "slaballoc.c"
#elif defined(MALLOC_sysmalloc)
# include "sysmalloc.c"
#elif defined(MALLOC_ptmalloc)
# include "xptmalloc.c"
#else
# error "No allocator specified."
#endif
#ifdef NO_MEM_BLOCK_SIZE
# if defined(GC_SUPPORT) || defined(REPLACE_MALLOC)
# error "For GC_SUPPORT or REPLACE_MALLOC, mem_block_size() must exist!"
# endif
#endif
#if defined(USE_PTHREADS) && !defined(MEM_THREADSAFE) && !defined(MEM_MAIN_THREADSAFE)
# warning ""
# warning "-----------------------------------"
# warning "PThreads enabled, but the allocator"
# warning "is not threadsafe!"
# warning "-----------------------------------"
# warning ""
#endif
#if defined(USE_SQLITE) && defined(SQLITE3_USES_PTHREADS) && !defined(MEM_THREADSAFE) && !defined(MEM_MAIN_THREADSAFE)
# warning ""
# warning "-----------------------------------"
# warning "SQLite3 uses PThreads, but the allocator"
# warning "is not threadsafe!"
# warning "-----------------------------------"
# warning ""
#endif
#if defined(MALLOC_ptmalloc) && defined(GC_SUPPORT) && defined(__FreeBSD__)
# warning ""
# warning "-----------------------------------"
# warning "PTMalloc selected, but the allocator"
# warning "doesn't support GC under FreeBSD!"
# warning "-----------------------------------"
# warning ""
#endif
/*-------------------------------------------------------------------------*/
size_t
xalloced_size (POINTER p
#ifdef NO_MEM_BLOCK_SIZE
UNUSED
#endif /* NO_MEM_BLOCK_SIZE */
)
/* Return the allocation size (incl. overhead) of the block <p>.
*/
{
#ifndef NO_MEM_BLOCK_SIZE
return mem_block_size((word_t*)p - XM_OVERHEAD);
#else
# ifdef __MWERKS__
# pragma unused(p)
# endif
return 0;
#endif
} /* xalloced_size() */
/*-------------------------------------------------------------------------*/
size_t
xalloc_overhead (void)
/* Return the total overhead of an allocation - xalloc and allocator.
*/
{
return mem_overhead() + XM_OVERHEAD_SIZE;
} /* xalloc_overhead() */
/*-------------------------------------------------------------------------*/
static Bool
retry_alloc (size_t size MTRACE_DECL)
/* An allocation for <size> bytes just failed - try to free the reserves.
* Return TRUE if the allocation can be retried, FALSE is not.
* If allocation privilege is SYSTEM and the allocation can't be retried,
* abort the driver.
*/
{
/* Out of memory - try to recover */
static char mess1[] =
"Temporarily out of MEMORY. Freeing user reserve.\n";
static char mess2[] =
"Temporarily out of MEMORY. Freeing master reserve.\n";
static char mess3[] =
"Temporarily out of MEMORY. Freeing system reserve.\n";
static char mess4[] =
"Totally out of MEMORY.\n";
static char mess_d1[] =
"Low on MEMORY: Trying to allocate ";
static char mess_d2[] =
" bytes for ";
static char mess_d3[] =
" bytes request";
static char mess_d4[] =
" (";
static char mess_d7[] =
", prog ";
static char mess_d6[] =
")";
#if defined(MALLOC_TRACE)
static char mess_d5[] =
" line ";
#endif
static char mess_nl[] =
"\n";
/* Print the Out-Of-Mem diagnostic */
writes(2, mess_d1);
writed(2, size);
writes(2, mess_d2);
writed(2, size - XM_OVERHEAD_SIZE);
writes(2, mess_d3);
#ifdef MALLOC_TRACE
writes(2, mess_d4);
writes(2, malloc_trace_file);
writes(2, mess_d5);
writed(2, malloc_trace_line);
writes(2, mess_d6);
#endif
writes(2, mess_d4);
writes(2, current_object ? get_txt(current_object->name) : "<null>");
writes(2, mess_d7);
writes(2, current_prog ? get_txt(current_prog->name) : "<null>");
writes(2, mess_d6);
writes(2, mess_nl);
/* Free the next reserve, the try again */
if (gc_request == gcDont)
gc_request = gcMalloc;
extra_jobs_to_do = MY_TRUE;
if (reserved_user_area)
{
xfree(reserved_user_area);
reserved_user_area = NULL;
writes(2, mess1);
return MY_TRUE;
}
if (malloc_privilege >= MALLOC_MASTER && reserved_master_area)
{
xfree(reserved_master_area);
reserved_master_area = NULL;
writes(2, mess2);
return MY_TRUE;
}
if (malloc_privilege >= MALLOC_SYSTEM && reserved_system_area)
{
xfree(reserved_system_area);
reserved_system_area = 0;
writes(2, mess3);
return MY_TRUE;
}
if (malloc_privilege < MALLOC_SYSTEM)
{
out_of_memory = MY_TRUE;
return MY_FALSE;
}
/* Totally out of memory: exit */
max_malloced = 0; /* Disable the checking for a clean exit */
going_to_exit = MY_TRUE; /* Prevent recursions */
writes(2, mess4);
(void)dump_trace(MY_FALSE, NULL);
fatal("Out of memory (%lu bytes)\n", (unsigned long)size);
/* NOTREACHED */
return MY_FALSE;
} /* retry_alloc() */
/*-------------------------------------------------------------------------*/
static INLINE Bool
check_max_malloced (void)
/* If max_malloced is set, check if the allocated memory exceeds it.
* If not, return FALSE.
* If yes, and malloc_privilege < MALLOC_SYSTEM: return TRUE.
* If yes, and malloc_privilege == MALLOC_SYSTEM: abort.
*/
{
#ifndef NO_MEM_BLOCK_SIZE
if (max_malloced > 0 && (mp_int)xalloc_stat.size > max_malloced)
{
static const char mess[] = "MAX_MALLOCED limit reached.\n";
writes(2, mess);
if (malloc_privilege < MALLOC_SYSTEM)
return MY_TRUE;
/* Totally out of memory: exit */
max_malloced = 0; /* Disable the checking for a clean exit */
going_to_exit = MY_TRUE; /* Prevent recursions */
(void)dump_trace(MY_FALSE, NULL);
fatal("Out of memory.\n");
/* NOTREACHED */
}
#endif
return MY_FALSE;
} /* check_max_malloced() */
/*-------------------------------------------------------------------------*/
POINTER
xalloc_traced (size_t size MTRACE_DECL)
/* 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.
*/
{
word_t *p;
if (going_to_exit) /* A recursive call while we're exiting */
exit(3);
#ifdef MALLOC_SBRK_TRACE
mdb_size = size;
mdb_object = current_object;
#ifdef MALLOC_TRACE
mdb_file = malloc_trace_file;
mdb_line = malloc_trace_line;
#endif
#endif /* MALLOC_SBRK_TRACE */
size += XM_OVERHEAD_SIZE;
do {
p = mem_alloc(size);
} while (p == NULL && retry_alloc(size MTRACE_PASS));
if (p == NULL)
{
return NULL;
}
#ifdef MALLOC_TRACE
p[XM_FILE] = (word_t)malloc_trace_file;
p[XM_LINE] = (word_t)malloc_trace_line;
#endif
#ifdef MALLOC_LPC_TRACE
p[XM_OBJ] = (word_t)current_object;
p[XM_PROG] = current_prog ? current_prog->id_number : 0;
p[XM_PC] = (word_t)inter_pc;
#endif
#ifdef NO_MEM_BLOCK_SIZE
count_up(&xalloc_stat, XM_OVERHEAD_SIZE);
#else
count_up(&xalloc_stat, mem_block_size(p));
if (check_max_malloced())
return NULL;
#endif
return (POINTER)(p + XM_OVERHEAD);
} /* xalloc_traced() */
/*-------------------------------------------------------------------------*/
void
xfree (POINTER p)
/* Free the memory block <p>.
*/
{
if (NULL != p)
{
word_t *q = (word_t*)p - XM_OVERHEAD;
#ifdef NO_MEM_BLOCK_SIZE
count_back(&xalloc_stat, XM_OVERHEAD_SIZE);
#else
count_back(&xalloc_stat, mem_block_size(q));
#endif
mem_free(q);
}
} /* xfree() */
/*-------------------------------------------------------------------------*/
POINTER
pxalloc_traced (size_t size MTRACE_DECL)
/* Allocate a block of <size> bytes - like xalloc(), just that the
* memory is not subject to GC.
*/
{
POINTER temp;
temp = xalloc_traced(size MTRACE_PASS);
if (temp)
{
mem_mark_permanent((word_t *)temp - XM_OVERHEAD);
}
return temp;
} /* pxalloc_traced() */
/*-------------------------------------------------------------------------*/
void
pfree (POINTER p)
/* Deallocate a permanent block <p>.
*/
{
if (p)
{
mem_mark_collectable((word_t *)p - XM_OVERHEAD);
}
xfree(p);
} /* pfree() */
/*-------------------------------------------------------------------------*/
POINTER
prexalloc_traced (POINTER p, size_t size MTRACE_DECL)
/* Reallocate block <p> to the new size of <size> and return the pointer.
* The memory is not subject to GC.
#ifdef MALLOC_TRACE
* The trace arguments are admittedly unused in the function, but come
* in handy if the allocation code needs to be instrumented for debugging
* purposes.
#endif
*/
{
POINTER temp;
if (p)
{
mem_mark_collectable((word_t *)p - XM_OVERHEAD);
}
temp = rexalloc_traced(p, size MTRACE_PASS);
if (temp)
{
mem_mark_permanent((word_t *)temp - XM_OVERHEAD);
}
return temp;
} /* prexalloc_traced() */
/*-------------------------------------------------------------------------*/
void *
malloc_increment_size (void *vp, size_t size)
/* Try to extent the allocation block for <vp> in place to hold <size> more
* bytes. If this is not possible, return NULL, otherwise return a pointer
* to the start of the block extension.
*/
{
word_t * block = (word_t*)vp - XM_OVERHEAD;
#ifndef NO_MEM_BLOCK_SIZE
size_t old_size;
#endif
void * rc;
if (going_to_exit) /* A recursive call while we're exiting */
exit(3);
#ifndef NO_MEM_BLOCK_SIZE
old_size = mem_block_size(block);
#endif
rc = mem_increment_size(block, size);
#ifndef NO_MEM_BLOCK_SIZE
if (rc != NULL)
{
count_back(&xalloc_stat, old_size);
count_up(&xalloc_stat, mem_block_size(block));
if (check_max_malloced())
return NULL;
}
#endif
return rc;
} /* malloc_increment_size() */
/*-------------------------------------------------------------------------*/
POINTER
rexalloc_traced (POINTER p, size_t size MTRACE_DECL
#ifndef MALLOC_SBRK_TRACE
UNUSED
#endif /* MALLOC_SBRK_TRACE */
)
/* Reallocate block <p> to the new size of <size> and return the pointer.
* The memory is not aligned and subject to GC.
#ifdef MALLOC_TRACE
* The trace arguments are admittedly unused in the function, but come
* in handy if the allocation code needs to be instrumented for debugging
* purposes.
#endif
*/
{
#ifndef MALLOC_SBRK_TRACE
#ifdef MALLOC_TRACE
# ifdef __MWERKS__
# pragma unused(malloc_trace_file)
# pragma unused(malloc_trace_line)
# endif
#endif /* MALLOC_TRACE */
#endif /* MALLOC_SBRK_TRACE */
word_t *block, *t;
#ifndef NO_MEM_BLOCK_SIZE
size_t old_size;
#endif
if (going_to_exit) /* A recursive call while we're exiting */
exit(3);
if (!p)
{
return xalloc_traced(size MTRACE_ARG);
}
#ifdef MALLOC_SBRK_TRACE
mdb_size = size;
mdb_object = current_object;
#ifdef MALLOC_TRACE
mdb_file = malloc_trace_file;
mdb_line = malloc_trace_line;
#endif
#endif /* MALLOC_SBRK_TRACE */
size += XM_OVERHEAD_SIZE;
block = (word_t *)p - XM_OVERHEAD;
#ifndef NO_MEM_BLOCK_SIZE
old_size = mem_block_size(block);
t = malloc_increment_size(p, size - old_size);
if (t)
return p;
#endif
do {
t = mem_realloc(block, size);
} while (t == NULL && retry_alloc(size MTRACE_ARG));
if (t)
{
#ifndef NO_MEM_BLOCK_SIZE
count_back(&xalloc_stat, old_size);
count_up(&xalloc_stat, mem_block_size(t));
if (check_max_malloced())
return NULL;
#endif
t += XM_OVERHEAD;
}
return (POINTER)t;
} /* rexalloc() */
/*=========================================================================*/
/* GARBAGE COLLECTOR */
#ifdef GC_SUPPORT
/*-------------------------------------------------------------------------*/
void
x_clear_ref (POINTER p)
/* GC Support: Clear the 'referenced' flag for block <p>.
*/
{
mem_clear_ref((word_t *)p - XM_OVERHEAD);
} /* x_clear_ref() */
/*-------------------------------------------------------------------------*/
int
x_mark_ref (POINTER p)
/* GC Support: Set the 'referenced' flag for block <p>.
* This function returns a value (1) so that it can be used in macros
* more easily.
*/
{
mem_mark_ref((word_t *)p - XM_OVERHEAD);
return 1;
} /* x_mark_ref() */
/*-------------------------------------------------------------------------*/
Bool
x_test_ref (POINTER p)
/* GC Support: Check the memory block marker for <p>, return TRUE if _not_
* set.
*/
{
return mem_test_ref((word_t *)p - XM_OVERHEAD);
} /* x_test_ref() */
/*-------------------------------------------------------------------------*/
#ifdef MALLOC_TRACE
static int num_dispatched_types = 0;
/* Used size of the dispatch_table
*/
static struct {
char *file;
word_t line;
void (*func)(int, void *, int);
} dispatch_table[12];
/* The dispatch table used to recognize and print datablocks.
* The recognition is simple and uses the file/line information received
* from sample allocations done by gcollect. If an allocation matches
* one entry in the table, the function is called with (filedescriptor,
* blockaddress, 0).
*/
#ifdef CHECK_OBJECT_GC_REF
/*-------------------------------------------------------------------------*/
/* Some extra variables to explicitely store the location of program
* and object allocations.
*/
static char * object_file;
static word_t object_line;
static char * program_file;
static word_t program_line;
void
note_object_allocation_info ( void *block )
{
object_file = (char *)((word_t*)block)[XM_FILE-XM_OVERHEAD];
object_line = ((word_t*)block)[XM_LINE-XM_OVERHEAD];
}
void
note_program_allocation_info ( void *block )
{
program_file = (char *)((word_t*)block)[XM_FILE-XM_OVERHEAD];
program_line = ((word_t*)block)[XM_LINE-XM_OVERHEAD];
}
Bool
is_object_allocation ( void *block )
{
return (object_file == (char *)((word_t*)block)[XM_FILE-XM_OVERHEAD])
&& (object_line == ((word_t*)block)[XM_LINE-XM_OVERHEAD]);
}
Bool
is_program_allocation ( void *block )
{
return (program_file == (char *)((word_t*)block)[XM_FILE-XM_OVERHEAD])
&& (program_line == ((word_t*)block)[XM_LINE-XM_OVERHEAD]);
}
#endif
/*-------------------------------------------------------------------------*/
void
store_print_block_dispatch_info (void *block
, void (*func)(int, void *, int)
)
/* Add a new block type: get the file/line information from the
* allocation <block> and store it with the <func>tion.
*/
{
int i;
i = num_dispatched_types++;
if (i >= (int)(sizeof(dispatch_table)/sizeof(dispatch_table[0])))
{
writes(2, "dispatch_table for print_block() to small\n");
return;
}
dispatch_table[i].file = (char *)((word_t*)block)[XM_FILE-XM_OVERHEAD];
dispatch_table[i].line = ((word_t*)block)[XM_LINE-XM_OVERHEAD];
dispatch_table[i].func = func;
} /* store_print_block_dispatch_info() */
/*-------------------------------------------------------------------------*/
Bool
is_freed (void *p, p_uint minsize)
/* Check if block for the allocation <p> is a free block of at least
* <minsize>. Blocks outside the heap always qualify.
* The function might return false for joined blocks.
*/
{
word_t *block;
block = (word_t *) p - XM_OVERHEAD;
return mem_is_freed(block, minsize + XM_OVERHEAD_SIZE);
} /* is_freed() */
#endif /* MALLOC_TRACE */
/*-------------------------------------------------------------------------*/
static void
print_block (int d, word_t *block)
/* Block <block> was recognized as lost - print it onto file <d>.
* If possible, use the information in the dispatch_table, otherwise
* print the first characters as they are.
*/
{
word_t size;
int i;
#ifdef MALLOC_TRACE
char *file = (char *)block[XM_FILE];
word_t line = block[XM_LINE];
for (i = num_dispatched_types; --i >= 0; )
{
if (dispatch_table[i].file == file
&& dispatch_table[i].line == line)
{
(*dispatch_table[i].func)(d, (char *)(block+XM_OVERHEAD), 0);
write(d, "\n", 1);
return;
}
}
#endif
/* Print a hexdump, but not more than 80 bytes */
{
int limit = 80;
char * cp;
size = mem_block_size(block) - XM_OVERHEAD;
cp = (char *)(block + XM_OVERHEAD);
while (size > 0 && limit > 0)
{
/* Start of line: print the address */
dprintf1(d, "%x:", (p_int)cp);
/* Print the up to 16 bytes after cp as hex values */
for (i = 0; i < 16 && i < (int)size && i < limit; i++)
dprintf1(d, " %X", cp[i]);
/* Align foward to the character interpretation */
for (; i < 16; i++)
writes(d, " ");
writes(d, " ");
/* Print the same data as characters */
for (i = 0; i < 16 && i < (int)size && i < limit; i++)
{
if (isprint((unsigned char)cp[i]))
write(d, cp+i, 1);
else
writes(d, ".");
}
writes(d, "\n");
cp += i;
size -= i;
limit -= i;
}
}
writes(d, "\n");
} /* print_block() */
#endif /* GC_SUPPORT */
/*-------------------------------------------------------------------------*/
#ifdef MALLOC_LPC_TRACE
static void
write_lpc_trace (int d, word_t *p, int oneline)
/* Write the object and program which allocated the memory block <p>
* onto file <d>.
* if <oneline> is true, all information is printed in one line.
*/
{
object_t *obj, *o;
bytecode_p pc;
program_t *prog;
int line;
int32 id;
/* Try to find the object which allocated this block */
if (!oneline)
{
if ( NULL != (obj = (object_t *)p[XM_OBJ]) )
{
writes(d, " By object: ");
if (obj->flags & O_DESTRUCTED)
writes(d, "(destructed) ");
for (o = obj_list; o && o != obj; o = o->next_all) NOOP;
if (!o)
writes(d, "(not in list) ");
else if (o->name)
writes(d, get_txt(o->name)); /* TODO: If this cores, it has to go again */
}
else
writes(d, " No object.");
writes(d, "\n");
}
else
{
if ( NULL != (obj = (object_t *)p[XM_OBJ]) )
{
for (o = obj_list; o && o != obj; o = o->next_all) NOOP;
if (!o || !o->name)
writes(d, "?");
else
writes(d, get_txt(o->name)); /* TODO: If this cores, it has to go again */
if (obj->flags & O_DESTRUCTED)
writes(d, " (destructed)");
}
else
writes(d, "-");
}
/* Try to find the program which allocated this block */
if ( 0 != (id = p[XM_PROG]) )
{
pc = NULL;
prog = NULL;
for ( o = obj_list
; o
&& !(o->flags & O_DESTRUCTED)
&& ((p_int)o->prog&1 || o->prog->id_number != id); )
o = o->next_all;
/* Unlikely, but possible: ids might have been renumbered. */
if (o)
{
pc = (bytecode_p)p[XM_PC];
prog = o->prog;
if (prog->program > pc || pc > PROGRAM_END(*prog))
o = NULL;
}
if (o)
{
string_t *file;
line = get_line_number(pc, prog, &file);
if (!oneline)
{
dprintf2(d, " By program: %s line:%d\n", (p_int)get_txt(file), line);
}
else
{
dprintf2(d, " , %s %d", (p_int)get_txt(file), line);
}
if (file)
free_mstring(file);
}
else
{
if (!oneline)
writes(d, " By program: Not found at old address.\n");
}
}
if (oneline)
writes(d, "\n");
} /* write_lpc_trace() */
#endif /* MALLOC_LPC_TRACE */
/*-------------------------------------------------------------------------*/
void
dump_lpc_trace (int d
, void *p
#ifndef MALLOC_LPC_TRACE
UNUSED
#endif
)
/* Write the object and program which allocated the memory block <p>
* onto file <d>.
* In contrast to write_lpc_trace(), the address of the memory block is
* the one returned by xalloc(), ie. pointing after the memory block
* header.
*/
{
#if defined(MALLOC_LPC_TRACE)
write_lpc_trace(d, ((word_t *)p) - XM_OVERHEAD, MY_FALSE);
#else
# ifdef __MWERKS__
# pragma unused(p)
# endif
writes(d, "No malloc lpc trace.\n");
#endif /* MALLOC_LPC_TRACE */
} /* dump_lpc_trace() */
/*-------------------------------------------------------------------------*/
void
dump_malloc_trace (int d
, void *adr
#if !defined(MALLOC_TRACE) && !defined(MALLOC_LPC_TRACE)
UNUSED
#endif
)
/* Write the allocation information (file, linenumber, object and such) of
* the memory block <adr> onto file <d>.
* <adr> is the address returned by xalloc(), ie. pointing after the memory
* block header.
*/
{
#if !defined(MALLOC_TRACE) && !defined(MALLOC_LPC_TRACE)
# ifdef __MWERKS__
# pragma unused(adr)
# endif
writes(d, "No malloc trace.\n");
#else
word_t *p = ((word_t *)adr) - XM_OVERHEAD;
# ifdef MALLOC_TRACE
word_t size = mem_block_size(p);
dprintf3(d, " %s %d size 0x%x\n",
p[XM_FILE], p[XM_LINE], size
);
# endif
# ifdef MALLOC_LPC_TRACE
write_lpc_trace(d, p, MY_FALSE);
# endif
#endif
} /* dump_malloc_trace() */
/*=========================================================================*/
/* CLIB ALLOCATION FUNCTIONS */
#if defined(REPLACE_MALLOC) && defined(SBRK_OK)
/*-------------------------------------------------------------------------*/
static POINTER
amalloc (size_t size)
/* Allocate an aligned block of <size> bytes, if necessary, with
* SYSTEM privilege. The block is not subject to GC.
* Result is the pointer to the allocated block, or NULL.
*/
{
char *temp;
#if MALLOC_ALIGN > MEM_ALIGN
#if defined(HAVE_MADVISE)
size_t orig_size = size;
#endif
size += (MALLOC_ALIGN-MEM_ALIGN);
#endif
temp = (char *)pxalloc(size);
if (!temp)
{
int save_privilege = malloc_privilege;
malloc_privilege = MALLOC_SYSTEM;
temp = (char *)pxalloc(size);
malloc_privilege = save_privilege;
}
#if MALLOC_ALIGN > MEM_ALIGN
if (temp)
{
/* Set the first byte of the alignment area to 0xAF - afree(0
* is going to look for it - and the rest to 0.
*/
*temp++ = 0xAF;
while ((p_int)temp & (MALLOC_ALIGN-1))
*temp++ = 0;
MADVISE(temp, orig_size);
}
#endif
return (POINTER)temp;
} /* amalloc() */
/*-------------------------------------------------------------------------*/
static void
afree (POINTER p)
/* Free the aligned memory block <p>.
*/
{
char *q = (char *)p;
if (!q)
return;
#if MALLOC_ALIGN > MEM_ALIGN
/* amalloc() filled the alignment area with 0s except for the first byte.
* Search backwards to find that marker and with it the real block begin.
*/
while (!*--q) NOOP;
#endif
pfree(q);
} /* afree() */
/*-------------------------------------------------------------------------*/
static INLINE long
get_block_size (POINTER ptr)
/* Get the allocated block size for the block with user area starting
* at <ptr>. This function is meant only for block allocated with (a)malloc().
* Result is the size in bytes inclusive overhead.
*/
{
long size = 0;
if (ptr)
{
/* Get the allocated size of the block for the statistics */
char *q;
q = (char *)ptr;
#if MALLOC_ALIGN > MEM_ALIGN
while ( !(size = *--q) ) NOOP;
#endif
size = xalloced_size(q) + mem_overhead();
}
return size;
} /* get_block_size() */
/*-------------------------------------------------------------------------*/
POINTER
malloc (size_t size)
/* Allocate an empty memory block of size <sizel>.
* The memory is aligned and not subject to GC.
*/
{
POINTER result;
result = amalloc(size);
if (result)
{
count_up(&clib_alloc_stat, get_block_size(result));
}
return result;
} /* malloc() */
/*-------------------------------------------------------------------------*/
FREE_RETURN_TYPE
free (POINTER ptr)
/* Free the memory block <ptr> which was allocated with malloc().
*/
{
if (ptr)
{
count_back(&clib_alloc_stat, get_block_size(ptr));
}
afree(ptr);
FREE_RETURN
} /* free() */
/*-------------------------------------------------------------------------*/
POINTER
calloc (size_t nelem, size_t sizel)
/* Allocate an empty memory block for <nelem> objects of size <sizel>.
* The memory is aligned and not subject to GC.
*/
{
char *p;
if (nelem == 0 || sizel == 0)
return NULL;
p = malloc(nelem * sizel);
if (p == NULL)
return NULL;
memset(p, '\0', nelem * sizel);
return p;
} /* calloc() */
/*-------------------------------------------------------------------------*/
POINTER
realloc (POINTER p, size_t size)
/* Reallocate block <p> to the new size of <size> and return the pointer.
* The memory is aligned and not subject to GC.
*/
{
size_t old_size;
POINTER t;
if (!p)
return malloc(size);
old_size = get_block_size(p) - mem_overhead();
if (old_size >= size)
return p;
t = malloc(size);
if (t == NULL)
return NULL;
memcpy(t, p, old_size);
free(p);
return t;
} /* realloc() */
#endif /* REPLACE_MALLOC */
/* ======================================================================= */
/*-------------------------------------------------------------------------*/
void
get_stack_direction (void)
/* Find the direction of the stackgrowth and store the result (+1 or -1)
* into the global stack_direction.
*/
{
char local; /* to get stack address */
if (initial_stack == NULL) /* initial call */
{
initial_stack = &local;
get_stack_direction (); /* recurse once */
}
else /* recursive call */
if (&local > initial_stack)
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;
char * stack_start, * stack_end;
ptrdiff_t gap;
char local; /* used to yield a stack address */
/* 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.
* If the stack-gap check is not necessary, the 'condition' will be set to
* Fatal, otherwise the check will be enabled by setting condition to
* 'Normal'.
*/
if (condition == Initial)
{
/* Currently there are no limitations on checking the heap/stack gap.
*/
condition = Normal;
}
/* Determine the stack limits */
if (stack_direction < 0)
{
stack_start = &local;
stack_end = initial_stack;
}
else
{
stack_start = initial_stack;
stack_end = &local;
}
/* Check if the heap and stack overlap */
if ((stack_end > (char *)heap_end && stack_start < (char *)heap_end)
|| (stack_end > (char *)heap_start && stack_start < (char *)heap_start)
)
{
if (condition != Fatal)
{
condition = Fatal;
fatal("Out of memory: Stack (%p..%p) overlaps heap (%p..%p).\n"
, stack_start, stack_end, heap_start, heap_end);
/* NOTREACHED */
}
return; /* else: Recursive call during fatal() handling */
}
/* Check if the stack grows towards the heap. If it doesn't
* we don't have to worry about the gap.
*/
if ((stack_direction > 0) ? (stack_start > (char *)heap_end)
: (stack_end < (char *)heap_start)
)
{
/* No worries about the gap */
condition = Normal;
return;
}
/* Heap and stack may overlap - do the normal gap checking.
* Note that on machines with big address spaces the computation
* may overflow.
*/
if (stack_direction < 0)
gap = (char *)stack_start - (char *)heap_end;
else
gap = (char *)heap_start - stack_end;
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;
errorf("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)
{
#ifdef VERBOSE
printf("%s max_malloced is %ld bytes, "
"but driver requires %ld bytes.\n"
, time_stamp(), (long)max_malloced, (long)required_mem
);
#endif
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
{
#ifdef VERBOSE
printf("%s Failed to allocate MIN_MALLOCED block of %ld bytes.\n"
, time_stamp(), (long)min_malloced);
#endif
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)
{
#ifdef VERBOSE
printf("%s Failed to allocate system reserve of %ld bytes.\n"
, time_stamp(), (long)reserved_system_area);
#endif
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)
{
#ifdef VERBOSE
printf("%s Failed to allocate master reserve of %ld bytes.\n"
, time_stamp(), (long)reserved_master_area);
#endif
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)
{
#ifdef VERBOSE
printf("%s Failed to allocate user reserve of %ld bytes.\n"
, time_stamp(), (long)reserved_user_area);
#endif
debug_message("%s Failed to allocate user reserve of %ld bytes.\n"
, time_stamp(), (long)reserved_user_area);
}
}
} /* reserve_memory() */
/*-------------------------------------------------------------------------*/
void
reallocate_reserved_areas (void)
/* Try to reallocate the reserved memory areas. If this is possible,
* a pending slow-shutdown is canceled and the out_of_memory flag is reset.
*/
{
char *p;
malloc_privilege = MALLOC_USER;
if (reserved_system_size && !reserved_system_area) {
if ( !(reserved_system_area = xalloc((size_t)reserved_system_size)) ) {
slow_shut_down_to_do = 1;
return;
}
else {
p = "Reallocated System reserve.\n";
write(1, p, strlen(p));
}
}
if (reserved_master_size && !reserved_master_area) {
if ( !(reserved_master_area = xalloc((size_t)reserved_master_size)) ) {
slow_shut_down_to_do = 1;
return;
}
else {
p = "Reallocated Master reserve.\n";
write(1, p, strlen(p));
}
}
if (reserved_user_size && !reserved_user_area) {
if ( !(reserved_user_area = xalloc((size_t)reserved_user_size)) ) {
slow_shut_down_to_do = 6;
return;
}
else {
p = "Reallocated User reserve.\n";
write(1, p, strlen(p));
}
}
slow_shut_down_to_do = 0;
out_of_memory = MY_FALSE;
} /* reallocate_reserved_areas() */
/*-------------------------------------------------------------------------*/
char *
string_copy_traced (const char *str MTRACE_DECL)
/* string_copy() acts like strdup() with the additional bonus that it can
* trace file/line of the calling place if MALLOC_TRACE is defined.
*/
{
char *p;
size_t len;
len = strlen(str)+1;
memsafe(p = xalloc_traced(len MTRACE_PASS), len, "string_copy");
if (p)
{
(void)memcpy(p, str, len);
}
return p;
} /* string_copy_traced() */
/***************************************************************************/