blob: 207b6ef2c2c2e481fbf4a56dd6b1143a5d6824f6 [file] [log] [blame]
Rich Felkerb052f132011-04-20 15:55:58 -04001#define _GNU_SOURCE
Rich Felker0b44a032011-02-12 00:22:29 -05002#include <stdlib.h>
3#include <string.h>
4#include <limits.h>
5#include <stdint.h>
6#include <errno.h>
7#include <sys/mman.h>
8#include "libc.h"
9#include "atomic.h"
10#include "pthread_impl.h"
11
12uintptr_t __brk(uintptr_t);
13void *__mmap(void *, size_t, int, int, int, off_t);
14int __munmap(void *, size_t);
15void *__mremap(void *, size_t, size_t, int, ...);
16int __madvise(void *, size_t, int);
17
18struct chunk {
19 size_t data[1];
20 struct chunk *next;
21 struct chunk *prev;
22};
23
24struct bin {
25 int lock[2];
26 struct chunk *head;
27 struct chunk *tail;
28};
29
30static struct {
31 uintptr_t brk;
32 size_t *heap;
33 uint64_t binmap;
34 struct bin bins[64];
35 int brk_lock[2];
36 int free_lock[2];
37} mal;
38
39
40#define SIZE_ALIGN (4*sizeof(size_t))
41#define SIZE_MASK (-SIZE_ALIGN)
42#define OVERHEAD (2*sizeof(size_t))
43#define MMAP_THRESHOLD (0x1c00*SIZE_ALIGN)
44#define DONTCARE 16
45#define RECLAIM 163840
46
47#define CHUNK_SIZE(c) ((c)->data[0] & SIZE_MASK)
48#define CHUNK_PSIZE(c) ((c)->data[-1] & SIZE_MASK)
49#define PREV_CHUNK(c) ((struct chunk *)((char *)(c) - CHUNK_PSIZE(c)))
50#define NEXT_CHUNK(c) ((struct chunk *)((char *)(c) + CHUNK_SIZE(c)))
51#define MEM_TO_CHUNK(p) (struct chunk *)((size_t *)p - 1)
52#define CHUNK_TO_MEM(c) (void *)((c)->data+1)
53#define BIN_TO_CHUNK(i) (MEM_TO_CHUNK(&mal.bins[i].head))
54
55#define C_INUSE ((size_t)1)
56#define C_FLAGS ((size_t)3)
57#define C_SIZE SIZE_MASK
58
59#define IS_MMAPPED(c) !((c)->data[0] & (C_INUSE))
60
61
62/* Synchronization tools */
63
64static void lock(volatile int *lk)
65{
66 if (!libc.threads_minus_1) return;
67 while(a_swap(lk, 1)) __wait(lk, lk+1, 1, 1);
68}
69
70static void unlock(volatile int *lk)
71{
72 if (!libc.threads_minus_1) return;
73 a_store(lk, 0);
74 if (lk[1]) __wake(lk, 1, 1);
75}
76
77static void lock_bin(int i)
78{
79 if (libc.threads_minus_1)
80 lock(mal.bins[i].lock);
81 if (!mal.bins[i].head)
82 mal.bins[i].head = mal.bins[i].tail = BIN_TO_CHUNK(i);
83}
84
85static void unlock_bin(int i)
86{
87 if (!libc.threads_minus_1) return;
88 unlock(mal.bins[i].lock);
89}
90
91static int first_set(uint64_t x)
92{
93#if 1
94 return a_ctz_64(x);
95#else
96 static const char debruijn64[64] = {
97 0, 1, 2, 53, 3, 7, 54, 27, 4, 38, 41, 8, 34, 55, 48, 28,
98 62, 5, 39, 46, 44, 42, 22, 9, 24, 35, 59, 56, 49, 18, 29, 11,
99 63, 52, 6, 26, 37, 40, 33, 47, 61, 45, 43, 21, 23, 58, 17, 10,
100 51, 25, 36, 32, 60, 20, 57, 16, 50, 31, 19, 15, 30, 14, 13, 12
101 };
102 static const char debruijn32[32] = {
103 0, 1, 23, 2, 29, 24, 19, 3, 30, 27, 25, 11, 20, 8, 4, 13,
104 31, 22, 28, 18, 26, 10, 7, 12, 21, 17, 9, 6, 16, 5, 15, 14
105 };
106 if (sizeof(long) < 8) {
107 uint32_t y = x;
108 if (!y) {
109 y = x>>32;
110 return 32 + debruijn32[(y&-y)*0x076be629 >> 27];
111 }
112 return debruijn32[(y&-y)*0x076be629 >> 27];
113 }
114 return debruijn64[(x&-x)*0x022fdd63cc95386dull >> 58];
115#endif
116}
117
118static int bin_index(size_t x)
119{
120 x = x / SIZE_ALIGN - 1;
121 if (x <= 32) return x;
122 if (x > 0x1c00) return 63;
Rich Felker2afebbb2011-06-12 10:53:42 -0400123 return ((union { float v; uint32_t r; }){(int)x}.r>>21) - 496;
Rich Felker0b44a032011-02-12 00:22:29 -0500124}
125
126static int bin_index_up(size_t x)
127{
128 x = x / SIZE_ALIGN - 1;
129 if (x <= 32) return x;
Rich Felker2afebbb2011-06-12 10:53:42 -0400130 return ((union { float v; uint32_t r; }){(int)x}.r+0x1fffff>>21) - 496;
Rich Felker0b44a032011-02-12 00:22:29 -0500131}
132
133#if 0
134void __dump_heap(int x)
135{
136 struct chunk *c;
137 int i;
138 for (c = (void *)mal.heap; CHUNK_SIZE(c); c = NEXT_CHUNK(c))
139 fprintf(stderr, "base %p size %zu (%d) flags %d/%d\n",
140 c, CHUNK_SIZE(c), bin_index(CHUNK_SIZE(c)),
141 c->data[0] & 15,
142 NEXT_CHUNK(c)->data[-1] & 15);
143 for (i=0; i<64; i++) {
144 if (mal.bins[i].head != BIN_TO_CHUNK(i) && mal.bins[i].head) {
145 fprintf(stderr, "bin %d: %p\n", i, mal.bins[i].head);
146 if (!(mal.binmap & 1ULL<<i))
147 fprintf(stderr, "missing from binmap!\n");
148 } else if (mal.binmap & 1ULL<<i)
149 fprintf(stderr, "binmap wrongly contains %d!\n", i);
150 }
151}
152#endif
153
154static struct chunk *expand_heap(size_t n)
155{
156 struct chunk *w;
157 uintptr_t new;
158
159 lock(mal.brk_lock);
160
161 if (n > SIZE_MAX - mal.brk - 2*PAGE_SIZE) goto fail;
162 new = mal.brk + n + SIZE_ALIGN + PAGE_SIZE - 1 & -PAGE_SIZE;
163 n = new - mal.brk;
164
165 if (__brk(new) != new) goto fail;
166
167 w = MEM_TO_CHUNK(new);
168 w->data[-1] = n | C_INUSE;
169 w->data[0] = 0 | C_INUSE;
170
171 w = MEM_TO_CHUNK(mal.brk);
172 w->data[0] = n | C_INUSE;
173 mal.brk = new;
174
175 unlock(mal.brk_lock);
176
177 return w;
178fail:
179 unlock(mal.brk_lock);
180 return 0;
181}
182
Rich Felkerbf878582011-04-01 23:07:03 -0400183static int init_malloc(size_t n)
Rich Felker0b44a032011-02-12 00:22:29 -0500184{
185 static int init, waiters;
186 int state;
187 struct chunk *c;
188
189 if (init == 2) return 0;
190
191 while ((state=a_swap(&init, 1)) == 1)
192 __wait(&init, &waiters, 1, 1);
193 if (state) {
194 a_store(&init, 2);
195 return 0;
196 }
197
198 mal.brk = __brk(0) + 2*SIZE_ALIGN-1 & -SIZE_ALIGN;
199
Rich Felkerbf878582011-04-01 23:07:03 -0400200 c = expand_heap(n);
Rich Felker0b44a032011-02-12 00:22:29 -0500201
202 if (!c) {
203 a_store(&init, 0);
204 if (waiters) __wake(&init, 1, 1);
205 return -1;
206 }
207
208 mal.heap = (void *)c;
209 c->data[-1] = 0 | C_INUSE;
210 free(CHUNK_TO_MEM(c));
211
212 a_store(&init, 2);
213 if (waiters) __wake(&init, -1, 1);
Rich Felkerbf878582011-04-01 23:07:03 -0400214 return 1;
Rich Felker0b44a032011-02-12 00:22:29 -0500215}
216
217static int adjust_size(size_t *n)
218{
219 /* Result of pointer difference must fit in ptrdiff_t. */
Rich Felker26031da2011-02-20 16:16:33 -0500220 if (*n-1 > PTRDIFF_MAX - SIZE_ALIGN - PAGE_SIZE) {
221 if (*n) {
222 errno = ENOMEM;
223 return -1;
224 } else {
225 *n = SIZE_ALIGN;
226 return 0;
227 }
Rich Felker0b44a032011-02-12 00:22:29 -0500228 }
229 *n = (*n + OVERHEAD + SIZE_ALIGN - 1) & SIZE_MASK;
230 return 0;
231}
232
233static void unbin(struct chunk *c, int i)
234{
235 if (c->prev == c->next)
236 a_and_64(&mal.binmap, ~(1ULL<<i));
237 c->prev->next = c->next;
238 c->next->prev = c->prev;
239 c->data[0] |= C_INUSE;
240 NEXT_CHUNK(c)->data[-1] |= C_INUSE;
241}
242
243static int alloc_fwd(struct chunk *c)
244{
245 int i;
246 size_t k;
247 while (!((k=c->data[0]) & C_INUSE)) {
248 i = bin_index(k);
249 lock_bin(i);
250 if (c->data[0] == k) {
251 unbin(c, i);
252 unlock_bin(i);
253 return 1;
254 }
255 unlock_bin(i);
256 }
257 return 0;
258}
259
260static int alloc_rev(struct chunk *c)
261{
262 int i;
263 size_t k;
264 while (!((k=c->data[-1]) & C_INUSE)) {
265 i = bin_index(k);
266 lock_bin(i);
267 if (c->data[-1] == k) {
268 unbin(PREV_CHUNK(c), i);
269 unlock_bin(i);
270 return 1;
271 }
272 unlock_bin(i);
273 }
274 return 0;
275}
276
277
278/* pretrim - trims a chunk _prior_ to removing it from its bin.
279 * Must be called with i as the ideal bin for size n, j the bin
280 * for the _free_ chunk self, and bin j locked. */
281static int pretrim(struct chunk *self, size_t n, int i, int j)
282{
283 size_t n1;
284 struct chunk *next, *split;
285
286 /* We cannot pretrim if it would require re-binning. */
287 if (j < 40) return 0;
288 if (j < i+3) {
289 if (j != 63) return 0;
290 n1 = CHUNK_SIZE(self);
291 if (n1-n <= MMAP_THRESHOLD) return 0;
292 } else {
293 n1 = CHUNK_SIZE(self);
294 }
295 if (bin_index(n1-n) != j) return 0;
296
297 next = NEXT_CHUNK(self);
298 split = (void *)((char *)self + n);
299
300 split->prev = self->prev;
301 split->next = self->next;
302 split->prev->next = split;
303 split->next->prev = split;
304 split->data[-1] = n | C_INUSE;
305 split->data[0] = n1-n;
306 next->data[-1] = n1-n;
307 self->data[0] = n | C_INUSE;
308 return 1;
309}
310
311static void trim(struct chunk *self, size_t n)
312{
313 size_t n1 = CHUNK_SIZE(self);
314 struct chunk *next, *split;
315
316 if (n >= n1 - DONTCARE) return;
317
318 next = NEXT_CHUNK(self);
319 split = (void *)((char *)self + n);
320
321 split->data[-1] = n | C_INUSE;
322 split->data[0] = n1-n | C_INUSE;
323 next->data[-1] = n1-n | C_INUSE;
324 self->data[0] = n | C_INUSE;
325
326 free(CHUNK_TO_MEM(split));
327}
328
329void *malloc(size_t n)
330{
331 struct chunk *c;
332 int i, j;
333
Rich Felker26031da2011-02-20 16:16:33 -0500334 if (adjust_size(&n) < 0) return 0;
Rich Felker0b44a032011-02-12 00:22:29 -0500335
336 if (n > MMAP_THRESHOLD) {
Rich Felkerb761bd12011-04-04 17:26:41 -0400337 size_t len = n + OVERHEAD + PAGE_SIZE - 1 & -PAGE_SIZE;
Rich Felker0b44a032011-02-12 00:22:29 -0500338 char *base = __mmap(0, len, PROT_READ|PROT_WRITE,
339 MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
340 if (base == (void *)-1) return 0;
341 c = (void *)(base + SIZE_ALIGN - sizeof(size_t));
342 c->data[0] = len - (SIZE_ALIGN - sizeof(size_t));
343 c->data[-1] = SIZE_ALIGN - sizeof(size_t);
344 return CHUNK_TO_MEM(c);
345 }
346
347 i = bin_index_up(n);
348 for (;;) {
349 uint64_t mask = mal.binmap & -(1ULL<<i);
350 if (!mask) {
Rich Felkerbf878582011-04-01 23:07:03 -0400351 if (init_malloc(n) > 0) continue;
Rich Felker0b44a032011-02-12 00:22:29 -0500352 c = expand_heap(n);
353 if (!c) return 0;
354 if (alloc_rev(c)) {
355 struct chunk *x = c;
356 c = PREV_CHUNK(c);
357 NEXT_CHUNK(x)->data[-1] = c->data[0] =
358 x->data[0] + CHUNK_SIZE(c);
359 }
360 break;
361 }
362 j = first_set(mask);
363 lock_bin(j);
364 c = mal.bins[j].head;
365 if (c != BIN_TO_CHUNK(j) && j == bin_index(c->data[0])) {
366 if (!pretrim(c, n, i, j)) unbin(c, j);
367 unlock_bin(j);
368 break;
369 }
370 unlock_bin(j);
371 }
372
373 /* Now patch up in case we over-allocated */
374 trim(c, n);
375
376 return CHUNK_TO_MEM(c);
377}
378
379void *realloc(void *p, size_t n)
380{
381 struct chunk *self, *next;
382 size_t n0, n1;
383 void *new;
384
385 if (!p) return malloc(n);
Rich Felker0b44a032011-02-12 00:22:29 -0500386
387 if (adjust_size(&n) < 0) return 0;
388
389 self = MEM_TO_CHUNK(p);
390 n1 = n0 = CHUNK_SIZE(self);
391
392 if (IS_MMAPPED(self)) {
393 size_t extra = self->data[-1];
394 char *base = (char *)self - extra;
395 size_t oldlen = n0 + extra;
396 size_t newlen = n + extra;
Rich Felker09582002011-03-23 13:24:00 -0400397 /* Crash on realloc of freed chunk */
Rich Felker71a80c52011-06-06 18:10:43 -0400398 if ((uintptr_t)base < mal.brk) *(volatile char *)0=0;
Rich Felker0b44a032011-02-12 00:22:29 -0500399 if (newlen < PAGE_SIZE && (new = malloc(n))) {
400 memcpy(new, p, n-OVERHEAD);
401 free(p);
402 return new;
403 }
404 newlen = (newlen + PAGE_SIZE-1) & -PAGE_SIZE;
405 if (oldlen == newlen) return p;
406 base = __mremap(base, oldlen, newlen, MREMAP_MAYMOVE);
407 if (base == (void *)-1)
408 return newlen < oldlen ? p : 0;
409 self = (void *)(base + extra);
410 self->data[0] = newlen - extra;
411 return CHUNK_TO_MEM(self);
412 }
413
414 next = NEXT_CHUNK(self);
415
416 /* Merge adjacent chunks if we need more space. This is not
417 * a waste of time even if we fail to get enough space, because our
418 * subsequent call to free would otherwise have to do the merge. */
419 if (n > n1 && alloc_fwd(next)) {
420 n1 += CHUNK_SIZE(next);
421 next = NEXT_CHUNK(next);
422 }
423 /* FIXME: find what's wrong here and reenable it..? */
424 if (0 && n > n1 && alloc_rev(self)) {
425 self = PREV_CHUNK(self);
426 n1 += CHUNK_SIZE(self);
427 }
428 self->data[0] = n1 | C_INUSE;
429 next->data[-1] = n1 | C_INUSE;
430
431 /* If we got enough space, split off the excess and return */
432 if (n <= n1) {
433 //memmove(CHUNK_TO_MEM(self), p, n0-OVERHEAD);
434 trim(self, n);
435 return CHUNK_TO_MEM(self);
436 }
437
438 /* As a last resort, allocate a new chunk and copy to it. */
439 new = malloc(n-OVERHEAD);
440 if (!new) return 0;
441 memcpy(new, p, n0-OVERHEAD);
442 free(CHUNK_TO_MEM(self));
443 return new;
444}
445
446void free(void *p)
447{
448 struct chunk *self = MEM_TO_CHUNK(p);
449 struct chunk *next;
450 size_t final_size, new_size, size;
451 int reclaim=0;
452 int i;
453
454 if (!p) return;
455
456 if (IS_MMAPPED(self)) {
457 size_t extra = self->data[-1];
458 char *base = (char *)self - extra;
459 size_t len = CHUNK_SIZE(self) + extra;
Rich Felker09582002011-03-23 13:24:00 -0400460 /* Crash on double free */
Rich Felker71a80c52011-06-06 18:10:43 -0400461 if ((uintptr_t)base < mal.brk) *(volatile char *)0=0;
Rich Felker0b44a032011-02-12 00:22:29 -0500462 __munmap(base, len);
463 return;
464 }
465
466 final_size = new_size = CHUNK_SIZE(self);
467 next = NEXT_CHUNK(self);
468
469 for (;;) {
470 /* Replace middle of large chunks with fresh zero pages */
471 if (reclaim && (self->data[-1] & next->data[0] & C_INUSE)) {
472 uintptr_t a = (uintptr_t)self + SIZE_ALIGN+PAGE_SIZE-1 & -PAGE_SIZE;
473 uintptr_t b = (uintptr_t)next - SIZE_ALIGN & -PAGE_SIZE;
474#if 1
475 __madvise((void *)a, b-a, MADV_DONTNEED);
476#else
477 __mmap((void *)a, b-a, PROT_READ|PROT_WRITE,
478 MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0);
479#endif
480 }
481
482 if (self->data[-1] & next->data[0] & C_INUSE) {
483 self->data[0] = final_size | C_INUSE;
484 next->data[-1] = final_size | C_INUSE;
485 i = bin_index(final_size);
486 lock_bin(i);
487 lock(mal.free_lock);
488 if (self->data[-1] & next->data[0] & C_INUSE)
489 break;
490 unlock(mal.free_lock);
491 unlock_bin(i);
492 }
493
494 if (alloc_rev(self)) {
495 self = PREV_CHUNK(self);
496 size = CHUNK_SIZE(self);
497 final_size += size;
498 if (new_size+size > RECLAIM && (new_size+size^size) > size)
499 reclaim = 1;
500 }
501
502 if (alloc_fwd(next)) {
503 size = CHUNK_SIZE(next);
504 final_size += size;
505 if (new_size+size > RECLAIM && (new_size+size^size) > size)
506 reclaim = 1;
507 next = NEXT_CHUNK(next);
508 }
509 }
510
511 self->data[0] = final_size;
512 next->data[-1] = final_size;
513 unlock(mal.free_lock);
514
515 self->next = BIN_TO_CHUNK(i);
516 self->prev = mal.bins[i].tail;
517 self->next->prev = self;
518 self->prev->next = self;
519
520 if (!(mal.binmap & 1ULL<<i))
521 a_or_64(&mal.binmap, 1ULL<<i);
522
523 unlock_bin(i);
524}