feat(malloc): first-fit allocator with mmap fallback
This commit is contained in:
@@ -0,0 +1,112 @@
|
|||||||
|
#ifndef VLIBC_STDLIB_H
|
||||||
|
#define VLIBC_STDLIB_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — <stdlib.h>.
|
||||||
|
*
|
||||||
|
* This header is the shared home for the stdlib declarations; it currently
|
||||||
|
* holds only the memory-management family (todo 7). Later todos extend it
|
||||||
|
* in place: todo 11 adds the numeric conversions (atoi/strtol/strtod/...),
|
||||||
|
* todo 12 the pseudo-random and search/divide functions
|
||||||
|
* (rand/srand/qsort/bsearch/abs/div/...), and todo 13 the environment and
|
||||||
|
* multibyte helpers (getenv/setenv/mblen/mbtowc/...).
|
||||||
|
*
|
||||||
|
* Memory management functions, gated by the active compatibility profile
|
||||||
|
* (see include/vlibc/features.h). Levels are cumulative:
|
||||||
|
*
|
||||||
|
* Level 1 (onlyposix): ISO C core + POSIX base — malloc, free, calloc,
|
||||||
|
* realloc, aligned_alloc, posix_memalign.
|
||||||
|
* Level 2 (muslmimic): malloc_usable_size (BSD/musl).
|
||||||
|
*
|
||||||
|
* This header includes <vlibc/features.h> itself, so the gates below always
|
||||||
|
* see the configured VLIBC_LEVEL even when the caller included no vlibc
|
||||||
|
* header first, and <stddef.h> for size_t.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <vlibc/features.h>
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Level 1: memory management (always present). */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allocate size bytes, 16-byte aligned. The memory is uninitialized.
|
||||||
|
* malloc(0) returns a unique minimum-size block (never NULL on success).
|
||||||
|
* NULL + errno ENOMEM on failure.
|
||||||
|
* malloc: the result does not alias any other pointer and has size bytes.
|
||||||
|
*/
|
||||||
|
__attribute__((malloc, alloc_size(1))) void *
|
||||||
|
malloc(size_t size);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Release the block at ptr, which must be NULL or a value returned by an
|
||||||
|
* earlier allocation in this family. free(NULL) is a no-op.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
free(void *ptr);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allocate an array of nmemb elements of size bytes each, all bits zero.
|
||||||
|
* The product is overflow-checked: on overflow NULL + errno ENOMEM.
|
||||||
|
* calloc with a zero product returns a unique zeroed minimum-size block.
|
||||||
|
* malloc: the result does not alias any other pointer and has nmemb*size
|
||||||
|
* bytes.
|
||||||
|
*/
|
||||||
|
__attribute__((malloc, alloc_size(1, 2))) void *
|
||||||
|
calloc(size_t nmemb, size_t size);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Resize the block at ptr to size bytes, preserving the first min(old,
|
||||||
|
* size) bytes. realloc(NULL, size) behaves as malloc(size); realloc(ptr, 0)
|
||||||
|
* frees ptr and returns NULL. The old block is freed on success and left
|
||||||
|
* untouched on failure (NULL + errno ENOMEM).
|
||||||
|
* alloc_size(2): the result has size bytes.
|
||||||
|
*/
|
||||||
|
__attribute__((alloc_size(2))) void *
|
||||||
|
realloc(void *ptr, size_t size);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allocate size bytes aligned to alignment. alignment must be a power of
|
||||||
|
* two that is a multiple of sizeof(void *), and size must be a multiple of
|
||||||
|
* alignment; a violation fails with NULL + errno EINVAL (a non-power-of-two
|
||||||
|
* alignment is undefined behavior in C23, so only well-formed arguments
|
||||||
|
* reach the allocator). size 0 returns NULL. The result is released with
|
||||||
|
* free.
|
||||||
|
* malloc + alloc_align(1): the result does not alias any other pointer and
|
||||||
|
* is aligned to alignment.
|
||||||
|
*/
|
||||||
|
__attribute__((malloc, alloc_size(2), alloc_align(1))) void *
|
||||||
|
aligned_alloc(size_t alignment, size_t size);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allocate size bytes at address alignment and store the result in
|
||||||
|
* *memptr. alignment must be a power of two and a multiple of
|
||||||
|
* sizeof(void *). Returns 0 on success, EINVAL for a bad alignment, ENOMEM
|
||||||
|
* on allocation failure. Never sets errno itself, and never modifies
|
||||||
|
* *memptr on failure. size 0 returns a unique minimum-size block.
|
||||||
|
*/
|
||||||
|
__attribute__((access(write_only, 1))) int
|
||||||
|
posix_memalign(void **memptr, size_t alignment, size_t size);
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
/* Level 2 (muslmimic): BSD/musl extensions. */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the number of bytes actually available in the block at ptr,
|
||||||
|
* including any internal padding; at least as large as the requested size.
|
||||||
|
* ptr may be any block returned by the allocator family; NULL returns 0.
|
||||||
|
* pure: reads memory, no side effects.
|
||||||
|
*/
|
||||||
|
__attribute__((pure)) size_t
|
||||||
|
malloc_usable_size(void *ptr);
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* VLIBC_STDLIB_H */
|
||||||
@@ -0,0 +1,951 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <vlibc/features.h>
|
||||||
|
|
||||||
|
#include "../internal/malloc.h"
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — the heap allocator (todo 7).
|
||||||
|
*
|
||||||
|
* A first-fit allocator with segregated size bins, a brk-grown heap, and an
|
||||||
|
* mmap path for large blocks. Layout reference: musl's single-file malloc;
|
||||||
|
* this is a clean-room implementation with vlibc's own chunk format.
|
||||||
|
*
|
||||||
|
* Chunk layout (every chunk address is 16-aligned; every chunk size is a
|
||||||
|
* multiple of VLIBC_CHUNK_ALIGN = 32):
|
||||||
|
*
|
||||||
|
* struct chunk { size_t psize, csize; } (16-byte header)
|
||||||
|
* user pointer = chunk + 16
|
||||||
|
*
|
||||||
|
* csize low bits (masked with ~3 when reading sizes):
|
||||||
|
* bit 0 VLIBC_CHUNK_INUSE — chunk is allocated.
|
||||||
|
* bit 1 VLIBC_CHUNK_ALIGNED — chunk is the raw block of an
|
||||||
|
* aligned_alloc-style allocation.
|
||||||
|
* psize low bits (masked with ~7 when reading sizes):
|
||||||
|
* bit 0 — the previous chunk is in use (1) or free (0).
|
||||||
|
* bit 2 VLIBC_CHUNK_MMAP — this chunk lives in its own mmap, psize
|
||||||
|
* holds the mapping length.
|
||||||
|
*
|
||||||
|
* Free chunks keep their doubly-linked-list pointers (next/prev) in the
|
||||||
|
* first 16 bytes of the user area, which is why the minimum chunk is
|
||||||
|
* 16 (header) + 16 (pointers) = 32 bytes. There is never more than one
|
||||||
|
* adjacent pair of free chunks: free() coalesces immediately, and the
|
||||||
|
* top chunk (the free block at the end of the heap) is kept out of the
|
||||||
|
* bins.
|
||||||
|
*
|
||||||
|
* Allocation: requests whose normalized chunk size reaches
|
||||||
|
* VLIBC_MMAP_THRESHOLD (128 KiB) get a private SYS_mmap mapping (freed
|
||||||
|
* with SYS_munmap). Everything else searches the bins first-fit (bins 0-3
|
||||||
|
* hold the exact sizes 32/64/96/128, bins 4-12 hold log-size ranges up to
|
||||||
|
* just below the mmap threshold), then splits the top chunk, growing the
|
||||||
|
* heap with SYS_brk as needed. If brk fails, the request falls back to a
|
||||||
|
* single-chunk mmap.
|
||||||
|
*
|
||||||
|
* The brk heap never shrinks below its start address and releases whole
|
||||||
|
* trailing pages back to the kernel when the top chunk holds more than a
|
||||||
|
* page beyond the minimum chunk. sbrk() is deliberately not provided (see
|
||||||
|
* the plan: it conflicts with this allocator).
|
||||||
|
*
|
||||||
|
* Aligned allocations (alignment > 16): the raw block is a normal chunk
|
||||||
|
* marked VLIBC_CHUNK_ALIGNED; the returned pointer sits somewhere inside
|
||||||
|
* the block with a two-word descriptor directly before it:
|
||||||
|
*
|
||||||
|
* [q-16] = alignment (a power of two, so bit 0 is clear)
|
||||||
|
* [q-8] = raw pointer (the start of the block's user area)
|
||||||
|
*
|
||||||
|
* free() tells the two cases apart by the C_INUSE bit of the word at p-16:
|
||||||
|
* an allocated chunk always has it set, while a descriptor's alignment word
|
||||||
|
* always has it clear. realloc() on an aligned pointer preserves the
|
||||||
|
* alignment (via the descriptor) and malloc_usable_size() reports the raw
|
||||||
|
* block size minus the alignment offset.
|
||||||
|
*
|
||||||
|
* malloc(0) returns a unique minimum-size chunk. realloc(p, 0) frees p and
|
||||||
|
* returns NULL. calloc overflows and aligned_alloc/posix_memalign argument
|
||||||
|
* violations fail with NULL + errno (ENOMEM/EINVAL); posix_memalign returns
|
||||||
|
* the error number instead of setting errno and never modifies *memptr on
|
||||||
|
* failure. Over-requested brk chunks zero themselves when they come from a
|
||||||
|
* fresh page of the heap, which makes calloc cheap in the common case.
|
||||||
|
*
|
||||||
|
* Single-threaded for now: the pthread todo will add locking around the
|
||||||
|
* global allocator state.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define VLIBC_CHUNK_OVERHEAD 16 /* psize + csize */
|
||||||
|
#define VLIBC_CHUNK_ALIGN 32 /* chunk-size granularity */
|
||||||
|
#define VLIBC_CHUNK_MIN 32 /* smallest chunk: header + 2 pointers */
|
||||||
|
|
||||||
|
#define VLIBC_CHUNK_INUSE 1UL /* csize bit 0 */
|
||||||
|
#define VLIBC_CHUNK_ALIGNED 2UL /* csize bit 1 */
|
||||||
|
#define VLIBC_CHUNK_MMAP 4UL /* psize bit 2 (private mmap block) */
|
||||||
|
|
||||||
|
#define VLIBC_CSIZE_MASK (~(size_t)3)
|
||||||
|
#define VLIBC_PSIZE_MASK (~(size_t)7)
|
||||||
|
|
||||||
|
/* Requests whose normalized chunk size reaches this go straight to mmap. */
|
||||||
|
#define VLIBC_MMAP_THRESHOLD ((size_t)(128 * 1024))
|
||||||
|
|
||||||
|
/* 4 exact-size fast bins (32/64/96/128) + 9 log-range bins. */
|
||||||
|
#define VLIBC_BIN_COUNT 13
|
||||||
|
#define VLIBC_BIN_FAST 4
|
||||||
|
|
||||||
|
/* Largest user request that cannot overflow the chunk-size normalization. */
|
||||||
|
#define VLIBC_CHUNK_MAX_USER ((size_t)-1 - VLIBC_CHUNK_ALIGN - VLIBC_CHUNK_OVERHEAD - 1)
|
||||||
|
|
||||||
|
/* Minimum heap growth in bytes, rounded up to a page by the extender. */
|
||||||
|
#define VLIBC_HEAP_GROW ((size_t)32768)
|
||||||
|
|
||||||
|
struct vlibc_chunk
|
||||||
|
{
|
||||||
|
size_t psize;
|
||||||
|
size_t csize;
|
||||||
|
struct vlibc_chunk *next;
|
||||||
|
struct vlibc_chunk *prev;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct vlibc_chunk *vlibc_bins[VLIBC_BIN_COUNT];
|
||||||
|
static struct vlibc_chunk *vlibc_top; /* free block at the end of the heap */
|
||||||
|
static uintptr_t vlibc_heap_start; /* 0 = heap not yet queried */
|
||||||
|
static uintptr_t vlibc_heap_end; /* current brk */
|
||||||
|
static size_t vlibc_live; /* allocated chunks on the heap */
|
||||||
|
static size_t vlibc_live_mmap; /* allocated mmap chunks */
|
||||||
|
|
||||||
|
static void *
|
||||||
|
vlibc_aligned_core(size_t align, size_t size);
|
||||||
|
|
||||||
|
/* Round a user request up to a chunk size (multiple of VLIBC_CHUNK_ALIGN). */
|
||||||
|
static size_t
|
||||||
|
vlibc_norm(size_t n)
|
||||||
|
{
|
||||||
|
size_t sz =
|
||||||
|
(n + VLIBC_CHUNK_OVERHEAD + VLIBC_CHUNK_ALIGN - 1) & ~(size_t)(VLIBC_CHUNK_ALIGN - 1);
|
||||||
|
|
||||||
|
return sz < VLIBC_CHUNK_MIN ? VLIBC_CHUNK_MIN : sz;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Index of the bin a free chunk of the given (flag-free) size belongs to. */
|
||||||
|
static int
|
||||||
|
vlibc_bin_of(size_t sz)
|
||||||
|
{
|
||||||
|
if (sz <= (size_t)(VLIBC_BIN_FAST * VLIBC_CHUNK_ALIGN))
|
||||||
|
{
|
||||||
|
return (int)(sz / VLIBC_CHUNK_ALIGN) - 1;
|
||||||
|
}
|
||||||
|
return (int)(VLIBC_BIN_FAST + 63 - (unsigned)__builtin_clzll(sz / VLIBC_CHUNK_ALIGN) - 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
vlibc_bin_insert(struct vlibc_chunk *c)
|
||||||
|
{
|
||||||
|
int i = vlibc_bin_of(c->csize & VLIBC_CSIZE_MASK);
|
||||||
|
|
||||||
|
c->prev = NULL;
|
||||||
|
c->next = vlibc_bins[i];
|
||||||
|
if (vlibc_bins[i] != NULL)
|
||||||
|
{
|
||||||
|
vlibc_bins[i]->prev = c;
|
||||||
|
}
|
||||||
|
vlibc_bins[i] = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
vlibc_bin_remove(struct vlibc_chunk *c)
|
||||||
|
{
|
||||||
|
int i = vlibc_bin_of(c->csize & VLIBC_CSIZE_MASK);
|
||||||
|
|
||||||
|
if (c->prev != NULL)
|
||||||
|
{
|
||||||
|
c->prev->next = c->next;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vlibc_bins[i] = c->next;
|
||||||
|
}
|
||||||
|
if (c->next != NULL)
|
||||||
|
{
|
||||||
|
c->next->prev = c->prev;
|
||||||
|
}
|
||||||
|
c->next = NULL;
|
||||||
|
c->prev = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Record c (size sz, flag-free) as allocated and update the next header. */
|
||||||
|
static void
|
||||||
|
vlibc_mark_alloc(struct vlibc_chunk *c, size_t sz)
|
||||||
|
{
|
||||||
|
struct vlibc_chunk *next = (struct vlibc_chunk *)((char *)c + sz);
|
||||||
|
|
||||||
|
c->csize = sz | VLIBC_CHUNK_INUSE;
|
||||||
|
if ((uintptr_t)next < vlibc_heap_end && next != vlibc_top)
|
||||||
|
{
|
||||||
|
next->psize = c->csize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Query the brk once and remember the heap window. Returns 0 on success;
|
||||||
|
* -1 leaves the window uninitialized so callers fall back to mmap.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
vlibc_heap_init(void)
|
||||||
|
{
|
||||||
|
long r;
|
||||||
|
|
||||||
|
if (vlibc_heap_start != 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
r = __syscall1(SYS_brk, 0);
|
||||||
|
if (r < 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
vlibc_heap_start = ((uintptr_t)r + 15) & ~(uintptr_t)15;
|
||||||
|
vlibc_heap_end = vlibc_heap_start;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Grow the heap by at least need bytes. On success the top chunk covers the
|
||||||
|
* new space (created if the heap was fully consumed); on failure the state
|
||||||
|
* is unchanged and the caller falls back to a private mmap.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
vlibc_heap_extend(size_t need)
|
||||||
|
{
|
||||||
|
size_t want = need > VLIBC_HEAP_GROW ? need : VLIBC_HEAP_GROW;
|
||||||
|
uintptr_t new_end;
|
||||||
|
long r;
|
||||||
|
|
||||||
|
want = (want + 4095) & ~(size_t)4095;
|
||||||
|
new_end = vlibc_heap_end + want;
|
||||||
|
r = __syscall1(SYS_brk, (long)new_end);
|
||||||
|
if ((uintptr_t)r != new_end)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (vlibc_top != NULL)
|
||||||
|
{
|
||||||
|
vlibc_top->csize += want;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vlibc_top = (struct vlibc_chunk *)vlibc_heap_end;
|
||||||
|
vlibc_top->psize = 0 | VLIBC_CHUNK_INUSE; /* prev size unknown */
|
||||||
|
vlibc_top->csize = want;
|
||||||
|
}
|
||||||
|
vlibc_heap_end = new_end;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Give whole trailing pages of the top chunk back to the kernel. Always
|
||||||
|
* keeps at least a minimum chunk so the heap window never closes.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
vlibc_heap_trim(void)
|
||||||
|
{
|
||||||
|
size_t sz = vlibc_top->csize;
|
||||||
|
size_t release = (sz - VLIBC_CHUNK_MIN) & ~(size_t)4095;
|
||||||
|
uintptr_t new_end;
|
||||||
|
long r;
|
||||||
|
|
||||||
|
if (release < 4096)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
new_end = (uintptr_t)vlibc_top + sz - release;
|
||||||
|
r = __syscall1(SYS_brk, (long)new_end);
|
||||||
|
if ((uintptr_t)r == new_end)
|
||||||
|
{
|
||||||
|
vlibc_top->csize = sz - release;
|
||||||
|
vlibc_heap_end = new_end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private mmap block of normalized size nn. NULL on failure. */
|
||||||
|
static void *
|
||||||
|
vlibc_mmap_chunk(size_t nn)
|
||||||
|
{
|
||||||
|
size_t len;
|
||||||
|
long r;
|
||||||
|
struct vlibc_chunk *c;
|
||||||
|
|
||||||
|
if (nn > (size_t)-1 - VLIBC_CHUNK_OVERHEAD - 4095)
|
||||||
|
{
|
||||||
|
return NULL; /* the rounded mapping length would overflow */
|
||||||
|
}
|
||||||
|
len = (nn + VLIBC_CHUNK_OVERHEAD + 4095) & ~(size_t)4095;
|
||||||
|
r = __syscall6(SYS_mmap, 0, (long)len, 3, 0x22, -1, 0);
|
||||||
|
|
||||||
|
if ((uintptr_t)r > (uintptr_t)-4096)
|
||||||
|
{
|
||||||
|
return NULL; /* kernel returned -errno */
|
||||||
|
}
|
||||||
|
c = (struct vlibc_chunk *)r;
|
||||||
|
c->psize = len | VLIBC_CHUNK_MMAP;
|
||||||
|
c->csize = nn | VLIBC_CHUNK_INUSE;
|
||||||
|
vlibc_live_mmap++;
|
||||||
|
return (char *)c + VLIBC_CHUNK_OVERHEAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* First free chunk with size >= nn anywhere in bins [bin_of(nn), end). */
|
||||||
|
static struct vlibc_chunk *
|
||||||
|
vlibc_bin_find(size_t nn)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = vlibc_bin_of(nn); i < VLIBC_BIN_COUNT; i++)
|
||||||
|
{
|
||||||
|
struct vlibc_chunk *c;
|
||||||
|
|
||||||
|
for (c = vlibc_bins[i]; c != NULL; c = c->next)
|
||||||
|
{
|
||||||
|
if ((c->csize & VLIBC_CSIZE_MASK) >= nn)
|
||||||
|
{
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Split free chunk c (current size s) into an nn-byte allocation and a free
|
||||||
|
* remainder. Requires s >= nn + MIN. The chunk after the original extent
|
||||||
|
* gets its psize pointed at the remainder: without that update a later free
|
||||||
|
* there would walk back over the stale (too large) size and land on the
|
||||||
|
* wrong chunk. When the split chunk reaches the top chunk, the remainder is
|
||||||
|
* absorbed into the top instead of entering a bin — a free chunk directly
|
||||||
|
* before top would violate the coalescing invariant and leave the top's
|
||||||
|
* predecessor size stale.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
vlibc_split(struct vlibc_chunk *c, size_t s, size_t nn)
|
||||||
|
{
|
||||||
|
struct vlibc_chunk *rem = (struct vlibc_chunk *)((char *)c + nn);
|
||||||
|
struct vlibc_chunk *after = (struct vlibc_chunk *)((char *)c + s);
|
||||||
|
|
||||||
|
rem->psize = nn; /* prev still free at this instant */
|
||||||
|
rem->csize = s - nn;
|
||||||
|
c->csize = nn;
|
||||||
|
if (after == vlibc_top)
|
||||||
|
{
|
||||||
|
rem->csize += vlibc_top->csize;
|
||||||
|
rem->psize = nn | VLIBC_CHUNK_INUSE; /* c becomes allocated below */
|
||||||
|
vlibc_top = rem;
|
||||||
|
}
|
||||||
|
else if (after == (struct vlibc_chunk *)vlibc_heap_end)
|
||||||
|
{
|
||||||
|
rem->psize = nn | VLIBC_CHUNK_INUSE;
|
||||||
|
vlibc_top = rem;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ((uintptr_t)after < vlibc_heap_end)
|
||||||
|
{
|
||||||
|
after->psize = s - nn;
|
||||||
|
}
|
||||||
|
vlibc_bin_insert(rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Carve nn bytes out of the top chunk and return the user pointer. When the
|
||||||
|
* remainder is at least a minimum chunk it becomes the new top; otherwise
|
||||||
|
* the whole top is consumed and the next extension starts a fresh one.
|
||||||
|
*/
|
||||||
|
static void *
|
||||||
|
vlibc_carve_top(size_t nn)
|
||||||
|
{
|
||||||
|
struct vlibc_chunk *c = vlibc_top;
|
||||||
|
size_t s = c->csize;
|
||||||
|
|
||||||
|
if (s >= nn + VLIBC_CHUNK_MIN)
|
||||||
|
{
|
||||||
|
struct vlibc_chunk *rem = (struct vlibc_chunk *)((char *)c + nn);
|
||||||
|
|
||||||
|
rem->psize = nn | VLIBC_CHUNK_INUSE;
|
||||||
|
rem->csize = s - nn;
|
||||||
|
c->csize = nn | VLIBC_CHUNK_INUSE;
|
||||||
|
vlibc_top = rem;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
c->csize = s | VLIBC_CHUNK_INUSE;
|
||||||
|
vlibc_top = NULL;
|
||||||
|
}
|
||||||
|
vlibc_live++;
|
||||||
|
return (char *)c + VLIBC_CHUNK_OVERHEAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The allocator core (the internal seam src/internal/malloc.h documents).
|
||||||
|
* Allocates n bytes, 16-byte aligned; NULL + errno ENOMEM on failure. May
|
||||||
|
* return a unique pointer even when n == 0.
|
||||||
|
*/
|
||||||
|
hidden void *
|
||||||
|
__libc_malloc(size_t n) // NOLINT(bugprone-reserved-identifier)
|
||||||
|
{
|
||||||
|
size_t nn;
|
||||||
|
struct vlibc_chunk *c;
|
||||||
|
|
||||||
|
if (n > VLIBC_CHUNK_MAX_USER)
|
||||||
|
{
|
||||||
|
errno = ENOMEM;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
nn = vlibc_norm(n);
|
||||||
|
if (nn >= VLIBC_MMAP_THRESHOLD)
|
||||||
|
{
|
||||||
|
void *p = vlibc_mmap_chunk(nn);
|
||||||
|
|
||||||
|
if (p == NULL)
|
||||||
|
{
|
||||||
|
errno = ENOMEM;
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
c = vlibc_bin_find(nn);
|
||||||
|
if (c != NULL)
|
||||||
|
{
|
||||||
|
size_t s = c->csize & VLIBC_CSIZE_MASK;
|
||||||
|
|
||||||
|
vlibc_bin_remove(c);
|
||||||
|
if (s >= nn + VLIBC_CHUNK_MIN)
|
||||||
|
{
|
||||||
|
vlibc_split(c, s, nn);
|
||||||
|
vlibc_mark_alloc(c, nn);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vlibc_mark_alloc(c, s);
|
||||||
|
}
|
||||||
|
vlibc_live++;
|
||||||
|
return (char *)c + VLIBC_CHUNK_OVERHEAD;
|
||||||
|
}
|
||||||
|
if (vlibc_heap_init() == 0 && vlibc_heap_extend(nn) == 0)
|
||||||
|
{
|
||||||
|
return vlibc_carve_top(nn);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
void *p = vlibc_mmap_chunk(nn);
|
||||||
|
|
||||||
|
if (p == NULL)
|
||||||
|
{
|
||||||
|
errno = ENOMEM;
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Release a block returned by __libc_malloc; NULL is a no-op. Coalesces
|
||||||
|
* with adjacent free chunks and the top chunk, then trims trailing pages.
|
||||||
|
*/
|
||||||
|
hidden void
|
||||||
|
__libc_free(void *p) // NOLINT(bugprone-reserved-identifier)
|
||||||
|
{
|
||||||
|
struct vlibc_chunk *c;
|
||||||
|
size_t sz;
|
||||||
|
|
||||||
|
if (p == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
c = (struct vlibc_chunk *)((char *)p - VLIBC_CHUNK_OVERHEAD);
|
||||||
|
if ((c->csize & VLIBC_CHUNK_INUSE) == 0)
|
||||||
|
{
|
||||||
|
/* Aligned allocation: the descriptor sits right before p. */
|
||||||
|
void *raw = *(void **)((char *)p - sizeof(void *));
|
||||||
|
|
||||||
|
p = raw;
|
||||||
|
c = (struct vlibc_chunk *)((char *)raw - VLIBC_CHUNK_OVERHEAD);
|
||||||
|
}
|
||||||
|
if ((c->psize & VLIBC_CHUNK_MMAP) != 0)
|
||||||
|
{
|
||||||
|
(void)__syscall2(SYS_munmap, (long)c, (long)(c->psize & VLIBC_PSIZE_MASK));
|
||||||
|
vlibc_live_mmap--;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
vlibc_live--;
|
||||||
|
sz = c->csize & VLIBC_CSIZE_MASK;
|
||||||
|
c->csize = sz; /* clear INUSE and ALIGNED */
|
||||||
|
|
||||||
|
/* Coalesce with the following chunk, top chunk first. */
|
||||||
|
{
|
||||||
|
struct vlibc_chunk *next = (struct vlibc_chunk *)((char *)c + sz);
|
||||||
|
|
||||||
|
if (next == vlibc_top)
|
||||||
|
{
|
||||||
|
c->csize += vlibc_top->csize;
|
||||||
|
vlibc_top = c;
|
||||||
|
}
|
||||||
|
else if ((uintptr_t)next < vlibc_heap_end && (next->csize & VLIBC_CHUNK_INUSE) == 0)
|
||||||
|
{
|
||||||
|
struct vlibc_chunk *after;
|
||||||
|
|
||||||
|
vlibc_bin_remove(next);
|
||||||
|
c->csize += next->csize;
|
||||||
|
after = (struct vlibc_chunk *)((char *)c + c->csize);
|
||||||
|
if ((uintptr_t)after < vlibc_heap_end)
|
||||||
|
{
|
||||||
|
after->psize = c->csize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Coalesce with the preceding chunk when it is free. */
|
||||||
|
if ((c->psize & 1) == 0)
|
||||||
|
{
|
||||||
|
struct vlibc_chunk *prev =
|
||||||
|
(struct vlibc_chunk *)((char *)c - (c->psize & VLIBC_PSIZE_MASK));
|
||||||
|
struct vlibc_chunk *after;
|
||||||
|
|
||||||
|
vlibc_bin_remove(prev);
|
||||||
|
prev->csize += c->csize;
|
||||||
|
if (c == vlibc_top)
|
||||||
|
{
|
||||||
|
vlibc_top = prev;
|
||||||
|
}
|
||||||
|
c = prev;
|
||||||
|
after = (struct vlibc_chunk *)((char *)c + c->csize);
|
||||||
|
if ((uintptr_t)after < vlibc_heap_end)
|
||||||
|
{
|
||||||
|
after->psize = c->csize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A free block that ends exactly at the heap end is — or joins — the
|
||||||
|
* top chunk. This must also run when the top was fully consumed
|
||||||
|
* (vlibc_top == NULL): a freed trailing chunk that merely entered a bin
|
||||||
|
* would leave the allocator without a top, and a later extension would
|
||||||
|
* then grow over the bin's space while stale top state lets trim shrink
|
||||||
|
* the brk across live chunks.
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
struct vlibc_chunk *end = (struct vlibc_chunk *)((char *)c + (c->csize & VLIBC_CSIZE_MASK));
|
||||||
|
|
||||||
|
if (end == vlibc_top)
|
||||||
|
{
|
||||||
|
c->csize += vlibc_top->csize;
|
||||||
|
vlibc_top = c;
|
||||||
|
}
|
||||||
|
else if (end == (struct vlibc_chunk *)vlibc_heap_end)
|
||||||
|
{
|
||||||
|
vlibc_top = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == vlibc_top)
|
||||||
|
{
|
||||||
|
vlibc_heap_trim();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vlibc_bin_insert(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The public malloc: thin forward to the internal seam so that every
|
||||||
|
* allocation path in the library shares one implementation.
|
||||||
|
*/
|
||||||
|
void *
|
||||||
|
malloc(size_t n)
|
||||||
|
{
|
||||||
|
return __libc_malloc(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The public free: thin forward to the internal seam. */
|
||||||
|
void
|
||||||
|
free(void *p)
|
||||||
|
{
|
||||||
|
__libc_free(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allocate zeroed memory for nmemb elements of size bytes. The product is
|
||||||
|
* overflow-checked; NULL + errno ENOMEM on failure. calloc(n, 0) and
|
||||||
|
* calloc(0, n) return a unique zeroed minimum-size chunk like malloc(0).
|
||||||
|
*/
|
||||||
|
void *
|
||||||
|
calloc(size_t nmemb, size_t size)
|
||||||
|
{
|
||||||
|
size_t total;
|
||||||
|
void *p;
|
||||||
|
struct vlibc_chunk *c;
|
||||||
|
size_t usable;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
if (size != 0 && nmemb > (size_t)-1 / size)
|
||||||
|
{
|
||||||
|
errno = ENOMEM;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
total = nmemb * size;
|
||||||
|
p = malloc(total);
|
||||||
|
if (p == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
c = (struct vlibc_chunk *)((char *)p - VLIBC_CHUNK_OVERHEAD);
|
||||||
|
if ((c->psize & VLIBC_CHUNK_MMAP) != 0)
|
||||||
|
{
|
||||||
|
return p; /* a fresh mmap is already zero */
|
||||||
|
}
|
||||||
|
usable = (c->csize & VLIBC_CSIZE_MASK) - VLIBC_CHUNK_OVERHEAD;
|
||||||
|
for (i = 0; i < usable; i++)
|
||||||
|
{
|
||||||
|
((unsigned char *)p)[i] = 0;
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Resize the block at p to n bytes. realloc(NULL, n) is malloc(n);
|
||||||
|
* realloc(p, 0) frees p and returns NULL. Growth first tries to merge the
|
||||||
|
* following free chunk (or the top chunk); otherwise the block is copied to
|
||||||
|
* a fresh allocation and freed. On failure the original block is untouched
|
||||||
|
* and errno is ENOMEM.
|
||||||
|
*/
|
||||||
|
void *
|
||||||
|
realloc(void *p, size_t n)
|
||||||
|
{
|
||||||
|
struct vlibc_chunk *c;
|
||||||
|
size_t sz;
|
||||||
|
size_t nn;
|
||||||
|
void *np;
|
||||||
|
size_t old;
|
||||||
|
size_t copy;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
if (p == NULL)
|
||||||
|
{
|
||||||
|
return malloc(n);
|
||||||
|
}
|
||||||
|
if (n == 0)
|
||||||
|
{
|
||||||
|
free(p);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (n > VLIBC_CHUNK_MAX_USER)
|
||||||
|
{
|
||||||
|
errno = ENOMEM;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
nn = vlibc_norm(n);
|
||||||
|
|
||||||
|
c = (struct vlibc_chunk *)((char *)p - VLIBC_CHUNK_OVERHEAD);
|
||||||
|
if ((c->csize & VLIBC_CHUNK_INUSE) == 0)
|
||||||
|
{
|
||||||
|
/* Aligned allocation: re-allocate with the original alignment. */
|
||||||
|
size_t align = *(size_t *)((char *)p - 2 * sizeof(size_t));
|
||||||
|
void *raw = *(void **)((char *)p - sizeof(void *));
|
||||||
|
struct vlibc_chunk *rc = (struct vlibc_chunk *)((char *)raw - VLIBC_CHUNK_OVERHEAD);
|
||||||
|
|
||||||
|
old =
|
||||||
|
(rc->csize & VLIBC_CSIZE_MASK) - VLIBC_CHUNK_OVERHEAD - ((uintptr_t)p - (uintptr_t)raw);
|
||||||
|
if (n % align != 0)
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
np = vlibc_aligned_core(align, n);
|
||||||
|
if (np == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
copy = old < n ? old : n;
|
||||||
|
for (i = 0; i < copy; i++)
|
||||||
|
{
|
||||||
|
((unsigned char *)np)[i] = ((unsigned char *)p)[i];
|
||||||
|
}
|
||||||
|
free(p);
|
||||||
|
return np;
|
||||||
|
}
|
||||||
|
if ((c->psize & VLIBC_CHUNK_MMAP) != 0)
|
||||||
|
{
|
||||||
|
old = (c->csize & VLIBC_CSIZE_MASK) - VLIBC_CHUNK_OVERHEAD;
|
||||||
|
np = malloc(n);
|
||||||
|
if (np == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
copy = old < n ? old : n;
|
||||||
|
for (i = 0; i < copy; i++)
|
||||||
|
{
|
||||||
|
((unsigned char *)np)[i] = ((unsigned char *)p)[i];
|
||||||
|
}
|
||||||
|
free(p);
|
||||||
|
return np;
|
||||||
|
}
|
||||||
|
|
||||||
|
sz = c->csize & VLIBC_CSIZE_MASK;
|
||||||
|
if (nn <= sz)
|
||||||
|
{
|
||||||
|
if (sz >= nn + VLIBC_CHUNK_MIN)
|
||||||
|
{
|
||||||
|
/* Shrink in place, releasing the tail as a free chunk. */
|
||||||
|
vlibc_split(c, sz, nn);
|
||||||
|
vlibc_mark_alloc(c, nn);
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Try to grow into the following free chunk (or the top chunk). */
|
||||||
|
{
|
||||||
|
struct vlibc_chunk *next = (struct vlibc_chunk *)((char *)c + sz);
|
||||||
|
|
||||||
|
if (next == vlibc_top)
|
||||||
|
{
|
||||||
|
size_t total = sz + vlibc_top->csize;
|
||||||
|
|
||||||
|
if (total >= nn)
|
||||||
|
{
|
||||||
|
if (total >= nn + VLIBC_CHUNK_MIN)
|
||||||
|
{
|
||||||
|
struct vlibc_chunk *rem = (struct vlibc_chunk *)((char *)c + nn);
|
||||||
|
|
||||||
|
rem->psize = nn | VLIBC_CHUNK_INUSE;
|
||||||
|
rem->csize = total - nn;
|
||||||
|
c->csize = nn | VLIBC_CHUNK_INUSE;
|
||||||
|
vlibc_top = rem;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
c->csize = total | VLIBC_CHUNK_INUSE;
|
||||||
|
vlibc_top = NULL;
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ((uintptr_t)next < vlibc_heap_end && (next->csize & VLIBC_CHUNK_INUSE) == 0)
|
||||||
|
{
|
||||||
|
size_t total = sz + (next->csize & VLIBC_CSIZE_MASK);
|
||||||
|
|
||||||
|
if (total >= nn)
|
||||||
|
{
|
||||||
|
vlibc_bin_remove(next);
|
||||||
|
if (total >= nn + VLIBC_CHUNK_MIN)
|
||||||
|
{
|
||||||
|
struct vlibc_chunk *rem = (struct vlibc_chunk *)((char *)c + nn);
|
||||||
|
struct vlibc_chunk *after;
|
||||||
|
|
||||||
|
rem->psize = nn | VLIBC_CHUNK_INUSE;
|
||||||
|
rem->csize = total - nn;
|
||||||
|
c->csize = nn | VLIBC_CHUNK_INUSE;
|
||||||
|
after = (struct vlibc_chunk *)((char *)rem + rem->csize);
|
||||||
|
if (after == vlibc_top)
|
||||||
|
{
|
||||||
|
rem->csize += vlibc_top->csize;
|
||||||
|
vlibc_top = rem;
|
||||||
|
}
|
||||||
|
else if (after == (struct vlibc_chunk *)vlibc_heap_end)
|
||||||
|
{
|
||||||
|
vlibc_top = rem;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vlibc_bin_insert(rem);
|
||||||
|
if ((uintptr_t)after < vlibc_heap_end)
|
||||||
|
{
|
||||||
|
after->psize = rem->csize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
struct vlibc_chunk *after = (struct vlibc_chunk *)((char *)c + total);
|
||||||
|
|
||||||
|
c->csize = total | VLIBC_CHUNK_INUSE;
|
||||||
|
if ((uintptr_t)after < vlibc_heap_end)
|
||||||
|
{
|
||||||
|
after->psize = c->csize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
/* Keep next linked; fall through to alloc-copy-free. */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate fresh, copy the smaller of the two payloads, free the old. */
|
||||||
|
old = sz - VLIBC_CHUNK_OVERHEAD;
|
||||||
|
np = malloc(n);
|
||||||
|
if (np == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
copy = old < n ? old : n;
|
||||||
|
for (i = 0; i < copy; i++)
|
||||||
|
{
|
||||||
|
((unsigned char *)np)[i] = ((unsigned char *)p)[i];
|
||||||
|
}
|
||||||
|
free(p);
|
||||||
|
return np;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Core of the aligned family: allocate size bytes aligned to align (a
|
||||||
|
* power of two greater than 16). The returned pointer has a two-word
|
||||||
|
* descriptor directly before it ([q-16] = align, [q-8] = raw pointer).
|
||||||
|
*/
|
||||||
|
static void *
|
||||||
|
vlibc_aligned_core(size_t align, size_t size)
|
||||||
|
{
|
||||||
|
void *raw;
|
||||||
|
void *q;
|
||||||
|
struct vlibc_chunk *c;
|
||||||
|
|
||||||
|
raw = malloc(size + align + VLIBC_CHUNK_ALIGN);
|
||||||
|
if (raw == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
q = (void *)(((uintptr_t)raw + VLIBC_CHUNK_ALIGN + align - 1) & -(uintptr_t)align);
|
||||||
|
*(size_t *)((char *)q - 2 * sizeof(size_t)) = align;
|
||||||
|
*(void **)((char *)q - sizeof(void *)) = raw;
|
||||||
|
c = (struct vlibc_chunk *)((char *)raw - VLIBC_CHUNK_OVERHEAD);
|
||||||
|
c->csize |= VLIBC_CHUNK_ALIGNED;
|
||||||
|
return q;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allocate size bytes aligned to alignment. alignment must be a power of
|
||||||
|
* two that is a multiple of sizeof(void *), and size must be a multiple of
|
||||||
|
* alignment; a violation fails with NULL + errno EINVAL (a non-power-of-two
|
||||||
|
* alignment is undefined behavior in C23, so only well-formed arguments
|
||||||
|
* reach the allocator). size 0 returns NULL. The returned pointer is a
|
||||||
|
* valid malloc block and is released with free.
|
||||||
|
*/
|
||||||
|
void *
|
||||||
|
aligned_alloc(size_t alignment, size_t size)
|
||||||
|
{
|
||||||
|
if ((alignment & (alignment - 1)) != 0 || alignment % sizeof(void *) != 0 ||
|
||||||
|
size % alignment != 0)
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (size == 0)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (alignment <= VLIBC_CHUNK_OVERHEAD)
|
||||||
|
{
|
||||||
|
return malloc(size);
|
||||||
|
}
|
||||||
|
return vlibc_aligned_core(alignment, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allocate size bytes at address alignment and store the result in
|
||||||
|
* *memptr. alignment must be a power of two and a multiple of
|
||||||
|
* sizeof(void *). Returns 0 on success, EINVAL for a bad alignment, ENOMEM
|
||||||
|
* on allocation failure; never sets errno itself and never modifies
|
||||||
|
* *memptr on failure. size 0 returns a unique minimum-size block.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
posix_memalign(void **memptr, size_t alignment, size_t size)
|
||||||
|
{
|
||||||
|
void *p;
|
||||||
|
|
||||||
|
if (memptr == NULL || (alignment & (alignment - 1)) != 0 || alignment % sizeof(void *) != 0)
|
||||||
|
{
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
p = vlibc_aligned_core(alignment, size == 0 ? 1 : size);
|
||||||
|
if (p == NULL)
|
||||||
|
{
|
||||||
|
return ENOMEM;
|
||||||
|
}
|
||||||
|
*memptr = p;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
/*
|
||||||
|
* Return the number of bytes actually available in the block at p,
|
||||||
|
* including any internal padding. p may be any block returned by the
|
||||||
|
* allocator family. NULL returns 0.
|
||||||
|
*/
|
||||||
|
size_t
|
||||||
|
malloc_usable_size(void *p)
|
||||||
|
{
|
||||||
|
struct vlibc_chunk *c;
|
||||||
|
|
||||||
|
if (p == NULL)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
c = (struct vlibc_chunk *)((char *)p - VLIBC_CHUNK_OVERHEAD);
|
||||||
|
if ((c->csize & VLIBC_CHUNK_INUSE) == 0)
|
||||||
|
{
|
||||||
|
/* Aligned allocation: report the raw block minus the offset. */
|
||||||
|
void *raw = *(void **)((char *)p - sizeof(void *));
|
||||||
|
struct vlibc_chunk *rc = (struct vlibc_chunk *)((char *)raw - VLIBC_CHUNK_OVERHEAD);
|
||||||
|
|
||||||
|
if ((rc->psize & VLIBC_CHUNK_MMAP) != 0)
|
||||||
|
{
|
||||||
|
return (rc->psize & VLIBC_PSIZE_MASK) - VLIBC_CHUNK_OVERHEAD -
|
||||||
|
((uintptr_t)p - (uintptr_t)raw);
|
||||||
|
}
|
||||||
|
return (rc->csize & VLIBC_CSIZE_MASK) - VLIBC_CHUNK_OVERHEAD -
|
||||||
|
((uintptr_t)p - (uintptr_t)raw);
|
||||||
|
}
|
||||||
|
if ((c->psize & VLIBC_CHUNK_MMAP) != 0)
|
||||||
|
{
|
||||||
|
return (c->psize & VLIBC_PSIZE_MASK) - VLIBC_CHUNK_OVERHEAD;
|
||||||
|
}
|
||||||
|
return (c->csize & VLIBC_CSIZE_MASK) - VLIBC_CHUNK_OVERHEAD;
|
||||||
|
}
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Internal consistency hook used by the test suite: walk the whole heap
|
||||||
|
* chunk-by-chunk (an independent account of every live allocation), sum
|
||||||
|
* the in-use chunks, and compare against the allocator's own counters.
|
||||||
|
* Returns the total number of live blocks, or (size_t)-1 when the heap
|
||||||
|
* walk disagrees with the counters (corruption or an accounting bug).
|
||||||
|
*/
|
||||||
|
hidden size_t
|
||||||
|
__vlibc_malloc_check(void) // NOLINT(bugprone-reserved-identifier)
|
||||||
|
{
|
||||||
|
uintptr_t cur;
|
||||||
|
size_t live = 0;
|
||||||
|
|
||||||
|
if (vlibc_heap_start == 0)
|
||||||
|
{
|
||||||
|
return vlibc_live + vlibc_live_mmap == 0 ? 0 : (size_t)-1;
|
||||||
|
}
|
||||||
|
cur = vlibc_heap_start;
|
||||||
|
while (cur + VLIBC_CHUNK_MIN <= vlibc_heap_end)
|
||||||
|
{
|
||||||
|
struct vlibc_chunk *c = (struct vlibc_chunk *)cur;
|
||||||
|
size_t sz = c->csize & VLIBC_CSIZE_MASK;
|
||||||
|
|
||||||
|
if (sz < VLIBC_CHUNK_MIN || sz % VLIBC_CHUNK_ALIGN != 0 || cur + sz > vlibc_heap_end)
|
||||||
|
{
|
||||||
|
return (size_t)-1;
|
||||||
|
}
|
||||||
|
if ((c->csize & VLIBC_CHUNK_INUSE) != 0)
|
||||||
|
{
|
||||||
|
live++;
|
||||||
|
}
|
||||||
|
cur += sz;
|
||||||
|
}
|
||||||
|
if (cur != vlibc_heap_end || live != vlibc_live)
|
||||||
|
{
|
||||||
|
return (size_t)-1;
|
||||||
|
}
|
||||||
|
return live + vlibc_live_mmap;
|
||||||
|
}
|
||||||
@@ -0,0 +1,449 @@
|
|||||||
|
/*
|
||||||
|
* vlibc — malloc/free/calloc/realloc/aligned_alloc/posix_memalign test
|
||||||
|
* (todo 7).
|
||||||
|
*
|
||||||
|
* Exercises the heap allocator end to end:
|
||||||
|
*
|
||||||
|
* 1. malloc(1) / malloc(1KB) / malloc(1MB) / malloc(1GB) — the 1GB case
|
||||||
|
* takes the mmap path — all non-NULL, 16-byte aligned, distinct,
|
||||||
|
* writable; freed again.
|
||||||
|
* 2. malloc(0) returns a unique usable minimum-size block; two calls
|
||||||
|
* yield distinct pointers.
|
||||||
|
* 3. calloc(100, 100) returns 10000 zero bytes; a zero-product calloc
|
||||||
|
* returns a unique zeroed block.
|
||||||
|
* 4. realloc grows (contents preserved), shrinks in place, realloc(p, 0)
|
||||||
|
* frees and returns NULL, realloc(NULL, n) behaves as malloc(n).
|
||||||
|
* 5. aligned_alloc(64, 4096) and aligned_alloc(256, 8192) return
|
||||||
|
* correctly aligned usable blocks; free works on them.
|
||||||
|
* 6. posix_memalign(&p, 16, 100) returns 0 with p 16-aligned; the
|
||||||
|
* negative case posix_memalign(&p, 3, 8) returns EINVAL and leaves p
|
||||||
|
* unmodified.
|
||||||
|
* 7. malloc_usable_size reports at least the requested size (level 2).
|
||||||
|
* 8. A 10k random alloc/free churn (sizes up to 4 KiB plus occasional
|
||||||
|
* 256 KiB mmap blocks) ends with the allocator's own walk
|
||||||
|
* (__vlibc_malloc_check) reporting zero live chunks — the no-leak
|
||||||
|
* proof, no valgrind involved.
|
||||||
|
*
|
||||||
|
* The failure scenarios (allocation overflow, calloc overflow) live in the
|
||||||
|
* `-f` mode: those paths set errno inside the library, and under the host
|
||||||
|
* libc the TCB slot our errno macro addresses is glibc's private TLS state
|
||||||
|
* (writing it corrupts the host; see tests/syscall_test.c). The -f mode
|
||||||
|
* therefore exits through a raw SYS_exit_group without ever touching the
|
||||||
|
* host libc's atexit/cleanup machinery, and the default mode never invokes
|
||||||
|
* those paths at all. The default mode's negative case is posix_memalign,
|
||||||
|
* whose EINVAL is a RETURN VALUE, never an errno write.
|
||||||
|
*
|
||||||
|
* All diagnostics go through raw SYS_write (no stdio): under -Iinclude the
|
||||||
|
* vlibc public headers shadow GCC's internal ones, so a host <stdio.h>
|
||||||
|
* would not compile. The stdlib.h below is vlibc's own new header.
|
||||||
|
*
|
||||||
|
* Not part of the library proper; compiled manually for this todo (the
|
||||||
|
* tests/ + make check wiring is owned by a later todo).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include "../include/stdlib.h"
|
||||||
|
|
||||||
|
#include "../src/internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Internal no-leak hook from src/malloc/malloc.c (hidden, so it never
|
||||||
|
* leaves the archive as a dynamic symbol). Returns the number of live
|
||||||
|
* blocks, or (size_t)-1 when the heap walk disagrees with the counters.
|
||||||
|
*/
|
||||||
|
__attribute__((visibility("hidden"))) size_t
|
||||||
|
__vlibc_malloc_check(void); // NOLINT(bugprone-reserved-identifier)
|
||||||
|
|
||||||
|
static int failures;
|
||||||
|
|
||||||
|
/* Write a NUL-terminated string to fd via the raw syscall layer. */
|
||||||
|
static void
|
||||||
|
say(int fd, const char *s)
|
||||||
|
{
|
||||||
|
long n = 0;
|
||||||
|
|
||||||
|
while (s[n] != '\0')
|
||||||
|
{
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
__syscall3(SYS_write, fd, (long)s, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write v in decimal to fd. */
|
||||||
|
static void
|
||||||
|
say_dec(int fd, unsigned long v) // NOLINT(bugprone-easily-swappable-parameters)
|
||||||
|
{
|
||||||
|
char buf[24];
|
||||||
|
int i = (int)sizeof(buf);
|
||||||
|
|
||||||
|
buf[--i] = '\0';
|
||||||
|
do
|
||||||
|
{
|
||||||
|
buf[--i] = (char)('0' + (v % 10));
|
||||||
|
v /= 10;
|
||||||
|
} while (v != 0);
|
||||||
|
__syscall3(SYS_write, fd, (long)(buf + i), (long)(sizeof(buf) - 1 - i));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
check(int cond, const char *what)
|
||||||
|
{
|
||||||
|
if (cond)
|
||||||
|
{
|
||||||
|
say(1, "PASS: ");
|
||||||
|
say(1, what);
|
||||||
|
say(1, "\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(2, "FAIL: ");
|
||||||
|
say(2, what);
|
||||||
|
say(2, "\n");
|
||||||
|
failures++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* xorshift32; deterministic, allocation-independent. */
|
||||||
|
static unsigned
|
||||||
|
rng_next(unsigned *state)
|
||||||
|
{
|
||||||
|
unsigned x = *state;
|
||||||
|
|
||||||
|
x ^= x << 13;
|
||||||
|
x ^= x >> 17;
|
||||||
|
x ^= x << 5;
|
||||||
|
*state = x;
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allocation-family calls through noipa proxies so the compiler never sees
|
||||||
|
* constant arguments (a literal 0 product or a folded overflow would trip
|
||||||
|
* the alloc_size diagnostics — or, worse, let GCC treat the call as
|
||||||
|
* alloc_size-undefined and elide it, assuming a non-NULL result). noipa is
|
||||||
|
* required: plain noinline is defeated by same-TU interprocedural
|
||||||
|
* constant propagation.
|
||||||
|
*/
|
||||||
|
static __attribute__((noipa)) void *
|
||||||
|
malloc_proxy(size_t size)
|
||||||
|
{
|
||||||
|
return malloc(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static __attribute__((noipa)) void *
|
||||||
|
calloc_proxy(size_t nmemb, size_t size)
|
||||||
|
{
|
||||||
|
return calloc(nmemb, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static __attribute__((noipa)) void *
|
||||||
|
realloc_proxy(void *ptr, size_t size)
|
||||||
|
{
|
||||||
|
return realloc(ptr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 1. Plain allocations across four orders of magnitude, then free. */
|
||||||
|
static void
|
||||||
|
basic_alloc_free(void)
|
||||||
|
{
|
||||||
|
static const size_t sizes[] = {1, 1024, 1048576, 1073741824};
|
||||||
|
void *p[4];
|
||||||
|
unsigned i;
|
||||||
|
|
||||||
|
for (i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
p[i] = malloc(sizes[i]);
|
||||||
|
check(p[i] != NULL, "malloc returns non-NULL");
|
||||||
|
check(((uintptr_t)p[i] & 15) == 0, "malloc result is 16-byte aligned");
|
||||||
|
if (p[i] != NULL)
|
||||||
|
{
|
||||||
|
((unsigned char *)p[i])[0] = 0x5a;
|
||||||
|
((unsigned char *)p[i])[sizes[i] - 1] = 0xa5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check(p[0] != p[1] && p[1] != p[2] && p[2] != p[3], "distinct blocks for distinct sizes");
|
||||||
|
for (i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
free(p[i]);
|
||||||
|
}
|
||||||
|
check(__vlibc_malloc_check() == 0, "no live chunks after freeing all four");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. malloc(0) semantics: unique, usable, minimum-size. */
|
||||||
|
static void
|
||||||
|
zero_size_malloc(void)
|
||||||
|
{
|
||||||
|
void *a = malloc(0);
|
||||||
|
void *b = malloc(0);
|
||||||
|
|
||||||
|
check(a != NULL && b != NULL, "malloc(0) returns non-NULL");
|
||||||
|
check(a != b, "two malloc(0) calls return distinct pointers");
|
||||||
|
if (a != NULL)
|
||||||
|
{
|
||||||
|
((unsigned char *)a)[0] = 0x11;
|
||||||
|
}
|
||||||
|
free(a);
|
||||||
|
free(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. calloc zeroing, including a zero product. */
|
||||||
|
static void
|
||||||
|
calloc_zeroing(void)
|
||||||
|
{
|
||||||
|
unsigned char *p = calloc(100, 100);
|
||||||
|
size_t i;
|
||||||
|
int all_zero = 1;
|
||||||
|
|
||||||
|
check(p != NULL, "calloc(100, 100) returns non-NULL");
|
||||||
|
if (p != NULL)
|
||||||
|
{
|
||||||
|
for (i = 0; i < 10000; i++)
|
||||||
|
{
|
||||||
|
if (p[i] != 0)
|
||||||
|
{
|
||||||
|
all_zero = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check(all_zero, "calloc(100, 100) is 10000 zero bytes");
|
||||||
|
free(p);
|
||||||
|
p = calloc_proxy(0, 1);
|
||||||
|
check(p != NULL, "calloc(0, 1) returns a unique zeroed block");
|
||||||
|
free(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4. realloc grow / shrink / free-on-zero / NULL-as-malloc. */
|
||||||
|
static void
|
||||||
|
realloc_paths(void)
|
||||||
|
{
|
||||||
|
unsigned char *p = malloc(100);
|
||||||
|
unsigned char *q;
|
||||||
|
size_t i;
|
||||||
|
int preserved = 1;
|
||||||
|
|
||||||
|
check(p != NULL, "realloc setup: malloc(100) non-NULL");
|
||||||
|
for (i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
p[i] = (unsigned char)i;
|
||||||
|
}
|
||||||
|
q = realloc(p, 4096);
|
||||||
|
check(q != NULL, "realloc(p, 4096) grows and returns non-NULL");
|
||||||
|
for (i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
if (q[i] != (unsigned char)i)
|
||||||
|
{
|
||||||
|
preserved = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check(preserved, "realloc grow preserves all 100 bytes");
|
||||||
|
q[4095] = 0xee;
|
||||||
|
p = realloc(q, 100);
|
||||||
|
check(p != NULL, "realloc(q, 100) shrinks and returns non-NULL");
|
||||||
|
preserved = 1;
|
||||||
|
for (i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
if (p[i] != (unsigned char)i)
|
||||||
|
{
|
||||||
|
preserved = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check(preserved, "realloc shrink preserves all 100 bytes");
|
||||||
|
q = realloc(p, 0);
|
||||||
|
check(q == NULL, "realloc(p, 0) returns NULL");
|
||||||
|
q = realloc(NULL, 64);
|
||||||
|
check(q != NULL, "realloc(NULL, 64) behaves as malloc");
|
||||||
|
free(q);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 5. aligned_alloc alignment guarantees. */
|
||||||
|
static void
|
||||||
|
aligned_alloc_paths(void)
|
||||||
|
{
|
||||||
|
void *p = aligned_alloc(64, 4096);
|
||||||
|
void *q = aligned_alloc(256, 8192);
|
||||||
|
|
||||||
|
check(p != NULL, "aligned_alloc(64, 4096) returns non-NULL");
|
||||||
|
check(((uintptr_t)p & 63) == 0, "aligned_alloc(64, 4096) is 64-byte aligned");
|
||||||
|
check(q != NULL, "aligned_alloc(256, 8192) returns non-NULL");
|
||||||
|
check(((uintptr_t)q & 255) == 0, "aligned_alloc(256, 8192) is 256-byte aligned");
|
||||||
|
if (p != NULL)
|
||||||
|
{
|
||||||
|
((unsigned char *)p)[4095] = 0x33;
|
||||||
|
}
|
||||||
|
free(p);
|
||||||
|
free(q);
|
||||||
|
check(__vlibc_malloc_check() == 0, "no live chunks after freeing aligned blocks");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 6. posix_memalign happy + EINVAL negative. */
|
||||||
|
static void
|
||||||
|
posix_memalign_paths(void)
|
||||||
|
{
|
||||||
|
void *p = (void *)0x1; /* a recognizable sentinel */
|
||||||
|
void *before = p;
|
||||||
|
int rc = posix_memalign(&p, 16, 100);
|
||||||
|
|
||||||
|
check(rc == 0, "posix_memalign(&p, 16, 100) returns 0");
|
||||||
|
check(((uintptr_t)p & 15) == 0, "posix_memalign(16) result is 16-byte aligned");
|
||||||
|
free(p);
|
||||||
|
p = (void *)0x1;
|
||||||
|
rc = posix_memalign(&p, 3, 8);
|
||||||
|
check(rc == EINVAL, "posix_memalign(&p, 3, 8) returns EINVAL");
|
||||||
|
check(p == before, "posix_memalign failure leaves *memptr unmodified");
|
||||||
|
}
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
/* 7. malloc_usable_size reports at least the request. */
|
||||||
|
static void
|
||||||
|
usable_size_paths(void)
|
||||||
|
{
|
||||||
|
void *p = malloc(100);
|
||||||
|
|
||||||
|
if (p != NULL)
|
||||||
|
{
|
||||||
|
check(malloc_usable_size(p) >= 100, "malloc_usable_size(p) >= requested 100");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
check(0, "usable-size setup: malloc(100) non-NULL");
|
||||||
|
}
|
||||||
|
free(p);
|
||||||
|
check(malloc_usable_size(NULL) == 0, "malloc_usable_size(NULL) == 0");
|
||||||
|
}
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
/* 8. 10k random alloc/free churn ending with zero live chunks. */
|
||||||
|
static void
|
||||||
|
churn_test(void)
|
||||||
|
{
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
SLOTS = 1024
|
||||||
|
};
|
||||||
|
void *slot[SLOTS];
|
||||||
|
unsigned rng = 0x9e3779b9U;
|
||||||
|
unsigned i;
|
||||||
|
|
||||||
|
for (i = 0; i < SLOTS; i++)
|
||||||
|
{
|
||||||
|
slot[i] = NULL;
|
||||||
|
}
|
||||||
|
for (i = 0; i < 10000; i++)
|
||||||
|
{
|
||||||
|
unsigned idx = rng_next(&rng) % SLOTS;
|
||||||
|
size_t sz;
|
||||||
|
|
||||||
|
if (slot[idx] != NULL)
|
||||||
|
{
|
||||||
|
free(slot[idx]);
|
||||||
|
slot[idx] = NULL;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
sz = rng_next(&rng) % 4096;
|
||||||
|
if ((rng_next(&rng) & 31) == 0)
|
||||||
|
{
|
||||||
|
sz = 262144; /* occasional 256 KiB mmap block */
|
||||||
|
}
|
||||||
|
slot[idx] = malloc(sz == 0 ? 1 : sz);
|
||||||
|
check(slot[idx] != NULL, "churn: malloc returns non-NULL");
|
||||||
|
if (slot[idx] != NULL)
|
||||||
|
{
|
||||||
|
((unsigned char *)slot[idx])[0] = (unsigned char)sz;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (i = 0; i < SLOTS; i++)
|
||||||
|
{
|
||||||
|
free(slot[i]);
|
||||||
|
slot[i] = NULL;
|
||||||
|
}
|
||||||
|
check(__vlibc_malloc_check() == 0, "churn ends with zero live chunks");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Failure scenarios (-f): allocation overflow returns NULL + errno ENOMEM. */
|
||||||
|
static int
|
||||||
|
failure_scenarios(void)
|
||||||
|
{
|
||||||
|
void *p;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SIZE_MAX - 10 overflows the internal chunk-size normalization
|
||||||
|
* deterministically (no syscall, no overcommit dependency — SIZE_MAX/2
|
||||||
|
* is a legitimate lazy 8 EiB mapping under Linux overcommit and can
|
||||||
|
* legitimately succeed). The noipa proxy keeps GCC from folding the
|
||||||
|
* constant and assuming the alloc_size-undefined call returns non-NULL.
|
||||||
|
*/
|
||||||
|
p = malloc_proxy((size_t)-1 - 10);
|
||||||
|
if (p != NULL)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: malloc(SIZE_MAX-10) returned non-NULL\n");
|
||||||
|
failures++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "PASS: malloc(SIZE_MAX-10) -> NULL (overflow)\n");
|
||||||
|
}
|
||||||
|
p = calloc_proxy(((size_t)-1) / 2, 2);
|
||||||
|
if (p != NULL)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: calloc(SIZE_MAX/2, 2) returned non-NULL\n");
|
||||||
|
failures++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "PASS: calloc(SIZE_MAX/2, 2) -> NULL (overflow)\n");
|
||||||
|
}
|
||||||
|
p = realloc_proxy(NULL, (size_t)-1 - 10);
|
||||||
|
if (p != NULL)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: realloc(NULL, SIZE_MAX-10) returned non-NULL\n");
|
||||||
|
failures++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "PASS: realloc(NULL, SIZE_MAX-10) -> NULL (overflow)\n");
|
||||||
|
}
|
||||||
|
return failures > 0 ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f')
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* The failure scenarios write errno inside the library; under the
|
||||||
|
* host libc that slot is glibc's private TLS state, so leave via
|
||||||
|
* the raw syscall without running host cleanup.
|
||||||
|
*/
|
||||||
|
rc = failure_scenarios();
|
||||||
|
__syscall1(SYS_exit_group, rc);
|
||||||
|
return rc; /* not reached */
|
||||||
|
}
|
||||||
|
|
||||||
|
basic_alloc_free();
|
||||||
|
zero_size_malloc();
|
||||||
|
calloc_zeroing();
|
||||||
|
realloc_paths();
|
||||||
|
aligned_alloc_paths();
|
||||||
|
posix_memalign_paths();
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
usable_size_paths();
|
||||||
|
#endif
|
||||||
|
churn_test();
|
||||||
|
|
||||||
|
if (failures > 0)
|
||||||
|
{
|
||||||
|
say(2, "FAILED (");
|
||||||
|
say_dec(2, (unsigned long)failures);
|
||||||
|
say(2, " check(s))\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
say(1, "all malloc tests passed\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user