1 #define JEMALLOC_CHUNK_C_
2 #include "jemalloc/internal/jemalloc_internal.h"
3
4 /******************************************************************************/
5 /* Data. */
6
7 const char *opt_dss = DSS_DEFAULT;
8 size_t opt_lg_chunk = LG_CHUNK_DEFAULT;
9
10 /* Used exclusively for gdump triggering. */
11 static size_t curchunks;
12 static size_t highchunks;
13
14 rtree_t chunks_rtree;
15
16 /* Various chunk-related settings. */
17 size_t chunksize;
18 size_t chunksize_mask; /* (chunksize - 1). */
19 size_t chunk_npages;
20
21 /******************************************************************************/
22
23 bool
chunk_register(const void * chunk,const extent_node_t * node)24 chunk_register(const void *chunk, const extent_node_t *node)
25 {
26
27 assert(extent_node_addr_get(node) == chunk);
28
29 if (rtree_set(&chunks_rtree, (uintptr_t)chunk, node))
30 return (true);
31 if (config_prof && opt_prof) {
32 size_t size = extent_node_size_get(node);
33 size_t nadd = (size == 0) ? 1 : size / chunksize;
34 size_t cur = atomic_add_z(&curchunks, nadd);
35 size_t high = atomic_read_z(&highchunks);
36 while (cur > high && atomic_cas_z(&highchunks, high, cur)) {
37 /*
38 * Don't refresh cur, because it may have decreased
39 * since this thread lost the highchunks update race.
40 */
41 high = atomic_read_z(&highchunks);
42 }
43 if (cur > high && prof_gdump_get_unlocked())
44 prof_gdump();
45 }
46
47 return (false);
48 }
49
50 void
chunk_deregister(const void * chunk,const extent_node_t * node)51 chunk_deregister(const void *chunk, const extent_node_t *node)
52 {
53 bool err;
54
55 err = rtree_set(&chunks_rtree, (uintptr_t)chunk, NULL);
56 assert(!err);
57 if (config_prof && opt_prof) {
58 size_t size = extent_node_size_get(node);
59 size_t nsub = (size == 0) ? 1 : size / chunksize;
60 assert(atomic_read_z(&curchunks) >= nsub);
61 atomic_sub_z(&curchunks, nsub);
62 }
63 }
64
65 /*
66 * Do first-best-fit chunk selection, i.e. select the lowest chunk that best
67 * fits.
68 */
69 static extent_node_t *
chunk_first_best_fit(arena_t * arena,extent_tree_t * chunks_szad,extent_tree_t * chunks_ad,size_t size)70 chunk_first_best_fit(arena_t *arena, extent_tree_t *chunks_szad,
71 extent_tree_t *chunks_ad, size_t size)
72 {
73 extent_node_t key;
74
75 assert(size == CHUNK_CEILING(size));
76
77 extent_node_init(&key, arena, NULL, size, false);
78 return (extent_tree_szad_nsearch(chunks_szad, &key));
79 }
80
81 static void *
chunk_recycle(arena_t * arena,extent_tree_t * chunks_szad,extent_tree_t * chunks_ad,bool cache,void * new_addr,size_t size,size_t alignment,bool * zero,bool dalloc_node)82 chunk_recycle(arena_t *arena, extent_tree_t *chunks_szad,
83 extent_tree_t *chunks_ad, bool cache, void *new_addr, size_t size,
84 size_t alignment, bool *zero, bool dalloc_node)
85 {
86 void *ret;
87 extent_node_t *node;
88 size_t alloc_size, leadsize, trailsize;
89 bool zeroed;
90
91 assert(new_addr == NULL || alignment == chunksize);
92 assert(dalloc_node || new_addr != NULL);
93
94 alloc_size = CHUNK_CEILING(s2u(size + alignment - chunksize));
95 /* Beware size_t wrap-around. */
96 if (alloc_size < size)
97 return (NULL);
98 malloc_mutex_lock(&arena->chunks_mtx);
99 if (new_addr != NULL) {
100 extent_node_t key;
101 extent_node_init(&key, arena, new_addr, alloc_size, false);
102 node = extent_tree_ad_search(chunks_ad, &key);
103 } else {
104 node = chunk_first_best_fit(arena, chunks_szad, chunks_ad,
105 alloc_size);
106 }
107 if (node == NULL || (new_addr != NULL && extent_node_size_get(node) <
108 size)) {
109 malloc_mutex_unlock(&arena->chunks_mtx);
110 return (NULL);
111 }
112 leadsize = ALIGNMENT_CEILING((uintptr_t)extent_node_addr_get(node),
113 alignment) - (uintptr_t)extent_node_addr_get(node);
114 assert(new_addr == NULL || leadsize == 0);
115 assert(extent_node_size_get(node) >= leadsize + size);
116 trailsize = extent_node_size_get(node) - leadsize - size;
117 ret = (void *)((uintptr_t)extent_node_addr_get(node) + leadsize);
118 zeroed = extent_node_zeroed_get(node);
119 if (zeroed)
120 *zero = true;
121 /* Remove node from the tree. */
122 extent_tree_szad_remove(chunks_szad, node);
123 extent_tree_ad_remove(chunks_ad, node);
124 arena_chunk_cache_maybe_remove(arena, node, cache);
125 if (leadsize != 0) {
126 /* Insert the leading space as a smaller chunk. */
127 extent_node_size_set(node, leadsize);
128 extent_tree_szad_insert(chunks_szad, node);
129 extent_tree_ad_insert(chunks_ad, node);
130 arena_chunk_cache_maybe_insert(arena, node, cache);
131 node = NULL;
132 }
133 if (trailsize != 0) {
134 /* Insert the trailing space as a smaller chunk. */
135 if (node == NULL) {
136 node = arena_node_alloc(arena);
137 if (node == NULL) {
138 malloc_mutex_unlock(&arena->chunks_mtx);
139 chunk_record(arena, chunks_szad, chunks_ad,
140 cache, ret, size, zeroed);
141 return (NULL);
142 }
143 }
144 extent_node_init(node, arena, (void *)((uintptr_t)(ret) + size),
145 trailsize, zeroed);
146 extent_tree_szad_insert(chunks_szad, node);
147 extent_tree_ad_insert(chunks_ad, node);
148 arena_chunk_cache_maybe_insert(arena, node, cache);
149 node = NULL;
150 }
151 malloc_mutex_unlock(&arena->chunks_mtx);
152
153 assert(dalloc_node || node != NULL);
154 if (dalloc_node && node != NULL)
155 arena_node_dalloc(arena, node);
156 if (*zero) {
157 if (!zeroed)
158 memset(ret, 0, size);
159 else if (config_debug) {
160 size_t i;
161 size_t *p = (size_t *)(uintptr_t)ret;
162
163 JEMALLOC_VALGRIND_MAKE_MEM_DEFINED(ret, size);
164 for (i = 0; i < size / sizeof(size_t); i++)
165 assert(p[i] == 0);
166 }
167 }
168 return (ret);
169 }
170
171 static void *
chunk_alloc_core_dss(arena_t * arena,void * new_addr,size_t size,size_t alignment,bool * zero)172 chunk_alloc_core_dss(arena_t *arena, void *new_addr, size_t size,
173 size_t alignment, bool *zero)
174 {
175 void *ret;
176
177 if ((ret = chunk_recycle(arena, &arena->chunks_szad_dss,
178 &arena->chunks_ad_dss, false, new_addr, size, alignment, zero,
179 true)) != NULL)
180 return (ret);
181 ret = chunk_alloc_dss(arena, new_addr, size, alignment, zero);
182 return (ret);
183 }
184
185 /*
186 * If the caller specifies (!*zero), it is still possible to receive zeroed
187 * memory, in which case *zero is toggled to true. arena_chunk_alloc() takes
188 * advantage of this to avoid demanding zeroed chunks, but taking advantage of
189 * them if they are returned.
190 */
191 static void *
chunk_alloc_core(arena_t * arena,void * new_addr,size_t size,size_t alignment,bool * zero,dss_prec_t dss_prec)192 chunk_alloc_core(arena_t *arena, void *new_addr, size_t size, size_t alignment,
193 bool *zero, dss_prec_t dss_prec)
194 {
195 void *ret;
196
197 assert(size != 0);
198 assert((size & chunksize_mask) == 0);
199 assert(alignment != 0);
200 assert((alignment & chunksize_mask) == 0);
201
202 /* "primary" dss. */
203 if (have_dss && dss_prec == dss_prec_primary && (ret =
204 chunk_alloc_core_dss(arena, new_addr, size, alignment, zero)) !=
205 NULL)
206 return (ret);
207 /* mmap. */
208 if (!config_munmap && (ret = chunk_recycle(arena,
209 &arena->chunks_szad_mmap, &arena->chunks_ad_mmap, false, new_addr,
210 size, alignment, zero, true)) != NULL)
211 return (ret);
212 /*
213 * Requesting an address is not implemented for chunk_alloc_mmap(), so
214 * only call it if (new_addr == NULL).
215 */
216 if (new_addr == NULL && (ret = chunk_alloc_mmap(size, alignment, zero))
217 != NULL)
218 return (ret);
219 /* "secondary" dss. */
220 if (have_dss && dss_prec == dss_prec_secondary && (ret =
221 chunk_alloc_core_dss(arena, new_addr, size, alignment, zero)) !=
222 NULL)
223 return (ret);
224
225 /* All strategies for allocation failed. */
226 return (NULL);
227 }
228
229 void *
chunk_alloc_base(size_t size)230 chunk_alloc_base(size_t size)
231 {
232 void *ret;
233 bool zero;
234
235 /*
236 * Directly call chunk_alloc_mmap() rather than chunk_alloc_core()
237 * because it's critical that chunk_alloc_base() return untouched
238 * demand-zeroed virtual memory.
239 */
240 zero = true;
241 ret = chunk_alloc_mmap(size, chunksize, &zero);
242 if (ret == NULL)
243 return (NULL);
244 if (config_valgrind)
245 JEMALLOC_VALGRIND_MAKE_MEM_UNDEFINED(ret, size);
246
247 return (ret);
248 }
249
250 void *
chunk_alloc_cache(arena_t * arena,void * new_addr,size_t size,size_t alignment,bool * zero,bool dalloc_node)251 chunk_alloc_cache(arena_t *arena, void *new_addr, size_t size, size_t alignment,
252 bool *zero, bool dalloc_node)
253 {
254
255 assert(size != 0);
256 assert((size & chunksize_mask) == 0);
257 assert(alignment != 0);
258 assert((alignment & chunksize_mask) == 0);
259
260 return (chunk_recycle(arena, &arena->chunks_szad_cache,
261 &arena->chunks_ad_cache, true, new_addr, size, alignment, zero,
262 dalloc_node));
263 }
264
265 static arena_t *
chunk_arena_get(unsigned arena_ind)266 chunk_arena_get(unsigned arena_ind)
267 {
268 arena_t *arena;
269
270 /* Dodge tsd for a0 in order to avoid bootstrapping issues. */
271 arena = (arena_ind == 0) ? a0get() : arena_get(tsd_fetch(), arena_ind,
272 false, true);
273 /*
274 * The arena we're allocating on behalf of must have been initialized
275 * already.
276 */
277 assert(arena != NULL);
278 return (arena);
279 }
280
281 static void *
chunk_alloc_arena(arena_t * arena,void * new_addr,size_t size,size_t alignment,bool * zero)282 chunk_alloc_arena(arena_t *arena, void *new_addr, size_t size, size_t alignment,
283 bool *zero)
284 {
285 void *ret;
286
287 ret = chunk_alloc_core(arena, new_addr, size, alignment, zero,
288 arena->dss_prec);
289 if (ret == NULL)
290 return (NULL);
291 if (config_valgrind)
292 JEMALLOC_VALGRIND_MAKE_MEM_UNDEFINED(ret, size);
293
294 return (ret);
295 }
296
297 /*
298 * Default arena chunk allocation routine in the absence of user override. This
299 * function isn't actually used by jemalloc, but it does the right thing if the
300 * application passes calls through to it during chunk allocation.
301 */
302 void *
chunk_alloc_default(void * new_addr,size_t size,size_t alignment,bool * zero,unsigned arena_ind)303 chunk_alloc_default(void *new_addr, size_t size, size_t alignment, bool *zero,
304 unsigned arena_ind)
305 {
306 arena_t *arena;
307
308 arena = chunk_arena_get(arena_ind);
309 return (chunk_alloc_arena(arena, new_addr, size, alignment, zero));
310 }
311
312 void *
chunk_alloc_wrapper(arena_t * arena,chunk_alloc_t * chunk_alloc,void * new_addr,size_t size,size_t alignment,bool * zero)313 chunk_alloc_wrapper(arena_t *arena, chunk_alloc_t *chunk_alloc, void *new_addr,
314 size_t size, size_t alignment, bool *zero)
315 {
316 void *ret;
317
318 ret = chunk_alloc(new_addr, size, alignment, zero, arena->ind);
319 if (ret == NULL)
320 return (NULL);
321 if (config_valgrind && chunk_alloc != chunk_alloc_default)
322 JEMALLOC_VALGRIND_MAKE_MEM_UNDEFINED(ret, chunksize);
323 return (ret);
324 }
325
326 void
chunk_record(arena_t * arena,extent_tree_t * chunks_szad,extent_tree_t * chunks_ad,bool cache,void * chunk,size_t size,bool zeroed)327 chunk_record(arena_t *arena, extent_tree_t *chunks_szad,
328 extent_tree_t *chunks_ad, bool cache, void *chunk, size_t size, bool zeroed)
329 {
330 bool unzeroed;
331 extent_node_t *node, *prev;
332 extent_node_t key;
333
334 assert(!cache || !zeroed);
335 unzeroed = cache || !zeroed;
336 JEMALLOC_VALGRIND_MAKE_MEM_NOACCESS(chunk, size);
337
338 malloc_mutex_lock(&arena->chunks_mtx);
339 extent_node_init(&key, arena, (void *)((uintptr_t)chunk + size), 0,
340 false);
341 node = extent_tree_ad_nsearch(chunks_ad, &key);
342 /* Try to coalesce forward. */
343 if (node != NULL && extent_node_addr_get(node) ==
344 extent_node_addr_get(&key)) {
345 /*
346 * Coalesce chunk with the following address range. This does
347 * not change the position within chunks_ad, so only
348 * remove/insert from/into chunks_szad.
349 */
350 extent_tree_szad_remove(chunks_szad, node);
351 arena_chunk_cache_maybe_remove(arena, node, cache);
352 extent_node_addr_set(node, chunk);
353 extent_node_size_set(node, size + extent_node_size_get(node));
354 extent_node_zeroed_set(node, extent_node_zeroed_get(node) &&
355 !unzeroed);
356 extent_tree_szad_insert(chunks_szad, node);
357 arena_chunk_cache_maybe_insert(arena, node, cache);
358 } else {
359 /* Coalescing forward failed, so insert a new node. */
360 node = arena_node_alloc(arena);
361 if (node == NULL) {
362 /*
363 * Node allocation failed, which is an exceedingly
364 * unlikely failure. Leak chunk after making sure its
365 * pages have already been purged, so that this is only
366 * a virtual memory leak.
367 */
368 if (cache) {
369 chunk_purge_wrapper(arena, arena->chunk_purge,
370 chunk, 0, size);
371 }
372 goto label_return;
373 }
374 extent_node_init(node, arena, chunk, size, !unzeroed);
375 extent_tree_ad_insert(chunks_ad, node);
376 extent_tree_szad_insert(chunks_szad, node);
377 arena_chunk_cache_maybe_insert(arena, node, cache);
378 }
379
380 /* Try to coalesce backward. */
381 prev = extent_tree_ad_prev(chunks_ad, node);
382 if (prev != NULL && (void *)((uintptr_t)extent_node_addr_get(prev) +
383 extent_node_size_get(prev)) == chunk) {
384 /*
385 * Coalesce chunk with the previous address range. This does
386 * not change the position within chunks_ad, so only
387 * remove/insert node from/into chunks_szad.
388 */
389 extent_tree_szad_remove(chunks_szad, prev);
390 extent_tree_ad_remove(chunks_ad, prev);
391 arena_chunk_cache_maybe_remove(arena, prev, cache);
392 extent_tree_szad_remove(chunks_szad, node);
393 arena_chunk_cache_maybe_remove(arena, node, cache);
394 extent_node_addr_set(node, extent_node_addr_get(prev));
395 extent_node_size_set(node, extent_node_size_get(prev) +
396 extent_node_size_get(node));
397 extent_node_zeroed_set(node, extent_node_zeroed_get(prev) &&
398 extent_node_zeroed_get(node));
399 extent_tree_szad_insert(chunks_szad, node);
400 arena_chunk_cache_maybe_insert(arena, node, cache);
401
402 arena_node_dalloc(arena, prev);
403 }
404
405 label_return:
406 malloc_mutex_unlock(&arena->chunks_mtx);
407 }
408
409 void
chunk_dalloc_cache(arena_t * arena,void * chunk,size_t size)410 chunk_dalloc_cache(arena_t *arena, void *chunk, size_t size)
411 {
412
413 assert(chunk != NULL);
414 assert(CHUNK_ADDR2BASE(chunk) == chunk);
415 assert(size != 0);
416 assert((size & chunksize_mask) == 0);
417
418 chunk_record(arena, &arena->chunks_szad_cache, &arena->chunks_ad_cache,
419 true, chunk, size, false);
420 arena_maybe_purge(arena);
421 }
422
423 void
chunk_dalloc_arena(arena_t * arena,void * chunk,size_t size,bool zeroed)424 chunk_dalloc_arena(arena_t *arena, void *chunk, size_t size, bool zeroed)
425 {
426
427 assert(chunk != NULL);
428 assert(CHUNK_ADDR2BASE(chunk) == chunk);
429 assert(size != 0);
430 assert((size & chunksize_mask) == 0);
431
432 if (have_dss && chunk_in_dss(chunk)) {
433 chunk_record(arena, &arena->chunks_szad_dss,
434 &arena->chunks_ad_dss, false, chunk, size, zeroed);
435 } else if (chunk_dalloc_mmap(chunk, size)) {
436 chunk_record(arena, &arena->chunks_szad_mmap,
437 &arena->chunks_ad_mmap, false, chunk, size, zeroed);
438 }
439 }
440
441 /*
442 * Default arena chunk deallocation routine in the absence of user override.
443 * This function isn't actually used by jemalloc, but it does the right thing if
444 * the application passes calls through to it during chunk deallocation.
445 */
446 bool
chunk_dalloc_default(void * chunk,size_t size,unsigned arena_ind)447 chunk_dalloc_default(void *chunk, size_t size, unsigned arena_ind)
448 {
449
450 chunk_dalloc_arena(chunk_arena_get(arena_ind), chunk, size, false);
451 return (false);
452 }
453
454 void
chunk_dalloc_wrapper(arena_t * arena,chunk_dalloc_t * chunk_dalloc,void * chunk,size_t size)455 chunk_dalloc_wrapper(arena_t *arena, chunk_dalloc_t *chunk_dalloc, void *chunk,
456 size_t size)
457 {
458
459 chunk_dalloc(chunk, size, arena->ind);
460 if (config_valgrind && chunk_dalloc != chunk_dalloc_default)
461 JEMALLOC_VALGRIND_MAKE_MEM_NOACCESS(chunk, size);
462 }
463
464 bool
chunk_purge_arena(arena_t * arena,void * chunk,size_t offset,size_t length)465 chunk_purge_arena(arena_t *arena, void *chunk, size_t offset, size_t length)
466 {
467
468 assert(chunk != NULL);
469 assert(CHUNK_ADDR2BASE(chunk) == chunk);
470 assert((offset & PAGE_MASK) == 0);
471 assert(length != 0);
472 assert((length & PAGE_MASK) == 0);
473
474 return (pages_purge((void *)((uintptr_t)chunk + (uintptr_t)offset),
475 length));
476 }
477
478 bool
chunk_purge_default(void * chunk,size_t offset,size_t length,unsigned arena_ind)479 chunk_purge_default(void *chunk, size_t offset, size_t length,
480 unsigned arena_ind)
481 {
482
483 return (chunk_purge_arena(chunk_arena_get(arena_ind), chunk, offset,
484 length));
485 }
486
487 bool
chunk_purge_wrapper(arena_t * arena,chunk_purge_t * chunk_purge,void * chunk,size_t offset,size_t length)488 chunk_purge_wrapper(arena_t *arena, chunk_purge_t *chunk_purge, void *chunk,
489 size_t offset, size_t length)
490 {
491
492 return (chunk_purge(chunk, offset, length, arena->ind));
493 }
494
495 static rtree_node_elm_t *
chunks_rtree_node_alloc(size_t nelms)496 chunks_rtree_node_alloc(size_t nelms)
497 {
498
499 return ((rtree_node_elm_t *)base_alloc(nelms *
500 sizeof(rtree_node_elm_t)));
501 }
502
503 bool
chunk_boot(void)504 chunk_boot(void)
505 {
506
507 /* Set variables according to the value of opt_lg_chunk. */
508 chunksize = (ZU(1) << opt_lg_chunk);
509 assert(chunksize >= PAGE);
510 chunksize_mask = chunksize - 1;
511 chunk_npages = (chunksize >> LG_PAGE);
512
513 if (have_dss && chunk_dss_boot())
514 return (true);
515 if (rtree_new(&chunks_rtree, (ZU(1) << (LG_SIZEOF_PTR+3)) -
516 opt_lg_chunk, chunks_rtree_node_alloc, NULL))
517 return (true);
518
519 return (false);
520 }
521
522 void
chunk_prefork(void)523 chunk_prefork(void)
524 {
525
526 chunk_dss_prefork();
527 }
528
529 void
chunk_postfork_parent(void)530 chunk_postfork_parent(void)
531 {
532
533 chunk_dss_postfork_parent();
534 }
535
536 void
chunk_postfork_child(void)537 chunk_postfork_child(void)
538 {
539
540 chunk_dss_postfork_child();
541 }
542