mirror of
git://git.psyced.org/git/psyclpc
synced 2024-08-15 03:20:16 +00:00
4010 lines
125 KiB
C
4010 lines
125 KiB
C
|
/*---------------------------------------------------------------------------
|
||
|
* 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 mem_alloc()/mem_free() 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_CHECK the allocated blocks are tagged with magic words. This
|
||
|
* costs a bit of time and memory, but is a good defense against the most
|
||
|
* basic memory misuses.
|
||
|
*
|
||
|
*
|
||
|
* -- Small Blocks --
|
||
|
*
|
||
|
* Small blocks are allocations of up to (SMALL_BLOCK_NUM+1)*4 Bytes, currently
|
||
|
* 68 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 doubly-linked 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
|
||
|
* and last 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. Small free blocks are defragmented as described below.
|
||
|
*
|
||
|
* 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.
|
||
|
* If finally a new small chunk has to be allocated, the memory not used to
|
||
|
* satisfy the allocation is entered as new oversized free small block into
|
||
|
* the appropriate freelist.
|
||
|
*
|
||
|
* When a small free block is split, the allocator makes sure that the
|
||
|
* remaining block is of at least SMALL_BLOCK_SPLIT_MIN words size (currently
|
||
|
* 3 words -> 12 Bytes).
|
||
|
* Experience has shown that the gamedriver doesn't need that many blocks of
|
||
|
* less than 12 Bytes. If the allocator's splits generated those small blocks,
|
||
|
* they would never be used and just tie up free space.
|
||
|
*
|
||
|
* To reduce memory fragmentation, the allocator implements a lazy
|
||
|
* defragmentation scheme for the small blocks: if checking free lists doesn't
|
||
|
* yield a suitable block, the allocator defragments the free lists and tries
|
||
|
* again. Only if this second attempt fails, a new small chunk is allocated.
|
||
|
* To save time, the defragmentation-on-allocation is terminated if a suitable
|
||
|
* block is can be constructed (but not before the current list under
|
||
|
* examination has been fully defragmented); a full defragmentation happens
|
||
|
* only as part of a garbage collection.
|
||
|
*
|
||
|
* Obviously the defragmenter can eat up a lot of time when the free lists
|
||
|
* get long, especially since most blocks can't be defragmented. So as an
|
||
|
* optimization the defragmenter sets the M_DEFRAG flag in each block it
|
||
|
* visited and decided as non-defragmentable. Newly freed blocks don't have
|
||
|
* this flag set and are also entered at the head of their free list; this way
|
||
|
* the defragmenter can stop scanning a free list as soon as it reaches the
|
||
|
* first block flagged to be non-defragmentable. And while it is possible
|
||
|
* that a flagged block changes to be defragmentable after all, this can
|
||
|
* happen only if another block is newly freed, and the defragmenter is
|
||
|
* guaranteed to find that one.
|
||
|
*
|
||
|
* -- Large Blocks --
|
||
|
*
|
||
|
* 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.
|
||
|
*
|
||
|
* 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.
|
||
|
*
|
||
|
#ifdef USE_AVL_FREELIST
|
||
|
* An AVL node is created for every distinct size of a large free block;
|
||
|
* if additional blocks of the same size are freed, they are kept in
|
||
|
* a double-linked list hanging of the node. Intention of this setup
|
||
|
* is to decrease the size of the AVL tree (and thus increase the
|
||
|
* locality large block lookups) in the case of multiple blocks of the
|
||
|
* same size.
|
||
|
*
|
||
|
* The double-links are not really required, as only the first block on the
|
||
|
* freelist or the node itself are selected for allocation, but it's
|
||
|
* algorithmically simple to implement, and we have the memory for the
|
||
|
* second pointer.
|
||
|
#ifdef MALLOC_ORDER_LARGE_FREELISTS
|
||
|
* The freelist is ordered by the starting address of the blocks (with
|
||
|
* the likely exception of the block holding the AVL node), in an attempt
|
||
|
* to reduce high heap fragmentation.
|
||
|
#endif
|
||
|
#else
|
||
|
* A new AVL node is created for every free large block.
|
||
|
#endif
|
||
|
*
|
||
|
#ifdef SBRK_OK && MALLOC_SBRK
|
||
|
* 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
|
||
|
*
|
||
|
* -- Privilege Level --
|
||
|
*
|
||
|
* 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.
|
||
|
*
|
||
|
*
|
||
|
* -- Block Structure --
|
||
|
*
|
||
|
* The zeroth word in each large block is used to hold the size of the
|
||
|
* block (incl. all overhead). This extra word is necessary as on some
|
||
|
* systems large blocks can span half of the address space; however, it
|
||
|
* is necessary only for large blocks.
|
||
|
*
|
||
|
* The first word in each allocated block is used for the 'flag&size' field
|
||
|
* which holds the flags for a block, plust for small blocks the size (incl.
|
||
|
* all overhead):
|
||
|
*
|
||
|
* THIS_BLOCK: small: set if this block is not allocated
|
||
|
* large: set if this block is allocated
|
||
|
* PREV_BLOCK: small: set if the previous block is not allocated
|
||
|
* large: set if the previous block is allocated
|
||
|
* M_GC_FREE : set in allocated blocks if the GC may free this block if
|
||
|
* found lost
|
||
|
* M_DEFRAG : set in small free blocks which can't be defragmented further
|
||
|
* M_REF : set if this block is referenced (used during a GC)
|
||
|
*
|
||
|
* Because of the PREV_BLOCK flag, all chunk allocations (either the
|
||
|
* small chunks for the small block allocator, or the esbrk() chunks for the
|
||
|
* large block allocator) are initialized with a fake permanently allocatod
|
||
|
* block at the end of the chunk, consisting of not much more than the
|
||
|
* flag&size field with THIS_BLOCK cleared.
|
||
|
*
|
||
|
#ifdef MALLOC_CHECK
|
||
|
* The second word holds the magic word, which for small blocks is determined
|
||
|
* also by the size of the block.
|
||
|
#endif
|
||
|
*
|
||
|
* When accessing these header fields, the code in general keeps a pointer
|
||
|
* to the flag&size word.
|
||
|
*
|
||
|
* The user (aka payload) area of free blocks is used to manage the various
|
||
|
* free lists.
|
||
|
*
|
||
|
* Small free blocks:
|
||
|
*
|
||
|
* The first word of the user area holds the 'next' link of the free
|
||
|
* list, which is NULL for the last block in the list.
|
||
|
*
|
||
|
* The last word in the user area holds the 'prev' link of the free
|
||
|
* list, which for the first block in the list points to the block
|
||
|
* itself.
|
||
|
*
|
||
|
* For an oversized small block, the 'prev' link has the lowest bit
|
||
|
* set, and the second-to-last word in the user area holds the size
|
||
|
* of the block in words.
|
||
|
*
|
||
|
* This setup allows the allocator to determine the size of a preceeding
|
||
|
* free block by simply following the 'prev' link for normally sized
|
||
|
* blocks, and by directly reading the size for an oversize block. At the
|
||
|
* same time the management structure requires just two words at minimum.
|
||
|
*
|
||
|
* Large free blocks:
|
||
|
*
|
||
|
* The first words of the user area hold the AVL tree node structure.
|
||
|
*
|
||
|
* The last word in the user area holds the size of the block in words.
|
||
|
*
|
||
|
#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 "mstrings.h"
|
||
|
#include "stdstrings.h"
|
||
|
#include "svalue.h"
|
||
|
#ifdef MALLOC_EXT_STATISTICS
|
||
|
#include "array.h"
|
||
|
#include "backend.h"
|
||
|
#endif /* MALLOC_EXT_STATISTICS */
|
||
|
|
||
|
#include "../mudlib/sys/debug_info.h"
|
||
|
|
||
|
/* Defines required by the xalloc.c wrapper */
|
||
|
|
||
|
#define MEM_ALIGN (SIZEOF_CHAR_P)
|
||
|
|
||
|
/* If possible, request memory using sbrk().
|
||
|
*/
|
||
|
|
||
|
#if defined(SBRK_OK)
|
||
|
# ifdef MALLOC_SBRK
|
||
|
# if MALLOC_ALIGN == 4
|
||
|
# define REPLACE_MALLOC
|
||
|
# else
|
||
|
# undef SBRK_OK
|
||
|
# endif
|
||
|
# else
|
||
|
# undef SBRK_OK
|
||
|
# endif
|
||
|
#endif
|
||
|
|
||
|
#define MEM_MAIN_THREADSAFE
|
||
|
|
||
|
/* #undef NO_MEM_BLOCK_SIZE */
|
||
|
|
||
|
#define SINT (SIZEOF_CHAR_P)
|
||
|
|
||
|
/* Initialiser macros for the tables */
|
||
|
|
||
|
#define INIT4 0, 0, 0, 0
|
||
|
#define INIT4 0, 0, 0, 0
|
||
|
#define INIT8 INIT4, INIT4
|
||
|
#define INIT12 INIT8, INIT4
|
||
|
#define INIT16 INIT8, INIT8
|
||
|
#define INIT24 INIT16, 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])
|
||
|
|
||
|
/* #undef DEBUG_AVL */
|
||
|
/* Define this to debug the AVL tree.
|
||
|
*/
|
||
|
|
||
|
/* The extra smalloc header fields.
|
||
|
*/
|
||
|
|
||
|
#define M_LSIZE (-1) /* (word_t) Size in word_t (large blocks only) */
|
||
|
#define M_SIZE (0) /* (word_t) Size in word_t, plus some flags */
|
||
|
|
||
|
#ifdef MALLOC_CHECK
|
||
|
# define M_OVERHEAD (2)
|
||
|
# define M_MAGIC (1) /* (word_t) The magic word */
|
||
|
#else
|
||
|
# define M_OVERHEAD (1)
|
||
|
#endif /* MALLOC_CHECK */
|
||
|
|
||
|
#define ML_OVERHEAD (M_OVERHEAD+1)
|
||
|
/* The overhead for large blocks. */
|
||
|
|
||
|
#define M_LINK M_OVERHEAD
|
||
|
/* Index of the 'next' link for the small free lists */
|
||
|
|
||
|
#define M_PLINK(size) ((size)-1)
|
||
|
/* Index of the 'prev' link for the small free lists.
|
||
|
* <size> is the block size in words.
|
||
|
*/
|
||
|
|
||
|
#define BLOCK_NEXT(block) ((word_t *)(block[M_LINK]))
|
||
|
/* The 'next' link of free block <block>, properly typed */
|
||
|
|
||
|
#define BLOCK_PREV(block,size) ((word_t *)(block[M_PLINK(size)]))
|
||
|
/* The 'prev' link of free block <block>, properly typed */
|
||
|
|
||
|
|
||
|
#define T_OVERHEAD (M_OVERHEAD + XM_OVERHEAD)
|
||
|
/* Total overhead: it is used by smalloc to make smart decisions
|
||
|
* about when to split blocks.
|
||
|
*/
|
||
|
|
||
|
#define TL_OVERHEAD (ML_OVERHEAD + XM_OVERHEAD)
|
||
|
/* Total overhead for large blocks.
|
||
|
*/
|
||
|
|
||
|
#define SMALL_BLOCK_MIN (2)
|
||
|
/* Minimum size of a small block in words */
|
||
|
|
||
|
#define SMALL_BLOCK_NUM (16)
|
||
|
/* Number of different small block sizes.
|
||
|
*/
|
||
|
|
||
|
#define INIT_SMALL_BLOCK INIT16
|
||
|
/* The proper initializer macro for all tables sized SMALL_BLOCK_NUM.
|
||
|
*/
|
||
|
|
||
|
#define SMALL_BLOCK_SPLIT_MIN (3)
|
||
|
/* Minimum size of a small block created by a split, in words */
|
||
|
|
||
|
/* Derived values */
|
||
|
|
||
|
#define SMALL_BLOCK_MIN_BYTES (SMALL_BLOCK_MIN * SINT)
|
||
|
/* Minimum payload size of a small block.
|
||
|
*/
|
||
|
|
||
|
#define SMALL_BLOCK_MAX (SMALL_BLOCK_NUM-1 + T_OVERHEAD + SMALL_BLOCK_MIN)
|
||
|
/* The maximum size (incl. overhead) of a small block in words
|
||
|
*/
|
||
|
|
||
|
#define SMALL_BLOCK_MAX_BYTES ((SMALL_BLOCK_NUM-1 + SMALL_BLOCK_MIN) * SINT)
|
||
|
/* Maximum payload size of a small block.
|
||
|
*/
|
||
|
|
||
|
|
||
|
/* 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 - (M_OVERHEAD+1)*SINT+SINT - EXTERN_MALLOC_OVERHEAD)
|
||
|
/* large_malloc overhead ^ */
|
||
|
|
||
|
# define CHUNK_SIZE (0x40000 - SINT - EXTERN_MALLOC_OVERHEAD)
|
||
|
#endif
|
||
|
|
||
|
|
||
|
/* Bitflags for the size field:
|
||
|
* 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_DEFRAG (M_GC_FREE) /* Non-defragmentable small block */
|
||
|
#define M_MASK 0x0fffffff /* Mask for the size, measured in word_t's */
|
||
|
|
||
|
|
||
|
#ifdef MALLOC_CHECK
|
||
|
|
||
|
/* 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_CHECK */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
/* Debugging macros */
|
||
|
|
||
|
/* 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_MALLOC_ALLOCS */
|
||
|
|
||
|
#ifdef DEBUG_MALLOC_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
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
|
||
|
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 --- */
|
||
|
|
||
|
#define DEFAULT_INITIAL_SC_SIZE (3 * SMALL_CHUNK_SIZE)
|
||
|
/* The default initial size of a small chunk. Currently the driver
|
||
|
* uses somewhat over 2*SMALL_CHUNK_SIZE for it's own tables.
|
||
|
*/
|
||
|
|
||
|
static word_t small_chunk_size = DEFAULT_INITIAL_SC_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_NUM+1] = {INIT_SMALL_BLOCK, 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.
|
||
|
*/
|
||
|
|
||
|
/* --- Large Block variables --- */
|
||
|
|
||
|
static word_t *heap_start = NULL;
|
||
|
/* First address on the heap.
|
||
|
*/
|
||
|
|
||
|
static word_t *heap_end = NULL;
|
||
|
/* Current end address of the heap.
|
||
|
*/
|
||
|
|
||
|
/* --- Statistics --- */
|
||
|
|
||
|
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.
|
||
|
* This includes the large-block overhead of the chunks themselves.
|
||
|
*/
|
||
|
|
||
|
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 permanent allocations functions (incl overhead). This
|
||
|
* figure is a subset of {small,large}_alloc_stat.
|
||
|
*/
|
||
|
|
||
|
static long defrag_calls_total = 0;
|
||
|
/* Total number of calls to defragment_small_lists().
|
||
|
*/
|
||
|
|
||
|
static long defrag_calls_req = 0;
|
||
|
/* Number of calls to defragment_small_lists() with a desired size.
|
||
|
*/
|
||
|
|
||
|
static long defrag_req_success = 0;
|
||
|
/* Number of times, a defragmentation for a desired size was successful.
|
||
|
*/
|
||
|
|
||
|
static long defrag_blocks_inspected = 0;
|
||
|
/* Number of blocks inspected during defragmentations.
|
||
|
*/
|
||
|
|
||
|
static long defrag_blocks_merged = 0;
|
||
|
/* Number of blocks merged during defragmentations.
|
||
|
*/
|
||
|
|
||
|
static long defrag_blocks_result = 0;
|
||
|
/* Number of defragmented blocks (ie. merge results).
|
||
|
*/
|
||
|
|
||
|
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().
|
||
|
*/
|
||
|
|
||
|
#ifdef USE_AVL_FREELIST
|
||
|
static long num_avl_nodes = 0;
|
||
|
/* Number of nodes in the AVL tree managing the large free blocks.
|
||
|
*/
|
||
|
#endif /* USE_AVL_FREELIST */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
/* Forward declarations */
|
||
|
|
||
|
static char *esbrk(word_t, size_t * pExtra);
|
||
|
|
||
|
static char *large_malloc(word_t, Bool);
|
||
|
#define large_malloc_int(size, force_m) large_malloc(size, force_m)
|
||
|
static void large_free(char *);
|
||
|
|
||
|
#ifdef MALLOC_EXT_STATISTICS
|
||
|
/*=========================================================================*/
|
||
|
|
||
|
/* EXTENDED STATISTICS */
|
||
|
|
||
|
/* Extended statistics, giving a better overview about what is allocated
|
||
|
* how often.
|
||
|
*/
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
/* --- struct extstat_s: Statistics for one block size
|
||
|
*/
|
||
|
typedef struct extstat_s {
|
||
|
unsigned long max_alloc; /* Max number of blocks allocated */
|
||
|
unsigned long cur_alloc; /* Current number of blocks allocated */
|
||
|
unsigned long max_free; /* Max number of free blocks */
|
||
|
unsigned long cur_free; /* Current number of free blocks */
|
||
|
unsigned long num_xalloc; /* Number of xalloc() requests since last avg */
|
||
|
unsigned long num_xfree; /* Number of xfree() requests since last avg */
|
||
|
double savg_xalloc; /* Sum of xalloc() requests/second */
|
||
|
double savg_xfree; /* Sum of xfree() requests/second */
|
||
|
/* On every call to mem_update_stats(), num_x{alloc,free} is averaged
|
||
|
* into a number of requests/second and then added to savg_. The
|
||
|
* total average is then later calculated using savg_ and the number
|
||
|
* of mem_update_stats() calls - this way overflows are avoided.
|
||
|
*/
|
||
|
} extstat_t;
|
||
|
|
||
|
static unsigned long num_update_calls = 0;
|
||
|
/* Number of mem_update_stats() calls.
|
||
|
*/
|
||
|
|
||
|
static mp_int last_update_time = 0;
|
||
|
/* Timestamp of last call to mem_update_stats().
|
||
|
*/
|
||
|
|
||
|
#define SIZE_EXTSTATS (SMALL_BLOCK_NUM+2)
|
||
|
|
||
|
static extstat_t extstats[SIZE_EXTSTATS];
|
||
|
/* The statistics array. [SMALL_BLOCK_NUM+1] is for the large blocks.
|
||
|
*/
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
static INLINE void
|
||
|
extstat_update_max (extstat_t * pStat)
|
||
|
{
|
||
|
if (pStat->cur_alloc > pStat->max_alloc)
|
||
|
pStat->max_alloc = pStat->cur_alloc;
|
||
|
if (pStat->cur_free > pStat->max_free)
|
||
|
pStat->max_free = pStat->cur_free;
|
||
|
} /* extstat_update_max() */
|
||
|
|
||
|
#endif /* MALLOC_EXT_STATISTICS */
|
||
|
|
||
|
/*=========================================================================*/
|
||
|
|
||
|
/* ASSOCIATED ROUTINES */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
static INLINE size_t
|
||
|
mem_block_total_size (POINTER p)
|
||
|
|
||
|
/* Return the size of block <p> (including internal overhead) in bytes.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
word_t *q = ((word_t *) p) - M_OVERHEAD;
|
||
|
word_t size = q[M_SIZE] & M_MASK;
|
||
|
if (size > SMALL_BLOCK_MAX)
|
||
|
size = q[M_LSIZE];
|
||
|
return size*SINT;
|
||
|
} /* mem_block_total_size() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
static INLINE size_t
|
||
|
mem_block_size (POINTER p)
|
||
|
|
||
|
/* Return the size of block <p> (sans internal overhead) in bytes.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
word_t * q = ((word_t *)p) - M_OVERHEAD;
|
||
|
|
||
|
word_t size = (q[M_SIZE] & M_MASK);
|
||
|
if (size > SMALL_BLOCK_MAX)
|
||
|
return mem_block_total_size(p) - ML_OVERHEAD*SINT;
|
||
|
return mem_block_total_size(p) - M_OVERHEAD*SINT;
|
||
|
} /* mem_block_size() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
static INLINE void
|
||
|
mem_mark_permanent (POINTER p)
|
||
|
|
||
|
/* Mark the allocated block at <p> as permanent, ie. it won't be subject
|
||
|
* to the GC.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
word_t * q = (word_t *)p;
|
||
|
if (q[-M_OVERHEAD] & M_GC_FREE)
|
||
|
{
|
||
|
q[-M_OVERHEAD] &= ~M_GC_FREE;
|
||
|
count_up(perm_alloc_stat, mem_block_total_size(q));
|
||
|
}
|
||
|
} /* mem_mark_permanent() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
static INLINE void
|
||
|
mem_mark_collectable (POINTER p)
|
||
|
|
||
|
/* Mark the allocated block at <p> as non-permant, ie. it is subject
|
||
|
* to the GC.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
word_t * q = (word_t *)p;
|
||
|
if (!(q[-M_OVERHEAD] & M_GC_FREE))
|
||
|
{
|
||
|
q[-M_OVERHEAD] |= (M_REF|M_GC_FREE);
|
||
|
count_back(perm_alloc_stat, mem_block_total_size(q));
|
||
|
}
|
||
|
} /* mem_mark_collectable() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
static INLINE size_t
|
||
|
mem_overhead (void)
|
||
|
|
||
|
/* Return the size of each block's overhead in bytes.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
return M_OVERHEAD*SINT;
|
||
|
} /* mem_overhead() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
#ifdef MALLOC_EXT_STATISTICS
|
||
|
void
|
||
|
mem_update_stats (void)
|
||
|
|
||
|
/* Update whatever extended statistics the allocator has. Called every
|
||
|
* backend cycle or so to allow for the calculation of averages over time.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
int i;
|
||
|
double time_diff;
|
||
|
|
||
|
if (!last_update_time)
|
||
|
last_update_time = boot_time;
|
||
|
|
||
|
if (current_time <= last_update_time)
|
||
|
return;
|
||
|
|
||
|
time_diff = current_time - last_update_time;
|
||
|
|
||
|
for (i = 0; i < SIZE_EXTSTATS; ++i)
|
||
|
{
|
||
|
extstats[i].savg_xalloc += (double)extstats[i].num_xalloc / time_diff;
|
||
|
extstats[i].savg_xfree += (double)extstats[i].num_xfree / time_diff;
|
||
|
extstats[i].num_xalloc = 0;
|
||
|
extstats[i].num_xfree = 0;
|
||
|
}
|
||
|
|
||
|
num_update_calls++;
|
||
|
last_update_time = current_time;
|
||
|
} /* mem_update_stats() */
|
||
|
#endif /* MALLOC_EXT_STATISTICS */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
void
|
||
|
mem_dump_data (strbuf_t *sbuf)
|
||
|
|
||
|
/* For the status commands and functions: add the smalloc statistic
|
||
|
* to the buffer <sbuf>.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
#ifdef SBRK_OK
|
||
|
t_stat clib_st;
|
||
|
#endif
|
||
|
t_stat sbrk_st, perm_st, xalloc_st;
|
||
|
t_stat l_alloc, l_free, l_wasted;
|
||
|
t_stat s_alloc, s_free, s_wasted, s_chunk;
|
||
|
|
||
|
/* Get a snapshot of the statistics - strbuf_add() might do further
|
||
|
* allocations while we're reading them.
|
||
|
*/
|
||
|
|
||
|
#ifdef MALLOC_EXT_STATISTICS
|
||
|
mem_update_stats();
|
||
|
#endif /* MALLOC_EXT_STATISTICS */
|
||
|
|
||
|
sbrk_st = sbrk_stat;
|
||
|
#ifdef SBRK_OK
|
||
|
clib_st = clib_alloc_stat;
|
||
|
#endif
|
||
|
xalloc_st = xalloc_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;
|
||
|
|
||
|
# define dump_stat(str,stat) strbuf_addf(sbuf, str,stat.counter,stat.size)
|
||
|
|
||
|
strbuf_add(sbuf, "Type Count Space (bytes)\n");
|
||
|
dump_stat("xallocs: %8u %10lu\n\n", xalloc_st);
|
||
|
dump_stat("sbrk requests: %8u %10lu (a)\n",sbrk_st);
|
||
|
dump_stat("large blocks: %8u %10lu (b)\n",l_alloc);
|
||
|
strbuf_addf(sbuf
|
||
|
, "large net avail: %10ld\n"
|
||
|
, l_alloc.size - l_alloc.counter * ML_OVERHEAD * SINT
|
||
|
);
|
||
|
dump_stat("large free blocks: %8u %10lu (c)\n",l_free);
|
||
|
dump_stat("large wasted: %8u %10lu (d)\n\n",l_wasted);
|
||
|
dump_stat("small chunks: %8u %10lu (e)\n",s_chunk);
|
||
|
dump_stat("small blocks: %8u %10lu (f)\n",s_alloc);
|
||
|
strbuf_addf(sbuf
|
||
|
, "small net avail: %10d\n"
|
||
|
, s_alloc.size - s_alloc.counter * M_OVERHEAD * SINT
|
||
|
);
|
||
|
dump_stat("small free blocks: %8u %10lu (g)\n",s_free);
|
||
|
dump_stat("small wasted: %8u %10lu (h)\n\n",s_wasted);
|
||
|
|
||
|
dump_stat("permanent blocks: %8u %10lu\n", perm_st);
|
||
|
#ifdef SBRK_OK
|
||
|
dump_stat("clib allocations: %8u %10lu\n", clib_st);
|
||
|
#else
|
||
|
strbuf_addf(sbuf, "clib allocations: n/a n/a\n");
|
||
|
#endif
|
||
|
strbuf_add(sbuf, "\n");
|
||
|
#ifdef USE_AVL_FREELIST
|
||
|
strbuf_addf(sbuf, "AVL nodes: %8u -\n", num_avl_nodes);
|
||
|
strbuf_add(sbuf, "\n");
|
||
|
#endif /* USE_AVL_FREELIST */
|
||
|
|
||
|
strbuf_addf(sbuf,
|
||
|
"malloc_increment_size: calls %lu success %lu total %lu\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) %10lu should equal (e) %10lu\n"
|
||
|
, s_alloc.size + s_free.size + s_wasted.size
|
||
|
, s_chunk.size
|
||
|
);
|
||
|
strbuf_addf(sbuf
|
||
|
, "Total storage in use: (b-g-h) %10lu net available: %10lu\n"
|
||
|
, l_alloc.size - s_free.size - s_wasted.size
|
||
|
, l_alloc.size - s_free.size - s_wasted.size
|
||
|
- l_alloc.counter * ML_OVERHEAD * SINT
|
||
|
- s_alloc.counter * M_OVERHEAD * SINT
|
||
|
- xalloc_st.counter * XM_OVERHEAD_SIZE
|
||
|
);
|
||
|
strbuf_addf(sbuf
|
||
|
, "Total storage unused: (c+d+g+h) %10lu\n\n"
|
||
|
, l_free.size + l_wasted.size
|
||
|
+ s_free.size + s_wasted.size
|
||
|
);
|
||
|
|
||
|
strbuf_addf(sbuf,
|
||
|
"Defragmentation: %lu calls (%lu for size: %lu successful)\n"
|
||
|
" %lu blocks inspected: %lu merged yielding %lu blocks\n"
|
||
|
, defrag_calls_total, defrag_calls_req, defrag_req_success
|
||
|
, defrag_blocks_inspected, defrag_blocks_merged, defrag_blocks_result
|
||
|
);
|
||
|
} /* mem_dump_data() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
void
|
||
|
mem_dump_extdata (strbuf_t *sbuf)
|
||
|
|
||
|
/* For the status commands and functions: add the extended smalloc statistic
|
||
|
* to the buffer <sbuf>.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
#ifdef MALLOC_EXT_STATISTICS
|
||
|
int i;
|
||
|
|
||
|
strbuf_add(sbuf,
|
||
|
"Detailed Block Statistics:\n\n"
|
||
|
);
|
||
|
for (i = 0; i < SIZE_EXTSTATS; ++i)
|
||
|
{
|
||
|
if (i < SMALL_BLOCK_NUM)
|
||
|
strbuf_addf(sbuf, " Size %3u: ", (i + SMALL_BLOCK_MIN) * SINT);
|
||
|
else if (i == SMALL_BLOCK_NUM)
|
||
|
strbuf_addf(sbuf, " Oversize: ");
|
||
|
else
|
||
|
strbuf_addf(sbuf, " Large: ");
|
||
|
strbuf_addf(sbuf, "Alloc: %7.1lf /s - %7lu / %7lu cur/max\n"
|
||
|
, num_update_calls ? extstats[i].savg_xalloc / num_update_calls
|
||
|
: 0.0
|
||
|
, extstats[i].cur_alloc, extstats[i].max_alloc
|
||
|
);
|
||
|
strbuf_addf(sbuf, " "
|
||
|
"Free: %7.1lf /s - %7lu / %7lu cur/max\n"
|
||
|
, num_update_calls ? extstats[i].savg_xfree / num_update_calls
|
||
|
: 0.0
|
||
|
, extstats[i].cur_free, extstats[i].max_free
|
||
|
);
|
||
|
}
|
||
|
#else
|
||
|
strbuf_add(sbuf, "No detailed blocks statistics available.\n");
|
||
|
#endif /* MALLOC_EXT_STATISTICS */
|
||
|
} /* mem_dump_extdata() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
void
|
||
|
mem_dinfo_data (svalue_t *svp, int value)
|
||
|
|
||
|
/* Fill in the data for debug_info(DINFO_DATA, DID_MEMORY) into the
|
||
|
* svalue-block svp.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
#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_ref_string(svp+DID_MEM_NAME, STR_SMALLOC);
|
||
|
else if (value == DID_MEM_NAME)
|
||
|
put_ref_string(svp, STR_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_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);
|
||
|
#ifdef SBRK_OK
|
||
|
ST_NUMBER(DID_MEM_CLIB, clib_alloc_stat.counter);
|
||
|
ST_NUMBER(DID_MEM_CLIB_SIZE, clib_alloc_stat.size);
|
||
|
#endif
|
||
|
ST_NUMBER(DID_MEM_PERM, perm_alloc_stat.counter);
|
||
|
ST_NUMBER(DID_MEM_PERM_SIZE, perm_alloc_stat.size);
|
||
|
ST_NUMBER(DID_MEM_OVERHEAD, T_OVERHEAD * SINT);
|
||
|
ST_NUMBER(DID_MEM_ALLOCATED, large_alloc_stat.size * SINT
|
||
|
- small_free_stat.size
|
||
|
- small_chunk_wasted.size);
|
||
|
ST_NUMBER(DID_MEM_USED, large_alloc_stat.size * SINT
|
||
|
- small_free_stat.size
|
||
|
- small_chunk_wasted.size
|
||
|
- large_alloc_stat.counter * ML_OVERHEAD * SINT
|
||
|
- small_alloc_stat.counter * M_OVERHEAD * SINT
|
||
|
- xalloc_stat.counter * XM_OVERHEAD_SIZE
|
||
|
);
|
||
|
ST_NUMBER(DID_MEM_TOTAL_UNUSED, large_free_stat.size * SINT
|
||
|
+ large_wasted_stat.size
|
||
|
+ small_free_stat.size
|
||
|
+ small_chunk_wasted.size
|
||
|
);
|
||
|
ST_NUMBER(DID_MEM_DEFRAG_CALLS, defrag_calls_total);
|
||
|
ST_NUMBER(DID_MEM_DEFRAG_CALLS_REQ, defrag_calls_req);
|
||
|
ST_NUMBER(DID_MEM_DEFRAG_REQ_SUCCESS, defrag_req_success);
|
||
|
ST_NUMBER(DID_MEM_DEFRAG_BLOCKS_INSPECTED, defrag_blocks_inspected);
|
||
|
ST_NUMBER(DID_MEM_DEFRAG_BLOCKS_MERGED, defrag_blocks_merged);
|
||
|
ST_NUMBER(DID_MEM_DEFRAG_BLOCKS_RESULT, defrag_blocks_result);
|
||
|
#ifdef USE_AVL_FREELIST
|
||
|
ST_NUMBER(DID_MEM_AVL_NODES, num_avl_nodes);
|
||
|
#else
|
||
|
ST_NUMBER(DID_MEM_AVL_NODES, large_free_stat.counter);
|
||
|
#endif /* USE_AVL_FREELIST */
|
||
|
#ifdef MALLOC_EXT_STATISTICS
|
||
|
do {
|
||
|
vector_t * top; /* Array holding the sub vectors */
|
||
|
int i;
|
||
|
Bool deallocate = MY_FALSE;
|
||
|
|
||
|
mem_update_stats();
|
||
|
top = allocate_array(SMALL_BLOCK_NUM+2);
|
||
|
|
||
|
if (!top)
|
||
|
break;
|
||
|
|
||
|
for (i = 0; i < SIZE_EXTSTATS; ++i)
|
||
|
{
|
||
|
vector_t *sub = allocate_array(DID_MEM_ES_MAX);
|
||
|
|
||
|
if (!sub)
|
||
|
{
|
||
|
deallocate = MY_TRUE;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
put_number(&sub->item[DID_MEM_ES_MAX_ALLOC], extstats[i].max_alloc);
|
||
|
put_number(&sub->item[DID_MEM_ES_CUR_ALLOC], extstats[i].cur_alloc);
|
||
|
put_number(&sub->item[DID_MEM_ES_MAX_FREE], extstats[i].max_free);
|
||
|
put_number(&sub->item[DID_MEM_ES_CUR_FREE], extstats[i].cur_free);
|
||
|
if (num_update_calls)
|
||
|
{
|
||
|
put_float(&sub->item[DID_MEM_ES_AVG_XALLOC], extstats[i].savg_xalloc / num_update_calls);
|
||
|
put_float(&sub->item[DID_MEM_ES_AVG_XFREE], extstats[i].savg_xfree / num_update_calls);
|
||
|
}
|
||
|
|
||
|
put_array(top->item + i, sub);
|
||
|
}
|
||
|
|
||
|
if (deallocate)
|
||
|
{
|
||
|
free_array(top);
|
||
|
ST_NUMBER(DID_MEM_EXT_STATISTICS, 0);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (value == -1)
|
||
|
put_array(svp+DID_MEM_EXT_STATISTICS, top);
|
||
|
else if (value == DID_MEM_EXT_STATISTICS)
|
||
|
put_array(svp, top);
|
||
|
}
|
||
|
} while(0);
|
||
|
#else
|
||
|
ST_NUMBER(DID_MEM_EXT_STATISTICS, 0);
|
||
|
#endif /* MALLOC_EXT_STATISTICS */
|
||
|
|
||
|
#undef ST_NUMBER
|
||
|
} /* mem_dinfo_data() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
#ifdef CHECK_MAPPING_TOTAL
|
||
|
mp_int
|
||
|
available_memory(void)
|
||
|
|
||
|
/* Return the amount of memory actually used by the driver. */
|
||
|
|
||
|
{
|
||
|
return large_alloc_stat.size * SINT
|
||
|
- small_free_stat.size
|
||
|
- small_chunk_wasted.size
|
||
|
- large_alloc_stat.counter * ML_OVERHEAD * SINT
|
||
|
- small_alloc_stat.counter * M_OVERHEAD * SINT;
|
||
|
} /* available_memory() */
|
||
|
#endif /* CHECK_MAPPING_TOTAL */
|
||
|
|
||
|
/*=========================================================================*/
|
||
|
|
||
|
/* SMALL BLOCKS */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
|
||
|
#define SIZE_INDEX(size) \
|
||
|
((size)/SINT - T_OVERHEAD - SMALL_BLOCK_MIN)
|
||
|
/* Index to the proper array entry for a small
|
||
|
* block of <size> (including overhead).
|
||
|
*/
|
||
|
|
||
|
#define SIZE_MOD_INDEX(size, table) \
|
||
|
(((size)/SINT - T_OVERHEAD - SMALL_BLOCK_MIN) % NELEM(table))
|
||
|
/* Index to the proper array entry for a small
|
||
|
* block of <size> (including overhead), limited to the size of <table>.
|
||
|
*/
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
static INLINE void
|
||
|
UNLINK_SMALL_FREE (word_t * block)
|
||
|
|
||
|
/* Unlink the small free <block> from its freelist
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
const word_t bsize = block[M_SIZE] & M_MASK;
|
||
|
int ix;
|
||
|
word_t flag;
|
||
|
|
||
|
if (bsize <= SMALL_BLOCK_MAX)
|
||
|
{
|
||
|
ix = bsize - T_OVERHEAD - SMALL_BLOCK_MIN;
|
||
|
flag = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ix = SMALL_BLOCK_NUM;
|
||
|
flag = 1;
|
||
|
}
|
||
|
|
||
|
#ifdef MALLOC_EXT_STATISTICS
|
||
|
extstats[ix].cur_free--;
|
||
|
#endif /* MALLOC_EXT_STATISTICS */
|
||
|
|
||
|
if (sfltable[ix] == block)
|
||
|
{
|
||
|
word_t * head = BLOCK_NEXT(block);
|
||
|
if (head)
|
||
|
head[M_PLINK(head[M_SIZE] & M_MASK)] = (word_t)head | flag;
|
||
|
sfltable[ix] = head;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
word_t * prev = (word_t *)(block[M_PLINK(bsize)] & ~1);
|
||
|
word_t * next = BLOCK_NEXT(block);
|
||
|
if (next)
|
||
|
next[M_PLINK(next[M_SIZE] & M_MASK)] = (word_t)prev | flag;
|
||
|
prev[M_LINK] = (word_t) next;
|
||
|
}
|
||
|
count_back(small_free_stat, bsize * SINT);
|
||
|
|
||
|
} /* UNLINK_SMALL_FREE() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
static INLINE
|
||
|
void MAKE_SMALL_FREE (word_t *block, word_t bsize)
|
||
|
|
||
|
/* The <bsize> words starting at <block> are a new free small block.
|
||
|
* Set it up and insert it into the appropriate free list.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
word_t * head;
|
||
|
int ix;
|
||
|
word_t flag;
|
||
|
|
||
|
if (bsize <= SMALL_BLOCK_MAX)
|
||
|
{
|
||
|
ix = bsize - T_OVERHEAD - SMALL_BLOCK_MIN;
|
||
|
flag = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ix = SMALL_BLOCK_NUM;
|
||
|
flag = 1;
|
||
|
}
|
||
|
|
||
|
#ifdef MALLOC_EXT_STATISTICS
|
||
|
extstats[ix].cur_free++;
|
||
|
extstat_update_max(extstats + ix);
|
||
|
#endif /* MALLOC_EXT_STATISTICS */
|
||
|
|
||
|
block[M_SIZE] = bsize | (THIS_BLOCK|M_REF)
|
||
|
| (block[M_SIZE] & PREV_BLOCK);
|
||
|
block[bsize] |= PREV_BLOCK;
|
||
|
|
||
|
head = sfltable[ix];
|
||
|
if (head)
|
||
|
head[M_PLINK(head[M_SIZE] & M_MASK)] = (word_t)block | flag;
|
||
|
block[M_LINK] = (word_t) head;
|
||
|
block[M_PLINK(bsize)] = (word_t)block | flag;
|
||
|
/* Let the PLINK point to block itself, to satisfy sanity checks */
|
||
|
|
||
|
if (flag)
|
||
|
block[M_PLINK(bsize)-1] = bsize;
|
||
|
|
||
|
sfltable[ix] = block;
|
||
|
count_up(small_free_stat, bsize * SINT);
|
||
|
|
||
|
#ifdef MALLOC_CHECK
|
||
|
block[M_MAGIC] = sfmagic[SIZE_MOD_INDEX(bsize * SINT, sfmagic)];
|
||
|
#endif
|
||
|
} /* MAKE_SMALL_FREE() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
static Bool
|
||
|
defragment_small_lists (int req)
|
||
|
|
||
|
/* Defragment some or all of the small block free lists.
|
||
|
*
|
||
|
* req = 0: defragment all of the free lists; result is MY_FALSE.
|
||
|
* req > 0: the routine is called from the allocator for a small block
|
||
|
* allocation for <req> words incl overhead. If the defragmentation
|
||
|
* creates a block of a suitable size, the defragmentation
|
||
|
* terminates at that point and the function returns MY_TRUE. If
|
||
|
* all blocks are defragmented and no suitable block is found, the
|
||
|
* function returns MY_FALSE.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
word_t split_size = 0;
|
||
|
/* Minimum blocksize for a block splittable into the requested
|
||
|
* size, incl. overhead in words; 0 for 'none'
|
||
|
*/
|
||
|
int ix; /* Freelist index */
|
||
|
Bool found = MY_FALSE; /* Set to TRUE if a suitable block is found */
|
||
|
|
||
|
defrag_calls_total++;
|
||
|
|
||
|
if (req > 0)
|
||
|
{
|
||
|
split_size = req + SMALL_BLOCK_MIN + T_OVERHEAD;
|
||
|
defrag_calls_req++;
|
||
|
}
|
||
|
else
|
||
|
req = 0; /* Just to make sure */
|
||
|
|
||
|
/* Loop over the freelists, starting at the largest.
|
||
|
*/
|
||
|
for (ix = SMALL_BLOCK_NUM; ix >= 0 && !found; ix--)
|
||
|
{
|
||
|
word_t *list = NULL;
|
||
|
/* Local list of the defragmented blocks, linked through [M_LINK].
|
||
|
*/
|
||
|
word_t *block;
|
||
|
word_t *pred;
|
||
|
/* <block> is the block currently analysed, <pred> the previously
|
||
|
* analyzed block or NULL for the list header.
|
||
|
*/
|
||
|
|
||
|
/* Walk the current freelist and look for defragmentable blocks.
|
||
|
* If one is found, remove it from the freelist, defragment it
|
||
|
* and store the result in the local list.
|
||
|
* Since the check for defragmentation occurs on both sides of
|
||
|
* the free block, the defragmentation will never remove blocks from
|
||
|
* this freelist which have already been visited.
|
||
|
*
|
||
|
* It is important that the loop is not terminated before the end
|
||
|
* of the section of newly freed blocks (M_DEFRAG not set) is reached,
|
||
|
* as the loop sets the M_DEFRAG flag as it goes along.
|
||
|
*/
|
||
|
for (block = sfltable[ix], pred = NULL
|
||
|
; block != NULL && !(block[M_SIZE] & M_DEFRAG)
|
||
|
; )
|
||
|
{
|
||
|
Bool merged;
|
||
|
word_t bsize = block[M_SIZE] & M_MASK;
|
||
|
|
||
|
defrag_blocks_inspected++;
|
||
|
|
||
|
/* Can this block be defragmented? */
|
||
|
if ( !((block+bsize)[M_SIZE] & THIS_BLOCK)
|
||
|
&& !(block[M_SIZE] & PREV_BLOCK)
|
||
|
)
|
||
|
{
|
||
|
/* No: flag this block as non-defragmentable and step to the
|
||
|
* next block.
|
||
|
*/
|
||
|
block[M_SIZE] |= M_DEFRAG;
|
||
|
pred = block;
|
||
|
block = (word_t *)block[M_LINK];
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/* Yes: remove it from the freelist */
|
||
|
UNLINK_SMALL_FREE(block);
|
||
|
|
||
|
/* Try to merge this free block with the following ones */
|
||
|
do {
|
||
|
word_t *next;
|
||
|
|
||
|
defrag_blocks_inspected++;
|
||
|
|
||
|
merged = MY_FALSE;
|
||
|
next = block + bsize;
|
||
|
if (next[M_SIZE] & THIS_BLOCK)
|
||
|
{
|
||
|
UNLINK_SMALL_FREE(next);
|
||
|
bsize += next[M_SIZE] & M_MASK;
|
||
|
merged = MY_TRUE;
|
||
|
|
||
|
defrag_blocks_merged++;
|
||
|
}
|
||
|
|
||
|
if (req > 0 && (bsize == (word_t)req || bsize >= split_size))
|
||
|
found = MY_TRUE;
|
||
|
|
||
|
} while (merged);
|
||
|
|
||
|
/* Try to merge the block with the ones in front of it */
|
||
|
while ((block[M_SIZE] & PREV_BLOCK))
|
||
|
{
|
||
|
word_t *prev;
|
||
|
|
||
|
defrag_blocks_inspected++;
|
||
|
|
||
|
/* We use the 'prev' pointer first to get the size of previous
|
||
|
* block, and from there we can determine it's address
|
||
|
*/
|
||
|
prev = (word_t *)(block[-1]);
|
||
|
if ((word_t)prev & 1)
|
||
|
prev = block - block[-2];
|
||
|
else
|
||
|
prev = block - (prev[M_SIZE] & M_MASK);
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
/* TODO: Remove this test once it's been proven stable */
|
||
|
if (!(prev[M_SIZE] & THIS_BLOCK))
|
||
|
{
|
||
|
in_malloc = 0;
|
||
|
fatal("Block %p marks previous %p as free, but it isn't.\n"
|
||
|
, block, prev);
|
||
|
}
|
||
|
#endif
|
||
|
UNLINK_SMALL_FREE(prev);
|
||
|
bsize += prev[M_SIZE] & M_MASK;
|
||
|
|
||
|
block = prev;
|
||
|
|
||
|
defrag_blocks_merged++;
|
||
|
|
||
|
if (req >= 0 && (bsize == (word_t)req || bsize >= split_size))
|
||
|
found = MY_TRUE;
|
||
|
} /* while() */
|
||
|
|
||
|
/* Update the block's size and move it into the local list.
|
||
|
* Be careful not to clobber the flags in the size field.
|
||
|
*/
|
||
|
block[M_SIZE] = bsize | (block[M_SIZE] & ~M_MASK);
|
||
|
block[M_LINK] = (word_t)list;
|
||
|
list = block;
|
||
|
|
||
|
defrag_blocks_result++;
|
||
|
|
||
|
/* Step to the next block using the still-value <pred> */
|
||
|
if (pred)
|
||
|
block = (word_t *)pred[M_LINK];
|
||
|
else
|
||
|
block = sfltable[ix];
|
||
|
} /* for (blocks in freelist) */
|
||
|
|
||
|
/* Move the defragmented blocks from list back into their freelist.
|
||
|
*/
|
||
|
while (list != NULL)
|
||
|
{
|
||
|
block = list;
|
||
|
list = (word_t *)(list[M_LINK]);
|
||
|
MAKE_SMALL_FREE(block, block[M_SIZE] & M_MASK);
|
||
|
/* As we moved down the small block array, and the defragged
|
||
|
* blocks already visited have been sorted into a list, setting
|
||
|
* the M_DEFRAG flag would be possible here.
|
||
|
* However, by not setting it we make the algorithm a bit
|
||
|
* more robust, and lose only a few cycles the next time
|
||
|
* around.
|
||
|
*/
|
||
|
}
|
||
|
} /* for (ix = SMALL_BLOCK_NUM..0 && !found) */
|
||
|
|
||
|
if (found)
|
||
|
defrag_req_success++;
|
||
|
|
||
|
return found;
|
||
|
} /* defragment_small_lists() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
static INLINE void
|
||
|
defragment_small_block (word_t *block)
|
||
|
|
||
|
/* Try to merge the small free block <block> with any following free blocks.
|
||
|
* This routine is used by mem_increment_size().
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
word_t bsize = block[M_SIZE] & M_MASK;
|
||
|
Bool merged;
|
||
|
Bool defragged;
|
||
|
|
||
|
defragged = MY_FALSE;
|
||
|
|
||
|
UNLINK_SMALL_FREE(block);
|
||
|
|
||
|
/* Try to merge this free block with the following ones */
|
||
|
do {
|
||
|
word_t *next;
|
||
|
|
||
|
defrag_blocks_inspected++;
|
||
|
|
||
|
merged = MY_FALSE;
|
||
|
next = block + bsize;
|
||
|
if (next[M_SIZE] & THIS_BLOCK)
|
||
|
{
|
||
|
UNLINK_SMALL_FREE(next);
|
||
|
bsize += next[M_SIZE] & M_MASK;
|
||
|
merged = MY_TRUE;
|
||
|
defragged = MY_TRUE;
|
||
|
|
||
|
defrag_blocks_inspected++;
|
||
|
}
|
||
|
} while (merged);
|
||
|
|
||
|
if (defragged)
|
||
|
defrag_blocks_result++;
|
||
|
|
||
|
/* Reinsert the block into the freelists */
|
||
|
MAKE_SMALL_FREE(block, bsize);
|
||
|
} /* defragment_small_block() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
/* Macro MAKE_SMALL_CHECK(block, size)
|
||
|
* Macro MAKE_SMALL_CHECK_UNCHECKED(block, size)
|
||
|
* If MALLOC_CHECK is defined, fill in the CHECK 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_CHECK
|
||
|
# define MAKE_SMALL_CHECK(block, size) do { \
|
||
|
if (block[M_MAGIC] != sfmagic[SIZE_MOD_INDEX(size, sfmagic)] ) \
|
||
|
{ \
|
||
|
in_malloc = 0; \
|
||
|
fatal("allocation from free list for %lu bytes: " \
|
||
|
"block %p (user %p) magic match failed, " \
|
||
|
"expected %08lx, found %08lx\n" \
|
||
|
, (unsigned long) size, block, block+T_OVERHEAD \
|
||
|
, sfmagic[SIZE_MOD_INDEX(size, sfmagic)] \
|
||
|
, block[M_MAGIC]); \
|
||
|
} \
|
||
|
block[M_MAGIC] = samagic[SIZE_MOD_INDEX(size, samagic)]; \
|
||
|
} while(0)
|
||
|
# define MAKE_SMALL_CHECK_UNCHECKED(block, size) do { \
|
||
|
block[M_MAGIC] = samagic[SIZE_MOD_INDEX(size, samagic)]; \
|
||
|
} while(0)
|
||
|
#else
|
||
|
# define MAKE_SMALL_CHECK(block, size) (void)0
|
||
|
# define MAKE_SMALL_CHECK_UNCHECKED(block, size) (void)0
|
||
|
#endif
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
static POINTER
|
||
|
mem_alloc (size_t size)
|
||
|
|
||
|
/* 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.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
word_t *temp;
|
||
|
Bool retry;
|
||
|
#if defined(HAVE_MADVISE) || defined(DEBUG_MALLOC_ALLOCS)
|
||
|
size_t orig_size = size;
|
||
|
#endif
|
||
|
|
||
|
assert_stack_gap();
|
||
|
|
||
|
smalloc_size = size;
|
||
|
|
||
|
if (size == 0)
|
||
|
{
|
||
|
size++;
|
||
|
}
|
||
|
|
||
|
/* TODO: For the following test, see SIZET_limits in port.h */
|
||
|
if (size >= ULONG_MAX - (T_OVERHEAD+SMALL_BLOCK_MIN)*SINT
|
||
|
|| size >= (M_MASK + 1 - (T_OVERHEAD+SMALL_BLOCK_MIN))*SINT
|
||
|
)
|
||
|
{
|
||
|
in_malloc = 0;
|
||
|
if (malloc_privilege == MALLOC_SYSTEM)
|
||
|
fatal("Malloc size exceeds numerical limit.\n");
|
||
|
debug_message("Malloc size exceeds numerical limit.\n");
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
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 + XM_OVERHEAD_SIZE)
|
||
|
{
|
||
|
void * rc = large_malloc(size, MY_FALSE);
|
||
|
in_malloc--;
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
/* --- It's a small block --- */
|
||
|
|
||
|
/* Get the block size rounded to the next multiple of a word
|
||
|
* and with the overhead.
|
||
|
*/
|
||
|
if (size < SMALL_BLOCK_MIN_BYTES + XM_OVERHEAD_SIZE)
|
||
|
size = SMALL_BLOCK_MIN_BYTES + XM_OVERHEAD_SIZE;
|
||
|
|
||
|
size = (size+M_OVERHEAD*SINT+SINT-1) & ~(SINT-1);
|
||
|
|
||
|
/* Update statistics */
|
||
|
count_up(small_alloc_stat,size);
|
||
|
#ifdef MALLOC_EXT_STATISTICS
|
||
|
extstats[SIZE_INDEX(size)].num_xalloc++;
|
||
|
extstats[SIZE_INDEX(size)].cur_alloc++;
|
||
|
extstat_update_max(extstats + SIZE_INDEX(size));
|
||
|
#endif /* MALLOC_EXT_STATISTICS */
|
||
|
|
||
|
|
||
|
/* Try allocating the block from one of the free lists.
|
||
|
*
|
||
|
* This is done in a loop: if at the first attempt no block can be found,
|
||
|
* the small block lists will be defragmented and the allocation attempt
|
||
|
* is repeated. If that fails as well, the loop ends and we try to
|
||
|
* get a new small chunk.
|
||
|
*/
|
||
|
|
||
|
retry = MY_FALSE;
|
||
|
do {
|
||
|
int ix;
|
||
|
|
||
|
/* First, check if the free list for this block size has one */
|
||
|
|
||
|
if ( NULL != (temp = sfltable[SIZE_INDEX(size)]))
|
||
|
{
|
||
|
/* allocate from the free list */
|
||
|
|
||
|
UNLINK_SMALL_FREE(temp);
|
||
|
|
||
|
/* Fill in the header (M_SIZE is already mostly ok) */
|
||
|
MAKE_SMALL_CHECK(temp, size);
|
||
|
temp[M_SIZE] |= (M_GC_FREE|M_REF);
|
||
|
temp[M_SIZE] &= ~THIS_BLOCK;
|
||
|
temp[temp[M_SIZE] & M_MASK] &= ~PREV_BLOCK;
|
||
|
|
||
|
temp += M_OVERHEAD;
|
||
|
|
||
|
MADVISE(temp, orig_size);
|
||
|
|
||
|
in_malloc--;
|
||
|
return (POINTER)temp;
|
||
|
}
|
||
|
|
||
|
/* There is nothing suitable in the normal free list - next try
|
||
|
* allocating from the oversized block list.
|
||
|
*/
|
||
|
{
|
||
|
word_t *this;
|
||
|
word_t wsize = size / SINT; /* size incl overhead in words */
|
||
|
|
||
|
for (this = sfltable[SMALL_BLOCK_NUM] ; this; this = BLOCK_NEXT(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 + T_OVERHEAD + SMALL_BLOCK_SPLIT_MIN)
|
||
|
continue;
|
||
|
|
||
|
/* 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
|
||
|
* but keep this block in the oversized list.
|
||
|
*/
|
||
|
if (rsize <= SMALL_BLOCK_MAX)
|
||
|
{
|
||
|
/* Unlink it from this list */
|
||
|
UNLINK_SMALL_FREE(this);
|
||
|
|
||
|
/* Put it into the real free list */
|
||
|
MAKE_SMALL_FREE(this, rsize);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* Just modify the size and move the .prev pointer
|
||
|
* and size field.
|
||
|
*/
|
||
|
count_back(small_free_stat, bsize * SINT);
|
||
|
|
||
|
this[M_SIZE] &= (PREV_BLOCK|M_DEFRAG);
|
||
|
this[M_SIZE] |= rsize | (THIS_BLOCK|M_REF);
|
||
|
this[M_PLINK(rsize)] = this[M_PLINK(bsize)];
|
||
|
this[M_PLINK(rsize)-1] = rsize;
|
||
|
|
||
|
#ifdef MALLOC_CHECK
|
||
|
this[M_MAGIC] = sfmagic[SIZE_MOD_INDEX(rsize*SINT, sfmagic)];
|
||
|
#endif
|
||
|
|
||
|
count_up(small_free_stat, rsize * SINT);
|
||
|
}
|
||
|
|
||
|
/* Split off the allocated small block from the end
|
||
|
* The front remains in the freelist.
|
||
|
*/
|
||
|
this += rsize;
|
||
|
|
||
|
/* Fill in the header */
|
||
|
this[M_SIZE] = wsize | (PREV_BLOCK|M_GC_FREE|M_REF);
|
||
|
this[wsize] &= ~PREV_BLOCK;
|
||
|
MAKE_SMALL_CHECK_UNCHECKED(this,size);
|
||
|
|
||
|
this += M_OVERHEAD;
|
||
|
|
||
|
MADVISE(this, orig_size);
|
||
|
|
||
|
#ifdef DEBUG_MALLOC_ALLOCS
|
||
|
ulog2f("smalloc(%d / %d): Split oversized block "
|
||
|
, orig_size, size);
|
||
|
dprintf2( gcollect_outfd, "(%d / %d bytes): left with block of "
|
||
|
, (p_int)(bsize - T_OVERHEAD) * SINT, (p_int)bsize * SINT);
|
||
|
dprintf2( gcollect_outfd, "%d / %d bytes.\n"
|
||
|
, (p_int)(rsize - T_OVERHEAD) * SINT
|
||
|
, (p_int)rsize * SINT);
|
||
|
#endif
|
||
|
|
||
|
in_malloc--;
|
||
|
return (POINTER)this;
|
||
|
}
|
||
|
} /* allocation from oversized lists */
|
||
|
|
||
|
/* Next, try splitting off the memory from one of the larger
|
||
|
* listed free small blocks.
|
||
|
* Search from the largest blocks, and stop when splits
|
||
|
* would result in too small blocks.
|
||
|
*/
|
||
|
for ( ix = SMALL_BLOCK_NUM-1
|
||
|
; ix >= (int)(SIZE_INDEX(size) + T_OVERHEAD + SMALL_BLOCK_SPLIT_MIN)
|
||
|
; 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];
|
||
|
UNLINK_SMALL_FREE(pt);
|
||
|
|
||
|
/* Split off the unused part as new block */
|
||
|
split = pt + wsize;
|
||
|
usize = ix + T_OVERHEAD + SMALL_BLOCK_MIN - wsize;
|
||
|
split[M_SIZE] = 0; /* No PREV_BLOCK */
|
||
|
MAKE_SMALL_FREE(split, usize);
|
||
|
|
||
|
/* Initialize the header of the new block */
|
||
|
pt[M_SIZE] &= PREV_BLOCK;
|
||
|
pt[M_SIZE] |= wsize | (M_GC_FREE|M_REF);
|
||
|
MAKE_SMALL_CHECK_UNCHECKED(pt, size);
|
||
|
|
||
|
pt += M_OVERHEAD;
|
||
|
|
||
|
MADVISE(pt, orig_size);
|
||
|
|
||
|
#ifdef DEBUG_MALLOC_ALLOCS
|
||
|
ulog2f("smalloc(%d / %d): Split block "
|
||
|
, orig_size, size);
|
||
|
dprintf2( gcollect_outfd, "(%d / %d bytes): left with block of "
|
||
|
, (p_int)(ix - T_OVERHEAD) * SINT, (p_int)ix * SINT);
|
||
|
dprintf2( gcollect_outfd, "%d / %d bytes.\n"
|
||
|
, (p_int)(usize - T_OVERHEAD) * SINT, (p_int)usize * SINT);
|
||
|
#endif
|
||
|
|
||
|
in_malloc--;
|
||
|
return (POINTER)pt;
|
||
|
} /* allocation from larger blocks */
|
||
|
|
||
|
/* At this point, there was nothing in the free lists.
|
||
|
* If this is the first pass, defragment the memory and try again
|
||
|
* (the defragmentation routine returns whether a retry is useful).
|
||
|
* If this is the second pass, force retry to be FALSE, thus
|
||
|
* terminating the loop.
|
||
|
*/
|
||
|
if (!retry)
|
||
|
retry = defragment_small_lists(size / SINT);
|
||
|
else
|
||
|
retry = MY_FALSE;
|
||
|
} while (retry);
|
||
|
|
||
|
/* At this point, we couldn't find a suitable block in the free lists,
|
||
|
* thus allocate a new small chunk.
|
||
|
* If this is the first small chunk of all, or if the small_chunk_size
|
||
|
* has been modified from the default (happens for the second small
|
||
|
* chunk allocation), try to get fresh memory from the system.
|
||
|
*
|
||
|
* Note: Changes here must be mirrored in mem_consolidate().
|
||
|
*/
|
||
|
{
|
||
|
word_t * new_chunk;
|
||
|
word_t chunk_size;
|
||
|
|
||
|
new_chunk = (word_t*)large_malloc_int(small_chunk_size + sizeof(word_t*)
|
||
|
, (last_small_chunk == NULL)
|
||
|
|| (small_chunk_size != SMALL_CHUNK_SIZE)
|
||
|
);
|
||
|
|
||
|
/* 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 (new_chunk == 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;
|
||
|
new_chunk = (word_t*)large_malloc_int(small_chunk_size+sizeof(word_t*)
|
||
|
, MY_TRUE);
|
||
|
}
|
||
|
|
||
|
if (new_chunk == 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 chunk list and reset the small_cunk_size
|
||
|
* to the ongoing value.
|
||
|
*/
|
||
|
|
||
|
chunk_size = new_chunk[-ML_OVERHEAD] - ML_OVERHEAD - 2;
|
||
|
/* The payload size of the new chunk in words (ie. sans
|
||
|
* large block overhead, chunk link pointer and final sentinel block).
|
||
|
*/
|
||
|
|
||
|
*new_chunk = (word_t)last_small_chunk;
|
||
|
last_small_chunk = new_chunk++;
|
||
|
|
||
|
count_up(small_chunk_stat, new_chunk[-ML_OVERHEAD-1] * SINT);
|
||
|
count_up(small_chunk_wasted, SINT*(M_OVERHEAD+1));
|
||
|
|
||
|
small_chunk_size = SMALL_CHUNK_SIZE;
|
||
|
|
||
|
/* The last word in the chunk is a fake permanently allocated small
|
||
|
* block, so that the block merging routine has something to put the
|
||
|
* PREV_BLOCK flag into.
|
||
|
*/
|
||
|
new_chunk[chunk_size] = 1 | M_REF;
|
||
|
|
||
|
/* Use the end of the new chunk before the sentinel block to satisfy
|
||
|
* the allocation and enter the rest at the front as free block into
|
||
|
* the oversized free list.
|
||
|
*/
|
||
|
chunk_size -= size / SINT;
|
||
|
new_chunk[M_SIZE] = 0;
|
||
|
MAKE_SMALL_FREE(new_chunk, chunk_size);
|
||
|
|
||
|
temp = new_chunk + chunk_size;
|
||
|
|
||
|
temp[M_SIZE] = (size / SINT) | (PREV_BLOCK|M_GC_FREE|M_REF);
|
||
|
MAKE_SMALL_CHECK_UNCHECKED(temp, size);
|
||
|
|
||
|
temp += M_OVERHEAD;
|
||
|
|
||
|
MADVISE(temp, orig_size);
|
||
|
in_malloc--;
|
||
|
return (POINTER)temp;
|
||
|
} /* allocate from a new small chunk */
|
||
|
} /* mem_alloc() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
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 bsize, i;
|
||
|
|
||
|
if (!ptr)
|
||
|
return;
|
||
|
|
||
|
mem_mark_collectable(ptr);
|
||
|
|
||
|
/* Get the real block address and size */
|
||
|
block = (word_t *) ptr;
|
||
|
block -= M_OVERHEAD;
|
||
|
bsize = i = block[M_SIZE] & M_MASK;
|
||
|
|
||
|
if (bsize > SMALL_BLOCK_MAX)
|
||
|
{
|
||
|
/* It's a big block */
|
||
|
large_free(ptr);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* It's a small block: put it back into the free list */
|
||
|
|
||
|
count_back(small_alloc_stat, bsize * SINT);
|
||
|
i -= SMALL_BLOCK_MIN + T_OVERHEAD;
|
||
|
|
||
|
#ifdef MALLOC_EXT_STATISTICS
|
||
|
extstats[i].num_xfree++;
|
||
|
extstats[i].cur_alloc--;
|
||
|
#endif /* MALLOC_EXT_STATISTICS */
|
||
|
|
||
|
#ifdef MALLOC_CHECK
|
||
|
if (block[M_MAGIC] == sfmagic[i % NELEM(sfmagic)])
|
||
|
{
|
||
|
in_malloc = 0;
|
||
|
fatal("mem_free: 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("mem_free: block %p magic match failed: "
|
||
|
"size %lu, expected %lx, found %lx\n"
|
||
|
, block, (unsigned long)(i * SINT), samagic[i], block[M_MAGIC]);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
MAKE_SMALL_FREE(block, bsize);
|
||
|
} /* sfree() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
static void
|
||
|
mem_free (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 mem_free()\n");
|
||
|
fatal("Multiple threads in mem_free()\n");
|
||
|
/* NOTREACHED */
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
sfree(ptr);
|
||
|
in_malloc--;
|
||
|
} /* mem_free() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
static POINTER
|
||
|
mem_realloc (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.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
POINTER t;
|
||
|
size_t old_size;
|
||
|
|
||
|
old_size = mem_block_size(p);
|
||
|
if (old_size >= size)
|
||
|
return p;
|
||
|
|
||
|
t = mem_alloc(size);
|
||
|
if (t == NULL)
|
||
|
return NULL;
|
||
|
|
||
|
memcpy(t, p, old_size);
|
||
|
mem_free(p);
|
||
|
return t;
|
||
|
} /* mem_realloc() */
|
||
|
|
||
|
/*=========================================================================*/
|
||
|
|
||
|
/* LARGE BLOCKS */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
|
||
|
#define l_next_block(p) ((p) + (p)[M_LSIZE] )
|
||
|
#define l_prev_block(p) ((p) - (*((p)-2)) )
|
||
|
/* 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.
|
||
|
*/
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
|
||
|
/* Extra types and definitions for the AVL routines */
|
||
|
|
||
|
#if 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(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;
|
||
|
#ifdef USE_AVL_FREELIST
|
||
|
struct free_block * prev; /* prev free block in freelist
|
||
|
* NULL for the AVL node
|
||
|
*/
|
||
|
struct free_block * next; /* next free block in freelist */
|
||
|
#endif /* USE_AVL_FREELIST */
|
||
|
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
|
||
|
#ifdef USE_AVL_FREELIST
|
||
|
, /* prev */ 0, /* next */ 0
|
||
|
#endif /* USE_AVL_FREELIST */
|
||
|
};
|
||
|
|
||
|
struct free_block dummy2 =
|
||
|
{ /* size */ 0
|
||
|
, /* parent */ 0, /* left */ &dummy, /* right */ 0, /* balance */ -1
|
||
|
#ifdef USE_AVL_FREELIST
|
||
|
, /* prev */ 0, /* next */ 0
|
||
|
#endif /* USE_AVL_FREELIST */
|
||
|
};
|
||
|
|
||
|
static struct free_block *free_tree = &dummy2;
|
||
|
|
||
|
#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)-ML_OVERHEAD;
|
||
|
q += *q;
|
||
|
}
|
||
|
_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: invalid balance!\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: invalid parent!\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;
|
||
|
} /* check_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)-M_OVERHEAD*SINT);
|
||
|
size = p[M_LSIZE];
|
||
|
if (!(*(p+size) & THIS_BLOCK))
|
||
|
{
|
||
|
if (!contains(free_tree, (struct free_block *)(p+size+T_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_CHECK
|
||
|
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
|
||
|
p = (struct free_block *)(ptr+M_OVERHEAD);
|
||
|
count_back(large_free_stat, p->size);
|
||
|
#ifdef MALLOC_EXT_STATISTICS
|
||
|
extstats[SMALL_BLOCK_NUM+1].cur_free--;
|
||
|
#endif /* MALLOC_EXT_STATISTICS */
|
||
|
#ifdef USE_AVL_FREELIST
|
||
|
/* Unlink from AVL freelist */
|
||
|
if (p->prev) p->prev->next = p->next;
|
||
|
if (p->next) p->next->prev = p->prev;
|
||
|
/* If the block is not the AVL node itself, we're done */
|
||
|
if (p->prev)
|
||
|
return;
|
||
|
|
||
|
/* <p> is the AVL node itself, but if there is another block free of
|
||
|
* the same size, just transfer over the node.
|
||
|
*/
|
||
|
if (p->next)
|
||
|
{
|
||
|
struct free_block *next = p->next;
|
||
|
|
||
|
if (p == free_tree)
|
||
|
{
|
||
|
#ifdef DEBUG
|
||
|
if (p->parent)
|
||
|
{
|
||
|
fatal("(remove_from_free_list) Node %p (size %ld) is the AVL tree root, but has a parent\n", p, (long)p->size);
|
||
|
}
|
||
|
#endif
|
||
|
free_tree = p->next;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
#ifdef DEBUG
|
||
|
if (!p->parent)
|
||
|
{
|
||
|
fatal("(remove_from_free_list) Node %p (size %ld) has neither a parent nor is it the AVL tree root.\n", p, (long)p->size);
|
||
|
}
|
||
|
#endif
|
||
|
if (p->parent->left == p)
|
||
|
p->parent->left = p->next;
|
||
|
else
|
||
|
p->parent->right = p->next;
|
||
|
}
|
||
|
|
||
|
/* We must not clobber p->next->next when copying the node! */
|
||
|
p->next = next->next;
|
||
|
*next = *p;
|
||
|
|
||
|
/* Now adjust the parent pointer of the sub-nodes to the new
|
||
|
* parent node.
|
||
|
*/
|
||
|
if (p->left) p->left->parent = next;
|
||
|
if (p->right) p->right->parent = next;
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* It's the AVL itself, and there is no other free block of the same
|
||
|
* size: remove the node from the tree.
|
||
|
*/
|
||
|
num_avl_nodes--;
|
||
|
#endif /* USE_AVL_FREELIST */
|
||
|
#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) ) {
|
||
|
/* 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 */
|
||
|
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 */
|
||
|
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
|
||
|
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) {
|
||
|
/* left from parent */
|
||
|
goto balance_left_s;
|
||
|
} else {
|
||
|
/* 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;
|
||
|
|
||
|
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
|
||
|
dprintf1(2, "r->balance: %d\n", r->balance);
|
||
|
#endif
|
||
|
if ( NULL != (p->right = s = r->left) ) {
|
||
|
s->parent = p;
|
||
|
}
|
||
|
/* subtree relocated */
|
||
|
r->left = p;
|
||
|
s = p->parent;
|
||
|
p->parent = r;
|
||
|
b -= 1;
|
||
|
r->balance = b;
|
||
|
b = -b;
|
||
|
#ifdef DEBUG_AVL
|
||
|
/* 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) {
|
||
|
/* left from parent */
|
||
|
goto balance_left_s;
|
||
|
} else {
|
||
|
/* 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;
|
||
|
|
||
|
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
|
||
|
*/
|
||
|
|
||
|
#ifdef MALLOC_CHECK
|
||
|
ptr[M_MAGIC] = LFMAGIC;
|
||
|
#endif
|
||
|
size = ptr[M_LSIZE];
|
||
|
#ifdef DEBUG_AVL
|
||
|
dprintf1(2, "size:%d\n",size);
|
||
|
#endif
|
||
|
q = (struct free_block *)size; /* this assignment is just a hint for
|
||
|
* register choice
|
||
|
*/
|
||
|
r = (struct free_block *)(ptr+M_OVERHEAD);
|
||
|
count_up(large_free_stat, size);
|
||
|
#ifdef MALLOC_EXT_STATISTICS
|
||
|
extstats[SMALL_BLOCK_NUM+1].cur_free++;
|
||
|
extstat_update_max(extstats+SMALL_BLOCK_NUM+1);
|
||
|
#endif /* MALLOC_EXT_STATISTICS */
|
||
|
|
||
|
r->size = size;
|
||
|
r->parent = NULL;
|
||
|
r->left = 0;
|
||
|
r->right = 0;
|
||
|
r->balance = 0;
|
||
|
#ifdef USE_AVL_FREELIST
|
||
|
r->prev = NULL;
|
||
|
r->next = NULL;
|
||
|
#endif /* USE_AVL_FREELIST */
|
||
|
|
||
|
q = free_tree;
|
||
|
for ( ; ; /*p = q*/) {
|
||
|
p = q;
|
||
|
#ifdef DEBUG_AVL
|
||
|
dprintf1(2, "checked node size %d\n",p->size);
|
||
|
#endif
|
||
|
|
||
|
#ifdef USE_AVL_FREELIST
|
||
|
if (size == p->size)
|
||
|
{
|
||
|
/* We can attach the block to an existing node */
|
||
|
#ifdef MALLOC_ORDER_LARGE_FREELISTS
|
||
|
struct free_block * tail = p;
|
||
|
|
||
|
/* Find the proper node after which to insert */
|
||
|
if (p->next != NULL)
|
||
|
{
|
||
|
while (tail->next && tail->next < r)
|
||
|
{
|
||
|
tail = tail->next;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
r->next = tail->next;
|
||
|
r->prev = tail;
|
||
|
|
||
|
if (r->next)
|
||
|
r->next->prev = r;
|
||
|
tail->next = r;
|
||
|
#else
|
||
|
r->next = p->next;
|
||
|
r->prev = p;
|
||
|
|
||
|
if (r->next)
|
||
|
r->next->prev = r;
|
||
|
p->next = r;
|
||
|
#endif /* MALLOC_ORDER_LARGE_FREELISTS */
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
#endif /* USE_AVL_FREELIST */
|
||
|
if (size < p->size) {
|
||
|
if ( NULL != (q = p->left) ) {
|
||
|
continue;
|
||
|
}
|
||
|
/* add left */
|
||
|
p->left = r;
|
||
|
break;
|
||
|
} else /* >= */ {
|
||
|
if ( NULL != (q = p->right) ) {
|
||
|
continue;
|
||
|
}
|
||
|
/* add right */
|
||
|
p->right = r;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* new leaf */
|
||
|
r->parent = p;
|
||
|
#ifdef USE_AVL_FREELIST
|
||
|
num_avl_nodes++;
|
||
|
#endif /* USE_AVL_FREELIST */
|
||
|
#ifdef DEBUG_AVL
|
||
|
dprintf2(2, "p %x->balance:%d\n",p, 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: %x\n", p);
|
||
|
dprintf1(2, " p->size: %d\n", p->size);
|
||
|
dprintf1(2, " p->balance: %d := -1\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
|
||
|
/* growth propagation from left side */
|
||
|
p->balance = -1;
|
||
|
} else if (b < 0) {
|
||
|
#ifdef DEBUG_AVL
|
||
|
dprintf2(2, "p %x->balance:%d\n",p, p->balance);
|
||
|
#endif
|
||
|
if (r->balance < 0) {
|
||
|
/* 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
|
||
|
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;
|
||
|
}
|
||
|
/* relocated right subtree */
|
||
|
t->right = p;
|
||
|
if ( NULL != (r->right = s = t->left) ) {
|
||
|
s->parent = r;
|
||
|
}
|
||
|
/* 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;
|
||
|
/* 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);
|
||
|
/* LR-Rotation completed */
|
||
|
#endif
|
||
|
}
|
||
|
break;
|
||
|
} else /* p->balance == +1 */ {
|
||
|
p->balance = 0;
|
||
|
/* growth of left side balanced the node */
|
||
|
break;
|
||
|
}
|
||
|
} else /* r == p->right */ {
|
||
|
balance_t b;
|
||
|
|
||
|
if ( !(b = p->balance) )
|
||
|
{
|
||
|
#ifdef DEBUG_AVL
|
||
|
dprintf1(2, "p: %x\n", p);
|
||
|
dprintf1(2, " p->size: %d\n", p->size);
|
||
|
dprintf1(2, " p->balance: %d += 1\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
|
||
|
/* growth propagation from right side */
|
||
|
p->balance++;
|
||
|
} else if (b > 0) {
|
||
|
if (r->balance > 0) {
|
||
|
/* 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
|
||
|
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;
|
||
|
}
|
||
|
/* relocated left subtree */
|
||
|
t->left = p;
|
||
|
if ( NULL != (r->left = s = t->right) ) {
|
||
|
s->parent = r;
|
||
|
}
|
||
|
/* 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;
|
||
|
}
|
||
|
/* RL-Rotation completed */
|
||
|
}
|
||
|
break;
|
||
|
} else /* p->balance == -1 */ {
|
||
|
#ifdef DEBUG_AVL
|
||
|
dprintf1(2, "p: %x\n", p);
|
||
|
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;
|
||
|
/* growth of right side balanced the node */
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
r = p;
|
||
|
p = p->parent;
|
||
|
} while ( NULL != (q = p) );
|
||
|
} /* add_to_free_list() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
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);
|
||
|
ptr[M_LSIZE] = size;
|
||
|
*(ptr+size-2) = size; /* copy the size information */
|
||
|
*(ptr) = tmp | M_MASK; /* 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() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
static word_t *
|
||
|
add_large_free (word_t *ptr, word_t block_size)
|
||
|
|
||
|
/* The large memory block <ptr> with size <block_size> is free:
|
||
|
* add it to the free list after trying to coagulate it with adjacent
|
||
|
* ones.
|
||
|
* Result is the resulting pointer to the (possibly coagulated) free block.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
/* If the next block is free, coagulate */
|
||
|
if (!(*(ptr+block_size) & THIS_BLOCK))
|
||
|
{
|
||
|
remove_from_free_list(ptr+block_size);
|
||
|
block_size += (ptr+block_size)[M_LSIZE];
|
||
|
}
|
||
|
|
||
|
/* If the previous block is free, coagulate */
|
||
|
if (l_prev_free(ptr))
|
||
|
{
|
||
|
remove_from_free_list(l_prev_block(ptr));
|
||
|
block_size += l_prev_block(ptr)[M_LSIZE];
|
||
|
ptr = l_prev_block(ptr);
|
||
|
}
|
||
|
|
||
|
/* Mark the block as free and add it to the freelist */
|
||
|
build_block(ptr, block_size);
|
||
|
add_to_free_list(ptr);
|
||
|
|
||
|
return ptr;
|
||
|
} /* add_large_free() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
static char *
|
||
|
large_malloc ( word_t size, Bool force_more)
|
||
|
|
||
|
/* Allocate a large or <size> bytes.
|
||
|
*
|
||
|
* 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_MALLOC_ALLOCS)
|
||
|
size_t orig_size = size;
|
||
|
#endif
|
||
|
|
||
|
size = (size + SINT*ML_OVERHEAD + SINT-1) / SINT; /* incl. overhead */
|
||
|
|
||
|
retry:
|
||
|
ptr = NULL;
|
||
|
if (!force_more)
|
||
|
{
|
||
|
|
||
|
/* Find the best fit in the AVL tree */
|
||
|
|
||
|
struct free_block *p, *q, *r;
|
||
|
word_t minsplit;
|
||
|
word_t tempsize;
|
||
|
|
||
|
ptr += M_OVERHEAD; /* 'NULL' including overhead */
|
||
|
minsplit = size + SMALL_BLOCK_MAX + 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 -= M_OVERHEAD;
|
||
|
} /* if (!force_more) */
|
||
|
|
||
|
if (!ptr)
|
||
|
{
|
||
|
/* No match, allocate more memory */
|
||
|
|
||
|
word_t chunk_size, block_size;
|
||
|
size_t extra = 0; /* extra memory allocated by esbrk() */
|
||
|
|
||
|
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 - T_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
|
||
|
&& ( (mp_int)(sbrk_stat.size + (heap_start ? 0 : SINT)) >= max_malloced
|
||
|
|| (chunk_size = max_malloced - sbrk_stat.size
|
||
|
- (heap_start?0:SINT) )
|
||
|
< block_size
|
||
|
)
|
||
|
)
|
||
|
{
|
||
|
static const char mess[] = "MAX_MALLOCED limit reached.\n";
|
||
|
writes(2, mess);
|
||
|
|
||
|
ptr = NULL;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ptr = (word_t *)esbrk(chunk_size, &extra);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (ptr == NULL)
|
||
|
{
|
||
|
/* Out of memory - try to recover */
|
||
|
ulog2f("lmalloc(%d / %d): Didn't get the memory from the system.\n"
|
||
|
, orig_size, block_size);
|
||
|
|
||
|
if (force_more)
|
||
|
{
|
||
|
/* The system is out of memory, but maybe we have some left
|
||
|
* in the freelist.
|
||
|
*/
|
||
|
force_more = MY_FALSE;
|
||
|
goto retry;
|
||
|
}
|
||
|
|
||
|
/* Give up */
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/* Enough of the scary words - we got our memory block */
|
||
|
|
||
|
chunk_size += extra;
|
||
|
block_size = chunk_size / SINT;
|
||
|
|
||
|
/* Add the block to the free memory */
|
||
|
ptr = add_large_free(ptr, block_size);
|
||
|
} /* end of creating a new chunk */
|
||
|
|
||
|
/* ptr is now a pointer to a free block in the free list */
|
||
|
|
||
|
#ifdef USE_AVL_FREELIST
|
||
|
/* If there is more than one free block for this size, take
|
||
|
* the first block from the freelist to avoid copying around
|
||
|
* the AVL node information.
|
||
|
*/
|
||
|
{
|
||
|
struct free_block *p = (struct free_block *)(ptr + M_OVERHEAD);
|
||
|
if (p->next)
|
||
|
ptr = ((word_t *)(p->next)) - M_OVERHEAD;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
remove_from_free_list(ptr);
|
||
|
real_size = ptr[M_LSIZE];
|
||
|
|
||
|
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);
|
||
|
#ifdef DEBUG
|
||
|
if (real_size - size <= SMALL_BLOCK_MAX)
|
||
|
{
|
||
|
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 * SINT);
|
||
|
#ifdef DEBUG_MALLOC_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 * 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)
|
||
|
{
|
||
|
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);
|
||
|
count_up(large_alloc_stat, size);
|
||
|
#ifdef MALLOC_EXT_STATISTICS
|
||
|
extstats[SMALL_BLOCK_NUM+1].num_xalloc++;
|
||
|
extstats[SMALL_BLOCK_NUM+1].cur_alloc++;
|
||
|
extstat_update_max(extstats+SMALL_BLOCK_NUM+1);
|
||
|
#endif /* MALLOC_EXT_STATISTICS */
|
||
|
#ifdef MALLOC_CHECK
|
||
|
ptr[M_MAGIC] = LAMAGIC;
|
||
|
#endif
|
||
|
MADVISE(ptr+M_OVERHEAD, orig_size);
|
||
|
return (char *) (ptr + M_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 -= M_OVERHEAD;
|
||
|
size = p[M_LSIZE];
|
||
|
count_back(large_alloc_stat, size);
|
||
|
#ifdef MALLOC_EXT_STATISTICS
|
||
|
extstats[SMALL_BLOCK_NUM+1].num_xfree++;
|
||
|
extstats[SMALL_BLOCK_NUM+1].cur_alloc--;
|
||
|
#endif /* MALLOC_EXT_STATISTICS */
|
||
|
|
||
|
#ifdef MALLOC_CHECK
|
||
|
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(%p): block %p magic match failed: size %lu, "
|
||
|
"expected %lx, found %lx\n"
|
||
|
, ptr, p
|
||
|
, (unsigned long)(size * SINT)
|
||
|
, (unsigned long)LAMAGIC
|
||
|
, (unsigned long)p[M_MAGIC]
|
||
|
);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
(void)add_large_free(p, size);
|
||
|
} /* large_free() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
static char *
|
||
|
esbrk (word_t size, size_t * pExtra)
|
||
|
|
||
|
/* Allocate a block of <size> bytes from the system and return its pointer.
|
||
|
* If esbrk() allocated a larger block, the difference to <size> is
|
||
|
* returned in *<pExtra>.
|
||
|
*
|
||
|
#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
|
||
|
|
||
|
mdb_log_sbrk(size);
|
||
|
*pExtra = 0;
|
||
|
if (!heap_end)
|
||
|
{
|
||
|
/* First call: allocate the first fake block */
|
||
|
heap_start = heap_end = (word_t *)sbrk(0);
|
||
|
if (!esbrk(2*SINT, pExtra))
|
||
|
{
|
||
|
in_malloc = 0;
|
||
|
fatal("Couldn't malloc anything\n");
|
||
|
}
|
||
|
*heap_start = 2;
|
||
|
*(heap_start+1) = PREV_BLOCK | M_MASK;
|
||
|
count_up(large_wasted_stat, 2*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 | M_MASK;
|
||
|
heap_end[-2] = M_MASK;
|
||
|
return (char *)(heap_end - 1) - size; /* overlap old memory block */
|
||
|
|
||
|
#else /* not SBRK_OK */
|
||
|
|
||
|
char *block;
|
||
|
word_t *p; /* start of the fake block */
|
||
|
const int overhead = TL_OVERHEAD;
|
||
|
size_t overlap = 0;
|
||
|
/* How much extra memory esbrk() could recycle from joining
|
||
|
* the new allocation with the old one.
|
||
|
*/
|
||
|
|
||
|
|
||
|
mdb_log_sbrk(size);
|
||
|
size += overhead * SINT; /* for the extra fake "allocated" block */
|
||
|
|
||
|
block = malloc(size);
|
||
|
if (!block)
|
||
|
{
|
||
|
*pExtra = 0;
|
||
|
return NULL;
|
||
|
}
|
||
|
assert_stack_gap();
|
||
|
|
||
|
/* p points to the start of the fake allocated block used
|
||
|
* as sentinel/bridge
|
||
|
*/
|
||
|
p = (word_t *)(block + size) - overhead + 1;
|
||
|
|
||
|
#ifdef MALLOC_TRACE
|
||
|
p[M_OVERHEAD+XM_FILE] = (word_t)"sentinel/bridge";
|
||
|
p[M_OVERHEAD+XM_LINE] = 0;
|
||
|
#endif
|
||
|
#ifdef MALLOC_LPC_TRACE
|
||
|
p[M_OVERHEAD+XM_OBJ] = 0;
|
||
|
p[M_OVERHEAD+XM_PROG] = 0;
|
||
|
p[M_OVERHEAD+XM_PC] = 0;
|
||
|
#endif
|
||
|
|
||
|
if (!heap_end)
|
||
|
{
|
||
|
/* First call: create the inital fake block */
|
||
|
heap_start = (word_t*)block;
|
||
|
heap_end = (word_t*)(block + size);
|
||
|
*((word_t *)block+1) = PREV_BLOCK;
|
||
|
p[M_LSIZE] = overhead;
|
||
|
p[M_SIZE] = THIS_BLOCK | M_MASK; /* 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+1) = PREV_BLOCK|M_MASK; /* Lower sentinel */
|
||
|
if (block + size == (char *)heap_start)
|
||
|
{
|
||
|
/* We can join with the existing heap */
|
||
|
p[overhead] &= ~PREV_BLOCK;
|
||
|
overlap = SINT;
|
||
|
count_back(large_wasted_stat, overlap);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* Separate from the heap */
|
||
|
p[M_LSIZE] = (heap_start - p + 1);
|
||
|
p[M_SIZE] = THIS_BLOCK | M_MASK; /* no M_GC_FREE */
|
||
|
overlap = 0;
|
||
|
}
|
||
|
|
||
|
heap_start = (word_t *)block;
|
||
|
}
|
||
|
else if (block >= (char *)heap_end)
|
||
|
{
|
||
|
/* New block after the known heap */
|
||
|
|
||
|
p[M_SIZE] = THIS_BLOCK | M_MASK; /* no M_GC_FREE */
|
||
|
p[M_LSIZE] = overhead;
|
||
|
if (block == (char *)heap_end)
|
||
|
{
|
||
|
/* We can join with the existing heap */
|
||
|
heap_end = (word_t *)(block + size);
|
||
|
block -= overhead;
|
||
|
overlap = overhead * SINT;
|
||
|
count_back(large_wasted_stat, overlap);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* Separate from the heap */
|
||
|
p = (word_t *)heap_end - overhead + 1;
|
||
|
p[M_SIZE] = (p[M_SIZE] & (PREV_BLOCK|THIS_BLOCK|M_GC_FREE)) | M_MASK;
|
||
|
p[M_LSIZE] = (word_t *)block - p + 1;
|
||
|
heap_end = (word_t *)(block + size);
|
||
|
*((word_t *)block+1) = 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;
|
||
|
} while (next < (word_t *)block);
|
||
|
overlap = 0;
|
||
|
|
||
|
if ((word_t *)block == prev + overhead)
|
||
|
{
|
||
|
/* Our block directly follows the one we found */
|
||
|
block -= overhead;
|
||
|
overlap += overhead * SINT;
|
||
|
count_back(large_wasted_stat, overhead * SINT);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* We have to create a new bridge block */
|
||
|
prev++;
|
||
|
prev[M_SIZE] = (prev[M_SIZE] & (PREV_BLOCK|THIS_BLOCK|M_GC_FREE)) | M_MASK;
|
||
|
prev[M_LSIZE] = (word_t*)block - prev + 1;
|
||
|
#ifdef MALLOC_TRACE
|
||
|
prev[M_OVERHEAD+XM_FILE] = (word_t)"block";
|
||
|
prev[M_OVERHEAD+XM_LINE] = 0;
|
||
|
#endif
|
||
|
#ifdef MALLOC_LPC_TRACE
|
||
|
prev[M_OVERHEAD+XM_OBJ] = 0;
|
||
|
prev[M_OVERHEAD+XM_PROG] = 0;
|
||
|
prev[M_OVERHEAD+XM_PC] = 0;
|
||
|
#endif
|
||
|
*((word_t *)block+1) = PREV_BLOCK | M_MASK;
|
||
|
}
|
||
|
|
||
|
if (next - p == overhead)
|
||
|
{
|
||
|
/* Our block directly preceedes the next one */
|
||
|
*(next+1) &= ~PREV_BLOCK;
|
||
|
overlap += overhead * SINT;
|
||
|
count_back(large_wasted_stat, overhead * SINT);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* We have to create a new bridge block */
|
||
|
p[M_SIZE] = THIS_BLOCK | M_MASK; /* no M_GC_FREE */
|
||
|
p[M_LSIZE] = next - p + 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
count_up(sbrk_stat, size);
|
||
|
count_up(large_wasted_stat, overhead * SINT);
|
||
|
|
||
|
*pExtra = overlap;
|
||
|
return block + SINT;
|
||
|
#endif /* !SBRK_OK */
|
||
|
} /* esbrk() */
|
||
|
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
static INLINE void *
|
||
|
mem_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.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
char *p = vp;
|
||
|
word_t *start, *start2, *start3, old_size, next;
|
||
|
const word_t wsize = (size + SINT - 1)/ SINT;
|
||
|
|
||
|
malloc_increment_size_calls++;
|
||
|
|
||
|
start = (word_t*)p - M_OVERHEAD;
|
||
|
|
||
|
old_size = start[M_SIZE] & M_MASK;
|
||
|
|
||
|
if (old_size <= SMALL_BLOCK_MAX)
|
||
|
{
|
||
|
/* Extend a small block */
|
||
|
|
||
|
word_t new_size = (old_size + wsize) * SINT;
|
||
|
start2 = &start[old_size];
|
||
|
next = *start2;
|
||
|
|
||
|
if (old_size + wsize > SMALL_BLOCK_MAX)
|
||
|
return NULL; /* New block would be too large */
|
||
|
|
||
|
if (!(next & THIS_BLOCK))
|
||
|
return NULL; /* no following free block */
|
||
|
|
||
|
/* Make sure the following free block is as large as possible */
|
||
|
defragment_small_block(start2);
|
||
|
|
||
|
next = start2[M_SIZE] & M_MASK; /* Might have changed */
|
||
|
|
||
|
if (next == wsize)
|
||
|
{
|
||
|
/* Next block fits perfectly */
|
||
|
|
||
|
UNLINK_SMALL_FREE(start2);
|
||
|
start2[next] &= ~PREV_BLOCK;
|
||
|
|
||
|
start[M_SIZE] += wsize;
|
||
|
MAKE_SMALL_CHECK_UNCHECKED(start, new_size);
|
||
|
|
||
|
malloc_increment_size_success++;
|
||
|
malloc_increment_size_total += (start2 - start) - M_OVERHEAD;
|
||
|
|
||
|
count_add(small_alloc_stat, wsize * SINT);
|
||
|
|
||
|
#ifdef MALLOC_EXT_STATISTICS
|
||
|
extstats[SIZE_INDEX(old_size * SINT)].cur_alloc--;
|
||
|
extstats[SIZE_INDEX(new_size)].cur_alloc++;
|
||
|
extstat_update_max(extstats+SIZE_INDEX(new_size));
|
||
|
#endif /* MALLOC_EXT_STATISTICS */
|
||
|
|
||
|
return start2;
|
||
|
}
|
||
|
|
||
|
if (next >= wsize + SMALL_BLOCK_SPLIT_MIN + T_OVERHEAD)
|
||
|
{
|
||
|
/* Next block is large enough to be split */
|
||
|
|
||
|
UNLINK_SMALL_FREE(start2);
|
||
|
|
||
|
start3 = start2 + wsize;
|
||
|
start3[M_SIZE] = 0; /* Clear PREV_BLOCK */
|
||
|
MAKE_SMALL_FREE(start3, next - wsize);
|
||
|
|
||
|
start[M_SIZE] += wsize;
|
||
|
MAKE_SMALL_CHECK_UNCHECKED(start, new_size);
|
||
|
|
||
|
malloc_increment_size_success++;
|
||
|
malloc_increment_size_total += (start2 - start) - M_OVERHEAD;
|
||
|
|
||
|
count_add(small_alloc_stat, wsize * SINT);
|
||
|
|
||
|
#ifdef MALLOC_EXT_STATISTICS
|
||
|
extstats[SIZE_INDEX(old_size * SINT)].cur_alloc--;
|
||
|
extstats[SIZE_INDEX(new_size)].cur_alloc++;
|
||
|
extstat_update_max(extstats+SIZE_INDEX(new_size));
|
||
|
#endif /* MALLOC_EXT_STATISTICS */
|
||
|
|
||
|
return start2;
|
||
|
}
|
||
|
|
||
|
return NULL; /* No success */
|
||
|
}
|
||
|
|
||
|
/* Extend a large block */
|
||
|
|
||
|
old_size = start[M_LSIZE];
|
||
|
start2 = &start[old_size];
|
||
|
|
||
|
if (start2[M_SIZE] & THIS_BLOCK)
|
||
|
return NULL; /* no following free block */
|
||
|
|
||
|
next = start2[M_LSIZE];
|
||
|
|
||
|
if (next == wsize)
|
||
|
{
|
||
|
/* Next block fits perfectly */
|
||
|
remove_from_free_list(start2);
|
||
|
start2[next] |= PREV_BLOCK;
|
||
|
start[M_LSIZE] += wsize;
|
||
|
malloc_increment_size_success++;
|
||
|
malloc_increment_size_total += (start2 - start) - M_OVERHEAD;
|
||
|
count_add(large_alloc_stat, wsize);
|
||
|
|
||
|
return start2+M_LSIZE;
|
||
|
}
|
||
|
|
||
|
if (next > wsize + SMALL_BLOCK_MAX + 1)
|
||
|
{
|
||
|
/* Split the next block, it is large enough */
|
||
|
|
||
|
remove_from_free_list(start2);
|
||
|
start2[next-2] -= wsize;
|
||
|
start3 = start2 + wsize;
|
||
|
start3[M_SIZE] = M_MASK | PREV_BLOCK;
|
||
|
start3[M_LSIZE] = (next-wsize);
|
||
|
add_to_free_list(start3);
|
||
|
start[M_LSIZE] += wsize;
|
||
|
malloc_increment_size_success++;
|
||
|
malloc_increment_size_total += (start2 - start) - M_OVERHEAD;
|
||
|
count_add(large_alloc_stat, wsize);
|
||
|
return start2+M_LSIZE;
|
||
|
}
|
||
|
|
||
|
/* No success */
|
||
|
return NULL;
|
||
|
} /* mem_increment_size() */
|
||
|
|
||
|
|
||
|
/*=========================================================================*/
|
||
|
|
||
|
/* GARBAGE COLLECTOR */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
static INLINE void
|
||
|
mem_clear_ref (POINTER p)
|
||
|
|
||
|
/* GC Support: Clear the 'referenced' flag for block <p>.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
((word_t *)(p))[-M_OVERHEAD] &= ~M_REF;
|
||
|
} /* mem_clear_ref() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
static INLINE void
|
||
|
mem_mark_ref (POINTER p)
|
||
|
|
||
|
/* GC Support: Set the 'referenced' flag for block <p>.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
((word_t *)(p))[-M_OVERHEAD] |= M_REF;
|
||
|
} /* mem_mark_ref() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
static INLINE Bool
|
||
|
mem_test_ref (POINTER p)
|
||
|
|
||
|
/* GC Support: Check the memory block marker for <p>, return TRUE if _not_
|
||
|
* set.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
return !( ((word_t *)(p))[-M_OVERHEAD] & M_REF );
|
||
|
} /* mem_test_ref() */
|
||
|
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
Bool
|
||
|
mem_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 -= M_OVERHEAD;
|
||
|
|
||
|
if (block < heap_start || block + M_OVERHEAD >= heap_end)
|
||
|
return MY_TRUE;
|
||
|
|
||
|
i = block[M_SIZE] & M_MASK;
|
||
|
if (i >= SMALL_BLOCK_MAX)
|
||
|
i = block[M_LSIZE];
|
||
|
|
||
|
if (i < M_OVERHEAD + ((minsize + SINT - 1) / SINT)
|
||
|
|| block + i >= heap_end)
|
||
|
return MY_TRUE;
|
||
|
|
||
|
if (i >= SMALL_BLOCK_MAX)
|
||
|
{
|
||
|
word_t* block2;
|
||
|
|
||
|
block2 = block + i;
|
||
|
return !(block[M_SIZE] & THIS_BLOCK)
|
||
|
#ifdef MALLOC_CHECK
|
||
|
|| block[M_MAGIC] != LAMAGIC
|
||
|
#endif /* MALLOC_CHECK */
|
||
|
|| !(*block2 & PREV_BLOCK);
|
||
|
}
|
||
|
|
||
|
return (block[M_SIZE] & THIS_BLOCK) != 0;
|
||
|
} /* mem_is_freed() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
void
|
||
|
mem_clear_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 - TL_OVERHEAD;
|
||
|
for (p = heap_start; p < last; )
|
||
|
{
|
||
|
p[1] &= ~M_REF;
|
||
|
if (p + *p > heap_end)
|
||
|
{
|
||
|
in_malloc = 0;
|
||
|
fatal("pointer larger than brk: %p + %lx = %p > %p\n"
|
||
|
, p, *p, p + *p , last);
|
||
|
}
|
||
|
p += *p;
|
||
|
}
|
||
|
|
||
|
/* 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;
|
||
|
|
||
|
mem_mark_ref(p);
|
||
|
end = p - ML_OVERHEAD + p[-ML_OVERHEAD];
|
||
|
#ifdef DEBUG
|
||
|
dprintf2(gcollect_outfd, "clearing M_REF in chunk %x, end %x\n",
|
||
|
(word_t)(p - ML_OVERHEAD), (word_t)end
|
||
|
);
|
||
|
#endif
|
||
|
for (q = p+1; q < end; )
|
||
|
{
|
||
|
word_t size = *q;
|
||
|
|
||
|
*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_NUM + 1; i++)
|
||
|
{
|
||
|
for (p = sfltable[i]; p; p = BLOCK_NEXT(p) ) {
|
||
|
*p |= M_REF;
|
||
|
}
|
||
|
}
|
||
|
} /* mem_clear_ref_flags() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
void
|
||
|
mem_free_unrefed_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 - TL_OVERHEAD;
|
||
|
for (p = heap_start; p < last; )
|
||
|
{
|
||
|
word_t size, flags;
|
||
|
|
||
|
size = *p;
|
||
|
flags = p[1];
|
||
|
if ( (flags & (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, flags2;
|
||
|
|
||
|
success++;
|
||
|
count_back(xalloc_stat, mem_block_size(p+ML_OVERHEAD));
|
||
|
#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[XM_FILE+ML_OVERHEAD], p[XM_LINE+ML_OVERHEAD], size & M_MASK
|
||
|
);
|
||
|
#endif
|
||
|
#ifdef MALLOC_LPC_TRACE
|
||
|
write_lpc_trace(gcollect_outfd, p + ML_OVERHEAD, MY_FALSE);
|
||
|
#endif
|
||
|
print_block(gcollect_outfd, p + ML_OVERHEAD);
|
||
|
size2 = p[size];
|
||
|
flags2 = p[size + 1];
|
||
|
large_free((char *)(p+ML_OVERHEAD));
|
||
|
if ( !(flags2 & THIS_BLOCK) )
|
||
|
size += size2;
|
||
|
}
|
||
|
p += size;
|
||
|
}
|
||
|
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 - ML_OVERHEAD + p[-ML_OVERHEAD];
|
||
|
#ifdef DEBUG
|
||
|
dprintf2(gcollect_outfd, "scanning chunk %x, end %x for unref'd blocks\n",
|
||
|
(word_t)(p - M_OVERHEAD), (word_t)end
|
||
|
);
|
||
|
#endif
|
||
|
for (q = p+1; q < end; )
|
||
|
{
|
||
|
word_t size = *q;
|
||
|
|
||
|
if ((*q & (M_REF|M_GC_FREE)) == M_GC_FREE)
|
||
|
{
|
||
|
/* Unref'd small blocks are definitely lost */
|
||
|
success++;
|
||
|
count_back(xalloc_stat, mem_block_size(q+M_OVERHEAD));
|
||
|
dprintf2(gcollect_outfd, "freeing small block 0x%x (user 0x%x)"
|
||
|
, (p_uint)q, (p_uint)(q+M_OVERHEAD));
|
||
|
#ifdef MALLOC_TRACE
|
||
|
dprintf2(gcollect_outfd, " %s %d"
|
||
|
, q[XM_FILE+M_OVERHEAD], q[XM_LINE+M_OVERHEAD]);
|
||
|
#endif
|
||
|
writes(gcollect_outfd, "\n");
|
||
|
#ifdef MALLOC_LPC_TRACE
|
||
|
write_lpc_trace(gcollect_outfd, q + M_OVERHEAD, MY_FALSE);
|
||
|
#endif
|
||
|
print_block(gcollect_outfd, q + M_OVERHEAD);
|
||
|
|
||
|
/* Recover the block */
|
||
|
*q |= M_REF;
|
||
|
sfree(q+M_OVERHEAD);
|
||
|
}
|
||
|
q += size & M_MASK;
|
||
|
}
|
||
|
}
|
||
|
if (success) {
|
||
|
dprintf1(gcollect_outfd, "%d small blocks freed\n", success);
|
||
|
}
|
||
|
} /* mem_free_unrefed_memory() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
Bool
|
||
|
mem_dump_memory (int fd)
|
||
|
|
||
|
/* Print the location, size, and (if available) the TRACE information
|
||
|
* of all memory blocks to file <fd>, and return TRUE.
|
||
|
* If the allocator doesn't support this operation, print nothing
|
||
|
* and return FALSE.
|
||
|
*
|
||
|
* If <fd> is -1, just return TRUE or FALSE (this is used to check if
|
||
|
* the allocator supports memory dumps).
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
word_t *p, *q, *last;
|
||
|
|
||
|
if (fd < 0)
|
||
|
return MY_TRUE;
|
||
|
|
||
|
writes(fd, "\n--- Large Blocks\n");
|
||
|
|
||
|
/* Dump the heap blocks */
|
||
|
last = heap_end - TL_OVERHEAD;
|
||
|
for (p = heap_start; p < last; )
|
||
|
{
|
||
|
word_t size, flags;
|
||
|
|
||
|
size = *p;
|
||
|
flags = p[1];
|
||
|
if ( flags & THIS_BLOCK )
|
||
|
{
|
||
|
Bool isSmallChunk = MY_FALSE;
|
||
|
|
||
|
dprintf4(fd, "%x .. %x %s size %x "
|
||
|
, (p_uint)p, (p_uint)(p + size) - 1
|
||
|
, (p_uint)((flags & M_GC_FREE) ? " " : "P")
|
||
|
, (p_uint)size * SINT
|
||
|
);
|
||
|
for (q = last_small_chunk; q && !isSmallChunk; q = *(word_t**)q)
|
||
|
{
|
||
|
isSmallChunk = (q - ML_OVERHEAD == p);
|
||
|
}
|
||
|
|
||
|
if (isSmallChunk)
|
||
|
writes(fd, ": small chunk\n");
|
||
|
else
|
||
|
{
|
||
|
#ifdef MALLOC_TRACE
|
||
|
if (p[XM_FILE+ML_OVERHEAD])
|
||
|
dprintf2(fd, ": %s %d "
|
||
|
, p[XM_FILE+ML_OVERHEAD], p[XM_LINE+ML_OVERHEAD]
|
||
|
);
|
||
|
else
|
||
|
writes(fd, ": - -");
|
||
|
#endif
|
||
|
#ifdef MALLOC_LPC_TRACE
|
||
|
if (p[ML_OVERHEAD + XM_OBJ])
|
||
|
{
|
||
|
writes(fd, ": ");
|
||
|
write_lpc_trace(fd, p + ML_OVERHEAD, MY_TRUE);
|
||
|
}
|
||
|
else
|
||
|
#endif
|
||
|
writes(fd, "\n");
|
||
|
}
|
||
|
}
|
||
|
p += size;
|
||
|
}
|
||
|
|
||
|
/* Dump the small chunks and their small blocks.
|
||
|
*/
|
||
|
for (p = last_small_chunk; p; p = *(word_t**)p)
|
||
|
{
|
||
|
word_t *end;
|
||
|
|
||
|
end = p - ML_OVERHEAD + (p[-ML_OVERHEAD]);
|
||
|
|
||
|
{
|
||
|
word_t size, flags;
|
||
|
|
||
|
q = p - ML_OVERHEAD;
|
||
|
size = *q;
|
||
|
flags = q[1];
|
||
|
|
||
|
dprintf4(fd, "\n--- Small Chunk: %x .. %x %s size %x\n"
|
||
|
, (p_uint)q, (p_uint)(q + size) - 1
|
||
|
, (p_uint)((flags & M_GC_FREE) ? " " : "P")
|
||
|
, (p_uint)size * SINT
|
||
|
);
|
||
|
/* No trace information for small chunks */
|
||
|
}
|
||
|
|
||
|
for (q = p+1; q < end; )
|
||
|
{
|
||
|
word_t size = *q;
|
||
|
|
||
|
if (!(*q & THIS_BLOCK))
|
||
|
{
|
||
|
dprintf4(fd, "%x .. %x %s size %x "
|
||
|
, (p_uint)q, (p_uint)(q + (size&M_MASK)) - 1
|
||
|
, (p_uint)((size & M_GC_FREE) ? " " : "P")
|
||
|
, (p_uint)(size & M_MASK) * SINT
|
||
|
);
|
||
|
|
||
|
/* The sentinel blocks in a small chunk consist of just
|
||
|
* the size byte - don't try to dump those.
|
||
|
*/
|
||
|
if ((size & M_MASK) > M_OVERHEAD)
|
||
|
{
|
||
|
#ifdef MALLOC_TRACE
|
||
|
if (q[XM_FILE+M_OVERHEAD])
|
||
|
dprintf2(fd, ": %s %d "
|
||
|
, q[XM_FILE+M_OVERHEAD], q[XM_LINE+M_OVERHEAD]
|
||
|
);
|
||
|
else
|
||
|
writes(fd, ": - - ");
|
||
|
#endif
|
||
|
#ifdef MALLOC_LPC_TRACE
|
||
|
if (q[M_OVERHEAD + XM_OBJ])
|
||
|
{
|
||
|
writes(fd, ": ");
|
||
|
write_lpc_trace(fd, q + M_OVERHEAD, MY_TRUE);
|
||
|
}
|
||
|
else
|
||
|
#endif
|
||
|
writes(fd, "\n");
|
||
|
}
|
||
|
else
|
||
|
writes(fd, "\n");
|
||
|
}
|
||
|
q += size & M_MASK;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return MY_TRUE;
|
||
|
} /* mem_dump_memory() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
void
|
||
|
mem_consolidate (Bool force)
|
||
|
|
||
|
/* Consolidate the memory.
|
||
|
*
|
||
|
* If <force> is TRUE, the small free blocks are defragmented first.
|
||
|
* Then, the function walks the list of small chunks and free all which are
|
||
|
* totally unused.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
word_t *prev, *this;
|
||
|
|
||
|
if (force)
|
||
|
defragment_small_lists(0);
|
||
|
|
||
|
for (prev = NULL, this = last_small_chunk ; this != NULL ; )
|
||
|
{
|
||
|
word_t chunk_size = this[-ML_OVERHEAD];
|
||
|
|
||
|
/* If the chunk holds only one free block, it can be freed
|
||
|
* altogether.
|
||
|
*/
|
||
|
if ((this[1+M_SIZE] & THIS_BLOCK)
|
||
|
&& (this[1+M_SIZE] & M_MASK) == chunk_size - M_OVERHEAD - 2
|
||
|
)
|
||
|
{
|
||
|
word_t * next = *(word_t **) this;
|
||
|
|
||
|
if (!prev)
|
||
|
last_small_chunk = next;
|
||
|
else
|
||
|
*(word_t **)prev = next;
|
||
|
|
||
|
UNLINK_SMALL_FREE(this+1);
|
||
|
large_free((char *)this);
|
||
|
|
||
|
count_back(small_chunk_stat, chunk_size * SINT);
|
||
|
count_back(small_chunk_wasted, SINT*(M_OVERHEAD+2));
|
||
|
|
||
|
this = next;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
prev = this;
|
||
|
this = *(word_t**)this;
|
||
|
}
|
||
|
} /* for (all chunks) */
|
||
|
} /* mem_consolidate() */
|
||
|
|
||
|
/***************************************************************************/
|