Skip to content

Commit 503bd39

Browse files
committed
import mallocng
the files added come from the mallocng development repo, commit 2ed58817cca5bc055974e5a0e43c280d106e696b. they comprise a new malloc implementation, developed over the past 9 months, to replace the old allocator (since dubbed "oldmalloc") with one that retains low code size and minimal baseline memory overhead while avoiding fundamental flaws in oldmalloc and making significant enhancements. these include highly controlled fragmentation, fine-grained ability to return memory to the system when freed, and strong hardening against dynamic memory usage errors by the caller. internally, mallocng derives most of these properties from tightly structuring memory, creating space for allocations as uniform-sized slots within individually mmapped (and individually freeable) allocation groups. smaller-than-pagesize groups are created within slots of larger ones. minimal group size is very small, and larger sizes (in geometric progression) only come into play when usage is high. all data necessary for maintaining consistency of the allocator state is tracked in out-of-band metadata, reachable via a validated path from minimal in-band metadata. all pointers passed (to free, etc.) are validated before any stores to memory take place. early reuse of freed slots is avoided via approximate LRU order of freed slots. further hardening against use-after-free and double-free, even in the case where the freed slot has been reused, is made by cycling the offset within the slot at which the allocation is placed; this is possible whenever the slot size is larger than the requested allocation.
1 parent 785752a commit 503bd39

File tree

7 files changed

+938
-13
lines changed

7 files changed

+938
-13
lines changed

src/malloc/mallocng/README.mallocng

-13
This file was deleted.

src/malloc/mallocng/aligned_alloc.c

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#include <stdlib.h>
2+
#include <errno.h>
3+
#include "meta.h"
4+
5+
void *aligned_alloc(size_t align, size_t len)
6+
{
7+
if ((align & -align) != align) {
8+
errno = EINVAL;
9+
return 0;
10+
}
11+
12+
if (len > SIZE_MAX - align || align >= (1ULL<<31)*UNIT) {
13+
errno = ENOMEM;
14+
return 0;
15+
}
16+
17+
if (DISABLE_ALIGNED_ALLOC) {
18+
errno = ENOMEM;
19+
return 0;
20+
}
21+
22+
if (align <= UNIT) align = UNIT;
23+
24+
unsigned char *p = malloc(len + align - UNIT);
25+
struct meta *g = get_meta(p);
26+
int idx = get_slot_index(p);
27+
size_t stride = get_stride(g);
28+
unsigned char *start = g->mem->storage + stride*idx;
29+
unsigned char *end = g->mem->storage + stride*(idx+1) - IB;
30+
size_t adj = -(uintptr_t)p & (align-1);
31+
32+
if (!adj) {
33+
set_size(p, end, len);
34+
return p;
35+
}
36+
p += adj;
37+
uint32_t offset = (size_t)(p-g->mem->storage)/UNIT;
38+
if (offset <= 0xffff) {
39+
*(uint16_t *)(p-2) = offset;
40+
p[-4] = 0;
41+
} else {
42+
// use a 32-bit offset if 16-bit doesn't fit. for this,
43+
// 16-bit field must be zero, [-4] byte nonzero.
44+
*(uint16_t *)(p-2) = 0;
45+
*(uint32_t *)(p-8) = offset;
46+
p[-4] = 1;
47+
}
48+
p[-3] = idx;
49+
set_size(p, end, len);
50+
// store offset to aligned enframing. this facilitates cycling
51+
// offset and also iteration of heap for debugging/measurement.
52+
// for extreme overalignment it won't fit but these are classless
53+
// allocations anyway.
54+
*(uint16_t *)(start - 2) = (size_t)(p-start)/UNIT;
55+
start[-3] = 7<<5;
56+
return p;
57+
}

