mirror of https://github.com/Wilfred/difftastic/
164 lines
4.7 KiB
Plaintext
164 lines
4.7 KiB
Plaintext
// This is a simple memory allocator, based on Appel, Andrew W., and David A.
|
|
// Naumann. "Verified sequential malloc/free." It is not thread-safe.
|
|
//
|
|
// Large allocations are handled with mmap.
|
|
//
|
|
// For small allocations, we set up 50 bins, where each bin is responsible for
|
|
// 16 different allocation sizes (e.g. bin 1 handles allocations from 10 thru 26
|
|
// bytes); except for the first and last bin, which are responsible for fewer
|
|
// than 16 allocation sizes.
|
|
//
|
|
// Each bin is 1MiB (BIGBLOCK) in size. We ceil the allocation size to the
|
|
// largest size supported for this bin, then break the bin up into smaller
|
|
// blocks. Each block is structured as [{sz: size, data..., link: *void}...];
|
|
// where sz is the size of this (small) block, data is is set aside for the
|
|
// user's actual allocation, and link is a pointer to the next bin's data field.
|
|
//
|
|
// In short, a bin for a particular size is pre-filled with all allocations of
|
|
// that size, and the first word of each allocation is set to a pointer to the
|
|
// next allocation. As such, malloc becomes:
|
|
//
|
|
// 1. Look up bin; pre-fill if not already allocated
|
|
// 2. Let p = bin; bin = *bin; return p
|
|
//
|
|
// Then, free is simply:
|
|
// 1. Look up bin
|
|
// 2. *p = bin;
|
|
// 3. bin = p;
|
|
//
|
|
// Note that over time this can cause the ordering of the allocations in each
|
|
// bin to become non-continuous. This has no consequences for performance or
|
|
// correctness.
|
|
|
|
def ALIGN: size = 2;
|
|
def WORD: size = size(size);
|
|
def WASTE: size = WORD * ALIGN - WORD;
|
|
def BIGBLOCK: size = (2 << 16) * WORD;
|
|
|
|
let bins: [50]nullable *void = [null...];
|
|
|
|
fn bin2size(b: size) size = ((b + 1) * ALIGN - 1) * WORD;
|
|
|
|
fn size2bin(s: size) size = {
|
|
assert(s <= bin2size(len(bins) - 1), "Size exceeds maximum for bin");
|
|
return (s + (WORD * (ALIGN - 1) - 1)) / (WORD * ALIGN);
|
|
};
|
|
|
|
// Allocates n bytes of memory and returns a pointer to them, or null if there
|
|
// is insufficient memory.
|
|
export fn malloc(n: size) nullable *void = {
|
|
return if (n == 0) null
|
|
else if (n > bin2size(len(bins) - 1)) malloc_large(n)
|
|
else malloc_small(n);
|
|
};
|
|
|
|
fn malloc_large(n: size) nullable *void = {
|
|
let p = segmalloc(n + WASTE + WORD);
|
|
if (p == null) {
|
|
return null;
|
|
};
|
|
let bsize = (p: uintptr + WASTE: uintptr): *[1]size;
|
|
bsize[0] = n;
|
|
return (p: uintptr + WASTE: uintptr + WORD: uintptr): nullable *void;
|
|
};
|
|
|
|
fn malloc_small(n: size) nullable *void = {
|
|
const b = size2bin(n);
|
|
let p = bins[b];
|
|
if (p == null) {
|
|
p = fill_bin(b);
|
|
if (p != null) {
|
|
bins[b] = p;
|
|
};
|
|
};
|
|
return if (p != null) {
|
|
let q = *(p: **void);
|
|
bins[b] = q;
|
|
p;
|
|
} else null;
|
|
};
|
|
|
|
fn fill_bin(b: size) nullable *void = {
|
|
const s = bin2size(b);
|
|
let p = segmalloc(BIGBLOCK);
|
|
return if (p == null) null else list_from_block(s, p: uintptr);
|
|
};
|
|
|
|
fn list_from_block(s: size, p: uintptr) nullable *void = {
|
|
const nblocks = (BIGBLOCK - WASTE) / (s + WORD);
|
|
|
|
let q = p + WASTE: uintptr; // align q+WORD
|
|
for (let j = 0z; j != nblocks - 1; j += 1) {
|
|
let sz = q: *size;
|
|
let useralloc = q + WORD: uintptr; // aligned
|
|
let next = (useralloc + s: uintptr + WORD: uintptr): *void;
|
|
*sz = s;
|
|
*(useralloc: **void) = next;
|
|
q += s: uintptr + WORD: uintptr;
|
|
};
|
|
|
|
// Terminate last block:
|
|
(q: *[1]size)[0] = s;
|
|
*((q + 1: uintptr): *nullable *void) = null;
|
|
|
|
// Return first block:
|
|
return (p + WASTE: uintptr + WORD: uintptr): *void;
|
|
};
|
|
|
|
// Frees a pointer previously allocated with [malloc].
|
|
export @symbol("rt.free") fn free_(_p: nullable *void) void = {
|
|
if (_p != null) {
|
|
let p = _p: *void;
|
|
let bsize = (p: uintptr - size(size): uintptr): *[1]size;
|
|
let s = bsize[0];
|
|
if (s <= bin2size(len(bins) - 1)) free_small(p, s)
|
|
else free_large(p, s);
|
|
};
|
|
};
|
|
|
|
fn free_large(_p: *void, s: size) void = {
|
|
let p = (_p: uintptr - (WASTE: uintptr + WORD: uintptr)): *void;
|
|
segfree(p, s + WASTE + WORD);
|
|
};
|
|
|
|
fn free_small(p: *void, s: size) void = {
|
|
let b = size2bin(s);
|
|
let q = bins[b];
|
|
*(p: **void) = q;
|
|
bins[b] = p: nullable *void;
|
|
};
|
|
|
|
// Changes the allocation size of a pointer to n bytes. If n is smaller than
|
|
// the prior allocation, it is truncated; otherwise the allocation is expanded
|
|
// and the values of the new bytes are undefined. May return a different pointer
|
|
// than the one given if there is insufficient space to expand the pointer
|
|
// in-place. Returns null if there is insufficient memory to support the
|
|
// request.
|
|
export fn realloc(_p: nullable *void, n: size) nullable *void = {
|
|
if (n == 0) {
|
|
free_(_p);
|
|
return null;
|
|
} else if (_p == null) {
|
|
return malloc(n);
|
|
};
|
|
|
|
let p = _p: *void;
|
|
let bsize = (p: uintptr - size(size): uintptr): *size;
|
|
let s = *bsize;
|
|
if (s >= n) {
|
|
return p;
|
|
};
|
|
|
|
if (n < bin2size(len(bins) - 1) && size2bin(n) == size2bin(s)) {
|
|
return p;
|
|
};
|
|
|
|
let new = malloc(n);
|
|
if (new != null) {
|
|
memcpy(new: *void, p, s);
|
|
free(p);
|
|
};
|
|
|
|
return new;
|
|
};
|