952 lines
27 KiB
C
952 lines
27 KiB
C
#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;
|
|
}
|