ldmud-3.2.9/doc/
ldmud-3.2.9/doc/efun/
ldmud-3.2.9/mud/
ldmud-3.2.9/mud/heaven7/
ldmud-3.2.9/mud/heaven7/lib/
ldmud-3.2.9/mud/lp-245/
ldmud-3.2.9/mud/lp-245/banish/
ldmud-3.2.9/mud/lp-245/doc/
ldmud-3.2.9/mud/lp-245/doc/examples/
ldmud-3.2.9/mud/lp-245/doc/sefun/
ldmud-3.2.9/mud/lp-245/log/
ldmud-3.2.9/mud/lp-245/obj/Go/
ldmud-3.2.9/mud/lp-245/players/lars/
ldmud-3.2.9/mud/lp-245/room/death/
ldmud-3.2.9/mud/lp-245/room/maze1/
ldmud-3.2.9/mud/lp-245/room/sub/
ldmud-3.2.9/mud/lp-245/secure/
ldmud-3.2.9/mud/morgengrauen/
ldmud-3.2.9/mud/morgengrauen/lib/
ldmud-3.2.9/mud/sticklib/
ldmud-3.2.9/mud/sticklib/src/
ldmud-3.2.9/mudlib/uni-crasher/
ldmud-3.2.9/pkg/
ldmud-3.2.9/pkg/debugger/
ldmud-3.2.9/pkg/diff/
ldmud-3.2.9/pkg/misc/
ldmud-3.2.9/src/autoconf/
ldmud-3.2.9/src/bugs/
ldmud-3.2.9/src/bugs/MudCompress/
ldmud-3.2.9/src/bugs/b-020916-files/
ldmud-3.2.9/src/bugs/doomdark/
ldmud-3.2.9/src/bugs/ferrycode/ferry/
ldmud-3.2.9/src/bugs/ferrycode/obj/
ldmud-3.2.9/src/bugs/psql/
ldmud-3.2.9/src/done/
ldmud-3.2.9/src/done/order_alist/
ldmud-3.2.9/src/done/order_alist/obj/
ldmud-3.2.9/src/done/order_alist/room/
ldmud-3.2.9/src/gcc/
ldmud-3.2.9/src/gcc/2.7.0/
ldmud-3.2.9/src/gcc/2.7.1/
ldmud-3.2.9/src/hosts/
ldmud-3.2.9/src/hosts/GnuWin32/
ldmud-3.2.9/src/hosts/amiga/NetIncl/
ldmud-3.2.9/src/hosts/amiga/NetIncl/netinet/
ldmud-3.2.9/src/hosts/amiga/NetIncl/sys/
ldmud-3.2.9/src/hosts/i386/
ldmud-3.2.9/src/hosts/msdos/byacc/
ldmud-3.2.9/src/hosts/msdos/doc/
ldmud-3.2.9/src/hosts/os2/
ldmud-3.2.9/src/hosts/win32/
ldmud-3.2.9/src/util/
ldmud-3.2.9/src/util/erq/
ldmud-3.2.9/src/util/indent/hosts/next/
ldmud-3.2.9/src/util/xerq/
ldmud-3.2.9/src/util/xerq/lpc/
ldmud-3.2.9/src/util/xerq/lpc/www/
/*---------------------------------------------------------------------------
 * 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 */

/***************************************************************************/