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