/*---------------------------------------------------------------------------
* SMalloc Memory Manager
*
* Written and put into the public domain by Sean T. "Satoria" Barrett.
* FAST_FIT algorithm by Joern Rennecke.
* Small block consolidation by Lars Duening.
*---------------------------------------------------------------------------
* Satoria's malloc intended to be optimized for lpmud. This memory manager
* distinguishes between two sizes of blocks: small and large. It manages
* them separately in the hopes of avoiding fragmentation between them.
* It expects small blocks to mostly be temporaries. It expects an equal
* number of future requests as small block deallocations.
*
* smalloc IS NOT THREADSAFE. And the wrong way of fixing this would be
* to wrap all smalloc()/xfree() calls into a mutex. The right way of doing
* this would be to allocate a set of management structures for each thread
* so that only the calls to system malloc()/sbrk() need to be guarded
* by mutexes.
*
* Allocations are measured in 'word_t's which are the same size as void*.
*
* For MALLOC_TRACE and MALLOC_LPC_TRACE the allocated blocks are tagged
* with magic words and points to the allocating context. This costs
* time and memory, but helps greatly in debugging a faulty driver.
*
* Small blocks are allocations of up to SMALL_BLOCK_MAX*4 Bytes, currently
* 128 Bytes. Such blocks are initially allocated from large memory blocks,
* called "small chunks", of 16 or 32 KByte size.
* To reduce fragmentation, the initial small chunk (ok, the first small chunk
* allocated after all arguments have been parsed) is of size
* min_small_malloced, hopefully chosen to be a large multiple of the typical
* small chunk size.
* When a small block is
* freed, it is entered in a free list for blocks of its size, so that
* later allocations can use the pre-allocated blocks in the free lists.
* The free lists use the first word in the "user area" for their link
* pointers; the small chunks are themselves kept in a list and use their
* first word for the list pointer.
*
* If a small block can't be allocated from the appropriate free list nor the
* small chunk, the system tries two more strategies before allocating a
* new small chunk. First, it checks the list of oversized free small blocks
* for a block large enough to be split. If no such block exists,
* the allocator then searches the freelists of larger block sizes for
* a possible split.
*
* The idea behind this strategy is to improve the reuse of free small block
* space, at the cost of higher fragmentation. To battle the fragmentation,
* the allocator allows to 'consolidate' the free lists: it searches the
* whole small block space for free small blocks, merges adjacent ones
* and rebuilds the free lists from ground up. If a merged block is larger
* than SMALL_BLOCK_MAX*4 Bytes, it is put into the free list of oversized
* blocks. Additionally, the consolidation returns small chunks which are
* found to be completely unused to the large memory area.
*
* Small chunks are usually allocated directly from the system. Only when
* the system runs out of memory, the small chunks are allocated from
* the large block free list, possibly fragmenting the large block area and
* reducing the locality of memory accesses.
*
* Large blocks are allocated from the system - if large allocation is
* too small (less than 256 KByte), the allocator allocates a 256 KByte
* block and enters the 'unnecessary' extra memory into the freelist.
* Large blocks are stored with boundary tags: the size field without flags
* is replicated in the last word of the block.
*
#if FIT_STYLE_FAST_FIT
* The free large blocks are stored in an AVL tree for fast retrieval
* of best fits. The AVL structures are stored in the user area of the blocks.
#else
* The free large blocks are stored in a double-linked list with pointers
* in the begin of the user area. The allocator offers FIRST_FIT and BEST_FIT
* (and a HYBRID mode), but in fact this system hasn't been used for years.
#endif
*
#ifdef SBRK_OK
* Memory is allocated from the system with sbrk() and brk(), which puts
* the whole heap under our control.
*
* In this mode, several functions like amalloc() are compiled as malloc(),
* implementing all libc memory functions.
#else
* malloc() is used to allocate a new block of memory. If this block borders
* previous ones, the blocks are joined.
*
* The allocated block (modulo joints) is tagged at both ends with fake
* "allocated" blocks of which cover the unallocated areas - large_malloc()
* will perceive this as a fragmented heap.
#endif
*
* Memory is managed at three privilege levels: USER, MASTER and SYSTEM.
* For every level, a special memory reserve is held by the backend.
* If the system runs out of memory (or if the max_malloced limit has
* been reached), the following steps are taken:
*
* - free the user reserve, then try again.
* - if MASTER privilege: free the master reserve, then try again.
* - if SYSTEM privilege: free the system reserve, then try again.
* - if !SYSTEM privilege: set out_of_memory and return NULL
* - if SYSTEM privilege: dump the lpc backtrace and exit(2) resp. fatal().
*
* If any of the reserves is freed, the gc_request flag is set.
*
*
* SMalloc also implements support for a garbage collector: every memory
* block is equipped with a M_REF flag for use by the collector.
*
#ifdef HAVE_MADVISE
* TODO: Not tested for in configure, not documented.
#endif
*---------------------------------------------------------------------------
*/
#include "driver.h"
#include "typedefs.h"
#ifdef HAVE_MADVISE
# include <sys/mman.h>
# define MADVISE(new,old) madvise(new,old,MADV_RANDOM)
#else
# define MADVISE(new,old) NOOP
#endif
#include "smalloc.h"
#include "backend.h"
#include "gcollect.h"
#include "interpret.h"
#include "main.h"
#include "simulate.h"
#include "svalue.h"
#ifdef MALLOC_LPC_TRACE
#include "exec.h"
#include "object.h"
#endif
#include "../mudlib/sys/debug_info.h"
#define SINT (SIZEOF_CHAR_P)
/* Initialiser macros for the tables */
#define INIT8 0, 0, 0, 0, 0, 0, 0, 0
#define INIT16 INIT8, INIT8
#define INIT32 INIT16, INIT16
/*-------------------------------------------------------------------------*/
/* A handy macro to statically determine the number of
* elements in an array.
*/
#define NELEM(a) (sizeof (a) / sizeof (a)[0])
typedef p_uint word_t;
/* Our 'word' type.
*/
/* Fitstyle: how the large block allocator looks for free blocks:
*/
#define FIT_STYLE_FAST_FIT
/* The free blocks are kept in an AVL tree.
*/
/* #undef DEBUG_AVL */
/* Define this to debug the AVL tree.
*/
#define BEST_FIT 0
#define FIRST_FIT 1
#define HYBRID 2
#define fit_style BEST_FIT
/* When not using FIT_STYLE_FAST_FIT: the fitstyle.
*/
typedef struct { unsigned long counter, size; } t_stat;
/* A counter type for statistics and its functions: */
#define count(a,b) { a.size+=(b); if ((b)<0) --a.counter; else ++a.counter; }
#define count_up(a,b) { a.size+=(b); ++a.counter; }
#define count_add(a,b) { a.size+=(b); }
#define count_back(a,b) { a.size-=(b); --a.counter; }
/* The extra smalloc header fields.
*/
#define M_SIZE 0 /* (word_t) Size in word_t, plus some flags */
#ifdef MALLOC_LPC_TRACE
# define M_OBJ 1 /* (object_t*) the allocating object */
# define M_PROG 2 /* (int32) allocating program's id-number */
# define M_PC 3 /* (bytecode_p) inter_pc at the allocation */
# ifdef MALLOC_TRACE
# define OVERHEAD (7)
# define M_FILE 4 /* (const char*) allocating source file */
# define M_LINE 5 /* (word_t) allocating line in source file */
# define M_MAGIC 6 /* (word_t) The magic word */
# else
# define OVERHEAD (4)
# endif /* MALLOC_TRACE */
#else /* !MALLOC_LPC_TRACE */
# ifdef MALLOC_TRACE
# define OVERHEAD (4)
# define M_FILE 1 /* (const char*) allocating source file */
# define M_LINE 2 /* (word_t) allocating line in source file */
# define M_MAGIC 3 /* (word_t) The magic word */
# else
# define OVERHEAD (1)
# endif /* MALLOC_TRACE */
#endif /* MALLOC_LPC_TRACE */
#define M_LINK OVERHEAD /* (word_t*) Link for the free lists */
/* TODO: Use M_LINK in more places */
#define SMALL_BLOCK_MAX (32)
/* Number of different small block sizes.
*/
#define SMALL_BLOCK_MAX_BYTES (SMALL_BLOCK_MAX * SINT)
/* Maximum payload size of a small block.
*/
#define INIT_SMALL_BLOCK_MAX INIT32
/* The proper initializer macro for all tables sized SMALL_BLOCK_MAX.
*/
/* The chunk sizes.
* SMALL_CHUNK_SIZE: size of a chunk from which small blocks
* are allocated. The actual allocation size is
* SMALL_CHUNK_SIZE+sizeof(word_t*) to account for the block list
* pointer.
* CHUNK_SIZE: size of a chunk from which large blocks are allocated.
*/
#ifdef SBRK_OK
# define SMALL_CHUNK_SIZE 0x04000 /* 16 KByte */
# define CHUNK_SIZE 0x40000
#else
/* It seems to be advantagous to be just below a power of two
* make sure that the resulting chunk sizes are SINT aligned.
*/
# define SMALL_CHUNK_SIZE \
(0x8000 - (OVERHEAD+1)*SINT+SINT - EXTERN_MALLOC_OVERHEAD)
/* large_malloc overhead ^ */
# define CHUNK_SIZE (0x40000 - SINT - EXTERN_MALLOC_OVERHEAD)
#endif
/* Bitflags for the size field (some are duplicates from smalloc.h):
* TODO: Assumes a 32-Bit word_t.
*/
#define PREV_BLOCK 0x80000000 /* Previous block is allocated */
#define THIS_BLOCK 0x40000000 /* This block is allocated */
#define M_REF 0x20000000 /* Block is referenced */
#define M_GC_FREE 0x10000000 /* GC may free this block */
#define M_MASK 0x0fffffff /* Mask for the size, measured in word_t's */
#ifdef MALLOC_TRACE
/* The magic words for free and allocated small blocks.
* Every small block size should get its own magic words, but
* if there are more sizes than words, the words of lower sizes
* are reused.
*
* There are enough words for 64 different sizes.
*/
static word_t sfmagic[]
= { 0xde8f7d2d , 0xbd89a4b6 , 0xb667bbec , 0x475fae0a
, 0x39fc5582 , 0x32041f46 , 0x624a92f0 , 0x16dd36e2
, 0x7be9c1bd , 0x088aa102 , 0x3d38509b , 0x746b9fbe
, 0x2d04417f , 0x775d4351 , 0x53c48d96 , 0x02b26e0b
, 0x418fedcf , 0x19dbc19e , 0x78512adb , 0x1a1f5e2b
, 0x307d7761 , 0x6584c1f0 , 0x24e3c36f , 0x2232310f
, 0x2dac5ceb , 0x106e8b5a , 0x5a05a938 , 0x5e6392b6
, 0x66b90348 , 0x75264901 , 0x4174f402 , 0x618b18a4
, 0x3a6ab4bf , 0x3c4bc289 , 0x2657a9a9 , 0x4e68589b
, 0x09648aa6 , 0x3fc489bb , 0x1c1b715c , 0x054e4c63
, 0x484f2abd , 0x5953c1f8 , 0x79b9ec22 , 0x75536c3d
, 0x50b10549 , 0x4d7e79b8 , 0x7805da48 , 0x1240f318
, 0x675a3b56 , 0x70570523 , 0x2c605143 , 0x17d7b2b7
, 0x55dbc713 , 0x514414b2 , 0x3a09e3c7 , 0x038823ff
, 0x61b2a00c , 0x140f8cff , 0x61ebb6b5 , 0x486ba354
, 0x2360aab8 , 0x29f6bbf8 , 0x43a08abf , 0x5fac6d41
};
static word_t samagic[]
= { 0x34706efd , 0xfc2a5830 , 0x4e62041a , 0x2713945e
, 0xab58d8ab , 0xa372eeab , 0x71093c08 , 0xed2e6cc9
, 0x504e65a2 , 0x1208e35b , 0x6910f7e7 , 0x1012ef5d
, 0x2e2454b7 , 0x6e5f444b , 0x58621a1b , 0x077816af
, 0x6819306d , 0x4db58658 , 0x58291bf8 , 0x3597aa25
, 0x45bb60a0 , 0x6a6a0f11 , 0x1cf1e57c , 0x361265c4
, 0x16ca6054 , 0x34c99833 , 0x0bee2cd7 , 0x680e7507
, 0x6ed37bfa , 0x0f7650d6 , 0x49c11513 , 0x02e308f9
, 0x7162078c , 0x122cb868 , 0x0c18defa , 0x14c2b244
, 0x3c237460 , 0x4fb969b9 , 0x746f1f85 , 0x0c71da02
, 0x61c24d14 , 0x5d80176d , 0x1c84c960 , 0x0fe6a1cc
, 0x4bdf5bb8 , 0x74e6e37b , 0x175eb87b , 0x33f88c25
, 0x429c69d3 , 0x6f87d474 , 0x6990364a , 0x0857ca73
, 0x59f1e385 , 0x06821bc6 , 0x3e6a3037 , 0x70bc43d9
, 0x3b4bb3fa , 0x4a585d0f , 0x58cab8e0 , 0x2a1f2ff4
, 0x59ceade5 , 0x228bcdf4 , 0x2d0238ee , 0x4b30b571
};
#define LFMAGIC 0xdfaff2ee /* Magic word for free large blocks */
#define LAMAGIC 0xf460146e /* Magic word for allocated large blocks */
#endif /* MALLOC_TRACE */
/*-------------------------------------------------------------------------*/
/* Debugging macros */
/* #undef LARGE_TRACE */
#define fake(s) (void)0
#ifndef fake
# define fake(s) dprintf1(2, "%s\n",(p_int)(s)),debug_message(s)
#endif
/* Define this macro to get a log of all allocation requests which can't be
* immediately satisfied from a freelist. The log is written to
* gcollect_outfd.
*/
/* #define DEBUG_SMALLOC_ALLOCS */
#ifdef DEBUG_SMALLOC_ALLOCS
# define ulog(s) \
write(gcollect_outfd, s, strlen(s))
# define ulog1f(s,t) \
dprintf1(gcollect_outfd, s, (p_int)(t))
# define ulog2f(s,t1,t2) \
dprintf2(gcollect_outfd, s, (p_int)(t1), (p_int)(t2))
# define ulog3f(s,t1,t2,t3) \
dprintf3(gcollect_outfd, s, (p_int)(t1), (p_int)(t2), (p_int)(t3))
#else
# define ulog(s) (void)0
# define ulog1f(s,t) (void)0
# define ulog2f(s,t1,t2) (void)0
# define ulog3f(s,t1,t2,t3) (void)0
#endif
/*-------------------------------------------------------------------------*/
Bool debugmalloc = MY_FALSE;
/* TRUE when malloc-debug functions shall be active.
*/
static size_t smalloc_size;
/* Size of the current smalloc() request.
* It is used to print a diagnostic when the memory is running out.
*/
/* --- Small Block variables --- */
static word_t small_chunk_size = SMALL_CHUNK_SIZE;
/* The size of a small chunk. Usually SMALL_CHUNK_SIZE, the first
* small chunk of all can be allocated of min_small_malloced size;
* the allocator will then reset the value to SMALL_CHUNK_SIZE.
*/
static word_t *last_small_chunk = NULL;
/* Pointer the most recently allocated small chunk.
* The first word of each chunk points to the previously allocated
* one.
*/
static word_t *sfltable[SMALL_BLOCK_MAX+1] = {INIT_SMALL_BLOCK_MAX, 0};
/* List of free small blocks of the various sizes.
* The blocks are linked through the first non-header word_t.
* The last list is special: it keeps the oversized free blocks created
* by consolidate_freelists().
*/
static word_t *next_unused = NULL;
/* Pointer to the unused area in the current small chunk.
*/
static word_t unused_size = 0;
/* Size left in the last small chunk in byte.
*/
/* --- Large Block variables --- */
#ifndef FIT_STYLE_FAST_FIT
static word_t *free_list = NULL;
#endif /* FIT_STYLE_FAST_FIT */
static word_t *heap_start = NULL;
/* First address on the heap.
*/
static word_t *heap_end = NULL;
/* Current end address of the heap.
*/
#ifndef SBRK_OK
static int overlap;
/* How much extra memory esbrk() could recycle from joining
* the new allocation with the old one.
*/
#endif
/* --- Statistics --- */
static long small_count[SMALL_BLOCK_MAX] = {INIT_SMALL_BLOCK_MAX};
/* Allocated number of small blocks of the various sizes.
*/
static long small_total[SMALL_BLOCK_MAX] = {INIT_SMALL_BLOCK_MAX};
/* Total number of small blocks of the various sizes.
*/
static long small_max[SMALL_BLOCK_MAX] = {INIT_SMALL_BLOCK_MAX};
/* Max number of small blocks allocated at any time.
*/
static long small_free[SMALL_BLOCK_MAX+1] = {INIT_SMALL_BLOCK_MAX, 0};
/* Number of free small blocks of the various sizes.
*/
static t_stat small_alloc_stat = {0,0};
/* Number and size of small block allocations (incl overhead).
*/
static t_stat small_free_stat = {0,0};
/* Number and size of small blocks deallocations (incl overhead).
*/
static t_stat small_chunk_stat = {0,0};
/* Number and size of allocated small chunks.
*/
static t_stat small_chunk_wasted = {0,0};
/* Number and size of wasted blocks in small chunks.
* These blocks are too small to serve even as small blocks.
*/
static t_stat large_free_stat = {0,0};
/* Number and size of free large blocks.
*/
static t_stat large_alloc_stat = {0,0};
/* Number and size of allocated large blocks.
*/
static t_stat large_wasted_stat = {0,0};
/* Number and size of unusable memory frags around the large blocks.
*/
static t_stat sbrk_stat;
/* Number and size of allocated heap blocks.
*/
static t_stat perm_alloc_stat = {0,0};
/* Number and size of allocations done through the clib emulation
* functions (incl overhead). This figure is a subset of
* {small,large}_alloc_stat.
*/
static t_stat clib_alloc_stat = {0,0};
/* Number and size of allocations done through the clib emulation
* functions (incl overhead). This figure is a subset of
* {small,large}_alloc_stat resp. perm_alloc_stat.
*/
static long malloc_increment_size_calls = 0;
/* Number of calls to malloc_increment_size().
*/
static long malloc_increment_size_success = 0;
/* Number of successfull calls to malloc_increment_size().
*/
static long malloc_increment_size_total = 0;
/* Total memory allocated through to malloc_increment_size().
*/
/*-------------------------------------------------------------------------*/
/* Forward declarations */
static char *esbrk(word_t);
#ifdef MALLOC_TRACE
static char *_large_malloc(word_t, Bool, const char *, int);
#define large_malloc(size, force_m) _large_malloc(size, force_m, file, line)
#define large_malloc_int(size, force_m) _large_malloc(size, force_m, __FILE__, __LINE__)
#else
static char *large_malloc(word_t, Bool);
#define large_malloc_int(size, force_m) large_malloc(size, force_m)
#endif
static void large_free(char *);
/*=========================================================================*/
/* SMALL BLOCKS */
/*-------------------------------------------------------------------------*/
#define s_size_ptr(p) (p)
/* Pointer to the "size left" field in the small chunk.
*/
#define s_next_ptr(p) ((word_t **) (p+OVERHEAD))
/* Pointer to the 'next link' filed in the small chunk.
*/
#define SIZE_INDEX(u_array, size) \
(*(word_t*) ((char*)u_array-OVERHEAD*SINT-SINT+size))
/* Access the '_count' or 'magic' array <u_array> entry for a small
* block of <size> (including overhead).
*/
#define SIZE_MOD_INDEX(u_array, size) \
(*(word_t*) ((char*)u_array+(size-OVERHEAD*SINT-SINT)%(sizeof(u_array))))
/* Access the '_count' or 'magic' array <u_array> entry for a small
* block of <size> (including overhead), %ed to the size of the array.
*/
#define SIZE_PNT_INDEX(u_array, size) \
(*(word_t**)((char*)u_array-OVERHEAD*SINT-SINT+size))
/* Access the 'table' array <u_array> entry for a small
* block of <size> (including overhead).
*/
#define SIZE_INDEX_VALUE(size) \
(size/SINT - OVERHEAD - 1)
/* Index to the proper 'table' array entry for a small
* block of <size> (including overhead).
*/
/* Macro MAKE_SMALL_FREE(word_t *block, word_t size)
* The <size> bytes starting at <block> are a new free small block.
* Set it up and insert it into the appropriate free list.
*
* Note: Changes here need to be mirrored in consolidate_freelists().
*/
#define MAKE_SMALL_FREE_BASIC(block,size) \
*s_size_ptr(block) = ((size) / SINT) | (M_GC_FREE|M_REF); \
*s_next_ptr(block) = SIZE_PNT_INDEX(sfltable, size); \
SIZE_PNT_INDEX(sfltable, size) = block; \
count_up(small_free_stat, size); \
small_free[SIZE_INDEX_VALUE(size)]++;
#ifdef MALLOC_TRACE
#define MAKE_SMALL_FREE(block,size) do {\
MAKE_SMALL_FREE_BASIC(block,size); \
block[M_MAGIC] = SIZE_MOD_INDEX(sfmagic, size); \
} while(0)
#else
#define MAKE_SMALL_FREE(block,size) do {\
MAKE_SMALL_FREE_BASIC(block,size) \
} while(0)
#endif
/* Macro MAKE_SMALL_LPC_TRACE(block)
* If MALLOC_LPC_TRACE is defined, fill in the LPC_TRACE information
* in the small block <block>.
*/
#ifdef MALLOC_LPC_TRACE
# define MAKE_SMALL_LPC_TRACE(block) do {\
block[M_OBJ] = (word_t)current_object; \
block[M_PROG] = current_prog ? current_prog->id_number : 0; \
block[M_PC] = (word_t)inter_pc; \
}while(0)
#else
# define MAKE_SMALL_LPC_TRACE(block) (void)0
#endif
/* Macro MAKE_SMALL_TRACE(block, size)
* Macro MAKE_SMALL_TRACE_UNCHECKED(block, size)
* If MALLOC_TRACE is defined, fill in the TRACE information
* in the small block <block> of size <size> (in bytes incl overhead).
* The _UNCHECKED macro is like the basic macro, except that it doesn't
* check the M_MAGIC word before setting it.
*/
#ifdef MALLOC_TRACE
# define MAKE_SMALL_TRACE(block, size) do { \
block[M_FILE] = (word_t)file; \
block[M_LINE] = line; \
if (block[M_MAGIC] != SIZE_MOD_INDEX(sfmagic, size) ) \
{ \
in_malloc = 0; \
fatal("allocation from free list for %lu bytes: " \
"block %p magic match failed, " \
"expected %lx, found %lx\n" \
, (unsigned long) size, block \
, SIZE_MOD_INDEX(sfmagic, size), block[M_MAGIC]); \
} \
block[M_MAGIC] = SIZE_MOD_INDEX(samagic, size); \
} while(0)
# define MAKE_SMALL_TRACE_UNCHECKED(block, size) do { \
block[M_FILE] = (word_t)file; \
block[M_LINE] = line; \
block[M_MAGIC] = SIZE_MOD_INDEX(samagic, size); \
} while(0)
#else
# define MAKE_SMALL_TRACE(block, size) (void)0
# define MAKE_SMALL_TRACE_UNCHECKED(block, size) (void)0
#endif
/*-------------------------------------------------------------------------*/
POINTER
smalloc (size_t size
# ifdef MALLOC_TRACE
, const char * file, int line
# endif
)
/* Allocate a memory block for <size> bytes at the source <file>:<line>.
* Result is the pointer the memory block, or NULL when out of memory
* (not in SYSTEM privilege).
*
* If the system runs out of memory, the following steps are taken:
*
* - free the user reserve, then try again.
* - if MASTER privilege: free the master reserve, then try again.
* - if SYSTEM privilege: free the system reserve, then try again.
* - if !SYSTEM privilege: set out_of_memory and return NULL
* - if SYSTEM privilege: dump the lpc backtrace and exit(2) resp. fatal().
*
* If any of the reserves is freed, the gc_request flag is set.
*/
{
word_t *temp;
#if defined(HAVE_MADVISE) || defined(DEBUG_SMALLOC_ALLOCS)
size_t orig_size = size;
#endif
assert_stack_gap();
smalloc_size = size;
if (size == 0)
{
in_malloc = 0;
fatal("Malloc size = 0.\n");
}
/* TODO: For the following test, see SIZET_limits in port.h */
if (size >= ULONG_MAX - OVERHEAD*SINT - SINT)
{
in_malloc = 0;
fatal("Malloc size exceeds numerical limit.\n");
}
if (in_malloc++)
{
in_malloc = 0;
writes(1,"Multiple threads in smalloc()\n");
fatal("Multiple threads in smalloc()\n");
/* NOTREACHED */
return NULL;
}
if (size > SMALL_BLOCK_MAX_BYTES)
{
void * rc = large_malloc(size, MY_FALSE);
in_malloc--;
return rc;
}
/* It's a small block */
size = (size+OVERHEAD*SINT+SINT-1) & ~(SINT-1); /* block size in bytes */
/* Update statistics */
count_up(small_alloc_stat,size);
SIZE_INDEX(small_count, size) += 1;
SIZE_INDEX(small_total, size) += 1;
if (SIZE_INDEX(small_count, size) > SIZE_INDEX(small_max, size))
SIZE_INDEX(small_max, size) = SIZE_INDEX(small_count, size);
if ( NULL != (temp = SIZE_PNT_INDEX(sfltable, size)) )
{
/* allocate from the free list */
count_back(small_free_stat, size);
/* Fill in the header (M_SIZE is already ok) */
MAKE_SMALL_LPC_TRACE(temp);
MAKE_SMALL_TRACE(temp,size);
temp += OVERHEAD;
SIZE_PNT_INDEX(sfltable, size) = *(word_t**) temp;
fake("From free list.");
MADVISE(temp, orig_size);
in_malloc--;
return (POINTER)temp;
}
/* There is nothing suitable in the normal free lists - next try
* allocating from the current small chunk.
* If that one is too small, try scrounging off some memory from
* the bigger free small blocks, and if that fails, allocate a new
* small chunk.
*
* We do it in this order to keep the most common case (allocation
* from the small chunk) the fastest.
*/
if (unused_size < size)
{
/* Well, the small chunk is exhausted. Try some other strategies. */
int ix;
/* Try to get memory from the list of oversized free small
* blocks first.
*/
{
word_t *prev, *this;
word_t wsize = size / SINT; /* size incl overhead in words */
for (prev = NULL, this = sfltable[SMALL_BLOCK_MAX]
; this; prev = this, this = *s_next_ptr(this))
{
word_t bsize = *this & M_MASK;
word_t rsize = bsize - wsize;
/* Make sure that the split leaves a legal block behind */
if (bsize < wsize + OVERHEAD + 1)
continue;
count_back(small_free_stat, bsize * SINT);
/* If the split leaves behind a normally sized small
* block, move it over to the appropriate free list.
* Otherwise, just update the size and magic header fields.
*/
if (rsize <= SMALL_BLOCK_MAX + OVERHEAD)
{
/* Unlink it from this list */
if (prev)
*s_next_ptr(prev) = *s_next_ptr(this);
else
sfltable[SMALL_BLOCK_MAX] = *s_next_ptr(this);
small_free[SMALL_BLOCK_MAX]--;
/* Put it into the real free list */
MAKE_SMALL_FREE(this, rsize * SINT);
}
else
{
this[M_SIZE] = rsize | (M_GC_FREE|M_REF);
count_up(small_free_stat, rsize * SINT);
}
/* Split off the allocated small block from the end */
this += rsize;
/* Fill in the header */
this[M_SIZE] = wsize | (M_GC_FREE|M_REF);
MAKE_SMALL_LPC_TRACE(this);
MAKE_SMALL_TRACE_UNCHECKED(this,size);
this += OVERHEAD;
fake("From oversized free list.");
MADVISE(this, orig_size);
#ifdef DEBUG_SMALLOC_ALLOCS
ulog2f("smalloc(%d / %d): Split oversized block "
, orig_size, size);
dprintf2( gcollect_outfd, "(%d / %d bytes): left with block of "
, (p_int)(bsize - OVERHEAD) * SINT, (p_int)bsize * SINT);
dprintf2( gcollect_outfd, "%d / %d bytes.\n"
, (p_int)(rsize - OVERHEAD) * SINT
, (p_int)rsize * SINT);
#endif
in_malloc--;
return (POINTER)this;
}
}
/* Search from the largest blocks, and stop when splits
* would result in too small blocks (a hunch: 2 words payload)
*/
for ( ix = SMALL_BLOCK_MAX-1
; ix >= SIZE_INDEX_VALUE(size) + OVERHEAD + 2
; ix--
)
{
word_t *pt, *split;
size_t wsize, usize;
if (!sfltable[ix]) /* No block available */
continue;
wsize = size / SINT; /* size incl. overhead in words */
/* Remove the block from the free list */
pt = sfltable[ix];
count_back(small_free_stat, (ix + OVERHEAD + 1) * SINT);
sfltable[ix] = *(word_t**) (pt+OVERHEAD);
/* Split off the unused part as new block */
split = pt + wsize;
usize = ix + OVERHEAD + 1 - wsize;
MAKE_SMALL_FREE(split, usize * SINT);
/* Initialize the header of the new block */
pt[M_SIZE] = wsize | (M_GC_FREE|M_REF);
MAKE_SMALL_LPC_TRACE(pt);
MAKE_SMALL_TRACE_UNCHECKED(pt, size);
pt += OVERHEAD;
fake("From free list.");
MADVISE(pt, orig_size);
#ifdef DEBUG_SMALLOC_ALLOCS
ulog2f("smalloc(%d / %d): Split block "
, orig_size, size);
dprintf2( gcollect_outfd, "(%d / %d bytes): left with block of "
, (p_int)(ix - OVERHEAD) * SINT, (p_int)ix * SINT);
dprintf2( gcollect_outfd, "%d / %d bytes.\n"
, (p_int)(usize - OVERHEAD) * SINT, (p_int)usize * SINT);
#endif
in_malloc--;
return (POINTER)pt;
}
#if defined(DEBUG_SMALLOC_ALLOCS)
{
int ft;
if (unused_size < size)
ulog2f("smalloc(%d / %d): Small chunk exhausted ", orig_size, size);
else
ulog2f("smalloc(%d / %d): Trying small chunk ", orig_size, size);
dprintf2(gcollect_outfd, "(%d / %d left)\n"
, (unused_size >= OVERHEAD * SINT)
? ((p_int)unused_size - OVERHEAD * SINT) : 0
, (p_int)unused_size);
ulog("Free lists:");
for (ft = 0; ft < SMALL_BLOCK_MAX+1; ft++)
{
word_t *pt = sfltable[ft];
int count = 0;
while (pt)
{
count++;
pt = *s_next_ptr(pt);
}
dprintf1(gcollect_outfd, " %d,", count);
}
write(gcollect_outfd, "\n", 1);
}
#endif
/* Not enough space in the small chunk left and all other strategies
* failed, too - get a new small chunk */
fake("Allocating new small chunk.");
if (unused_size)
{
if (unused_size < SINT + OVERHEAD*SINT)
{
*s_size_ptr(next_unused) = 0;
count_up(small_chunk_wasted, unused_size);
}
else
{
/* Add the remaining memory from the small chunk
* the small block free list.
*/
#ifdef DEBUG
int unused_size_waste;
unused_size_waste = unused_size % SINT;
if (unused_size_waste)
{
dprintf2(2, "DEBUG: unused_size %d not a multiple of %d\n"
, (p_int)unused_size, (p_int)SINT);
unused_size -= unused_size_waste;
count_up(small_chunk_wasted, unused_size_waste);
}
#endif
MAKE_SMALL_FREE(next_unused, unused_size);
}
}
/* Get a new small chunk; if possible, from fresh memory */
next_unused = (word_t*)large_malloc_int(small_chunk_size + sizeof(word_t*)
, MY_TRUE);
/* If this is the first small chunk allocation, it might fail because
* the driver was configured with a too big min_small_malloced value.
* If that happens, try again with the normal value.
*/
if (next_unused == NULL && small_chunk_size != SMALL_CHUNK_SIZE)
{
dprintf1(2, "Low on MEMORY: Failed to allocated MIN_SMALL_MALLOCED "
"block of %d bytes.\n"
, (p_int)(small_chunk_size)
);
small_chunk_size = SMALL_CHUNK_SIZE;
next_unused = (word_t*)large_malloc_int(small_chunk_size+sizeof(word_t*)
, MY_TRUE);
}
if (next_unused == NULL)
{
dprintf1(2, "Low on MEMORY: Failed to allocated small chunk "
"block of %d bytes.\n"
, (p_int)(small_chunk_size)
);
in_malloc--;
return NULL;
}
/* Enter the chunk into the list */
*next_unused = (word_t)last_small_chunk;
last_small_chunk = next_unused++;
*(next_unused) = 0; /* Sentinel in chunk */
count_up(small_chunk_stat, small_chunk_size+SINT*OVERHEAD+sizeof(word_t*));
count_up(small_chunk_wasted, SINT*OVERHEAD+sizeof(word_t*));
unused_size = small_chunk_size;
small_chunk_size = SMALL_CHUNK_SIZE;
}
else
{
fake("Allocate from chunk.");
} /* if (unused_size < size) */
/* Allocate the small block from the free area in the
* current small chunk.
*/
temp = (word_t *) s_next_ptr(next_unused);
/* The address we're going to return.
*/
/* Fill in the header of the block */
*s_size_ptr(next_unused) = size / SINT | (M_GC_FREE|M_REF);
MAKE_SMALL_LPC_TRACE(next_unused);
MAKE_SMALL_TRACE_UNCHECKED(next_unused, size);
/* Reduce the free size in the small chunk */
next_unused += size / SINT;
unused_size -= size;
if (unused_size >= SINT)
*(next_unused) = 0; /* Sentinel in chunk */
fake("allocation from chunk successful\n");
MADVISE(temp, orig_size);
in_malloc--;
return (POINTER)temp;
} /* smalloc() */
/*-------------------------------------------------------------------------*/
static void
sfree (POINTER ptr)
/* Return the memoryblock <ptr> to the allocator.
* This function does the actual freeing, without checking for mutexes
* and stack gaps; it is also used internally by the allocator to free
* the memory reserves.
*/
{
word_t *block;
word_t i;
if (!ptr)
return;
/* Get the real block address and size */
block = (word_t *) ptr;
block -= OVERHEAD;
i = (*s_size_ptr(block) & M_MASK);
if (i > SMALL_BLOCK_MAX + OVERHEAD)
{
/* It's a big block */
fake("xfree calls large_free");
large_free(ptr);
return;
}
/* It's a small block: put it back into the free list */
count_back(small_alloc_stat, i * SINT);
count_up(small_free_stat, i * SINT);
i -= 1 + OVERHEAD;
#ifdef MALLOC_TRACE
if (block[M_MAGIC] == sfmagic[i % NELEM(samagic)])
{
in_malloc = 0;
fatal("xfree: block %lx size %lu (user %lx) freed twice\n"
, (unsigned long)block, (unsigned long)(i * SINT)
, (unsigned long)ptr);
}
if (block[M_MAGIC] != samagic[i % NELEM(samagic)])
{
in_malloc = 0;
fatal("xfree: block %p magic match failed: "
"size %lu, expected %lx, found %lx\n"
, block, (unsigned long)(i * SINT), samagic[i], block[M_MAGIC]);
}
block[M_MAGIC] = sfmagic[i % NELEM(sfmagic)];
#endif
*s_next_ptr(block) = sfltable[i];
sfltable[i] = block;
small_free[i] += 1;
fake("Freed");
} /* sfree() */
/*-------------------------------------------------------------------------*/
void
xfree (POINTER ptr)
/* Return the memoryblock <ptr> to the allocator.
* This is a wrapper for sfree() which performs checks for stack-gaps
* and such.
*/
{
if (!ptr)
return;
assert_stack_gap();
if (in_malloc++)
{
in_malloc = 0;
writes(1, "Multiple threads in xfree()\n");
fatal("Multiple threads in xfree()\n");
/* NOTREACHED */
return;
}
sfree(ptr);
in_malloc--;
} /* xfree() */
/*=========================================================================*/
/* LARGE BLOCKS */
/*-------------------------------------------------------------------------*/
#define l_size_ptr(p) (p)
#define l_next_ptr(p) (*((word_t **) ((p)+OVERHEAD)))
#define l_prev_ptr(p) (*((word_t **) (p+2)))
/* Non-AVL-Freelist pointers.
*/
#define l_next_block(p) ((p) + (M_MASK & (*(p))) )
#define l_prev_block(p) ((p) - (M_MASK & (*((p)-1))) )
/* Address the previous resp. this block.
*/
#define l_prev_free(p) (!(*p & PREV_BLOCK))
#define l_next_free(p) (!(*l_next_block(p) & THIS_BLOCK))
/* Check if the previous resp. the next block is free.
*/
#ifdef FIT_STYLE_FAST_FIT
/*-------------------------------------------------------------------------*/
/* Extra types and definitions for the AVL routines */
#if defined(atarist) || defined (sun) || defined(AMIGA) || defined(__linux__) || defined(__BEOS__)
/* there is a type signed char */
typedef signed char balance_t;
# define BALANCE_T_BITS 8
#else
typedef short balance_t;
# define BALANCE_T_BITS 16
#endif
#if (defined(atarist) && !defined(ATARI_TT)) || defined(sparc) || defined(AMIGA)
/* try to avoid multiple shifts, because these are costly */
# define NO_BARREL_SHIFT
#endif
struct free_block
{
word_t size;
struct free_block *parent, *left, *right;
balance_t balance;
short align_dummy;
};
/* Prepare two nodes for the free tree that will never be removed,
* so that we can always assume that the tree is and remains non-empty.
*/
extern struct free_block dummy2; /* forward */
static struct free_block dummy =
{ /*size*/0, /*parent*/&dummy2, /*left*/0, /*right*/0, /*balance*/0 };
struct free_block dummy2 =
{ /*size*/0, /*parent*/0, /*left*/&dummy, /*right*/0, /*balance*/-1 };
static struct free_block *free_tree = &dummy2;
#define WRITES(d, s) write((d), (s), strlen(s))
#ifdef DEBUG_AVL
static Bool inconsistency = MY_FALSE;
/*-------------------------------------------------------------------------*/
static void
_check_next (struct free_block *p)
/* DEBUG_AVL: check this node <p> and both subnodes.
*/
{
if (!p)
return;
{
word_t *q;
q = ((word_t *)p)-OVERHEAD;
q += *q & M_MASK;
}
_check_next(p->left);
_check_next(p->right);
} /* _check_next() */
/*-------------------------------------------------------------------------*/
static void
check_next(void)
/* DEBUG_AVL: check the free_tree.
*/
{
_check_next(free_tree);
} /* check_next() */
/*-------------------------------------------------------------------------*/
static int
check_avl (struct free_block *parent, struct free_block *p)
/* DEBUG_AVL: check the consistency of the AVL-(sub)tree in node <p>.
* Return the size of the subtree, and set inconsistency to TRUE
* if it is inconsistent.
*/
{
int left, right;
if (!p)
return 0;
left = check_avl(p, p->left );
right = check_avl(p, p->right);
if (p->balance != right - left || p->balance < -1 || p->balance > 1)
{
WRITES (2, "Inconsistency in avl node!\n");
dprintf1(2, "node:%x\n",(p_uint)p);
dprintf1(2, "size: %d\n", p->size);
dprintf1(2, "left node:%x\n",(p_uint)p->left);
dprintf1(2, "left height: %d\n",left );
dprintf1(2, "right node:%x\n",(p_uint)p->right);
dprintf1(2, "right height: %d\n",right);
dprintf1(2, "alleged balance: %d\n",p->balance);
inconsistency = MY_TRUE;
}
if (p->parent != parent)
{
WRITES (2, "Inconsistency in avl node!\n");
dprintf1(2, "node:%x\n",(p_uint)p);
dprintf1(2, "size: %d\n", p->size);
dprintf1(2, "parent: %x\n", (p_uint)parent);
dprintf1(2, "parent size: %d\n", parent->size);
dprintf1(2, "alleged parent: %x\n", (p_uint)p->parent);
dprintf1(2, "alleged parent size: %d\n", p->parent->size);
dprintf1(2, "left height: %d\n",left );
dprintf1(2, "right height: %d\n",right);
dprintf1(2, "alleged balance: %d\n",p->balance);
inconsistency = MY_TRUE;
}
return left > right ? left+1 : right+1;
} /* debug_avl() */
/*-------------------------------------------------------------------------*/
static int
do_check_avl(void)
/* DEBUG_AVL: Check the free_tree for consistency.
* Return 0 on success, otherwise abort the driver.
*/
{
check_avl(0, free_tree);
if (inconsistency)
{
fflush(stderr);
fflush(stdout);
in_malloc = 0;
fatal("Inconsistency could crash the driver\n");
}
return 0;
} /* do_check_avl() */
/*-------------------------------------------------------------------------*/
static Bool
contains (struct free_block *p, struct free_block *q)
/* DEBUG_AVL: Check if free_block <q> is contained in the sub-tree at
* and below <p>.
*/
{
return p == q || (p && (contains(p->left, q) || contains(p->right, q)));
} /* contains() */
/*-------------------------------------------------------------------------*/
static int
check_free_block (void *m)
/* DEBUG_AVL: Check if free memory at <m> is contained in the free_tree.
* Return 0 on success, otherwise abort the driver.
*/
{
word_t *p;
int size;
p = (word_t *)(((char *)m)-OVERHEAD*SINT);
size = *p & M_MASK;
if (!(*(p+size) & THIS_BLOCK))
{
if (!contains(free_tree, (struct free_block *)(p+size+OVERHEAD)) )
{
in_malloc = 0;
fatal("not found\n");
}
}
return 0;
} /* check_free_block() */
#else /* !DEBUG_AVL */
#define do_check_avl() 0
#endif /* DEBUG_AVL */
/*-------------------------------------------------------------------------*/
static void
remove_from_free_list (word_t *ptr)
/* Remove the memory block <ptr> from the free list.
*/
{
struct free_block *p, *q, *r, *s, *t;
#ifdef MALLOC_TRACE
if (ptr[M_MAGIC] != LFMAGIC)
{
in_malloc = 0;
fatal("remove_from_free_list: block %p, "
"magic match failed: expected %lx, "
"found %lx\n"
, ptr
, (unsigned long)LFMAGIC
, (unsigned long)ptr[M_MAGIC]
);
}
#endif
fake((do_check_avl(),"remove_from_free_list called"));
p = (struct free_block *)(ptr+OVERHEAD);
count_back(large_free_stat, p->size);
#ifdef DEBUG_AVL
dprintf1(2, "node:%x\n",(p_uint)p);
dprintf1(2, "size:%d\n",p->size);
#endif
if (p->left) {
if ( NULL != (q = p->right) ) {
fake("two childs");
s = q;
do { r = q; q = r->left; } while(q != NULL);
if (r == s) {
r->left = s = p->left;
s->parent = r;
if ( NULL != (r->parent = s = p->parent) ) {
if (p == s->left) {
s->left = r;
} else {
s->right = r;
}
} else {
free_tree = r;
}
r->balance = p->balance;
p = r;
goto balance_right;
} else {
t = r->parent;
if ( NULL != (t->left = s = r->right) ) {
s->parent = t;
}
r->balance = p->balance;
r->left = s = p->left;
s->parent = r;
r->right = s = p->right;
s->parent = r;
if ( NULL != (r->parent = s = p->parent) ) {
if (p == s->left) {
s->left = r;
} else {
s->right = r;
}
} else {
free_tree = r;
}
p = t;
goto balance_left;
}
} else /* no right child, but left child */ {
/* We set up the free list in a way so that there will remain at
least two nodes, and the avl property ensures that the left
child is a leaf ==> there is a parent */
fake("no right child, but left child");
s = p;
p = s->parent;
r = s->left;
r->parent = p;
if (s == p->left) {
p->left = r;
goto balance_left;
} else {
p->right = r;
goto balance_right;
}
}
} else /* no left child */ {
/* We set up the free list in a way so that there is a node left
of all used nodes, so there is a parent */
fake("no left child");
s = p;
p = s->parent;
if ( NULL != (q = r = s->right) ) {
r->parent = p;
}
if (s == p->left) {
p->left = r;
goto balance_left;
} else {
p->right = r;
goto balance_right;
}
}
balance_q:
r = p;
p = q;
if (r == p->right) {
balance_t b;
balance_right:
b = p->balance;
if (b > 0) {
p->balance = 0;
if (NULL != (q = p->parent)) goto balance_q;
return;
} else if (b < 0) {
r = p->left;
b = r->balance;
if (b <= 0) {
/* R-Rotation */
#ifdef DEBUG_AVL
fake("R-Rotation.");
dprintf1(2, "r->balance: %d\n", r->balance);
#endif
if ( NULL != (p->left = s = r->right) ) {
s->parent = p;
}
r->right = p;
s = p->parent;
p->parent = r;
b += 1;
r->balance = b;
b = -b;
#ifdef DEBUG_AVL
dprintf1(2, "node r: %x\n", (p_uint)r);
dprintf1(2, "r->balance: %d\n", r->balance);
dprintf1(2, "node p: %x\n", (p_uint)p);
p->balance = b;
dprintf1(2, "p->balance: %d\n", p->balance);
dprintf1(2, "r-height: %d\n", check_avl(r->parent, r));
#endif
if ( NULL != (r->parent = s) ) {
if ( 0 != (p->balance = b) ) {
if (p == s->left) {
s->left = r;
return;
} else {
s->right = r;
return;
}
}
if (p == s->left) {
fake("left from parent");
goto balance_left_s;
} else {
fake("right from parent");
p = s;
p->right = r;
goto balance_right;
}
}
p->balance = b;
free_tree = r;
return;
} else /* r->balance == +1 */ {
/* LR-Rotation */
balance_t b2;
fake("LR-Rotation.");
t = r->right;
b = t->balance;
if ( NULL != (p->left = s = t->right) ) {
s->parent = p;
}
if ( NULL != (r->right = s = t->left) ) {
s->parent = r;
}
t->left = r;
t->right = p;
r->parent = t;
s = p->parent;
p->parent = t;
#ifdef NO_BARREL_SHIFT
b = -b;
b2 = b >> 1;
r->balance = b2;
b -= b2;
p->balance = b;
#else
b2 = (unsigned char)b >> 7;
p->balance = b2;
b2 = -b2 -b;
r->balance = b2;
#endif
t->balance = 0;
#ifdef DEBUG_AVL
dprintf1(2, "t-height: %d\n", check_avl(t->parent, t));
#endif
if ( NULL != (t->parent = s) ) {
if (p == s->left) {
p = s;
s->left = t;
goto balance_left;
} else {
p = s;
s->right = t;
goto balance_right;
}
}
free_tree = t;
return;
}
} else /* p->balance == 0 */ {
p->balance = -1;
return;
}
} else /* r == p->left */ {
balance_t b;
goto balance_left;
balance_left_s:
p = s;
s->left = r;
balance_left:
b = p->balance;
if (b < 0) {
p->balance = 0;
if ( NULL != (q = p->parent) ) goto balance_q;
return;
} else if (b > 0) {
r = p->right;
b = r->balance;
if (b >= 0) {
/* L-Rotation */
#ifdef DEBUG_AVL
fake("L-Rotation.");
dprintf1(2, "r->balance: %d\n", r->balance);
#endif
if ( NULL != (p->right = s = r->left) ) {
s->parent = p;
}
fake("subtree relocated");
r->left = p;
s = p->parent;
p->parent = r;
b -= 1;
r->balance = b;
b = -b;
#ifdef DEBUG_AVL
fake("balances calculated");
dprintf1(2, "node r: %x\n", (p_uint)r);
dprintf1(2, "r->balance: %d\n", r->balance);
dprintf1(2, "node p: %x\n", (p_uint)p);
p->balance = b;
dprintf1(2, "p->balance: %d\n", p->balance);
dprintf1(2, "r-height: %d\n", check_avl(r->parent, r));
#endif
if ( NULL != (r->parent = s) ) {
if ( 0 != (p->balance = b) ) {
if (p == s->left) {
s->left = r;
return;
} else {
s->right = r;
return;
}
}
if (p == s->left) {
fake("left from parent");
goto balance_left_s;
} else {
fake("right from parent");
p = s;
p->right = r;
goto balance_right;
}
}
p->balance = b;
free_tree = r;
return;
} else /* r->balance == -1 */ {
/* RL-Rotation */
balance_t b2;
fake("RL-Rotation.");
t = r->left;
b = t->balance;
if ( NULL != (p->right = s = t->left) ) {
s->parent = p;
}
if ( NULL != (r->left = s = t->right) ) {
s->parent = r;
}
t->right = r;
t->left = p;
r->parent = t;
s = p->parent;
p->parent = t;
#ifdef NO_BARREL_SHIFT
b = -b;
b2 = b >> 1;
p->balance = b2;
b -= b2;
r->balance = b;
#else
b2 = (unsigned char)b >> 7;
r->balance = b2;
b2 = -b2 -b;
p->balance = b2;
#endif
t->balance = 0;
if ( NULL != (t->parent = s) ) {
if (p == s->left) {
p = s;
s->left = t;
goto balance_left;
} else {
s->right = t;
p = s;
goto balance_right;
}
}
free_tree = t;
return;
}
} else /* p->balance == 0 */ {
p->balance++;
return;
}
}
} /* remove_from_free_list() */
/*-------------------------------------------------------------------------*/
static void
add_to_free_list (word_t *ptr)
/* Add the memory block <ptr> to the free list.
*/
{
word_t size;
struct free_block *p, *q, *r;
/* When there is a distinction between data and address registers and/or
accesses, gcc will choose data type for q, so an assignmnt to q will
faciliate branching
*/
fake((do_check_avl(),"add_to_free_list called"));
#ifdef MALLOC_TRACE
ptr[M_MAGIC] = LFMAGIC;
#endif
size = *ptr & M_MASK;
#ifdef DEBUG_AVL
dprintf1(2, "size:%d\n",size);
#endif
q = (struct free_block *)size; /* this assignment is a hint for register
* choice */
r = (struct free_block *)(ptr+OVERHEAD);
count_up(large_free_stat, size);
q = free_tree;
for ( ; ; /*p = q*/) {
p = (struct free_block *)q;
#ifdef DEBUG_AVL
dprintf1(2, "checked node size %d\n",p->size);
#endif
if (size < p->size) {
if ( NULL != (q = p->left) ) {
continue;
}
fake("add left");
p->left = r;
break;
} else /* >= */ {
if ( NULL != (q = p->right) ) {
continue;
}
fake("add right");
p->right = r;
break;
}
}
r->size = size;
r->parent = p;
r->left = 0;
r->right = 0;
r->balance = 0;
#ifdef DEBUG_AVL
fake("built new leaf.");
dprintf1(2, "p->balance:%d\n",p->balance);
#endif
do {
struct free_block *s;
if (r == p->left) {
balance_t b;
if ( !(b = p->balance) ) {
#ifdef DEBUG_AVL
dprintf1(2, "p->size: %d\n", p->size);
dprintf1(2, "p->balance: %d\n", p->balance);
dprintf1(2, "p->right-h: %d\n", check_avl(p, p->right));
dprintf1(2, "p->left -h: %d\n", check_avl(p, p->left ));
fake("growth propagation from left side");
#endif
p->balance = -1;
} else if (b < 0) {
#ifdef DEBUG_AVL
dprintf1(2, "p->balance:%d\n",p->balance);
#endif
if (r->balance < 0) {
/* R-Rotation */
fake("R-Rotation");
if ( NULL != (p->left = s = r->right) ) {
s->parent = p;
}
r->right = p;
p->balance = 0;
r->balance = 0;
s = p->parent;
p->parent = r;
if ( NULL != (r->parent = s) ) {
if ( s->left == p) {
s->left = r;
} else {
s->right = r;
}
} else {
free_tree = r;
}
} else /* r->balance == +1 */ {
/* LR-Rotation */
balance_t b2;
struct free_block *t = r->right;
#ifdef DEBUG_AVL
fake("LR-Rotation");
dprintf1(2, "t = %x\n",(p_uint)t);
dprintf1(2, "r->balance:%d\n",r->balance);
#endif
if ( NULL != (p->left = s = t->right) ) {
s->parent = p;
}
fake("relocated right subtree");
t->right = p;
if ( NULL != (r->right = s = t->left) ) {
s->parent = r;
}
fake("relocated left subtree");
t->left = r;
b = t->balance;
#ifdef NO_BARREL_SHIFT
b = -b;
b2 = b >> 1;
r->balance = b2;
b -= b2;
p->balance = b;
#else
b2 = (unsigned char)b >> 7;
p->balance = b2;
b2 = -b2 -b;
r->balance = b2;
#endif
t->balance = 0;
fake("balances calculated");
s = p->parent;
p->parent = t;
r->parent = t;
if ( NULL != (t->parent = s) ) {
if ( s->left == p) {
s->left = t;
} else {
s->right = t;
}
} else {
free_tree = t;
}
#ifdef DEBUG_AVL
dprintf1(2, "p->balance:%d\n",p->balance);
dprintf1(2, "r->balance:%d\n",r->balance);
dprintf1(2, "t->balance:%d\n",t->balance);
fake((do_check_avl(),"LR-Rotation completed."));
#endif
}
break;
} else /* p->balance == +1 */ {
p->balance = 0;
fake("growth of left side balanced the node");
break;
}
} else /* r == p->right */ {
balance_t b;
if ( !(b = p->balance) ) {
fake("growth propagation from right side");
p->balance++;
} else if (b > 0) {
if (r->balance > 0) {
/* L-Rotation */
fake("L-Rotation");
if ( NULL != (p->right = s = r->left) ) {
s->parent = p;
}
r->left = p;
p->balance = 0;
r->balance = 0;
s = p->parent;
p->parent = r;
if ( NULL != (r->parent = s) ) {
if ( s->left == p) {
s->left = r;
} else {
s->right = r;
}
} else {
free_tree = r;
}
} else /* r->balance == -1 */ {
/* RL-Rotation */
balance_t b2;
struct free_block *t = r->left;
#ifdef DEBUG_AVL
fake("RL-Rotation");
dprintf1(2, "t = %x\n",(p_uint)t);
dprintf1(2, "r->balance:%d\n",r->balance);
#endif
if ( NULL != (p->right = s = t->left) ) {
s->parent = p;
}
fake("relocated left subtree");
t->left = p;
if ( NULL != (r->left = s = t->right) ) {
s->parent = r;
}
fake("relocated right subtree");
t->right = r;
b = t->balance;
#ifdef NO_BARREL_SHIFT
b = -b;
b2 = b >> 1;
p->balance = b2;
b -= b2;
r->balance = b;
#else
b2 = (unsigned char)b >> 7;
r->balance = b2;
b2 = -b2 -b;
p->balance = b2;
#endif
t->balance = 0;
s = p->parent;
p->parent = t;
r->parent = t;
if ( NULL != (t->parent = s) ) {
if ( s->left == p) {
s->left = t;
} else {
s->right = t;
}
} else {
free_tree = t;
}
fake("RL-Rotation completed.");
}
break;
} else /* p->balance == -1 */ {
#ifdef DEBUG_AVL
dprintf1(2, "p->balance: %d\n", p->balance);
dprintf1(2, "p->right-h: %d\n", check_avl(p, p->right));
dprintf1(2, "p->left -h: %d\n", check_avl(p, p->left ));
#endif
p->balance = 0;
fake("growth of right side balanced the node");
break;
}
}
r = p;
p = p->parent;
} while ( NULL != (q = p) );
fake((do_check_avl(),"add_to_free_list successful"));
}
#else /* FIT_STYLE_FAST_FIT */
/*-------------------------------------------------------------------------*/
static void
remove_from_free_list (word_t *ptr)
{
#ifdef MALLOC_TRACE
if (ptr[M_MAGIC] != LFMAGIC)
{
in_malloc = 0;
fatal("remove_from_free_list: block %p magic match failed: "
"expected %lx, found %lx\n", ptr, LFMAGIC, ptr[M_MAGIC]);
}
#endif
count_back(large_free_stat, *ptr & M_MASK);
/* Unlink it from the freelist */
if (l_prev_ptr(ptr))
l_next_ptr(l_prev_ptr(ptr)) = l_next_ptr(ptr);
else
free_list = l_next_ptr(ptr);
if (l_next_ptr(ptr))
l_prev_ptr(l_next_ptr(ptr)) = l_prev_ptr(ptr);
} /* remove_from_free_list() */
/*-------------------------------------------------------------------------*/
static void
add_to_free_list (word_t *ptr)
/* Add memory block <ptr> to the free list.
*/
{
count_up(large_free_stat, *ptr & M_MASK);
#ifdef DEBUG
if (free_list && l_prev_ptr(free_list))
puts("Free list consistency error.");
#endif
l_next_ptr(ptr) = free_list;
if (free_list)
l_prev_ptr(free_list) = ptr;
l_prev_ptr(ptr) = NULL;
free_list = ptr;
} /* add_to_free_list() */
#endif /* FIT_STYLE_FAST_FIT */
/*-------------------------------------------------------------------------*/
static void
build_block (word_t *ptr, word_t size)
/* Mark the memory block <ptr> of size <size> words(!) as unallocated.
* Also used to initialize new memory blocks received from the system.
*/
{
word_t tmp;
tmp = (*ptr & PREV_BLOCK) | size;
*(ptr+size-1) = size; /* copy the size information */
*(ptr) = tmp; /* marks this block as free */
*(ptr+size) &= ~PREV_BLOCK; /* unset 'previous block' flag in next block */
} /* build_block() */
/*-------------------------------------------------------------------------*/
static void
mark_block (word_t *ptr)
/* Mark the memory block at <ptr> as allocated, used, and freeable
* by the GC.
*/
{
*l_next_block(ptr) |= PREV_BLOCK;
*ptr |= THIS_BLOCK | M_GC_FREE | M_REF;
} /* mark_block() */
/*-------------------------------------------------------------------------*/
#ifndef MALLOC_TRACE
static char *
large_malloc ( word_t size, Bool force_more)
#else
static char *
_large_malloc ( word_t size, Bool force_more
, const char *file, int line
)
#endif
/* Allocate a large or <size> bytes from source <file> and <line>.
*
* If <force_more> is TRUE, the function first tries to allocate
* more memory directly from the system. If that fails, it tries a normal
* allocation. This feature is used to allocate small chunks for the
* small block allocator.
*
* If memory from the system is allocated and <force_more>
* is not TRUE (or it is in the retry), the allocator allocates at least
* CHUNK_SIZE bytes. Any extra is immediately entered into the freelist.
*
* Return the pointer to the allocated memory block, or NULL when out
* of memory (not when running in SYSTEM privilege).
*
* If the system runs out of memory, the following steps are taken:
*
* - if <force_more> is TRUE, it is set to FALSE and the allocation is tried
* again, this time from the freelist.
* - free the user reserve, then try again.
* - if MASTER privilege: free the master reserve, then try again.
* - if SYSTEM privilege: free the system reserve, then try again.
* - if !SYSTEM privilege: set out_of_memory and return NULL
* - if SYSTEM privilege: dump the lpc backtrace and exit(2) resp. fatal().
*
* If any of the reserves is freed, the gc_request flag is set.
*/
{
word_t real_size;
word_t *ptr;
#if defined(HAVE_MADVISE) || defined(DEBUG) || defined(DEBUG_SMALLOC_ALLOCS)
size_t orig_size = size;
#endif
fake("large_malloc called");
# ifdef LARGE_TRACE
dprintf1(2, "request:%d.",size);
# endif
size = (size + SINT*OVERHEAD + SINT-1) / SINT; /* plus overhead */
count_up(large_alloc_stat, size);
retry:
ptr = NULL;
if (!force_more)
{
#ifdef FIT_STYLE_FAST_FIT
/* Find the best fit in the AVL tree */
struct free_block *p, *q, *r;
word_t minsplit;
word_t tempsize;
ptr += OVERHEAD; /* 'NULL' including overhead */
minsplit = size + SMALL_BLOCK_MAX + OVERHEAD + 1;
/* The split-off block must still count as 'large' */
q = free_tree;
for ( ; ; ) {
p = q;
#ifdef DEBUG_AVL
dprintf1(2, "checked node size %d\n",p->size);
#endif
tempsize = p->size;
if (minsplit < tempsize) {
ptr = (word_t*)p; /* remember this fit */
if ( NULL != (q = p->left) ) {
continue;
}
/* We don't need that much, but that's the best fit we have */
break;
} else if (size > tempsize) {
if ( NULL != (q = p->right) ) {
continue;
}
break;
} else /* size <= tempsize <= minsplit */ {
if (size == tempsize) {
ptr = (word_t*)p;
break;
}
/* size < tempsize */
if ( NULL != (q = p->left) ) {
r = p;
/* if r is used in the following loop instead of p,
* gcc will handle q very inefficient throughout the
* function large_malloc()
*/
for (;;) {
p = q;
tempsize = p->size;
if (size < tempsize) {
if ( NULL != (q = p->left) ) {
continue;
}
break;
} else if (size > tempsize ) {
if ( NULL != (q = p->right) ) {
continue;
}
break;
} else {
ptr = (word_t*)p;
goto found_fit;
}
}
p = r;
}
tempsize = p->size;
if (minsplit > tempsize) {
if ( NULL != (q = p->right) ) {
for (;;) {
p = q;
tempsize = p->size;
if (minsplit <= tempsize) {
ptr = (word_t*)p; /* remember this fit */
if ( NULL != (q = p->left) ) {
continue;
}
break;
} else /* minsplit > tempsize */ {
if ( NULL != (q = p->right) ) {
continue;
}
break;
}
} /* end inner for */
break;
}
break; /* no new fit */
}
/* minsplit == tempsize ==> best non-exact fit */
ptr = (word_t*)p;
break;
}
} /* end outer for */
found_fit:
ptr -= OVERHEAD;
#else /* FIT_STYLE_FAST_FIT */
/* Search the freelist for the first/best fit */
word_t best_size;
word_t *first, *best;
# ifdef LARGE_TRACE
word_t search_length = 0;
# endif
first = best = NULL;
best_size = M_MASK;
ptr = free_list;
while (ptr)
{
word_t tempsize;
# ifdef LARGE_TRACE
search_length++;
# endif
/* Perfect fit? */
tempsize = *ptr & M_MASK;
if (tempsize == size)
{
best = first = ptr;
break;
/* always accept perfect fit */
}
/* does it really even fit at all? */
if (tempsize > size + SMALL_BLOCK_MAX + OVERHEAD)
{
/* try first fit */
if (!first)
{
first = ptr;
if (fit_style == FIRST_FIT)
break;
/* just use this one! */
}
/* try best fit */
tempsize -= size;
if (tempsize > 0 && tempsize <= best_size)
{
best = ptr;
best_size = tempsize;
}
}
ptr = l_next_ptr(ptr);
} /* end while */
# ifdef LARGE_TRACE
dprintf1(2, "search length %d\n",search_length);
# endif
if (fit_style == BEST_FIT)
ptr = best;
else
ptr = first;
/* FIRST_FIT and HYBRID both leave it in first */
#endif /* FIT_STYLE_FAST_FIT */
} /* if (!force_more) */
if (!ptr)
{
/* No match, allocate more memory */
word_t chunk_size, block_size;
block_size = size*SINT;
/* If force_more is true (read: we have to allocate a SMALL_CHUNK)
* or if the if the requested block would leave only a 'small'
* block or no block in the usual CHUNK_SIZEd chunk, then allocate
* exactly the block requested. Otherwise allocate a CHUNK_SIZEd
* chunk, of which the unneeded part is entered into the freelist.
*/
if (force_more
|| block_size > CHUNK_SIZE - SMALL_BLOCK_MAX_BYTES - OVERHEAD*SINT )
{
chunk_size = block_size;
}
else
{
chunk_size = CHUNK_SIZE;
}
/* Some systems like Darwin don't like odd sbrk()/malloc()s, therefore
* we round up to the next multiple - 64 seems to work fine. That of
* course might give an overhang of less than SMALL_BLOCK_MAX_BYTES,
* so we have to add that and its own 64-Byte-fudge factor then, too.
*/
# define ALLOC_MULTIPLE 63 /* Ok, the (multiple-1) */
if ((chunk_size & ALLOC_MULTIPLE) != 0)
{
# if SMALL_BLOCK_MAX_BYTES > ALLOC_MULTIPLE
chunk_size += SMALL_BLOCK_MAX_BYTES + 2 * ALLOC_MULTIPLE;
# else
chunk_size += ALLOC_MULTIPLE;
# endif
chunk_size &= ~ALLOC_MULTIPLE;
}
if (force_more)
ulog3f("lmalloc(%d / %d): Forced allocate new chunk of %d bytes\n"
, orig_size, block_size, chunk_size);
else
ulog3f("lmalloc(%d / %d): Allocate new chunk of %d bytes\n"
, orig_size, block_size, chunk_size);
/* Get <chunk_size> more bytes from the system */
{
if (max_malloced > 0
&& (mp_int)(sbrk_stat.size + chunk_size) > max_malloced
&& ( sbrk_stat.size + (heap_start ? 0 : SINT) >= max_malloced
|| (chunk_size = max_malloced - sbrk_stat.size
- (heap_start?0:SINT) )
< block_size
)
)
{
static char mess[] = "MAX_MALLOCED limit reached.\n";
write(2, mess, sizeof(mess)-1);
ptr = NULL;
}
else
{
ptr = (word_t *)esbrk(chunk_size);
}
}
if (ptr == NULL)
{
/* Out of memory - try to recover */
static Bool going_to_exit = MY_FALSE;
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 large ";
static char mess_d2[] =
" bytes for ";
static char mess_d3[] =
" bytes request";
#ifdef MALLOC_TRACE
static char mess_d4[] =
" (";
static char mess_d5[] =
" line ";
static char mess_d6[] =
")";
#endif
static char mess_nl[] =
"\n";
ulog2f("lmalloc(%d / %d): Didn't get the memory from the system.\n"
, orig_size, block_size);
if (going_to_exit) /* A recursive call while we're already exiting */
exit(3);
if (force_more)
{
/* The system is out of memory, but maybe we have some left
* in the freelist.
*/
force_more = MY_FALSE;
goto retry;
}
else
{
/* The system is out of memory, and we don't have any left
* in the freelist.
*/
/* Print the Out-Of-Mem diagnostic */
write(2, mess_d1, sizeof(mess_d1)-1);
writed(2, size*SINT);
write(2, mess_d2, sizeof(mess_d2)-1);
writed(2, smalloc_size);
write(2, mess_d3, sizeof(mess_d3)-1);
#ifdef MALLOC_TRACE
write(2, mess_d4, sizeof(mess_d4)-1);
write(2, file, strlen(file));
write(2, mess_d5, sizeof(mess_d5)-1);
writed(2, line);
write(2, mess_d6, sizeof(mess_d6)-1);
#endif
write(2, mess_nl, sizeof(mess_nl)-1);
/* Free the next reserve, the try again */
if (gc_request == gcDont)
gc_request = gcMalloc;
extra_jobs_to_do = MY_TRUE;
if (reserved_user_area)
{
sfree(reserved_user_area);
reserved_user_area = NULL;
write(2, mess1, sizeof(mess1)-1);
goto retry;
}
if (malloc_privilege >= MALLOC_MASTER && reserved_master_area)
{
sfree(reserved_master_area);
reserved_master_area = NULL;
write(2, mess2, sizeof(mess2)-1);
goto retry;
}
if (malloc_privilege >= MALLOC_SYSTEM && reserved_system_area)
{
sfree(reserved_system_area);
reserved_system_area = 0;
write(2, mess3, sizeof(mess3)-1);
goto retry;
}
}
if (malloc_privilege < MALLOC_SYSTEM)
{
count_back(large_alloc_stat, size);
out_of_memory = MY_TRUE;
return NULL;
}
going_to_exit = MY_TRUE; /* Prevent recursions */
in_malloc = 0;
write(2, mess4, sizeof(mess4)-1);
(void)dump_trace(MY_FALSE, NULL);
fatal("Out of memory (%lu bytes for %lu byte req)\n"
, size * SINT, (unsigned long)smalloc_size);
}
/* Enough of the scary words - we got our memory block */
#ifndef SBRK_OK
chunk_size += overlap;
#endif /* SBRK_OK */
block_size = chunk_size / SINT;
/* configure header info on chunk */
build_block(ptr, block_size);
if (force_more)
fake("Build little block");
else
fake("Built memory block description.");
add_to_free_list(ptr);
} /* end of creating a new chunk */
/* ptr is now a pointer to a free block in the free list */
remove_from_free_list(ptr);
real_size = *ptr & M_MASK;
if (real_size - size)
{
/* split block pointed to by ptr into two blocks */
ptr[size] = 0; /* Init the header word */
build_block(ptr+size, real_size-size);
fake("Built empty block");
#ifdef DEBUG
if (real_size - size <= SMALL_BLOCK_MAX + OVERHEAD)
{
dprintf2(2,"DEBUG: lmalloc(%d / %d): "
, orig_size, size * SINT);
dprintf2(2
, "Split off block of %d bytes, small limit is %d bytes.\n"
, (p_int)(real_size - size) * SINT
, (p_int)(SMALL_BLOCK_MAX + OVERHEAD) * SINT);
#ifdef DEBUG_SMALLOC_ALLOCS
if (gcollect_outfd != 2)
{
dprintf2(gcollect_outfd
,"DEBUG: lmalloc(%d / %d): "
, orig_size, size * SINT);
dprintf2(gcollect_outfd
, "Split off block of %d bytes, small limit is %d bytes.\n"
, (p_int)(real_size - size) * SINT
, (p_int)(SMALL_BLOCK_MAX + OVERHEAD) * SINT);
}
#endif
}
#endif
# ifndef SBRK_OK
/* When we allocate a new chunk, it might differ slightly in size from
* the desired size.
*/
if (real_size - size <= SMALL_BLOCK_MAX + OVERHEAD)
{
mark_block(ptr+size);
*(ptr+size) &= ~M_GC_FREE; /* Hands off, GC! */
count_up(large_wasted_stat, (*(ptr+size) & M_MASK) * SINT);
}
else
# endif
{
/* At this point, it shouldn't happen that the split-off
* block is too small to be allocated as a small block.
*/
add_to_free_list(ptr+size);
}
build_block(ptr, size);
}
/* The block at ptr is all ours */
mark_block(ptr);
fake("built allocated block");
#ifdef MALLOC_LPC_TRACE
ptr[M_OBJ] = (word_t)current_object;
ptr[M_PROG] = current_prog ? current_prog->id_number : 0;
ptr[M_PC] = (word_t)inter_pc;
#endif
#ifdef MALLOC_TRACE
ptr[M_FILE] = (word_t)file;
ptr[M_LINE] = line;
ptr[M_MAGIC] = LAMAGIC;
#endif
MADVISE(ptr+OVERHEAD, orig_size);
return (char *) (ptr + OVERHEAD);
} /* large_malloc() */
/*-------------------------------------------------------------------------*/
static void
large_free (char *ptr)
/* Free the large memory block <ptr>. If possible, coagulate it with
* neighbouring free blocks into one.
*/
{
word_t size, *p;
/* Get the real block pointer */
p = (word_t *) ptr;
p -= OVERHEAD;
size = *p & M_MASK;
count_back(large_alloc_stat, size);
#ifdef MALLOC_TRACE
if (p[M_MAGIC] == LFMAGIC)
{
in_malloc = 0;
fatal("large_free: block %lx size %lu, (user %lx) freed twice\n"
, (unsigned long)p, (unsigned long)(size * SINT)
, (unsigned long)ptr);
}
if (p[M_MAGIC] != LAMAGIC)
{
in_malloc = 0;
fatal("large_free: block %p magic match failed: size %lu, "
"expected %lx, found %lx\n"
, p
, (unsigned long)(size * SINT)
, (unsigned long)LAMAGIC
, (unsigned long)p[M_MAGIC]
);
}
#endif
/* If the next block is free, coagulate */
if (!(*(p+size) & THIS_BLOCK))
{
remove_from_free_list(p+size);
size += (*(p+size) & M_MASK);
*p = (*p & PREV_BLOCK) | size;
}
/* If the previous block is free, coagulate */
if (l_prev_free(p))
{
remove_from_free_list(l_prev_block(p));
size += (*l_prev_block(p) & M_MASK);
p = l_prev_block(p);
}
/* Mark the block as free and add it to the freelist */
build_block(p, size);
add_to_free_list(p);
} /* large_free() */
/*-------------------------------------------------------------------------*/
static char *
esbrk (word_t size)
/* Allocate a block of <size> bytes from the system and return its pointer.
#ifdef SBRK_OK
* It is system dependent how sbrk() aligns data, so we simpy use brk()
* to insure that we have enough.
* At the end of the newly expanded heap we create a fake allocated
* block of 0 bytes so that large_malloc() knows where to stop.
#else
* Use malloc() to allocate a new block of memory. If this block borders
* to the previous one, both blocks are joined.
* The allocated block (modulo joints) is tagged at both ends with fake
* "allocated" blocks of which cover the unallocated areas - large_malloc()
* will perceive this as a fragmented heap.
#endif
*/
{
#ifdef SBRK_OK
#ifdef SunOS4
extern char *sbrk();
extern int brk();
#endif
if (!heap_end)
{
/* First call: allocate the first fake block */
heap_start = heap_end = (word_t *)sbrk(0);
if (!esbrk(SINT))
{
in_malloc = 0;
fatal("Couldn't malloc anything\n");
}
*heap_start = PREV_BLOCK;
fake("Allocated little fake block");
count_up(large_wasted_stat, SINT);
assert_stack_gap();
}
/* Get the new block */
if ((int)brk((char *)heap_end + size) == -1)
return NULL;
count_up(sbrk_stat, size);
heap_end = (word_t*)((char *)heap_end + size);
heap_end[-1] = THIS_BLOCK;
return (char *)(heap_end - 1) - size; /* overlap old memory block */
#else /* not SBRK_OK */
char *block;
word_t *p;
size += SINT; /* for the extra fake "allocated" block */
block = malloc(size);
if (!block)
return NULL;
assert_stack_gap();
p = (word_t *)(block + size) - 1;
if (!heap_end)
{
/* First call: create the inital fake block */
heap_start = (word_t*)block;
heap_end = (word_t*)(block + size);
*(word_t *)block = PREV_BLOCK;
*p = THIS_BLOCK; /* no M_GC_FREE */
overlap = 0;
}
else
{
/* Try to join with the existing heap */
if (block < (char *)heap_start)
{
/* New block before the known heap */
*(word_t *)block = PREV_BLOCK; /* Lower sentinel */
if (block + size == (char *)heap_start)
{
/* We can join with the existing heap */
p[1] &= ~PREV_BLOCK;
overlap = SINT;
count_back(large_wasted_stat, SINT);
}
else
{
/* Separate from the heap */
*p = THIS_BLOCK | (heap_start - p); /* no M_GC_FREE */
overlap = 0;
}
heap_start = (word_t *)block;
}
else if (block >= (char *)heap_end)
{
/* New block after the known heap */
*p = THIS_BLOCK; /* no M_GC_FREE */
if (block == (char *)heap_end)
{
/* We can join with the existing heap */
heap_end = (word_t *)(block + size);
block -= SINT;
overlap = SINT;
count_back(large_wasted_stat, SINT);
}
else
{
/* Separate from the heap */
p = (word_t *)heap_end - 1;
*p = (*p & (PREV_BLOCK|THIS_BLOCK|M_GC_FREE))
| ((word_t *)block - p);
heap_end = (word_t *)(block + size);
*(word_t *)block = PREV_BLOCK;
overlap = 0;
}
}
else
{
/* We got a block within the known heap.
* Try to find it in the fake blocks created earlier.
* This is slow, but it shouldn't happen too often.
*/
word_t *prev, *next;
/* Go to the block right before the one we got */
next = heap_start;
do {
prev = next;
next = prev + (*prev & M_MASK);
} while (next < (word_t *)block);
overlap = 0;
if ((word_t *)block == prev + 1)
{
/* Our block directly follows the one we found */
block -= SINT;
overlap += SINT;
count_back(large_wasted_stat, SINT);
}
else
{
/* We have to create a new bridge block */
*prev = (*prev & (PREV_BLOCK|THIS_BLOCK|M_GC_FREE))
| ((word_t*)block - prev);
*(word_t *)block = PREV_BLOCK;
}
if (next - p == 1)
{
/* Our block directly preceedes the next one */
*next &= ~PREV_BLOCK;
overlap += SINT;
count_back(large_wasted_stat, SINT);
}
else
{
/* We have to create a new bridge block */
*p = THIS_BLOCK | (next - p); /* no M_GC_FREE */
}
}
}
count_up(sbrk_stat, size);
count_up(large_wasted_stat, SINT);
return block;
#endif /* !SBRK_OK */
} /* esbrk() */
/*=========================================================================*/
/* SPECIALIZED ALLOCATIONS */
/*-------------------------------------------------------------------------*/
POINTER
pxalloc (size_t size)
/* Allocate a block of <size> bytes - like xalloc(), just that the
* memory is not subject to GC.
*/
{
word_t* temp;
temp = (word_t*)xalloc(size);
if (temp)
{
temp[-OVERHEAD] &= ~M_GC_FREE;
count_up(perm_alloc_stat, (temp[-OVERHEAD] & M_MASK)*SINT);
}
return (POINTER)temp;
} /* pxalloc() */
/*-------------------------------------------------------------------------*/
void
pfree (POINTER p)
/* Deallocate a permanent block <p>.
*/
{
if (p)
{
((word_t*)p)[-OVERHEAD] |= (M_REF|M_GC_FREE);
count_back(perm_alloc_stat, (((word_t*)p)[-OVERHEAD] & M_MASK)*SINT);
}
xfree(p);
} /* pfree() */
/*-------------------------------------------------------------------------*/
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.
*/
{
word_t *temp;
#if MALLOC_ALIGN > SINT
#if defined(HAVE_MADVISE)
size_t orig_size = size;
#endif
size += (MALLOC_ALIGN-SINT);
#endif
temp = (word_t *)pxalloc(size);
if (!temp)
{
int save_privilege = malloc_privilege;
malloc_privilege = MALLOC_SYSTEM;
temp = (word_t *)pxalloc(size);
malloc_privilege = save_privilege;
}
#if MALLOC_ALIGN > SINT
if (temp)
{
/* Fill the alignment area with 0 - afree() is going to
* look for the first non-0 byte.
*/
while ((word_t)temp & (MALLOC_ALIGN-1))
*temp++ = 0;
MADVISE(temp, orig_size);
}
#endif
return (POINTER)temp;
} /* amalloc() */
/*-------------------------------------------------------------------------*/
void
afree (POINTER p)
/* Free the aligned memory block <p>.
*/
{
word_t *q = (word_t *)p;
if (!q)
return;
#if MALLOC_ALIGN > SINT
/* amalloc() filled the alignment area with 0s.
* Search backwards to find the last non-null byte before
* the alignment area, and with it the real block begin.
*/
while (!*--q) NOOP;
q++;
#endif
pfree(q);
FREE_RETURN
} /* afree() */
/*-------------------------------------------------------------------------*/
POINTER
rexalloc (POINTER p, size_t size)
/* Reallocate block <p> to the new size of <size> and return the pointer.
* The memory is not aligned and subject to GC.
*/
{
word_t *q, old_size;
char *t;
q = (word_t *) p;
q -= OVERHEAD;
old_size = ((*q & M_MASK)-OVERHEAD)*SINT;
if (old_size >= size)
return p;
t = xalloc(size);
if (t == NULL)
return NULL;
memcpy(t, p, old_size);
xfree(p);
return t;
} /* rexalloc() */
/*-------------------------------------------------------------------------*/
#if defined(string_copy)
char *
smalloc_string_copy (const char *str, const char *file, int line)
/* 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;
p = smalloc(strlen(str)+1, file, line);
if (p) {
(void)strcpy(p, str);
}
return p;
} /* smalloc_string_copy() */
#endif
/*-------------------------------------------------------------------------*/
void *
malloc_increment_size (void *vp, size_t size)
/* Try to extent the allocation block for <vp> to hold <size> more bytes.
* If this is not possible, return NULL, otherwise return a pointer
* to the start of the block extension.
*
* This only works for large blocks which are followed by a free block.
*/
{
char *p = vp;
word_t *start, *start2, *start3, old_size, next;
size /= SINT;
malloc_increment_size_calls++;
start = (word_t*)p - OVERHEAD;
old_size = start[M_SIZE] & M_MASK;
if (old_size <= SMALL_BLOCK_MAX + OVERHEAD)
return NULL; /* can't extent a small block */
start2 = &start[old_size];
next = *start2;
if (next & THIS_BLOCK)
return NULL; /* no following free block */
next &= M_MASK;
if (next == (word_t)size)
{
/* Next block fits perfectly */
remove_from_free_list(start2);
start2[next] |= PREV_BLOCK;
start[M_SIZE] += size;
malloc_increment_size_success++;
malloc_increment_size_total += (start2 - start) - OVERHEAD;
count_add(large_alloc_stat, size);
return start2;
}
if (next >= (word_t)size + SMALL_BLOCK_MAX + OVERHEAD)
{
/* Split the next block */
remove_from_free_list(start2);
start2[next-1] -= size;
start3 = start2 + size;
start3[M_SIZE] = (next-size) | PREV_BLOCK;
add_to_free_list(start3);
start[M_SIZE] += size;
malloc_increment_size_success++;
malloc_increment_size_total += (start2 - start) - OVERHEAD;
count_add(large_alloc_stat, size);
return start2;
}
/* No success */
return NULL;
} /* malloc_increment_size() */
/*-------------------------------------------------------------------------*/
int
malloc_size_mask(void)
/* For external users: the mask for size-extraction.
* TODO: What for? M_MASK is in smalloc.h anyway.
*/
{
return M_MASK;
} /* malloc_size_mask() */
/*-------------------------------------------------------------------------*/
void
dump_malloc_data (strbuf_t *sbuf)
/* For the status commands and functions: add the smalloc statistic
* to the buffer <sbuf>.
*/
{
t_stat sbrk_st, clib_st, perm_st;
t_stat l_alloc, l_free, l_wasted;
t_stat s_alloc, s_free, s_wasted, s_chunk;
unsigned long unused;
/* Get a snapshot of the statistics - strbuf_add() might do further
* allocations while we're reading them.
*/
sbrk_st = sbrk_stat;
clib_st = clib_alloc_stat;
perm_st = perm_alloc_stat;
l_alloc = large_alloc_stat; l_alloc.size *= SINT;
l_free = large_free_stat; l_free.size *= SINT;
l_wasted = large_wasted_stat;
s_alloc = small_alloc_stat;
s_free = small_free_stat;
s_wasted = small_chunk_wasted;
s_chunk = small_chunk_stat;
unused = unused_size;
# define dump_stat(str,stat) strbuf_addf(sbuf, str,stat.counter,stat.size)
strbuf_add(sbuf, "Type Count Space (bytes)\n");
dump_stat("sbrk requests: %8d %10lu (a)\n",sbrk_st);
dump_stat("large blocks: %8d %10lu (b)\n",l_alloc);
strbuf_addf(sbuf
, "large net avail: %10d\n"
, l_alloc.size - l_alloc.counter * OVERHEAD * SINT
);
dump_stat("large free blocks: %8d %10lu (c)\n",l_free);
dump_stat("large wasted: %8d %10lu (d)\n\n",l_wasted);
dump_stat("small chunks: %8d %10lu (e)\n",s_chunk);
dump_stat("small blocks: %8d %10lu (f)\n",s_alloc);
strbuf_addf(sbuf
, "small net avail: %10d\n"
, s_alloc.size - s_alloc.counter * OVERHEAD * SINT
);
dump_stat("small free blocks: %8d %10lu (g)\n",s_free);
dump_stat("small wasted: %8d %10lu (h)\n",s_wasted);
strbuf_addf(sbuf,
"unused from current chunk %10lu (i)\n\n",unused);
dump_stat("permanent blocks: %8d %10lu\n", perm_st);
#ifdef SBRK_OK
dump_stat("clib allocations: %8d %10lu\n", clib_st);
#else
strbuf_addf(sbuf, "clib allocations: n/a n/a\n");
#endif
strbuf_add(sbuf, "\n");
strbuf_addf(sbuf,
"malloc_increment_size: calls %ld success %ld total %ld\n\n",
malloc_increment_size_calls,
malloc_increment_size_success,
malloc_increment_size_total
);
strbuf_addf(sbuf
, "Total storage: (b+c+d) %10lu should equal (a) %10lu\n"
, l_alloc.size + l_free.size + l_wasted.size
, sbrk_st.size
);
strbuf_addf(sbuf
, "Total small storage: (f+g+h+i) %10lu should equal (e) %10lu\n"
, s_alloc.size + s_free.size + s_wasted.size + unused
, s_chunk.size
);
strbuf_addf(sbuf
, "Total storage in use: (b-g-h-i) %10lu net available: %10lu\n"
, l_alloc.size - s_free.size - s_wasted.size - unused
, l_alloc.size - s_free.size - s_wasted.size - unused
- l_alloc.counter * OVERHEAD * SINT
- s_alloc.counter * OVERHEAD * SINT
);
strbuf_addf(sbuf
, "Total storage unused: (c+d+g+h+i) %10lu\n"
, l_free.size + l_wasted.size
+ s_free.size + s_wasted.size + unused
);
} /* dump_malloc_data() */
/*-------------------------------------------------------------------------*/
void
smalloc_dinfo_data (svalue_t *svp, int value)
/* Fill in the data for debug_info(DINFO_DATA, DID_MEMORY) into the
* svalue-block svp.
* If <value> is -1, <svp> points indeed to a value block; other it is
* the index of the desired value and <svp> points to a single svalue.
*/
{
#define ST_NUMBER(which,code) \
if (value == -1) svp[which].u.number = code; \
else if (value == which) svp->u.number = code
if (value == -1)
put_volatile_string(svp+DID_MEM_NAME, "smalloc");
else if (value == DID_MEM_NAME)
put_volatile_string(svp, "smalloc");
ST_NUMBER(DID_MEM_SBRK, sbrk_stat.counter);
ST_NUMBER(DID_MEM_SBRK_SIZE, sbrk_stat.size);
ST_NUMBER(DID_MEM_LARGE, large_alloc_stat.counter);
ST_NUMBER(DID_MEM_LARGE_SIZE, large_alloc_stat.size * SINT);
ST_NUMBER(DID_MEM_LFREE, large_free_stat.counter);
ST_NUMBER(DID_MEM_LFREE_SIZE, large_free_stat.size * SINT);
ST_NUMBER(DID_MEM_LWASTED, large_wasted_stat.counter);
ST_NUMBER(DID_MEM_LWASTED_SIZE, large_wasted_stat.size);
ST_NUMBER(DID_MEM_CHUNK, small_chunk_stat.counter);
ST_NUMBER(DID_MEM_CHUNK_SIZE, small_chunk_stat.size);
ST_NUMBER(DID_MEM_SMALL, small_alloc_stat.counter);
ST_NUMBER(DID_MEM_SMALL_SIZE, small_alloc_stat.size);
ST_NUMBER(DID_MEM_SFREE, small_free_stat.counter);
ST_NUMBER(DID_MEM_SFREE_SIZE, small_free_stat.size);
ST_NUMBER(DID_MEM_SWASTED, small_chunk_wasted.counter);
ST_NUMBER(DID_MEM_SWASTED_SIZE, small_chunk_wasted.size);
ST_NUMBER(DID_MEM_UNUSED, unused_size);
ST_NUMBER(DID_MEM_MINC_CALLS, malloc_increment_size_calls);
ST_NUMBER(DID_MEM_MINC_SUCCESS, malloc_increment_size_success);
ST_NUMBER(DID_MEM_MINC_SIZE, malloc_increment_size_total);
ST_NUMBER(DID_MEM_CLIB, clib_alloc_stat.counter);
ST_NUMBER(DID_MEM_CLIB_SIZE, clib_alloc_stat.size);
ST_NUMBER(DID_MEM_PERM, perm_alloc_stat.counter);
ST_NUMBER(DID_MEM_PERM_SIZE, perm_alloc_stat.size);
ST_NUMBER(DID_MEM_OVERHEAD, OVERHEAD * SINT);
ST_NUMBER(DID_MEM_ALLOCATED, large_alloc_stat.size * SINT
- small_free_stat.size
- small_chunk_wasted.size
- unused_size);
ST_NUMBER(DID_MEM_USED, large_alloc_stat.size * SINT
- small_free_stat.size
- small_chunk_wasted.size
- unused_size
- large_alloc_stat.counter * OVERHEAD * SINT
- small_alloc_stat.counter * OVERHEAD * SINT);
ST_NUMBER(DID_MEM_TOTAL_UNUSED, large_free_stat.size * SINT
+ large_wasted_stat.size
+ small_free_stat.size
+ small_chunk_wasted.size
+ unused_size);
#undef ST_NUMBER
} /* smalloc_dinfo_data() */
/*=========================================================================*/
#ifdef SBRK_OK
/* CLIB ALLOCATION FUNCTIONS */
/*-------------------------------------------------------------------------*/
static INLINE word_t
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.
*/
{
word_t size = 0;
if (ptr)
{
/* Get the allocated size of the block for the statistics */
word_t *q;
q = (word_t *)ptr;
#if MALLOC_ALIGN > SINT
while ( !(size = *--q) ) NOOP;
#if OVERHEAD != 1
size = (*(q + 1 - OVERHEAD) & M_MASK)*SINT;
#else
size = (size & M_MASK)*SINT;
#endif /* OVERHEAD */
#else /* MALLOC_ALIGN */
q -= OVERHEAD;
size = (*q & M_MASK)*SINT;
#endif /* MALLOC_ALIGN */
}
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.
*/
{
word_t old_size;
POINTER t;
if (!p)
return malloc(size);
old_size = get_block_size(p) - OVERHEAD * SINT;
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 /* SBRK_OK */
/*=========================================================================*/
/* GARBAGE COLLECTOR */
/*-------------------------------------------------------------------------*/
#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, char *, 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)[M_FILE-OVERHEAD];
object_line = ((word_t*)block)[M_LINE-OVERHEAD];
}
void
note_program_allocation_info ( void *block )
{
program_file = (char *)((word_t*)block)[M_FILE-OVERHEAD];
program_line = ((word_t*)block)[M_LINE-OVERHEAD];
}
Bool
is_object_allocation ( void *block )
{
return (object_file == (char *)((word_t*)block)[M_FILE-OVERHEAD])
&& (object_line == ((word_t*)block)[M_LINE-OVERHEAD]);
}
Bool
is_program_allocation ( void *block )
{
return (program_file == (char *)((word_t*)block)[M_FILE-OVERHEAD])
&& (program_line == ((word_t*)block)[M_LINE-OVERHEAD]);
}
#endif
/*-------------------------------------------------------------------------*/
void
store_print_block_dispatch_info (void *block
, void (*func)(int, char *, 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 >= 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)[M_FILE-OVERHEAD];
dispatch_table[i].line = ((word_t*)block)[M_LINE-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;
word_t i;
block = (word_t *) p;
block -= OVERHEAD;
if (block < heap_start || block + OVERHEAD >= heap_end)
return MY_TRUE;
i = (*s_size_ptr(block) & M_MASK);
if (i < OVERHEAD + ((minsize + 3) / SINT) || block + i >= heap_end)
return MY_TRUE;
if (i > SMALL_BLOCK_MAX + OVERHEAD)
{
word_t* block2;
block2 = block + i;
return !(*s_size_ptr(block) & THIS_BLOCK)
|| block[M_MAGIC] != LAMAGIC
|| !(*block2 & PREV_BLOCK);
}
i -= 1 + OVERHEAD;
return block[M_MAGIC] != samagic[i % NELEM(samagic)];
} /* is_freed() */
#endif /* MALLOC_TRACE */
/*-------------------------------------------------------------------------*/
#ifdef MALLOC_LPC_TRACE
static void
write_lpc_trace (int d, word_t *p)
/* Write the object and program which allocated the memory block <p>
* onto file <d>.
*/
{
object_t *obj, *o;
bytecode_p pc;
program_t *prog;
int line;
int32 id;
/* Try to find the object which allocated this block */
if ( NULL != (obj = (object_t *)p[M_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, o->name); /* TODO: If this cores, it has to go again */
}
else
WRITES(d, "No object.");
WRITES(d, "\n");
/* Try to find the program which allocated this block */
if ( 0 != (id = p[M_PROG]) )
{
pc = NULL;
prog = NULL;
WRITES(d, "By program: ");
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[M_PC];
prog = o->prog;
if (prog->program > pc || pc > PROGRAM_END(*prog))
o = NULL;
}
if (o)
{
char *file;
line = get_line_number(pc, prog, &file);
dprintf2(d, "%s line:%d\n", (p_int)file, line);
}
else
{
WRITES(d, "Not found at old address.\n");
}
}
} /* write_lpc_trace() */
#endif /* MALLOC_LPC_TRACE */
/*-------------------------------------------------------------------------*/
void
dump_lpc_trace (int d
, void *p
#if !defined(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)
# ifdef __MWERKS__
# pragma unused(p)
# endif
WRITES(d, "No malloc lpc trace.\n");
#else
write_lpc_trace(d, ((word_t *)p) - OVERHEAD);
#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) - OVERHEAD;
word_t size = *p;
# ifdef MALLOC_TRACE
dprintf3(d, " %s %d size 0x%x\n",
p[M_FILE], p[M_LINE], size & M_MASK
);
# endif
# ifdef MALLOC_LPC_TRACE
write_lpc_trace(d, p);
# endif
#endif
} /* dump_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;
#ifdef MALLOC_TRACE
int i;
char *file = (char *)block[M_FILE];
word_t line = block[M_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+OVERHEAD), 0);
write(d, "\n", 1);
return;
}
}
#endif
/* Print a hexdump, but not more than 70 characters */
size = ((*block & M_MASK) - OVERHEAD)*SINT;
if (size > 70)
{
write(d, "\n", 1);
return;
}
write(d, (char *)(block+OVERHEAD), size);
write(d, "\n\n", 2);
} /* print_block() */
/*-------------------------------------------------------------------------*/
void
clear_M_REF_flags (void)
/* Walk through all allocated blocks and clear the M_REF flag in preparation
* for a GC.
*/
{
word_t *p, *q, *last;
int i;
/* Clear the large blocks */
last = heap_end - 1;
for (p = heap_start; p < last; )
{
*p &= ~M_REF;
if (p + (*p & M_MASK) > heap_end)
{
in_malloc = 0;
fatal("pointer larger than brk()\n");
}
p += *p & M_MASK;
}
/* Now mark the memory used for the small chunks as ref'd,
* then clear the small blocks.
*/
for (p = last_small_chunk; p; p = *(word_t**)p)
{
word_t *end;
note_malloced_block_ref(p);
end = p - OVERHEAD + (p[-OVERHEAD] & M_MASK);
#ifdef DEBUG
dprintf2(gcollect_outfd, "clearing M_REF in chunk %x, end %x\n",
(word_t)(p - OVERHEAD), (word_t)end
);
/* Well, if we are so unlucky that write used malloc, next_unused
* might have changed.
*/
#endif
if (unused_size)
*next_unused = 0;
for (q = p+1; q < end; )
{
word_t size = *q;
if (!size) break; /* End of used area in this chunk */
*q &= ~M_REF;
q += size & M_MASK;
}
if (q > end)
{
/* pass some variables to fatal() so that the optimizer
* won't throw away the values, making them unreadable
* in the core.
*/
in_malloc = 0;
fatal("Small block error, start: %lx, %lx vs. %lx\n",
(long)(p+1), (long)q, (long)end);
}
}
/* We set M_REF for small free blocks that early for two reasons:
* - if it's referenced anywhere else, we get a fatal.
* - if the block gets malloced by the swapper in pass 5, it needs
* M_REF to be already set.
*/
for (i=0; i < SMALL_BLOCK_MAX + 1; i++)
{
for (p = sfltable[i]; p; p = *s_next_ptr(p) ) {
*p |= M_REF;
}
}
#if 0
/* TODO: DEBUG_SMALLOC code */
first_showsmallnewmalloced_call = 1;
#endif
} /* clear_M_REF_flags() */
/*-------------------------------------------------------------------------*/
void
free_unreferenced_memory (void)
/* The GC marked all used memory as REF'd, now recover all blocks which
* are allocated, but haven't been marked.
*/
{
word_t *p, *q, *last;
mp_int success = 0;
/* Scan the heap for lost large blocks */
last = heap_end - 1;
for (p = heap_start; p < last; )
{
word_t size;
size = *p;
if ( (size & (M_REF|THIS_BLOCK|M_GC_FREE)) == (THIS_BLOCK|M_GC_FREE) )
{
/* Large block marked as in use (THIS_BLOCK), but is not
* referenced (no M_REF) - recover it.
*/
word_t size2;
success++;
#if defined(MALLOC_TRACE) || defined(MALLOC_LPC_TRACE)
dprintf1(gcollect_outfd, "freeing large block 0x%x", (p_uint)p);
#endif
#ifdef MALLOC_TRACE
dprintf3(gcollect_outfd, " %s %d size 0x%x\n",
p[M_FILE], p[M_LINE], size & M_MASK
);
#endif
#ifdef MALLOC_LPC_TRACE
write_lpc_trace(gcollect_outfd, p);
#endif
print_block(gcollect_outfd, p);
size2 = p[size & M_MASK];
large_free((char *)(p+OVERHEAD));
if ( !(size2 & THIS_BLOCK) )
size += size2;
}
p += size & M_MASK;
}
if (success)
{
dprintf1(gcollect_outfd, "%d large blocks freed\n", success);
}
/* Scan the small chunks for lost small blocks.
* Remember that small blocks in the free-lists are marked as ref'd.
*/
success = 0;
for (p = last_small_chunk; p; p = *(word_t**)p)
{
word_t *end;
end = p - OVERHEAD + (p[-OVERHEAD] & M_MASK);
#ifdef DEBUG
dprintf2(gcollect_outfd, "scanning chunk %x, end %x for unref'd blocks\n",
(word_t)(p - OVERHEAD), (word_t)end
);
#endif
if (unused_size)
*next_unused = 0;
for (q = p+1; q < end; )
{
word_t size = *q;
if (!size) /* End of used area in this chunk */
break;
if ((*q & (M_REF|M_GC_FREE)) == M_GC_FREE)
{
/* Unref'd small blocks are definitely lost */
success++;
dprintf2(gcollect_outfd, "freeing small block 0x%x (user 0x%x)"
, (p_uint)q, (p_uint)(q+OVERHEAD));
#ifdef MALLOC_TRACE
dprintf2(gcollect_outfd, " %s %d", q[M_FILE], q[M_LINE]);
#endif
WRITES(gcollect_outfd, "\n");
#ifdef MALLOC_LPC_TRACE
write_lpc_trace(gcollect_outfd, q);
#endif
print_block(gcollect_outfd, q);
/* Recover the block */
*q |= M_REF;
xfree(q+OVERHEAD);
}
q += size & M_MASK;
}
}
if (success) {
dprintf1(gcollect_outfd, "%d small blocks freed\n", success);
}
} /* free_unreferenced_memory() */
/*-------------------------------------------------------------------------*/
#ifndef CHECK_SMALLOC_TOTAL
#define check_malloc_data() NOOP
#else
#define check_malloc_data() sm_check_malloc_data(__FILE__, __LINE__)
static void
sm_check_malloc_data (const char *file, int line)
/* For the status commands and functions: add the smalloc statistic
* to the buffer <sbuf>.
*/
{
t_stat sbrk_st, clib_st, perm_st;
t_stat l_alloc, l_free, l_wasted;
t_stat s_alloc, s_free, s_wasted, s_chunk;
unsigned long unused;
Bool didHeader;
didHeader = MY_FALSE;
/* Get a snapshot of the statistics. */
sbrk_st = sbrk_stat;
clib_st = clib_alloc_stat;
perm_st = perm_alloc_stat;
l_alloc = large_alloc_stat; l_alloc.size *= SINT;
l_free = large_free_stat; l_free.size *= SINT;
l_wasted = large_wasted_stat;
s_alloc = small_alloc_stat;
s_free = small_free_stat;
s_wasted = small_chunk_wasted;
s_chunk = small_chunk_stat;
unused = unused_size;
dprintf2(gcollect_outfd, "DEBUG: (%s:%d)"
, (p_int)file, (p_int)line);
dprintf4(gcollect_outfd, " total (alloc %d, free %d, wasted %d) %d "
, (p_int)l_alloc.size
, (p_int)l_free.size
, (p_int)l_wasted.size
, (p_int)(l_alloc.size + l_free.size + l_wasted.size)
);
if (l_alloc.size + l_free.size + l_wasted.size != sbrk_st.size)
writes(gcollect_outfd, "!=");
else
writes(gcollect_outfd, "==");
dprintf1(gcollect_outfd, " sbrk %d\n"
, (p_int)sbrk_st.size
);
dprintf2(gcollect_outfd, "DEBUG: (%s:%d)"
, (p_int)file, (p_int)line);
dprintf4(gcollect_outfd, " small (alloc %d, free %d, wasted %d, unused %d)"
, (p_int)s_alloc.size
, (p_int)s_free.size
, (p_int)s_wasted.size
, (p_int)unused
);
dprintf1(gcollect_outfd, " %d "
, (p_int)(s_alloc.size + s_free.size + s_wasted.size + unused)
);
if (s_alloc.size + s_free.size + s_wasted.size + unused != s_chunk.size)
writes(gcollect_outfd, "!=");
else
writes(gcollect_outfd, "==");
dprintf1(gcollect_outfd, " chunk %d\n"
, (p_int)s_chunk.size
);
} /* check_malloc_data() */
#endif
/*-------------------------------------------------------------------------*/
void
consolidate_freelists (void)
/* Consolidate the free small blocks, merging them into larger free blocks
* where possible, and rebuild the free lists.
*
* This method should be called right after a GC. It must not be called
* during a GC when the M_REF flags are not set.
*/
{
#if 0
# define DEB1(s,t1) dprintf1(2, s, (p_int)t1)
# define DEB2(s,t1,t2) dprintf2(2, s, (p_int)t1, (p_int)t2)
# define DEB3(s,t1,t2,t3) dprintf3(2, s, (p_int)t1, (p_int)t2, (p_int)t3)
#else
# define DEB1(s,t1) (void)0
# define DEB2(s,t1,t2) (void)0
# define DEB3(s,t1,t2,t3) (void)0
#endif
#if defined(CHECK_SMALLOC_TOTAL)
# define SDEB1(s,t1) dprintf1(gcollect_outfd, s, (p_int)t1)
# define SDEB2(s,t1,t2) dprintf2(gcollect_outfd, s, (p_int)t1, (p_int)t2)
# define SDEB3(s,t1,t2,t3) dprintf3(gcollect_outfd, s, (p_int)t1, (p_int)t2, (p_int)t3)
#else
# define SDEB1(s,t1) (void)0
# define SDEB2(s,t1,t2) (void)0
# define SDEB3(s,t1,t2,t3) (void)0
#endif
word_t *chunk, *prev_chunk;
int ix;
p_int bdelta = 0; /* Number of blocks merged */
p_int cfreed = 0; /* Number of small chunks freed */
p_int bfreed = 0; /* Number of small blocks in the freed chunks */
check_malloc_data();
#if defined(DEBUG_SMALLOC_ALLOCS)
{
int ft;
ulog("Free lists:");
for (ft = 0; ft < SMALL_BLOCK_MAX+1; ft++)
{
word_t *pt = sfltable[ft];
int count = 0;
while (pt)
{
count++;
pt = *s_next_ptr(pt);
}
dprintf1(gcollect_outfd, " %d,", count);
}
write(gcollect_outfd, "\n", 1);
}
#endif
/* Make sure that the M_REF flag is set for all small blocks.
* This should be the case after a GC, but even then it might
* not be.
*/
for (chunk = last_small_chunk; chunk; chunk = *(word_t**)chunk)
{
word_t *end, *block;
end = chunk - OVERHEAD + (chunk[-OVERHEAD] & M_MASK);
for (block = chunk+1; block < end; )
{
word_t size = *block;
if (!size) break; /* Reached unused space */
if (!(size & M_REF))
{
*block |= M_REF;
}
block += size & M_MASK;
}
}
/* Clear the M_REF flag in all small blocks in the free lists,
* and abolish the free lists. Small blocks in use keep their
* M_REF flag, that's how we're going to recognize them.
*/
for (ix = 0; ix < SMALL_BLOCK_MAX+1; ix++)
{
word_t *block;
for (block = sfltable[ix]; block; block = *s_next_ptr(block))
{
*block &= ~M_REF;
}
sfltable[ix] = NULL;
small_free[ix] = 0;
}
small_free_stat.counter = 0;
small_free_stat.size = 0;
/* The small chunks are invalid for now, too */
next_unused = NULL;
unused_size = 0;
/* Scan all small chunks and try to merge the free small
* blocks it contains, and re-insert them into the free lists.
*/
for (prev_chunk = NULL, chunk = last_small_chunk; chunk;)
{
word_t *block, *end;
p_int bchunk = 0;
word_t wasted_space = 0; /* in words */
Bool found_ref = MY_FALSE;
Bool found_free = MY_FALSE;
end = chunk - OVERHEAD + (chunk[-OVERHEAD] & M_MASK);
/* Merge all adjacent free small blocks in this chunk.
* Also set the found_ref flag if we find a used block.
*/
DEB2("Merge in Chunk %x..%x\n", chunk, end);
for (block = chunk+1; block < end; )
{
word_t size = *block;
if (!size)
{
wasted_space = end - block;
if (wasted_space > OVERHEAD)
found_free = MY_TRUE;
break; /* Reached the unused space */
}
bchunk++;
if (size & M_REF)
{
DEB1(" Used block %x\n", block);
found_ref = MY_TRUE;
}
else
{
/* It's a free block: try to merge it with the next ones */
word_t *next;
found_free = MY_TRUE;
DEB2(" Free block %x size %d\n", block, (size & M_MASK));
for (next = block + (size & M_MASK); next < end; )
{
word_t nsize = *next;
if (!nsize)
{
/* Add the unused space to the free block */
DEB3(" Unused space %x size %d, left %d\n"
, next, (nsize & M_MASK), (end-next));
if ((end-next) <= OVERHEAD)
{
count_back(small_chunk_wasted, (end-next) * SINT);
SDEB3("DEBUG: Unused space %x size %d, left %d\n"
, next, (nsize & M_MASK) * SINT
, (end-next) * SINT
);
}
size = ((size & M_MASK) + (end - next)) | (M_GC_FREE);
bdelta++;
break;
}
bchunk++;
if (nsize & M_REF)
{
DEB2(" Used block %x size %d\n", next
, (nsize & M_MASK));
break;
}
/* It's an adjacent free block: merge it */
DEB2(" Merge block %x size %d\n", next, (nsize & M_MASK));
SDEB3("DEBUG: Merge block %x size %d + %d\n"
, next
, (size & M_MASK) * SINT
, (nsize & M_MASK) * SINT
);
size = ((size & M_MASK) + (nsize & M_MASK)) | (M_GC_FREE);
bdelta++;
next += nsize & M_MASK;
}
/* We merged as much as possible: update the size field
* in the memory block.
*/
DEB2(" Update block %x size %d\n", block, (size & M_MASK));
*block = size;
}
block += size & M_MASK;
}
/* If the block is completely unused, get rid of it altogether
* and restart the loop.
*/
if (!found_ref)
{
word_t *next;
cfreed++;
bdelta = bchunk-1;
bfreed += bchunk;
next = *(word_t**)chunk;
DEB2("Chunk %x completely free, next is %x\n", chunk, next);
SDEB3("DEBUG: Chunk %x completely free: size %d, waste %d\n"
, chunk
, (chunk[-OVERHEAD] & M_MASK) * SINT
, SINT*OVERHEAD+sizeof(word_t*)+wasted_space
);
/* Remove the chunk from the list of chunks */
if (!prev_chunk)
last_small_chunk = next;
else
*(word_t **)prev_chunk = next;
count_back(small_chunk_stat, (chunk[-OVERHEAD] & M_MASK) * SINT);
count_back(small_chunk_wasted, SINT*OVERHEAD+sizeof(word_t*)+wasted_space);
xfree(chunk);
chunk = next;
continue;
}
/* If the block is completely used, continue to the next iteration */
if (!found_free)
{
DEB1("Chunk %x completely used.\n", chunk);
prev_chunk = chunk;
chunk = *(word_t**)chunk;
continue;
}
/* The chunk is at least partially used.
* Loop through its free blocks and reinsert them into
* the free lists.
* If the current chunk is also the first in the list, re-initialize
* the next_unused and unused_size variables.
*/
DEB2("Consolidate in Chunk %x..%x\n", chunk, end);
for (block = chunk+1; block < end; block += *block & M_MASK)
{
word_t size = *block;
if (!size) /* Reached the unused space */
{
/* If we're in the first chunk, this is the new unused space
* to use (if it's big enough).
*/
DEB3(" Unused %x, size %d, end-block %d\n", block, (size & M_MASK), (p_int)(end-block));
if (chunk == last_small_chunk)
{
SDEB3("DEBUG: Unused %x, size %d, end-block %d\n"
, block, (size & M_MASK) * SINT, (p_int)(end-block) * SINT);
if ((end - block) > OVERHEAD)
{
unused_size = (end - block) * SINT;
next_unused = block;
DEB2(" New 'next_unused' space: %x, size %d\n", next_unused
, unused_size);
}
else
{
/* The space is too small to be used: add it to the
* wasted space as the allocator hasn't detected this
* situation yet.
*/
count_up(small_chunk_wasted, SINT*(end - block));
}
}
else
{
/* Don't count up small_chunk_wasted, as its value
* already includes this wasted space.
*/
SDEB3("DEBUG: Unused %x already counted: size %d, end-block %d\n"
, block, (size & M_MASK) * SINT, (p_int)(end-block) * SINT);
NOOP;
}
break;
}
if (size & M_REF)
{
DEB2(" Used Block %x, size %d\n", block, (size & M_MASK));
continue;
}
DEB2(" Free Block %x, size %d\n", block, (size & M_MASK));
/* If this free block is the last block in the chunk, and we're in
* the first chunk, count all the space as 'unused'.
* The merge pass made sure that there is no following wasted
* space in the chunk.
*/
if (chunk == last_small_chunk && block + (size & M_MASK) >= end)
{
bdelta--;
unused_size = (size & M_MASK) * SINT;
next_unused = block;
SDEB3("DEBUG: Unused in chunk %x, size %d, end-block %d\n"
, block, (size & M_MASK) * SINT, (p_int)(end-block) * SINT);
DEB2(" New 'next_unused' space: %x, size %d\n", next_unused
, unused_size);
break;
}
size = size & M_MASK;
/* Create a new free small block and insert it into the
* appropriate freelist.
*/
if (size > SMALL_BLOCK_MAX + OVERHEAD)
{
/* Oversized block */
DEB1(" Oversized block [%d]\n", SMALL_BLOCK_MAX);
*s_size_ptr(block) = size | (M_GC_FREE|M_REF);
*s_next_ptr(block) = sfltable[SMALL_BLOCK_MAX];
sfltable[SMALL_BLOCK_MAX] = block;
count_up(small_free_stat, size * SINT);
SDEB1("DEBUG: Oversized block: size %d\n", size * SINT);
small_free[SMALL_BLOCK_MAX]++;
#ifdef MALLOC_TRACE
block[M_MAGIC] = SIZE_MOD_INDEX(sfmagic, size * SINT);
#endif
}
else
{
/* Normal sized block */
DEB1(" Small block [%d]\n", SIZE_INDEX_VALUE(size * SINT));
MAKE_SMALL_FREE(block, size * SINT);
SDEB1("DEBUG: Small free block: size %d\n", size * SINT);
}
} /* for() rebuild in small chunk */
/* Step to next chunk */
prev_chunk = chunk;
chunk = *(word_t**)chunk;
} /* for (small chunks) */
check_malloc_data();
/* All done. */
dprintf3(gcollect_outfd, "%s Consolidation merged %d blocks, "
"freed %d chunks holding "
, (p_int)time_stamp(), bdelta, cfreed);
dprintf1(gcollect_outfd, "%d blocks.\n", bfreed);
#if defined(DEBUG_SMALLOC_ALLOCS)
{
int ft;
ulog("Free lists:");
for (ft = 0; ft < SMALL_BLOCK_MAX+1; ft++)
{
word_t *pt = sfltable[ft];
int count = 0;
while (pt)
{
count++;
pt = *s_next_ptr(pt);
}
dprintf1(gcollect_outfd, " %d,", count);
}
write(gcollect_outfd, "\n", 1);
}
#endif
# undef DEB1
# undef DEB2
# undef DEB3
} /* consolidate_freelists() */
/*=========================================================================*/
/*
* Functions below can be used to debug malloc.
*/
#if 0
/*-------------------------------------------------------------------------*/
void
test_small(void)
/* Test the integrity of the small block structures.
*/
{
word_t *p, *q;
int i;
if (unused_size)
*next_unused = NULL;
for (p = last_small_chunk; p; p = *(word_t**)p)
{
word_t *end;
end = p - OVERHEAD + (p[-OVERHEAD] & M_MASK);
for (q = p+1; q < end; )
{
word_t size = *q;
if (!size)
break;
*q &= ~M_REF;
q += size & M_MASK;
}
if (q > end)
{
in_malloc = 0;
fatal("Small block error\n");
}
}
} /* test_small() */
/*-------------------------------------------------------------------------*/
void
walk_new_small_malloced (void (*func)(POINTER, long))
/* Scan the small blocks for those with the M_REF flag set - these
* are those which have been allocated since the last call.
* For every new small block, <func> is called with (block, size).
*/
{
int i;
word_t *p, *q;
/* Skip those small blocks we know to be free */
for (i = 0; i < SMALL_BLOCK_MAX; i++)
{
for (p = sfltable[i]; p; p = * (word_t **) (p + OVERHEAD) )
{
*s_size_ptr(p) &= ~M_REF;
}
}
for (p = last_small_chunk; p; p = *(word_t **)p)
{
word_t *end = p - OVERHEAD + (p[-OVERHEAD] & M_MASK);
dprintf2(2, "scanning chunk %x, end %x\n", (u)(p - OVERHEAD), (u)end);
if (unused_size)
*next_unused = NULL;
for (q = p+1; q < end; )
{
word_t size = *s_size_ptr(q);
if (!size) break;
if (size & M_REF)
{
(*func)(s_next_ptr(q), (size & M_MASK) * SINT);
*s_size_ptr(q) &= ~M_REF;
}
q += size & M_MASK;
}
}
/* Restore the M_REF flag of the free blocks */
for (i=0; i < SMALL_BLOCK_MAX; i++)
{
for (p = sfltable[i]; p; p = * (word_t **) (p + OVERHEAD) ) {
*s_size_ptr(p) |= M_REF;
}
}
} /* walk_new_small_malloced() */
/*-------------------------------------------------------------------------*/
void
show_block (word_t *ptr)
{
dprintf3(2, "[%c%d: %d] ",(*ptr & THIS_BLOCK ? '+' : '-'),
(int) ptr, *ptr & M_MASK);
} /* show_block() */
#endif /* 0 for debug code */
/***************************************************************************/