1 #define JEMALLOC_TCACHE_C_
2 #include "jemalloc/internal/jemalloc_preamble.h"
3 #include "jemalloc/internal/jemalloc_internal_includes.h"
4 
5 #include "jemalloc/internal/assert.h"
6 #include "jemalloc/internal/mutex.h"
7 #include "jemalloc/internal/sc.h"
8 
9 /******************************************************************************/
10 /* Data. */
11 
12 bool	opt_tcache = true;
13 ssize_t	opt_lg_tcache_max = LG_TCACHE_MAXCLASS_DEFAULT;
14 
15 cache_bin_info_t	*tcache_bin_info;
16 static unsigned		stack_nelms; /* Total stack elms per tcache. */
17 
18 unsigned		nhbins;
19 size_t			tcache_maxclass;
20 
21 tcaches_t		*tcaches;
22 
23 /* Index of first element within tcaches that has never been used. */
24 static unsigned		tcaches_past;
25 
26 /* Head of singly linked list tracking available tcaches elements. */
27 static tcaches_t	*tcaches_avail;
28 
29 /* Protects tcaches{,_past,_avail}. */
30 static malloc_mutex_t	tcaches_mtx;
31 
32 /******************************************************************************/
33 
34 size_t
tcache_salloc(tsdn_t * tsdn,const void * ptr)35 tcache_salloc(tsdn_t *tsdn, const void *ptr) {
36 	return arena_salloc(tsdn, ptr);
37 }
38 
39 void
tcache_event_hard(tsd_t * tsd,tcache_t * tcache)40 tcache_event_hard(tsd_t *tsd, tcache_t *tcache) {
41 	szind_t binind = tcache->next_gc_bin;
42 
43 	cache_bin_t *tbin;
44 	if (binind < SC_NBINS) {
45 		tbin = tcache_small_bin_get(tcache, binind);
46 	} else {
47 		tbin = tcache_large_bin_get(tcache, binind);
48 	}
49 	if (tbin->low_water > 0) {
50 		/*
51 		 * Flush (ceiling) 3/4 of the objects below the low water mark.
52 		 */
53 		if (binind < SC_NBINS) {
54 			tcache_bin_flush_small(tsd, tcache, tbin, binind,
55 			    tbin->ncached - tbin->low_water + (tbin->low_water
56 			    >> 2));
57 			/*
58 			 * Reduce fill count by 2X.  Limit lg_fill_div such that
59 			 * the fill count is always at least 1.
60 			 */
61 			cache_bin_info_t *tbin_info = &tcache_bin_info[binind];
62 			if ((tbin_info->ncached_max >>
63 			     (tcache->lg_fill_div[binind] + 1)) >= 1) {
64 				tcache->lg_fill_div[binind]++;
65 			}
66 		} else {
67 			tcache_bin_flush_large(tsd, tbin, binind, tbin->ncached
68 			    - tbin->low_water + (tbin->low_water >> 2), tcache);
69 		}
70 	} else if (tbin->low_water < 0) {
71 		/*
72 		 * Increase fill count by 2X for small bins.  Make sure
73 		 * lg_fill_div stays greater than 0.
74 		 */
75 		if (binind < SC_NBINS && tcache->lg_fill_div[binind] > 1) {
76 			tcache->lg_fill_div[binind]--;
77 		}
78 	}
79 	tbin->low_water = tbin->ncached;
80 
81 	tcache->next_gc_bin++;
82 	if (tcache->next_gc_bin == nhbins) {
83 		tcache->next_gc_bin = 0;
84 	}
85 }
86 
87 void *
tcache_alloc_small_hard(tsdn_t * tsdn,arena_t * arena,tcache_t * tcache,cache_bin_t * tbin,szind_t binind,bool * tcache_success)88 tcache_alloc_small_hard(tsdn_t *tsdn, arena_t *arena, tcache_t *tcache,
89     cache_bin_t *tbin, szind_t binind, bool *tcache_success) {
90 	void *ret;
91 
92 	assert(tcache->arena != NULL);
93 	arena_tcache_fill_small(tsdn, arena, tcache, tbin, binind,
94 	    config_prof ? tcache->prof_accumbytes : 0);
95 	if (config_prof) {
96 		tcache->prof_accumbytes = 0;
97 	}
98 	ret = cache_bin_alloc_easy(tbin, tcache_success);
99 
100 	return ret;
101 }
102 
103 /* Enabled with --enable-extra-size-check. */
104 #ifdef JEMALLOC_EXTRA_SIZE_CHECK
105 static void
tbin_extents_lookup_size_check(tsdn_t * tsdn,cache_bin_t * tbin,szind_t binind,size_t nflush,extent_t ** extents)106 tbin_extents_lookup_size_check(tsdn_t *tsdn, cache_bin_t *tbin, szind_t binind,
107     size_t nflush, extent_t **extents){
108 	rtree_ctx_t rtree_ctx_fallback;
109 	rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
110 
111 	/*
112 	 * Verify that the items in the tcache all have the correct size; this
113 	 * is useful for catching sized deallocation bugs, also to fail early
114 	 * instead of corrupting metadata.  Since this can be turned on for opt
115 	 * builds, avoid the branch in the loop.
116 	 */
117 	szind_t szind;
118 	size_t sz_sum = binind * nflush;
119 	for (unsigned i = 0 ; i < nflush; i++) {
120 		rtree_extent_szind_read(tsdn, &extents_rtree,
121 		    rtree_ctx, (uintptr_t)*(tbin->avail - 1 - i), true,
122 		    &extents[i], &szind);
123 		sz_sum -= szind;
124 	}
125 	if (sz_sum != 0) {
126 		abort();
127 	}
128 }
129 #endif
130 
131 void
tcache_bin_flush_small(tsd_t * tsd,tcache_t * tcache,cache_bin_t * tbin,szind_t binind,unsigned rem)132 tcache_bin_flush_small(tsd_t *tsd, tcache_t *tcache, cache_bin_t *tbin,
133     szind_t binind, unsigned rem) {
134 	bool merged_stats = false;
135 
136 	assert(binind < SC_NBINS);
137 	assert((cache_bin_sz_t)rem <= tbin->ncached);
138 
139 	arena_t *arena = tcache->arena;
140 	assert(arena != NULL);
141 	unsigned nflush = tbin->ncached - rem;
142 	VARIABLE_ARRAY(extent_t *, item_extent, nflush);
143 
144 #ifndef JEMALLOC_EXTRA_SIZE_CHECK
145 	/* Look up extent once per item. */
146 	for (unsigned i = 0 ; i < nflush; i++) {
147 		item_extent[i] = iealloc(tsd_tsdn(tsd), *(tbin->avail - 1 - i));
148 	}
149 #else
150 	tbin_extents_lookup_size_check(tsd_tsdn(tsd), tbin, binind, nflush,
151 	    item_extent);
152 #endif
153 	while (nflush > 0) {
154 		/* Lock the arena bin associated with the first object. */
155 		extent_t *extent = item_extent[0];
156 		unsigned bin_arena_ind = extent_arena_ind_get(extent);
157 		arena_t *bin_arena = arena_get(tsd_tsdn(tsd), bin_arena_ind,
158 		    false);
159 		unsigned binshard = extent_binshard_get(extent);
160 		assert(binshard < bin_infos[binind].n_shards);
161 		bin_t *bin = &bin_arena->bins[binind].bin_shards[binshard];
162 
163 		if (config_prof && bin_arena == arena) {
164 			if (arena_prof_accum(tsd_tsdn(tsd), arena,
165 			    tcache->prof_accumbytes)) {
166 				prof_idump(tsd_tsdn(tsd));
167 			}
168 			tcache->prof_accumbytes = 0;
169 		}
170 
171 		malloc_mutex_lock(tsd_tsdn(tsd), &bin->lock);
172 		if (config_stats && bin_arena == arena && !merged_stats) {
173 			merged_stats = true;
174 			bin->stats.nflushes++;
175 			bin->stats.nrequests += tbin->tstats.nrequests;
176 			tbin->tstats.nrequests = 0;
177 		}
178 		unsigned ndeferred = 0;
179 		for (unsigned i = 0; i < nflush; i++) {
180 			void *ptr = *(tbin->avail - 1 - i);
181 			extent = item_extent[i];
182 			assert(ptr != NULL && extent != NULL);
183 
184 			if (extent_arena_ind_get(extent) == bin_arena_ind
185 			    && extent_binshard_get(extent) == binshard) {
186 				arena_dalloc_bin_junked_locked(tsd_tsdn(tsd),
187 				    bin_arena, bin, binind, extent, ptr);
188 			} else {
189 				/*
190 				 * This object was allocated via a different
191 				 * arena bin than the one that is currently
192 				 * locked.  Stash the object, so that it can be
193 				 * handled in a future pass.
194 				 */
195 				*(tbin->avail - 1 - ndeferred) = ptr;
196 				item_extent[ndeferred] = extent;
197 				ndeferred++;
198 			}
199 		}
200 		malloc_mutex_unlock(tsd_tsdn(tsd), &bin->lock);
201 		arena_decay_ticks(tsd_tsdn(tsd), bin_arena, nflush - ndeferred);
202 		nflush = ndeferred;
203 	}
204 	if (config_stats && !merged_stats) {
205 		/*
206 		 * The flush loop didn't happen to flush to this thread's
207 		 * arena, so the stats didn't get merged.  Manually do so now.
208 		 */
209 		unsigned binshard;
210 		bin_t *bin = arena_bin_choose_lock(tsd_tsdn(tsd), arena, binind,
211 		    &binshard);
212 		bin->stats.nflushes++;
213 		bin->stats.nrequests += tbin->tstats.nrequests;
214 		tbin->tstats.nrequests = 0;
215 		malloc_mutex_unlock(tsd_tsdn(tsd), &bin->lock);
216 	}
217 
218 	memmove(tbin->avail - rem, tbin->avail - tbin->ncached, rem *
219 	    sizeof(void *));
220 	tbin->ncached = rem;
221 	if (tbin->ncached < tbin->low_water) {
222 		tbin->low_water = tbin->ncached;
223 	}
224 }
225 
226 void
tcache_bin_flush_large(tsd_t * tsd,cache_bin_t * tbin,szind_t binind,unsigned rem,tcache_t * tcache)227 tcache_bin_flush_large(tsd_t *tsd, cache_bin_t *tbin, szind_t binind,
228     unsigned rem, tcache_t *tcache) {
229 	bool merged_stats = false;
230 
231 	assert(binind < nhbins);
232 	assert((cache_bin_sz_t)rem <= tbin->ncached);
233 
234 	arena_t *tcache_arena = tcache->arena;
235 	assert(tcache_arena != NULL);
236 	unsigned nflush = tbin->ncached - rem;
237 	VARIABLE_ARRAY(extent_t *, item_extent, nflush);
238 
239 #ifndef JEMALLOC_EXTRA_SIZE_CHECK
240 	/* Look up extent once per item. */
241 	for (unsigned i = 0 ; i < nflush; i++) {
242 		item_extent[i] = iealloc(tsd_tsdn(tsd), *(tbin->avail - 1 - i));
243 	}
244 #else
245 	tbin_extents_lookup_size_check(tsd_tsdn(tsd), tbin, binind, nflush,
246 	    item_extent);
247 #endif
248 	while (nflush > 0) {
249 		/* Lock the arena associated with the first object. */
250 		extent_t *extent = item_extent[0];
251 		unsigned locked_arena_ind = extent_arena_ind_get(extent);
252 		arena_t *locked_arena = arena_get(tsd_tsdn(tsd),
253 		    locked_arena_ind, false);
254 		bool idump;
255 
256 		if (config_prof) {
257 			idump = false;
258 		}
259 
260 		bool lock_large = !arena_is_auto(locked_arena);
261 		if (lock_large) {
262 			malloc_mutex_lock(tsd_tsdn(tsd), &locked_arena->large_mtx);
263 		}
264 		for (unsigned i = 0; i < nflush; i++) {
265 			void *ptr = *(tbin->avail - 1 - i);
266 			assert(ptr != NULL);
267 			extent = item_extent[i];
268 			if (extent_arena_ind_get(extent) == locked_arena_ind) {
269 				large_dalloc_prep_junked_locked(tsd_tsdn(tsd),
270 				    extent);
271 			}
272 		}
273 		if ((config_prof || config_stats) &&
274 		    (locked_arena == tcache_arena)) {
275 			if (config_prof) {
276 				idump = arena_prof_accum(tsd_tsdn(tsd),
277 				    tcache_arena, tcache->prof_accumbytes);
278 				tcache->prof_accumbytes = 0;
279 			}
280 			if (config_stats) {
281 				merged_stats = true;
282 				arena_stats_large_nrequests_add(tsd_tsdn(tsd),
283 				    &tcache_arena->stats, binind,
284 				    tbin->tstats.nrequests);
285 				tbin->tstats.nrequests = 0;
286 			}
287 		}
288 		if (lock_large) {
289 			malloc_mutex_unlock(tsd_tsdn(tsd), &locked_arena->large_mtx);
290 		}
291 
292 		unsigned ndeferred = 0;
293 		for (unsigned i = 0; i < nflush; i++) {
294 			void *ptr = *(tbin->avail - 1 - i);
295 			extent = item_extent[i];
296 			assert(ptr != NULL && extent != NULL);
297 
298 			if (extent_arena_ind_get(extent) == locked_arena_ind) {
299 				large_dalloc_finish(tsd_tsdn(tsd), extent);
300 			} else {
301 				/*
302 				 * This object was allocated via a different
303 				 * arena than the one that is currently locked.
304 				 * Stash the object, so that it can be handled
305 				 * in a future pass.
306 				 */
307 				*(tbin->avail - 1 - ndeferred) = ptr;
308 				item_extent[ndeferred] = extent;
309 				ndeferred++;
310 			}
311 		}
312 		if (config_prof && idump) {
313 			prof_idump(tsd_tsdn(tsd));
314 		}
315 		arena_decay_ticks(tsd_tsdn(tsd), locked_arena, nflush -
316 		    ndeferred);
317 		nflush = ndeferred;
318 	}
319 	if (config_stats && !merged_stats) {
320 		/*
321 		 * The flush loop didn't happen to flush to this thread's
322 		 * arena, so the stats didn't get merged.  Manually do so now.
323 		 */
324 		arena_stats_large_nrequests_add(tsd_tsdn(tsd),
325 		    &tcache_arena->stats, binind, tbin->tstats.nrequests);
326 		tbin->tstats.nrequests = 0;
327 	}
328 
329 	memmove(tbin->avail - rem, tbin->avail - tbin->ncached, rem *
330 	    sizeof(void *));
331 	tbin->ncached = rem;
332 	if (tbin->ncached < tbin->low_water) {
333 		tbin->low_water = tbin->ncached;
334 	}
335 }
336 
337 void
tcache_arena_associate(tsdn_t * tsdn,tcache_t * tcache,arena_t * arena)338 tcache_arena_associate(tsdn_t *tsdn, tcache_t *tcache, arena_t *arena) {
339 	assert(tcache->arena == NULL);
340 	tcache->arena = arena;
341 
342 	if (config_stats) {
343 		/* Link into list of extant tcaches. */
344 		malloc_mutex_lock(tsdn, &arena->tcache_ql_mtx);
345 
346 		ql_elm_new(tcache, link);
347 		ql_tail_insert(&arena->tcache_ql, tcache, link);
348 		cache_bin_array_descriptor_init(
349 		    &tcache->cache_bin_array_descriptor, tcache->bins_small,
350 		    tcache->bins_large);
351 		ql_tail_insert(&arena->cache_bin_array_descriptor_ql,
352 		    &tcache->cache_bin_array_descriptor, link);
353 
354 		malloc_mutex_unlock(tsdn, &arena->tcache_ql_mtx);
355 	}
356 }
357 
358 static void
tcache_arena_dissociate(tsdn_t * tsdn,tcache_t * tcache)359 tcache_arena_dissociate(tsdn_t *tsdn, tcache_t *tcache) {
360 	arena_t *arena = tcache->arena;
361 	assert(arena != NULL);
362 	if (config_stats) {
363 		/* Unlink from list of extant tcaches. */
364 		malloc_mutex_lock(tsdn, &arena->tcache_ql_mtx);
365 		if (config_debug) {
366 			bool in_ql = false;
367 			tcache_t *iter;
368 			ql_foreach(iter, &arena->tcache_ql, link) {
369 				if (iter == tcache) {
370 					in_ql = true;
371 					break;
372 				}
373 			}
374 			assert(in_ql);
375 		}
376 		ql_remove(&arena->tcache_ql, tcache, link);
377 		ql_remove(&arena->cache_bin_array_descriptor_ql,
378 		    &tcache->cache_bin_array_descriptor, link);
379 		tcache_stats_merge(tsdn, tcache, arena);
380 		malloc_mutex_unlock(tsdn, &arena->tcache_ql_mtx);
381 	}
382 	tcache->arena = NULL;
383 }
384 
385 void
tcache_arena_reassociate(tsdn_t * tsdn,tcache_t * tcache,arena_t * arena)386 tcache_arena_reassociate(tsdn_t *tsdn, tcache_t *tcache, arena_t *arena) {
387 	tcache_arena_dissociate(tsdn, tcache);
388 	tcache_arena_associate(tsdn, tcache, arena);
389 }
390 
391 bool
tsd_tcache_enabled_data_init(tsd_t * tsd)392 tsd_tcache_enabled_data_init(tsd_t *tsd) {
393 	/* Called upon tsd initialization. */
394 	tsd_tcache_enabled_set(tsd, opt_tcache);
395 	tsd_slow_update(tsd);
396 
397 	if (opt_tcache) {
398 		/* Trigger tcache init. */
399 		tsd_tcache_data_init(tsd);
400 	}
401 
402 	return false;
403 }
404 
405 /* Initialize auto tcache (embedded in TSD). */
406 static void
tcache_init(tsd_t * tsd,tcache_t * tcache,void * avail_stack)407 tcache_init(tsd_t *tsd, tcache_t *tcache, void *avail_stack) {
408 	memset(&tcache->link, 0, sizeof(ql_elm(tcache_t)));
409 	tcache->prof_accumbytes = 0;
410 	tcache->next_gc_bin = 0;
411 	tcache->arena = NULL;
412 
413 	ticker_init(&tcache->gc_ticker, TCACHE_GC_INCR);
414 
415 	size_t stack_offset = 0;
416 	assert((TCACHE_NSLOTS_SMALL_MAX & 1U) == 0);
417 	memset(tcache->bins_small, 0, sizeof(cache_bin_t) * SC_NBINS);
418 	memset(tcache->bins_large, 0, sizeof(cache_bin_t) * (nhbins - SC_NBINS));
419 	unsigned i = 0;
420 	for (; i < SC_NBINS; i++) {
421 		tcache->lg_fill_div[i] = 1;
422 		stack_offset += tcache_bin_info[i].ncached_max * sizeof(void *);
423 		/*
424 		 * avail points past the available space.  Allocations will
425 		 * access the slots toward higher addresses (for the benefit of
426 		 * prefetch).
427 		 */
428 		tcache_small_bin_get(tcache, i)->avail =
429 		    (void **)((uintptr_t)avail_stack + (uintptr_t)stack_offset);
430 	}
431 	for (; i < nhbins; i++) {
432 		stack_offset += tcache_bin_info[i].ncached_max * sizeof(void *);
433 		tcache_large_bin_get(tcache, i)->avail =
434 		    (void **)((uintptr_t)avail_stack + (uintptr_t)stack_offset);
435 	}
436 	assert(stack_offset == stack_nelms * sizeof(void *));
437 }
438 
439 /* Initialize auto tcache (embedded in TSD). */
440 bool
tsd_tcache_data_init(tsd_t * tsd)441 tsd_tcache_data_init(tsd_t *tsd) {
442 	tcache_t *tcache = tsd_tcachep_get_unsafe(tsd);
443 	assert(tcache_small_bin_get(tcache, 0)->avail == NULL);
444 	size_t size = stack_nelms * sizeof(void *);
445 	/* Avoid false cacheline sharing. */
446 	size = sz_sa2u(size, CACHELINE);
447 
448 	void *avail_array = ipallocztm(tsd_tsdn(tsd), size, CACHELINE, true,
449 	    NULL, true, arena_get(TSDN_NULL, 0, true));
450 	if (avail_array == NULL) {
451 		return true;
452 	}
453 
454 	tcache_init(tsd, tcache, avail_array);
455 	/*
456 	 * Initialization is a bit tricky here.  After malloc init is done, all
457 	 * threads can rely on arena_choose and associate tcache accordingly.
458 	 * However, the thread that does actual malloc bootstrapping relies on
459 	 * functional tsd, and it can only rely on a0.  In that case, we
460 	 * associate its tcache to a0 temporarily, and later on
461 	 * arena_choose_hard() will re-associate properly.
462 	 */
463 	tcache->arena = NULL;
464 	arena_t *arena;
465 	if (!malloc_initialized()) {
466 		/* If in initialization, assign to a0. */
467 		arena = arena_get(tsd_tsdn(tsd), 0, false);
468 		tcache_arena_associate(tsd_tsdn(tsd), tcache, arena);
469 	} else {
470 		arena = arena_choose(tsd, NULL);
471 		/* This may happen if thread.tcache.enabled is used. */
472 		if (tcache->arena == NULL) {
473 			tcache_arena_associate(tsd_tsdn(tsd), tcache, arena);
474 		}
475 	}
476 	assert(arena == tcache->arena);
477 
478 	return false;
479 }
480 
481 /* Created manual tcache for tcache.create mallctl. */
482 tcache_t *
tcache_create_explicit(tsd_t * tsd)483 tcache_create_explicit(tsd_t *tsd) {
484 	tcache_t *tcache;
485 	size_t size, stack_offset;
486 
487 	size = sizeof(tcache_t);
488 	/* Naturally align the pointer stacks. */
489 	size = PTR_CEILING(size);
490 	stack_offset = size;
491 	size += stack_nelms * sizeof(void *);
492 	/* Avoid false cacheline sharing. */
493 	size = sz_sa2u(size, CACHELINE);
494 
495 	tcache = ipallocztm(tsd_tsdn(tsd), size, CACHELINE, true, NULL, true,
496 	    arena_get(TSDN_NULL, 0, true));
497 	if (tcache == NULL) {
498 		return NULL;
499 	}
500 
501 	tcache_init(tsd, tcache,
502 	    (void *)((uintptr_t)tcache + (uintptr_t)stack_offset));
503 	tcache_arena_associate(tsd_tsdn(tsd), tcache, arena_ichoose(tsd, NULL));
504 
505 	return tcache;
506 }
507 
508 static void
tcache_flush_cache(tsd_t * tsd,tcache_t * tcache)509 tcache_flush_cache(tsd_t *tsd, tcache_t *tcache) {
510 	assert(tcache->arena != NULL);
511 
512 	for (unsigned i = 0; i < SC_NBINS; i++) {
513 		cache_bin_t *tbin = tcache_small_bin_get(tcache, i);
514 		tcache_bin_flush_small(tsd, tcache, tbin, i, 0);
515 
516 		if (config_stats) {
517 			assert(tbin->tstats.nrequests == 0);
518 		}
519 	}
520 	for (unsigned i = SC_NBINS; i < nhbins; i++) {
521 		cache_bin_t *tbin = tcache_large_bin_get(tcache, i);
522 		tcache_bin_flush_large(tsd, tbin, i, 0, tcache);
523 
524 		if (config_stats) {
525 			assert(tbin->tstats.nrequests == 0);
526 		}
527 	}
528 
529 	if (config_prof && tcache->prof_accumbytes > 0 &&
530 	    arena_prof_accum(tsd_tsdn(tsd), tcache->arena,
531 	    tcache->prof_accumbytes)) {
532 		prof_idump(tsd_tsdn(tsd));
533 	}
534 }
535 
536 void
tcache_flush(tsd_t * tsd)537 tcache_flush(tsd_t *tsd) {
538 	assert(tcache_available(tsd));
539 	tcache_flush_cache(tsd, tsd_tcachep_get(tsd));
540 }
541 
542 static void
tcache_destroy(tsd_t * tsd,tcache_t * tcache,bool tsd_tcache)543 tcache_destroy(tsd_t *tsd, tcache_t *tcache, bool tsd_tcache) {
544 	tcache_flush_cache(tsd, tcache);
545 	arena_t *arena = tcache->arena;
546 	tcache_arena_dissociate(tsd_tsdn(tsd), tcache);
547 
548 	if (tsd_tcache) {
549 		/* Release the avail array for the TSD embedded auto tcache. */
550 		void *avail_array =
551 		    (void *)((uintptr_t)tcache_small_bin_get(tcache, 0)->avail -
552 		    (uintptr_t)tcache_bin_info[0].ncached_max * sizeof(void *));
553 		idalloctm(tsd_tsdn(tsd), avail_array, NULL, NULL, true, true);
554 	} else {
555 		/* Release both the tcache struct and avail array. */
556 		idalloctm(tsd_tsdn(tsd), tcache, NULL, NULL, true, true);
557 	}
558 
559 	/*
560 	 * The deallocation and tcache flush above may not trigger decay since
561 	 * we are on the tcache shutdown path (potentially with non-nominal
562 	 * tsd).  Manually trigger decay to avoid pathological cases.  Also
563 	 * include arena 0 because the tcache array is allocated from it.
564 	 */
565 	arena_decay(tsd_tsdn(tsd), arena_get(tsd_tsdn(tsd), 0, false),
566 	    false, false);
567 
568 	if (arena_nthreads_get(arena, false) == 0 &&
569 	    !background_thread_enabled()) {
570 		/* Force purging when no threads assigned to the arena anymore. */
571 		arena_decay(tsd_tsdn(tsd), arena, false, true);
572 	} else {
573 		arena_decay(tsd_tsdn(tsd), arena, false, false);
574 	}
575 }
576 
577 /* For auto tcache (embedded in TSD) only. */
578 void
tcache_cleanup(tsd_t * tsd)579 tcache_cleanup(tsd_t *tsd) {
580 	tcache_t *tcache = tsd_tcachep_get(tsd);
581 	if (!tcache_available(tsd)) {
582 		assert(tsd_tcache_enabled_get(tsd) == false);
583 		if (config_debug) {
584 			assert(tcache_small_bin_get(tcache, 0)->avail == NULL);
585 		}
586 		return;
587 	}
588 	assert(tsd_tcache_enabled_get(tsd));
589 	assert(tcache_small_bin_get(tcache, 0)->avail != NULL);
590 
591 	tcache_destroy(tsd, tcache, true);
592 	if (config_debug) {
593 		tcache_small_bin_get(tcache, 0)->avail = NULL;
594 	}
595 }
596 
597 void
tcache_stats_merge(tsdn_t * tsdn,tcache_t * tcache,arena_t * arena)598 tcache_stats_merge(tsdn_t *tsdn, tcache_t *tcache, arena_t *arena) {
599 	unsigned i;
600 
601 	cassert(config_stats);
602 
603 	/* Merge and reset tcache stats. */
604 	for (i = 0; i < SC_NBINS; i++) {
605 		cache_bin_t *tbin = tcache_small_bin_get(tcache, i);
606 		unsigned binshard;
607 		bin_t *bin = arena_bin_choose_lock(tsdn, arena, i, &binshard);
608 		bin->stats.nrequests += tbin->tstats.nrequests;
609 		malloc_mutex_unlock(tsdn, &bin->lock);
610 		tbin->tstats.nrequests = 0;
611 	}
612 
613 	for (; i < nhbins; i++) {
614 		cache_bin_t *tbin = tcache_large_bin_get(tcache, i);
615 		arena_stats_large_nrequests_add(tsdn, &arena->stats, i,
616 		    tbin->tstats.nrequests);
617 		tbin->tstats.nrequests = 0;
618 	}
619 }
620 
621 static bool
tcaches_create_prep(tsd_t * tsd)622 tcaches_create_prep(tsd_t *tsd) {
623 	bool err;
624 
625 	malloc_mutex_lock(tsd_tsdn(tsd), &tcaches_mtx);
626 
627 	if (tcaches == NULL) {
628 		tcaches = base_alloc(tsd_tsdn(tsd), b0get(), sizeof(tcache_t *)
629 		    * (MALLOCX_TCACHE_MAX+1), CACHELINE);
630 		if (tcaches == NULL) {
631 			err = true;
632 			goto label_return;
633 		}
634 	}
635 
636 	if (tcaches_avail == NULL && tcaches_past > MALLOCX_TCACHE_MAX) {
637 		err = true;
638 		goto label_return;
639 	}
640 
641 	err = false;
642 label_return:
643 	malloc_mutex_unlock(tsd_tsdn(tsd), &tcaches_mtx);
644 	return err;
645 }
646 
647 bool
tcaches_create(tsd_t * tsd,unsigned * r_ind)648 tcaches_create(tsd_t *tsd, unsigned *r_ind) {
649 	witness_assert_depth(tsdn_witness_tsdp_get(tsd_tsdn(tsd)), 0);
650 
651 	bool err;
652 
653 	if (tcaches_create_prep(tsd)) {
654 		err = true;
655 		goto label_return;
656 	}
657 
658 	tcache_t *tcache = tcache_create_explicit(tsd);
659 	if (tcache == NULL) {
660 		err = true;
661 		goto label_return;
662 	}
663 
664 	tcaches_t *elm;
665 	malloc_mutex_lock(tsd_tsdn(tsd), &tcaches_mtx);
666 	if (tcaches_avail != NULL) {
667 		elm = tcaches_avail;
668 		tcaches_avail = tcaches_avail->next;
669 		elm->tcache = tcache;
670 		*r_ind = (unsigned)(elm - tcaches);
671 	} else {
672 		elm = &tcaches[tcaches_past];
673 		elm->tcache = tcache;
674 		*r_ind = tcaches_past;
675 		tcaches_past++;
676 	}
677 	malloc_mutex_unlock(tsd_tsdn(tsd), &tcaches_mtx);
678 
679 	err = false;
680 label_return:
681 	witness_assert_depth(tsdn_witness_tsdp_get(tsd_tsdn(tsd)), 0);
682 	return err;
683 }
684 
685 static tcache_t *
tcaches_elm_remove(tsd_t * tsd,tcaches_t * elm,bool allow_reinit)686 tcaches_elm_remove(tsd_t *tsd, tcaches_t *elm, bool allow_reinit) {
687 	malloc_mutex_assert_owner(tsd_tsdn(tsd), &tcaches_mtx);
688 
689 	if (elm->tcache == NULL) {
690 		return NULL;
691 	}
692 	tcache_t *tcache = elm->tcache;
693 	if (allow_reinit) {
694 		elm->tcache = TCACHES_ELM_NEED_REINIT;
695 	} else {
696 		elm->tcache = NULL;
697 	}
698 
699 	if (tcache == TCACHES_ELM_NEED_REINIT) {
700 		return NULL;
701 	}
702 	return tcache;
703 }
704 
705 void
tcaches_flush(tsd_t * tsd,unsigned ind)706 tcaches_flush(tsd_t *tsd, unsigned ind) {
707 	malloc_mutex_lock(tsd_tsdn(tsd), &tcaches_mtx);
708 	tcache_t *tcache = tcaches_elm_remove(tsd, &tcaches[ind], true);
709 	malloc_mutex_unlock(tsd_tsdn(tsd), &tcaches_mtx);
710 	if (tcache != NULL) {
711 		/* Destroy the tcache; recreate in tcaches_get() if needed. */
712 		tcache_destroy(tsd, tcache, false);
713 	}
714 }
715 
716 void
tcaches_destroy(tsd_t * tsd,unsigned ind)717 tcaches_destroy(tsd_t *tsd, unsigned ind) {
718 	malloc_mutex_lock(tsd_tsdn(tsd), &tcaches_mtx);
719 	tcaches_t *elm = &tcaches[ind];
720 	tcache_t *tcache = tcaches_elm_remove(tsd, elm, false);
721 	elm->next = tcaches_avail;
722 	tcaches_avail = elm;
723 	malloc_mutex_unlock(tsd_tsdn(tsd), &tcaches_mtx);
724 	if (tcache != NULL) {
725 		tcache_destroy(tsd, tcache, false);
726 	}
727 }
728 
729 bool
tcache_boot(tsdn_t * tsdn)730 tcache_boot(tsdn_t *tsdn) {
731 	/* If necessary, clamp opt_lg_tcache_max. */
732 	if (opt_lg_tcache_max < 0 || (ZU(1) << opt_lg_tcache_max) <
733 	    SC_SMALL_MAXCLASS) {
734 		tcache_maxclass = SC_SMALL_MAXCLASS;
735 	} else {
736 		tcache_maxclass = (ZU(1) << opt_lg_tcache_max);
737 	}
738 
739 	if (malloc_mutex_init(&tcaches_mtx, "tcaches", WITNESS_RANK_TCACHES,
740 	    malloc_mutex_rank_exclusive)) {
741 		return true;
742 	}
743 
744 	nhbins = sz_size2index(tcache_maxclass) + 1;
745 
746 	/* Initialize tcache_bin_info. */
747 	tcache_bin_info = (cache_bin_info_t *)base_alloc(tsdn, b0get(), nhbins
748 	    * sizeof(cache_bin_info_t), CACHELINE);
749 	if (tcache_bin_info == NULL) {
750 		return true;
751 	}
752 	stack_nelms = 0;
753 	unsigned i;
754 	for (i = 0; i < SC_NBINS; i++) {
755 		if ((bin_infos[i].nregs << 1) <= TCACHE_NSLOTS_SMALL_MIN) {
756 			tcache_bin_info[i].ncached_max =
757 			    TCACHE_NSLOTS_SMALL_MIN;
758 		} else if ((bin_infos[i].nregs << 1) <=
759 		    TCACHE_NSLOTS_SMALL_MAX) {
760 			tcache_bin_info[i].ncached_max =
761 			    (bin_infos[i].nregs << 1);
762 		} else {
763 			tcache_bin_info[i].ncached_max =
764 			    TCACHE_NSLOTS_SMALL_MAX;
765 		}
766 		stack_nelms += tcache_bin_info[i].ncached_max;
767 	}
768 	for (; i < nhbins; i++) {
769 		tcache_bin_info[i].ncached_max = TCACHE_NSLOTS_LARGE;
770 		stack_nelms += tcache_bin_info[i].ncached_max;
771 	}
772 
773 	return false;
774 }
775 
776 void
tcache_prefork(tsdn_t * tsdn)777 tcache_prefork(tsdn_t *tsdn) {
778 	if (!config_prof && opt_tcache) {
779 		malloc_mutex_prefork(tsdn, &tcaches_mtx);
780 	}
781 }
782 
783 void
tcache_postfork_parent(tsdn_t * tsdn)784 tcache_postfork_parent(tsdn_t *tsdn) {
785 	if (!config_prof && opt_tcache) {
786 		malloc_mutex_postfork_parent(tsdn, &tcaches_mtx);
787 	}
788 }
789 
790 void
tcache_postfork_child(tsdn_t * tsdn)791 tcache_postfork_child(tsdn_t *tsdn) {
792 	if (!config_prof && opt_tcache) {
793 		malloc_mutex_postfork_child(tsdn, &tcaches_mtx);
794 	}
795 }
796