src/malloc/mallocng/free.c

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
#define _BSD_SOURCE
2+
#include <stdlib.h>
3+
#include <sys/mman.h>
4+
5+
#include "meta.h"
6+
7+
struct mapinfo {
8+
void *base;
9+
size_t len;
10+
};
11+
12+
static struct mapinfo nontrivial_free(struct meta *, int);
13+
14+
static struct mapinfo free_group(struct meta *g)
15+
{
16+
struct mapinfo mi = { 0 };
17+
int sc = g->sizeclass;
18+
if (sc < 48) {
19+
ctx.usage_by_class[sc] -= g->last_idx+1;
20+
}
21+
if (g->maplen) {
22+
step_seq();
23+
record_seq(sc);
24+
mi.base = g->mem;
25+
mi.len = g->maplen*4096UL;
26+
} else {
27+
void *p = g->mem;
28+
struct meta *m = get_meta(p);
29+
int idx = get_slot_index(p);
30+
g->mem->meta = 0;
31+
// not checking size/reserved here; it's intentionally invalid
32+
mi = nontrivial_free(m, idx);
33+
}
34+
free_meta(g);
35+
return mi;
36+
}
37+
38+
static int okay_to_free(struct meta *g)
39+
{
40+
int sc = g->sizeclass;
41+
42+
if (!g->freeable) return 0;
43+
44+
// always free individual mmaps not suitable for reuse
45+
if (sc >= 48 || get_stride(g) < UNIT*size_classes[sc])
46+
return 1;
47+
48+
// always free groups allocated inside another group's slot
49+
// since recreating them should not be expensive and they
50+
// might be blocking freeing of a much larger group.
51+
if (!g->maplen) return 1;
52+
53+
// if there is another non-full group, free this one to
54+
// consolidate future allocations, reduce fragmentation.
55+
if (g->next != g) return 1;
56+
57+
// free any group in a size class that's not bouncing
58+
if (!is_bouncing(sc)) return 1;
59+
60+
size_t cnt = g->last_idx+1;
61+
size_t usage = ctx.usage_by_class[sc];
62+
63+
// if usage is high enough that a larger count should be
64+
// used, free the low-count group so a new one will be made.
65+
if (9*cnt <= usage && cnt < 20)
66+
return 1;
67+
68+
// otherwise, keep the last group in a bouncing class.
69+
return 0;
70+
}
71+
72+
static struct mapinfo nontrivial_free(struct meta *g, int i)
73+
{
74+
uint32_t self = 1u<<i;
75+
int sc = g->sizeclass;
76+
uint32_t mask = g->freed_mask | g->avail_mask;
77+
78+
if (mask+self == (2u<<g->last_idx)-1 && okay_to_free(g)) {
79+
// any multi-slot group is necessarily on an active list
80+
// here, but single-slot groups might or might not be.
81+
if (g->next) {
82+
assert(sc < 48);
83+
int activate_new = (ctx.active[sc]==g);
84+
dequeue(&ctx.active[sc], g);
85+
if (activate_new && ctx.active[sc])
86+
activate_group(ctx.active[sc]);
87+
}
88+
return free_group(g);
89+
} else if (!mask) {
90+
assert(sc < 48);
91+
// might still be active if there were no allocations
92+
// after last available slot was taken.
93+
if (ctx.active[sc] != g) {
94+
queue(&ctx.active[sc], g);
95+
}
96+
}
97+
a_or(&g->freed_mask, self);
98+
return (struct mapinfo){ 0 };
99+
}
100+
101+
void free(void *p)
102+
{
103+
if (!p) return;
104+
105+
struct meta *g = get_meta(p);
106+
int idx = get_slot_index(p);
107+
size_t stride = get_stride(g);
108+
unsigned char *start = g->mem->storage + stride*idx;
109+
unsigned char *end = start + stride - IB;
110+
get_nominal_size(p, end);
111+
uint32_t self = 1u<<idx, all = (2u<<g->last_idx)-1;
112+
((unsigned char *)p)[-3] = 255;
113+
// invalidate offset to group header, and cycle offset of
114+
// used region within slot if current offset is zero.
115+
*(uint16_t *)((char *)p-2) = 0;
116+
117+
// release any whole pages contained in the slot to be freed
118+
// unless it's a single-slot group that will be unmapped.
119+
if (((uintptr_t)(start-1) ^ (uintptr_t)end) >= 2*PGSZ && g->last_idx) {
120+
unsigned char *base = start + (-(uintptr_t)start & (PGSZ-1));
121+
size_t len = (end-base) & -PGSZ;
122+
if (len) madvise(base, len, MADV_FREE);
123+
}
124+
125+
// atomic free without locking if this is neither first or last slot
126+
for (;;) {
127+
uint32_t freed = g->freed_mask;
128+
uint32_t avail = g->avail_mask;
129+
uint32_t mask = freed | avail;
130+
assert(!(mask&self));
131+
if (!freed || mask+self==all) break;
132+
if (!MT)
133+
g->freed_mask = freed+self;
134+
else if (a_cas(&g->freed_mask, freed, freed+self)!=freed)
135+
continue;
136+
return;
137+
}
138+
139+
wrlock();
140+
struct mapinfo mi = nontrivial_free(g, idx);
141+
unlock();
142+
if (mi.len) munmap(mi.base, mi.len);
143+
}

0 commit comments

Comments
 (0)